diff options
93 files changed, 3720 insertions, 629 deletions
diff --git a/.gitignore b/.gitignore index e8d2731ee5..9229e918cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ GIT-CFLAGS +GIT-GUI-VARS GIT-VERSION-FILE git git-add @@ -76,6 +77,7 @@ git-merge-ours git-merge-recursive git-merge-resolve git-merge-stupid +git-merge-subtree git-mergetool git-mktag git-mktree @@ -141,11 +143,13 @@ git-verify-tag git-whatchanged git-write-tree git-core-*/?* +gitk-wish gitweb/gitweb.cgi test-chmtime test-date test-delta test-dump-cache-tree +test-match-trees common-cmds.h *.tar.gz *.dsc diff --git a/Documentation/Makefile b/Documentation/Makefile index ad87736b0c..a637d8d559 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -65,6 +65,11 @@ install: man $(INSTALL) -m644 $(DOC_MAN7) $(DESTDIR)$(man7dir) +../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE + $(MAKE) -C ../ GIT-VERSION-FILE + +-include ../GIT-VERSION-FILE + # # Determine "include::" file references in asciidoc files. # @@ -94,17 +99,25 @@ cmd-list.made: cmd-list.perl $(MAN1_TXT) git.7 git.html: git.txt core-intro.txt clean: - rm -f *.xml *.html *.1 *.7 howto-index.txt howto/*.html doc.dep + rm -f *.xml *.xml+ *.html *.html+ *.1 *.7 howto-index.txt howto/*.html doc.dep rm -f $(cmds_txt) *.made %.html : %.txt - $(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf $(ASCIIDOC_EXTRA) $< + rm -f $@+ $@ + $(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf \ + $(ASCIIDOC_EXTRA) -o - $< | \ + sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' >$@+ + mv $@+ $@ %.1 %.7 : %.xml xmlto -m callouts.xsl man $< %.xml : %.txt - $(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf $< + rm -f $@+ $@ + $(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf \ + $(ASCIIDOC_EXTRA) -o - $< | \ + sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' >$@+ + mv $@+ $@ user-manual.xml: user-manual.txt user-manual.conf $(ASCIIDOC) -b docbook -d book $< @@ -135,3 +148,5 @@ install-webdoc : html quick-install: sh ./install-doc-quick.sh $(DOC_REF) $(mandir) + +.PHONY: .FORCE-GIT-VERSION-FILE diff --git a/Documentation/RelNotes-1.5.2.txt b/Documentation/RelNotes-1.5.2.txt new file mode 100644 index 0000000000..d93da608c7 --- /dev/null +++ b/Documentation/RelNotes-1.5.2.txt @@ -0,0 +1,119 @@ +GIT v1.5.2 Release Notes (draft) +======================== + +Updates since v1.5.1 +-------------------- + +* New commands and options. + + - "git bisect start" can optionally take a single bad commit and + zero or more good commits on the command line. + + - "git shortlog" can optionally be told to wrap its output. + + - "subtree" merge strategy allows another project to be merged in as + your subdirectory. + + - "git format-patch" learned a new --subject-prefix=<string> + option, to override the built-in "[PATCH]". + +* Updated behavior of existing commands. + + - "git diff --stat" shows size of preimage and postimage blobs + for binary contents. Earlier it only said "Bin". + + - "git lost-found" shows stuff that are unreachable except + from reflogs. + + - "git checkout branch^0" now detaches HEAD at the tip commit + on the named branch, instead of just switching to the + branch (use "git checkout branch" to switch to the branch, + as before). + + - "git bisect next" can be used after giving only a bad commit + without giving a good one (this starts bisection half-way to + the root commit). We used to refuse to operate without a + good and a bad commit. + + - "git push", when pushing into more than one repository, does + not stop at the first error. + + - "git archive" does not insist you to give --format parameter + anymore; it defaults to "tar". + +* Builds + + - git-p4import has never been installed; now there is an + installation option to do so. + + - gitk and git-gui can be configured out. + + - Generated documentation pages automatically get version + information from GIT_VERSION + + - Parallel build with "make -j" descending into subdirectory + was fixed. + +* Performance Tweaks + + - optimized "git-rev-list --bisect" (hence "git-bisect"). + + - optimized "git-add $path" in a large directory, most of + whose contents are ignored. + + +Fixes since v1.5.1 +------------------ + +The following are all in v1.5.1.x series, unless otherwise noted. + +* Documentation updates + + - Various documentation updates from J. Bruce Fields, Frank + Lichtenheld, Alex Riesen and others. Andrew Ruder started a + war on undocumented options. + +* Bugfixes + + - "git diff a/ b/" incorrectly fell in "diff between two + filesystem objects" codepath, when the user most likely + wanted to limit the extent of output to two tracked + directories. + + - git-quiltimport had the same bug as we fixed for + git-applymbox in v1.5.1.1 -- it gave an alarming "did not + have any patch" message (but did not actually fail and was + harmless). + + - various git-svn fixes. + + - Sample update hook incorrectly always refused requests to + delete branches through push. + + - git-blame on a very long working tree path had buffer + overrun problem. + + - Switching branches with "git checkout" refused to work when + a path changes from a file to a directory between the + current branch and the new branch, in order not to lose + possible local changes in the directory that is being turned + into a file with the switch. We now allow such a branch + switch after making sure that there is no locally modified + file nor un-ignored file in the directory. This has not + been backported to 1.5.1.x series, as it is rather an + intrusive change. + + - Merging branches that have a file in one and a directory in + another at the same path used to get quite confused. We + handle such a case a bit more carefully, even though that is + still left as a conflict for the user to sort out. This + will not be backported to 1.5.1.x series, as it is rather an + intrusive change. + +* Performance Tweaks + +-- +exec >/var/tmp/1 +O=v1.5.1.1-158-g86da9de +echo O=`git describe refs/heads/master` +git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 131bcff9b2..2386f496ee 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -22,6 +22,9 @@ Checklist (and a short version for the impatient): - provide additional information (which is unsuitable for the commit message) between the "---" and the diffstat - send the patch to the list _and_ the maintainer + - if you change, add, or remove a command line option or + make some other user interface change, the associated + documentation should be updated as well. Long version: diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf index 44b1ce4c6b..fa7dc94845 100644 --- a/Documentation/asciidoc.conf +++ b/Documentation/asciidoc.conf @@ -31,6 +31,25 @@ ifdef::backend-docbook[] {title#}</example> endif::backend-docbook[] +ifdef::doctype-manpage[] +ifdef::backend-docbook[] +[header] +template::[header-declarations] +<refentry> +<refmeta> +<refentrytitle>{mantitle}</refentrytitle> +<manvolnum>{manvolnum}</manvolnum> +<refmiscinfo class="source">Git</refmiscinfo> +<refmiscinfo class="version">@@GIT_VERSION@@</refmiscinfo> +<refmiscinfo class="manual">Git Manual</refmiscinfo> +</refmeta> +<refnamediv> + <refname>{manname}</refname> + <refpurpose>{manpurpose}</refpurpose> +</refnamediv> +endif::backend-docbook[] +endif::doctype-manpage[] + ifdef::backend-xhtml11[] [gitlink-inlinemacro] <a href="{target}.html">{target}{0?({0})}</a> diff --git a/Documentation/config.txt b/Documentation/config.txt index 7e41ca6a0d..2c0a666323 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -423,8 +423,34 @@ gitcvs.allbinary:: causes the client to treat all files as binary files which suppresses any newline munging it otherwise might do. A work-around for the fact that there is no way yet to set single files to mode '-kb'. + +gitcvs.dbname:: + Database used by git-cvsserver to cache revision information + derived from the git repository. The exact meaning depends on the + used database driver, for SQLite (which is the default driver) this + is a filename. Supports variable substitution (see + gitlink:git-cvsserver[1] for details). May not contain semicolons (`;`). + Default: '%Ggitcvs.%m.sqlite' + +gitcvs.dbdriver:: + Used Perl DBI driver. You can specify any available driver + for this here, but it might not work. git-cvsserver is tested + with 'DBD::SQLite', reported to work with 'DBD::Pg', and + reported *not* to work with 'DBD::mysql'. Experimental feature. + May not contain double colons (`:`). Default: 'SQLite'. See gitlink:git-cvsserver[1]. +gitcvs.dbuser, gitcvs.dbpass:: + Database user and password. Only useful if setting 'gitcvs.dbdriver', + since SQLite has no concept of database users and/or passwords. + 'gitcvs.dbuser' supports variable substitution (see + gitlink:git-cvsserver[1] for details). + +All gitcvs variables except for 'gitcvs.allbinary' can also specifed +as 'gitcvs.<access_method>.<varname>' (where 'access_method' is one +of "ext" and "pserver") to make them apply only for the given access +method. + http.sslVerify:: Whether to verify the SSL certificate when fetching or pushing over HTTPS. Can be overridden by the 'GIT_SSL_NO_VERIFY' environment diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index 493474b2ee..8d1041598e 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -30,7 +30,8 @@ OPTIONS ------- --format=<fmt>:: - Format of the resulting archive: 'tar', 'zip'... + Format of the resulting archive: 'tar', 'zip'... The default + is 'tar'. --list:: Show all available formats. diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index b2bc58d851..5f68ee1584 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -15,7 +15,7 @@ DESCRIPTION The command takes various subcommands, and different options depending on the subcommand: - git bisect start [<paths>...] + git bisect start [<bad> [<good>...]] [--] [<paths>...] git bisect bad <rev> git bisect good <rev> git bisect reset [<branch>] @@ -134,15 +134,26 @@ $ 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. -Cutting down bisection by giving path parameter to bisect start -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Cutting down bisection by giving more parameters to bisect start +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can further cut down the number of trials if you know what part of the tree is involved in the problem you are tracking down, by giving paths parameters when you say `bisect start`, like this: ------------ -$ git bisect start arch/i386 include/asm-i386 +$ git bisect start -- arch/i386 include/asm-i386 +------------ + +If you know beforehand more than one good commits, you can narrow the +bisect space down without doing the whole tree checkout every time you +give good commits. You give the bad revision immediately after `start` +and then you give all the good revisions you have: + +------------ +$ git bisect start v2.6.20-rc6 v2.6.20-rc4 v2.6.20-rc1 -- + # v2.6.20-rc6 is bad + # v2.6.20-rc4 and v2.6.20-rc1 are good ------------ Bisect run diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index f9e0c77379..d22844ba49 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -31,6 +31,10 @@ over pserver for anonymous CVS access. CVS clients cannot tag, branch or perform GIT merges. +git-cvsserver maps GIT branches to CVS modules. This is very different +from what most CVS users would expect since in CVS modules usually represent +one or more directories. + INSTALLATION ------------ @@ -65,9 +69,22 @@ env variable, you can rename git-cvsserver to cvs. ------ Note: you need to ensure each user that is going to invoke git-cvsserver has -write access to the log file and to the git repository. When offering anon -access via pserver, this means that the nobody user should have write access -to at least the sqlite database at the root of the repository. +write access to the log file and to the database (see +<<dbbackend,Database Backend>>. If you want to offer write access over +SSH, the users of course also need write access to the git repository itself. + +[[configaccessmethod]] +All configuration variables can also be overriden for a specific method of +access. Valid method names are "ext" (for SSH access) and "pserver". The +following example configuration would disable pserver access while still +allowing access over SSH. +------ + [gitcvs] + enabled=0 + + [gitcvs "ext"] + enabled=1 +------ -- 3. On the client machine you need to set the following variables. CVSROOT should be set as per normal, but the directory should point at the @@ -93,6 +110,90 @@ Example: cvs co -d project-master master ------ +[[dbbackend]] +Database Backend +---------------- + +git-cvsserver uses one database per git head (i.e. CVS module) to +store information about the repository for faster access. The +database doesn't contain any persitent data and can be completly +regenerated from the git repository at any time. The database +needs to be updated (i.e. written to) after every commit. + +If the commit is done directly by using git (as opposed to +using git-cvsserver) the update will need to happen on the +next repository access by git-cvsserver, independent of +access method and requested operation. + +That means that even if you offer only read access (e.g. by using +the pserver method), git-cvsserver should have write access to +the database to work reliably (otherwise you need to make sure +that the database if up-to-date all the time git-cvsserver is run). + +By default it uses SQLite databases in the git directory, named +`gitcvs.<module_name>.sqlite`. Note that the SQLite backend creates +temporary files in the same directory as the database file on +write so it might not be enough to grant the users using +git-cvsserver write access to the database file without granting +them write access to the directory, too. + +You can configure the database backend with the following +configuration variables: + +Configuring database backend +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +git-cvsserver uses the Perl DBI module. Please also read +its documentation if changing these variables, especially +about `DBI->connect()`. + +gitcvs.dbname:: + Database name. The exact meaning depends on the + used database driver, for SQLite this is a filename. + Supports variable substitution (see below). May + not contain semicolons (`;`). + Default: '%Ggitcvs.%m.sqlite' + +gitcvs.dbdriver:: + Used DBI driver. You can specify any available driver + for this here, but it might not work. cvsserver is tested + with 'DBD::SQLite', reported to work with + 'DBD::Pg', and reported *not* to work with 'DBD::mysql'. + Please regard this as an experimental feature. May not + contain double colons (`:`). + Default: 'SQLite' + +gitcvs.dbuser:: + Database user. Only useful if setting `dbdriver`, since + SQLite has no concept of database users. Supports variable + substitution (see below). + +gitcvs.dbpass:: + Database password. Only useful if setting `dbdriver`, since + SQLite has no concept of database passwords. + +All variables can also be set per access method, see <<configaccessmethod,above>>. + +Variable substitution +^^^^^^^^^^^^^^^^^^^^^ +In `dbdriver` and `dbuser` you can use the following variables: + +%G:: + git directory name +%g:: + git directory name, where all characters except for + alpha-numeric ones, `.`, and `-` are replaced with + `_` (this should make it easier to use the directory + name in a filename if wanted) +%m:: + CVS module/git head name +%a:: + access method (one of "ext" or "pserver") +%u:: + Name of the user running git-cvsserver. + If no name can be determined, the + numeric uid is used. + Eclipse CVS Client Notes ------------------------ diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 111d7c60bf..a33d157b97 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -10,11 +10,12 @@ SYNOPSIS -------- [verse] 'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--thread] - [--attach[=<boundary>] | --inline[=<boundary>]] - [-s | --signoff] [<common diff options>] [--start-number <n>] - [--in-reply-to=Message-Id] [--suffix=.<sfx>] - [--ignore-if-in-upstream] - <since>[..<until>] + [--attach[=<boundary>] | --inline[=<boundary>]] + [-s | --signoff] [<common diff options>] [--start-number <n>] + [--in-reply-to=Message-Id] [--suffix=.<sfx>] + [--ignore-if-in-upstream] + [--subject-prefix=Subject-Prefix] + <since>[..<until>] DESCRIPTION ----------- @@ -98,6 +99,12 @@ include::diff-options.txt[] patches being generated, and any patch that matches is ignored. +--subject-prefix=<Subject-Prefix>:: + Instead of the standard '[PATCH]' prefix in the subject + line, instead use '[<Subject-Prefix>]'. This + allows for useful naming of a patch series, and can be + combined with the --numbered option. + --suffix=.<sfx>:: Instead of using `.patch` as the suffix for generated filenames, use specifed suffix. A common alternative is diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index 058009d2fa..8c68cf0372 100644 --- a/Documentation/git-fsck.txt +++ b/Documentation/git-fsck.txt @@ -9,7 +9,7 @@ git-fsck - Verifies the connectivity and validity of the objects in the database SYNOPSIS -------- [verse] -'git-fsck' [--tags] [--root] [--unreachable] [--cache] +'git-fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs] [--full] [--strict] [<object>*] DESCRIPTION @@ -38,6 +38,12 @@ index file and all SHA1 references in .git/refs/* as heads. Consider any object recorded in the index also as a head node for an unreachability trace. +--no-reflogs:: + Do not consider commits that are referenced only by an + entry in a reflog to be reachable. This option is meant + only to search for commits that used to be in a ref, but + now aren't, but are still in that corresponding reflog. + --full:: Check not just objects in GIT_OBJECT_DIRECTORY ($GIT_DIR/objects), but also the ones found in alternate diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 0ff2890c7f..019c8bef7a 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index SYNOPSIS -------- -'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]) +'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]) DESCRIPTION @@ -86,6 +86,18 @@ OPTIONS file (usually '.gitignore') and allows such an untracked but explicitly ignored file to be overwritten. +--index-output=<file>:: + Instead of writing the results out to `$GIT_INDEX_FILE`, + write the resulting index in the named file. While the + command is operating, the original index file is locked + with the same mechanism as usual. The file must allow + to be rename(2)ed into from a temporary file that is + created next to the usual index file; typically this + means it needs to be on the same filesystem as the index + file itself, and you need write permission to the + directories the index file and index output file are + located in. + <tree-ish#>:: The id of the tree object(s) to be read/merged. diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 11ce395c98..77e068b15f 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -22,11 +22,13 @@ SYNOPSIS [ \--topo-order ] [ \--parents ] [ \--left-right ] + [ \--cherry-pick ] [ \--encoding[=<encoding>] ] [ \--(author|committer|grep)=<pattern> ] [ [\--objects | \--objects-edge] [ \--unpacked ] ] [ \--pretty | \--header ] [ \--bisect ] + [ \--bisect-vars ] [ \--merge ] [ \--reverse ] [ \--walk-reflogs ] @@ -223,6 +225,20 @@ limiting may be applied. In addition to the '<commit>' listed on the command line, read them from the standard input. +--cherry-pick:: + + Omit any commit that introduces the same change as + another commit on the "other side" when the set of + commits are limited with symmetric difference. ++ +For example, if you have two branches, `A` and `B`, a usual way +to list all commits on only one side of them is with +`--left-right`, like the example above in the description of +that option. It however shows the commits that were cherry-picked +from the other branch (for example, "3rd on b" may be cherry-picked +from branch A). With this option, such pairs of commits are +excluded from the output. + -g, --walk-reflogs:: Instead of walking the commit ancestry chain, walk @@ -280,6 +296,18 @@ introduces a regression is thus reduced to a binary search: repeatedly generate and test new 'midpoint's until the commit chain is of length one. +--bisect-vars:: + +This calculates the same as `--bisect`, but outputs text ready +to be eval'ed by the shell. These lines will assign the name of +the midpoint revision to the variable `bisect_rev`, and the +expected number of commits to be tested after `bisect_rev` is +tested to `bisect_nr`, the expected number of commits to be +tested if `bisect_rev` turns out to be good to `bisect_good`, +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`. + -- Commit Ordering diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt index 6feebc0400..a65f24a0f6 100644 --- a/Documentation/git-rm.txt +++ b/Documentation/git-rm.txt @@ -7,7 +7,7 @@ git-rm - Remove files from the working tree and from the index SYNOPSIS -------- -'git-rm' [-f] [-n] [-r] [--cached] [--] <file>... +'git-rm' [-f] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <file>... DESCRIPTION ----------- @@ -47,6 +47,13 @@ OPTIONS the paths only from the index, leaving working tree files. +\--ignore-unmatch:: + Exit with a zero status even if no files matched. + +\--quiet:: + git-rm normally outputs one line (in the form of an "rm" command) + for each file removed. This option suppresses that output. + DISCUSSION ---------- diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 2fe6c31967..d7ffc21ddf 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -117,6 +117,7 @@ The placeholders are: - '%Cgreen': switch color to green - '%Cblue': switch color to blue - '%Creset': reset color +- '%m': left, right or boundary mark - '%n': newline @@ -110,6 +110,14 @@ all:: # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's # MakeMaker (e.g. using ActiveState under Cygwin). # +# Define WITH_P4IMPORT to build and install Python git-p4import script. +# +# Define NO_TCLTK if you do not want Tcl/Tk GUI. +# +# The TCLTK_PATH variable governs the location of the Tck/Tk interpreter. +# If not set it defaults to the bare 'wish'. If it is set to the empty +# string then NO_TCLTK will be forced (this is used by configure script). +# GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -159,6 +167,7 @@ AR = ar TAR = tar INSTALL = install RPMBUILD = rpmbuild +TCLTK_PATH = wish # sparse is architecture-neutral, which means that we need to tell it # explicitly what architecture to check for. Fix this up for yours.. @@ -196,9 +205,20 @@ SCRIPT_PERL = \ git-svnimport.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl +SCRIPT_PYTHON = \ + git-p4import.py + +ifdef WITH_P4IMPORT SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ + $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ git-status git-instaweb +else +SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ + $(patsubst %.perl,%,$(SCRIPT_PERL)) \ + git-status git-instaweb +endif + # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS = \ @@ -231,6 +251,14 @@ BUILT_INS = \ # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) +ALL_PROGRAMS += git-merge-subtree$X + +# what 'all' will build but not install in gitexecdir +OTHER_PROGRAMS = git$X gitweb/gitweb.cgi +ifndef NO_TCLTK +OTHER_PROGRAMS += gitk-wish +endif + # Backward compatibility -- to be removed after 1.0 PROGRAMS += git-ssh-pull$X git-ssh-push$X @@ -241,6 +269,9 @@ endif ifndef PERL_PATH PERL_PATH = /usr/bin/perl endif +ifndef PYTHON_PATH + PYTHON_PATH = /usr/local/bin/python +endif export PERL_PATH @@ -252,7 +283,7 @@ LIB_H = \ diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.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 + utf8.h reflog-walk.h patch-ids.h decorate.h DIFF_OBJS = \ diff.o diff-lib.o diffcore-break.o diffcore-order.o \ @@ -264,16 +295,17 @@ LIB_OBJS = \ date.o diff-delta.o entry.o exec_cmd.o ident.o \ interpolate.o \ lockfile.o \ + patch-ids.o \ object.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \ reachable.o reflog-walk.o \ quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ revision.o pager.o tree-walk.o xdiff-interface.o \ - write_or_die.o trace.o list-objects.o grep.o \ + write_or_die.o trace.o list-objects.o grep.o match-trees.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \ - convert.o + convert.o decorate.o BUILTIN_OBJS = \ builtin-add.o \ @@ -609,7 +641,11 @@ ifdef NO_PERL_MAKEMAKER export NO_PERL_MAKEMAKER endif -QUIET_SUBDIR0 = $(MAKE) -C # space to separate -C and subdir +ifeq ($(TCLTK_PATH),) +NO_TCLTK=NoThanks +endif + +QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir QUIET_SUBDIR1 = ifneq ($(findstring $(MAKEFLAGS),w),w) @@ -625,7 +661,7 @@ ifndef V QUIET_LINK = @echo ' ' LINK $@; QUIET_BUILT_IN = @echo ' ' BUILTIN $@; QUIET_GEN = @echo ' ' GEN $@; - QUIET_SUBDIR0 = @subdir= + QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir export V @@ -647,6 +683,8 @@ prefix_SQ = $(subst ','\'',$(prefix)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) +PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH)) +TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) LIBS = $(GITLIBS) $(EXTLIBS) @@ -662,19 +700,27 @@ export prefix gitexecdir TAR INSTALL DESTDIR SHELL_PATH template_dir ### Build rules -all:: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi +all:: $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) ifneq (,$X) $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), rm -f '$p';) endif all:: - $(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) all +ifndef NO_TCLTK + $(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) TCLTK_PATH='$(TCLTK_PATH_SQ)' all +endif $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) strip: $(PROGRAMS) git$X $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X +gitk-wish: gitk GIT-GUI-VARS + $(QUIET_GEN)rm -f $@ $@+ && \ + sed -e '1,3s|^exec .* "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' <gitk >$@+ && \ + chmod +x $@+ && \ + mv -f $@+ $@ + git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS $(QUIET_LINK)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \ $(ALL_CFLAGS) -o $@ $(filter %.c,$^) \ @@ -682,6 +728,9 @@ git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS help.o: common-cmds.h +git-merge-subtree$X: git-merge-recursive$X + $(QUIET_BUILT_IN)rm -f $@ && ln git-merge-recursive$X $@ + $(BUILT_INS): git$X $(QUIET_BUILT_IN)rm -f $@ && ln git$X $@ @@ -700,6 +749,15 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak +$(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py + rm -f $@ $@+ + sed -e '1s|#!.*/python|#!$(PYTHON_PATH_SQ)|' \ + -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ + -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ + $@.py >$@+ + chmod +x $@+ + mv $@+ $@ + perl/perl.mak: GIT-CFLAGS $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F) @@ -853,6 +911,20 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS echo "$$FLAGS" >GIT-CFLAGS; \ fi +### Detect Tck/Tk interpreter path changes +ifndef NO_TCLTK +TRACK_VARS = $(subst ','\'',-DTCLTK_PATH='$(TCLTK_PATH_SQ)') + +GIT-GUI-VARS: .FORCE-GIT-GUI-VARS + @VARS='$(TRACK_VARS)'; \ + if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \ + echo 1>&2 " * new Tcl/Tk interpreter location"; \ + echo "$$VARS" >$@; \ + fi + +.PHONY: .FORCE-GIT-GUI-VARS +endif + ### Testing rules # GNU make supports exporting all variables by "export" without parameters. @@ -876,6 +948,9 @@ test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS) test-sha1$X: test-sha1.o $(GITLIBS) $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) +test-match-trees$X: test-match-trees.o $(GITLIBS) + $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) + test-chmtime$X: test-chmtime.c $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $< @@ -893,10 +968,13 @@ install: all $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(bindir_SQ)' $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)' - $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)' + $(INSTALL) git$X '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install $(MAKE) -C perl prefix='$(prefix_SQ)' install +ifndef NO_TCLTK + $(INSTALL) gitk-wish '$(DESTDIR_SQ)$(bindir_SQ)'/gitk $(MAKE) -C git-gui install +endif if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \ then \ ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \ @@ -975,10 +1053,13 @@ clean: rm -f gitweb/gitweb.cgi $(MAKE) -C Documentation/ clean $(MAKE) -C perl clean - $(MAKE) -C git-gui clean $(MAKE) -C templates/ clean $(MAKE) -C t/ clean - rm -f GIT-VERSION-FILE GIT-CFLAGS +ifndef NO_TCLTK + rm -f gitk-wish + $(MAKE) -C git-gui clean +endif + rm -f GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS .PHONY: all install clean strip .PHONY: .FORCE-GIT-VERSION-FILE TAGS tags .FORCE-GIT-CFLAGS @@ -1 +1 @@ -Documentation/RelNotes-1.5.1.2.txt
\ No newline at end of file +Documentation/RelNotes-1.5.2.txt
\ No newline at end of file diff --git a/builtin-add.c b/builtin-add.c index 9fcf514dbc..9ec292590c 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -87,7 +87,7 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec) } /* Read the directory and prune it */ - read_directory(dir, path, base, baselen); + read_directory(dir, path, base, baselen, pathspec); if (pathspec) prune_directory(dir, pathspec, baselen); } @@ -133,7 +133,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) git_config(git_add_config); - newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); + newfd = hold_locked_index(&lock_file, 1); for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -205,11 +205,11 @@ int cmd_add(int argc, const char **argv, const char *prefix) } for (i = 0; i < dir.nr; i++) - add_file_to_index(dir.entries[i]->name, verbose); + add_file_to_cache(dir.entries[i]->name, verbose); if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || - close(newfd) || commit_lock_file(&lock_file)) + close(newfd) || commit_locked_index(&lock_file)) die("Unable to write new index file"); } diff --git a/builtin-apply.c b/builtin-apply.c index db52722455..94311e7af4 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -30,7 +30,7 @@ static int unidiff_zero; static int p_value = 1; static int p_value_known; static int check_index; -static int write_index; +static int update_index; static int cached; static int diffstat; static int numstat; @@ -2308,7 +2308,7 @@ static void patch_stats(struct patch *patch) static void remove_file(struct patch *patch, int rmdir_empty) { - if (write_index) { + if (update_index) { if (remove_file_from_cache(patch->old_name) < 0) die("unable to remove %s from index", patch->old_name); cache_tree_invalidate_path(active_cache_tree, patch->old_name); @@ -2335,7 +2335,7 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned int namelen = strlen(path); unsigned ce_size = cache_entry_size(namelen); - if (!write_index) + if (!update_index) return; ce = xcalloc(1, ce_size); @@ -2661,10 +2661,10 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof) if (whitespace_error && (new_whitespace == error_on_whitespace)) apply = 0; - write_index = check_index && apply; - if (write_index && newfd < 0) - newfd = hold_lock_file_for_update(&lock_file, - get_index_file(), 1); + update_index = check_index && apply; + if (update_index && newfd < 0) + newfd = hold_locked_index(&lock_file, 1); + if (check_index) { if (read_cache() < 0) die("unable to read index file"); @@ -2869,9 +2869,9 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) whitespace_error == 1 ? "s" : ""); } - if (write_index) { + if (update_index) { if (write_cache(newfd, active_cache, active_nr) || - close(newfd) || commit_lock_file(&lock_file)) + close(newfd) || commit_locked_index(&lock_file)) die("Unable to write new index file"); } diff --git a/builtin-archive.c b/builtin-archive.c index 8ea6cb1efc..7f4e409c99 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -149,7 +149,7 @@ int parse_archive_args(int argc, const char **argv, struct archiver *ar) { const char *extra_argv[MAX_EXTRA_ARGS]; int extra_argc = 0; - const char *format = NULL; /* might want to default to "tar" */ + const char *format = "tar"; const char *base = ""; int verbose = 0; int i; @@ -190,8 +190,6 @@ int parse_archive_args(int argc, const char **argv, struct archiver *ar) /* We need at least one parameter -- tree-ish */ if (argc - 1 < i) usage(archive_usage); - if (!format) - die("You must specify an archive format"); if (init_archiver(format, ar) < 0) die("Unknown archive format '%s'", format); diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c index afe4b0e452..8460f97b66 100644 --- a/builtin-checkout-index.c +++ b/builtin-checkout-index.c @@ -202,10 +202,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "-u") || !strcmp(arg, "--index")) { state.refresh_cache = 1; if (newfd < 0) - newfd = hold_lock_file_for_update - (&lock_file, get_index_file(), 1); - if (newfd < 0) - die("cannot open index.lock file."); + newfd = hold_locked_index(&lock_file, 1); continue; } if (!strcmp(arg, "-z")) { @@ -302,7 +299,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) if (0 <= newfd && (write_cache(newfd, active_cache, active_nr) || - close(newfd) || commit_lock_file(&lock_file))) + close(newfd) || commit_locked_index(&lock_file))) die("Unable to write new index file"); return 0; } diff --git a/builtin-fsck.c b/builtin-fsck.c index 7c3b0a535f..05d98d2cfc 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -14,6 +14,7 @@ static int show_root; static int show_tags; static int show_unreachable; +static int include_reflogs = 1; static int check_full; static int check_strict; static int keep_cache_objects; @@ -348,7 +349,7 @@ static int fsck_tag(struct tag *tag) return 0; } -static int fsck_sha1(unsigned char *sha1) +static int fsck_sha1(const unsigned char *sha1) { struct object *obj = parse_object(sha1); if (!obj) { @@ -517,7 +518,8 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f static void get_default_heads(void) { for_each_ref(fsck_handle_ref, NULL); - for_each_reflog(fsck_handle_reflog, NULL); + if (include_reflogs) + for_each_reflog(fsck_handle_reflog, NULL); /* * Not having any default heads isn't really fatal, but @@ -624,6 +626,10 @@ int cmd_fsck(int argc, char **argv, const char *prefix) keep_cache_objects = 1; continue; } + if (!strcmp(arg, "--no-reflogs")) { + include_reflogs = 0; + continue; + } if (!strcmp(arg, "--full")) { check_full = 1; continue; @@ -656,11 +662,8 @@ int cmd_fsck(int argc, char **argv, const char *prefix) for (p = packed_git; p; p = p->next) { uint32_t i, num = num_packed_objects(p); - for (i = 0; i < num; i++) { - unsigned char sha1[20]; - nth_packed_object_sha1(p, i, sha1); - fsck_sha1(sha1); - } + for (i = 0; i < num; i++) + fsck_sha1(nth_packed_object_sha1(p, i)); } } diff --git a/builtin-log.c b/builtin-log.c index 71df957eaa..38bf52f100 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -12,16 +12,44 @@ #include "builtin.h" #include "tag.h" #include "reflog-walk.h" +#include "patch-ids.h" +#include "refs.h" static int default_show_root = 1; /* this is in builtin-diff.c */ void add_head(struct rev_info *revs); +static void add_name_decoration(const char *prefix, const char *name, struct object *obj) +{ + int plen = strlen(prefix); + int nlen = strlen(name); + struct name_decoration *res = xmalloc(sizeof(struct name_decoration) + plen + nlen); + memcpy(res->name, prefix, plen); + memcpy(res->name + plen, name, nlen + 1); + res->next = add_decoration(&name_decoration, obj, res); +} + +static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data) +{ + struct object *obj = parse_object(sha1); + if (!obj) + return 0; + add_name_decoration("", refname, obj); + while (obj->type == OBJ_TAG) { + obj = ((struct tag *)obj)->tagged; + if (!obj) + break; + add_name_decoration("tag: ", refname, obj); + } + return 0; +} + static void cmd_log_init(int argc, const char **argv, const char *prefix, struct rev_info *rev) { int i; + int decorate = 0; rev->abbrev = DEFAULT_ABBREV; rev->commit_format = CMIT_FMT_DEFAULT; @@ -38,8 +66,11 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, git_log_output_encoding = xstrdup(arg); else git_log_output_encoding = ""; - } - else + } else if (!strcmp(arg, "--decorate")) { + if (!decorate) + for_each_ref(add_ref_decoration, NULL); + decorate = 1; + } else die("unrecognized argument: %s", arg); } } @@ -333,25 +364,12 @@ static int reopen_stdout(struct commit *commit, int nr, int keep_subject) } -static int get_patch_id(struct commit *commit, struct diff_options *options, - unsigned char *sha1) -{ - if (commit->parents) - diff_tree_sha1(commit->parents->item->object.sha1, - commit->object.sha1, "", options); - else - diff_root_tree_sha1(commit->object.sha1, "", options); - diffcore_std(options); - return diff_flush_patch_id(options, sha1); -} - -static void get_patch_ids(struct rev_info *rev, struct diff_options *options, const char *prefix) +static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const char *prefix) { struct rev_info check_rev; struct commit *commit; struct object *o1, *o2; unsigned flags1, flags2; - unsigned char sha1[20]; if (rev->pending.nr != 2) die("Need exactly one range."); @@ -364,10 +382,7 @@ static void get_patch_ids(struct rev_info *rev, struct diff_options *options, co if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING)) die("Not a range."); - diff_setup(options); - options->recursive = 1; - if (diff_setup_done(options) < 0) - die("diff_setup_done failed"); + init_patch_ids(ids); /* given a range a..b get all patch ids for b..a */ init_revisions(&check_rev, prefix); @@ -382,8 +397,7 @@ static void get_patch_ids(struct rev_info *rev, struct diff_options *options, co if (commit->parents && commit->parents->next) continue; - if (!get_patch_id(commit, options, sha1)) - created_object(sha1, xcalloc(1, sizeof(struct object))); + add_commit_patch_id(commit, ids); } /* reset for next revision walk */ @@ -417,10 +431,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int numbered = 0; int start_number = -1; int keep_subject = 0; + int subject_prefix = 0; int ignore_if_in_upstream = 0; int thread = 0; const char *in_reply_to = NULL; - struct diff_options patch_id_opts; + struct patch_ids ids; char *add_signoff = NULL; char message_id[1024]; char ref_message_id[1024]; @@ -509,8 +524,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (i == argc) die("Need a Message-Id for --in-reply-to"); in_reply_to = argv[i]; - } - else if (!prefixcmp(argv[i], "--suffix=")) + } else if (!prefixcmp(argv[i], "--subject-prefix=")) { + subject_prefix = 1; + rev.subject_prefix = argv[i] + 17; + } else if (!prefixcmp(argv[i], "--suffix=")) fmt_patch_suffix = argv[i] + 9; else argv[j++] = argv[i]; @@ -521,6 +538,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) start_number = 1; if (numbered && keep_subject) die ("-n and -k are mutually exclusive."); + if (keep_subject && subject_prefix) + die ("--subject-prefix and -k are mutually exclusive."); argc = setup_revisions(argc, argv, &rev, "HEAD"); if (argc > 1) @@ -554,22 +573,19 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } if (ignore_if_in_upstream) - get_patch_ids(&rev, &patch_id_opts, prefix); + get_patch_ids(&rev, &ids, prefix); if (!use_stdout) realstdout = fdopen(dup(1), "w"); prepare_revision_walk(&rev); while ((commit = get_revision(&rev)) != NULL) { - unsigned char sha1[20]; - /* ignore merges */ if (commit->parents && commit->parents->next) continue; if (ignore_if_in_upstream && - !get_patch_id(commit, &patch_id_opts, sha1) && - lookup_object(sha1)) + has_commit_patch_id(commit, &ids)) continue; nr++; @@ -624,6 +640,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) fclose(stdout); } free(list); + if (ignore_if_in_upstream) + free_patch_ids(&ids); return 0; } @@ -646,7 +664,7 @@ static const char cherry_usage[] = int cmd_cherry(int argc, const char **argv, const char *prefix) { struct rev_info revs; - struct diff_options patch_id_opts; + struct patch_ids ids; struct commit *commit; struct commit_list *list = NULL; const char *upstream; @@ -692,7 +710,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) return 0; } - get_patch_ids(&revs, &patch_id_opts, prefix); + get_patch_ids(&revs, &ids, prefix); if (limit && add_pending_commit(limit, &revs, UNINTERESTING)) die("Unknown commit %s", limit); @@ -708,12 +726,10 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) } while (list) { - unsigned char sha1[20]; char sign = '+'; commit = list->item; - if (!get_patch_id(commit, &patch_id_opts, sha1) && - lookup_object(sha1)) + if (has_commit_patch_id(commit, &ids)) sign = '-'; if (verbose) { @@ -731,5 +747,6 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) list = list->next; } + free_patch_ids(&ids); return 0; } diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 4e1d5af634..74a6acacc1 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -216,7 +216,7 @@ static void show_files(struct dir_struct *dir, const char *prefix) if (baselen) path = base = prefix; - read_directory(dir, path, base, baselen); + read_directory(dir, path, base, baselen, pathspec); if (show_others) show_other_files(dir); if (show_killed) diff --git a/builtin-mv.c b/builtin-mv.c index 737af350b8..3563216aca 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -77,7 +77,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) git_config(git_default_config); - newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); + newfd = hold_locked_index(&lock_file, 1); if (read_cache() < 0) die("index file corrupt"); @@ -273,7 +273,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) for (i = 0; i < added.nr; i++) { const char *path = added.items[i].path; - add_file_to_index(path, verbose); + add_file_to_cache(path, verbose); } for (i = 0; i < deleted.nr; i++) { @@ -285,7 +285,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || close(newfd) || - commit_lock_file(&lock_file)) + commit_locked_index(&lock_file)) die("Unable to write new index file"); } } diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index b5f9648e80..45ac3e482a 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -222,7 +222,7 @@ static const unsigned char *find_packed_object_name(struct packed_git *p, off_t ofs) { struct revindex_entry *entry = find_packed_object(p, ofs); - return ((unsigned char *)p->index_data) + 4 * 256 + 24 * entry->nr + 4; + return nth_packed_object_sha1(p, entry->nr); } static void *delta_against(void *buf, unsigned long size, struct object_entry *entry) diff --git a/builtin-push.c b/builtin-push.c index 70b1168fa6..cb78401c94 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -297,7 +297,7 @@ static int read_config(const char *repo, const char *uri[MAX_URI]) static int do_push(const char *repo) { const char *uri[MAX_URI]; - int i, n; + int i, n, errs; int common_argc; const char **argv; int argc; @@ -317,6 +317,7 @@ static int do_push(const char *repo) argv[argc++] = receivepack; common_argc = argc; + errs = 0; for (i = 0; i < n; i++) { int err; int dest_argc = common_argc; @@ -339,21 +340,23 @@ static int do_push(const char *repo) err = run_command_v_opt(argv, RUN_GIT_CMD); if (!err) continue; + + error("failed to push to '%s'", uri[i]); switch (err) { case -ERR_RUN_COMMAND_FORK: - die("unable to fork for %s", sender); + error("unable to fork for %s", sender); case -ERR_RUN_COMMAND_EXEC: - die("unable to exec %s", sender); + error("unable to exec %s", sender); + break; case -ERR_RUN_COMMAND_WAITPID: case -ERR_RUN_COMMAND_WAITPID_WRONG_PID: case -ERR_RUN_COMMAND_WAITPID_SIGNAL: case -ERR_RUN_COMMAND_WAITPID_NOEXIT: - die("%s died with strange error", sender); - default: - return -err; + error("%s died with strange error", sender); } + errs++; } - return 0; + return !!errs; } int cmd_push(int argc, const char **argv, const char *prefix) diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 793eae0a5f..316fb0f8da 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -84,7 +84,7 @@ static void prime_cache_tree(void) } -static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] <sha1> [<sha2> [<sha3>]])"; +static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <sha1> [<sha2> [<sha3>]])"; static struct lock_file lock_file; @@ -100,7 +100,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) setup_git_directory(); git_config(git_default_config); - newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); + newfd = hold_locked_index(&lock_file, 1); git_config(git_default_config); @@ -128,6 +128,11 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) continue; } + if (!prefixcmp(arg, "--index-output=")) { + set_alternate_index_output(arg + 15); + continue; + } + /* "--prefix=<subdirectory>/" means keep the current index * entries and put the entries from the tree under the * given subdirectory. @@ -228,6 +233,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) if (0 <= pos) die("file '%.*s' already exists.", pfxlen-1, opts.prefix); + opts.pos = -1 - pos; } if (opts.merge) { @@ -267,7 +273,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) } if (write_cache(newfd, active_cache, active_nr) || - close(newfd) || commit_lock_file(&lock_file)) + close(newfd) || commit_locked_index(&lock_file)) die("unable to write new index file"); return 0; } diff --git a/builtin-rev-list.c b/builtin-rev-list.c index b86e7ca8b1..09774f9559 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -37,7 +37,8 @@ static const char rev_list_usage[] = " --abbrev-commit\n" " --left-right\n" " special purpose:\n" -" --bisect" +" --bisect\n" +" --bisect-vars" ; static struct rev_info revs; @@ -169,38 +170,273 @@ static void clear_distance(struct commit_list *list) } } -static struct commit_list *find_bisection(struct commit_list *list) +#define DEBUG_BISECT 0 + +static inline int weight(struct commit_list *elem) { - int nr, closest; - struct commit_list *p, *best; + return *((int*)(elem->item->util)); +} - nr = 0; - p = list; - while (p) { - if (!revs.prune_fn || (p->item->object.flags & TREECHANGE)) - nr++; - p = p->next; +static inline void weight_set(struct commit_list *elem, int weight) +{ + *((int*)(elem->item->util)) = weight; +} + +static int count_interesting_parents(struct commit *commit) +{ + struct commit_list *p; + int count; + + for (count = 0, p = commit->parents; p; p = p->next) { + if (p->item->object.flags & UNINTERESTING) + continue; + count++; } - closest = -1; - best = list; + return count; +} + +static inline int halfway(struct commit_list *p, int distance, int nr) +{ + /* + * Don't short-cut something we are not going to return! + */ + if (revs.prune_fn && !(p->item->object.flags & TREECHANGE)) + return 0; + if (DEBUG_BISECT) + return 0; + /* + * 2 and 3 are halfway of 5. + * 3 is halfway of 6 but 2 and 4 are not. + */ + distance *= 2; + switch (distance - nr) { + case -1: case 0: case 1: + return 1; + default: + return 0; + } +} + +#if !DEBUG_BISECT +#define show_list(a,b,c,d) do { ; } while (0) +#else +static void show_list(const char *debug, int counted, int nr, + struct commit_list *list) +{ + struct commit_list *p; + + fprintf(stderr, "%s (%d/%d)\n", debug, counted, nr); for (p = list; p; p = p->next) { - int distance; + struct commit_list *pp; + struct commit *commit = p->item; + unsigned flags = commit->object.flags; + enum object_type type; + unsigned long size; + char *buf = read_sha1_file(commit->object.sha1, &type, &size); + char *ep, *sp; + + fprintf(stderr, "%c%c%c ", + (flags & TREECHANGE) ? 'T' : ' ', + (flags & UNINTERESTING) ? 'U' : ' ', + (flags & COUNTED) ? 'C' : ' '); + if (commit->util) + fprintf(stderr, "%3d", weight(p)); + else + fprintf(stderr, "---"); + fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.sha1)); + for (pp = commit->parents; pp; pp = pp->next) + fprintf(stderr, " %.*s", 8, + sha1_to_hex(pp->item->object.sha1)); + + sp = strstr(buf, "\n\n"); + if (sp) { + sp += 2; + for (ep = sp; *ep && *ep != '\n'; ep++) + ; + fprintf(stderr, " %.*s", (int)(ep - sp), sp); + } + fprintf(stderr, "\n"); + } +} +#endif /* DEBUG_BISECT */ + +/* + * zero or positive weight is the number of interesting commits it can + * reach, including itself. Especially, weight = 0 means it does not + * reach any tree-changing commits (e.g. just above uninteresting one + * but traversal is with pathspec). + * + * weight = -1 means it has one parent and its distance is yet to + * be computed. + * + * weight = -2 means it has more than one parent and its distance is + * unknown. After running count_distance() first, they will get zero + * or positive distance. + */ + +static struct commit_list *find_bisection(struct commit_list *list, + int *reaches, int *all) +{ + int n, nr, on_list, counted, distance; + struct commit_list *p, *best, *next, *last; + int *weights; + + show_list("bisection 2 entry", 0, 0, list); - if (revs.prune_fn && !(p->item->object.flags & TREECHANGE)) + /* + * Count the number of total and tree-changing items on the + * list, while reversing the list. + */ + for (nr = on_list = 0, last = NULL, p = list; + p; + p = next) { + unsigned flags = p->item->object.flags; + + next = p->next; + if (flags & UNINTERESTING) continue; + p->next = last; + last = p; + if (!revs.prune_fn || (flags & TREECHANGE)) + nr++; + on_list++; + } + list = last; + show_list("bisection 2 sorted", 0, nr, list); + + *all = nr; + weights = xcalloc(on_list, sizeof(int*)); + counted = 0; + + for (n = 0, p = list; p; p = p->next) { + struct commit *commit = p->item; + unsigned flags = commit->object.flags; + + p->item->util = &weights[n++]; + switch (count_interesting_parents(commit)) { + case 0: + if (!revs.prune_fn || (flags & TREECHANGE)) { + weight_set(p, 1); + counted++; + show_list("bisection 2 count one", + counted, nr, list); + } + /* + * otherwise, it is known not to reach any + * tree-changing commit and gets weight 0. + */ + break; + case 1: + weight_set(p, -1); + break; + default: + weight_set(p, -2); + break; + } + } + show_list("bisection 2 initialize", counted, nr, list); + + /* + * If you have only one parent in the resulting set + * then you can reach one commit more than that parent + * can reach. So we do not have to run the expensive + * count_distance() for single strand of pearls. + * + * However, if you have more than one parents, you cannot + * just add their distance and one for yourself, since + * they usually reach the same ancestor and you would + * end up counting them twice that way. + * + * So we will first count distance of merges the usual + * way, and then fill the blanks using cheaper algorithm. + */ + for (p = list; p; p = p->next) { + if (p->item->object.flags & UNINTERESTING) + continue; + n = weight(p); + if (n != -2) + continue; distance = count_distance(p); clear_distance(list); + weight_set(p, distance); + + /* Does it happen to be at exactly half-way? */ + if (halfway(p, distance, nr)) { + p->next = NULL; + *reaches = distance; + free(weights); + return p; + } + counted++; + } + + show_list("bisection 2 count_distance", counted, nr, list); + + while (counted < nr) { + for (p = list; p; p = p->next) { + struct commit_list *q; + unsigned flags = p->item->object.flags; + + if (0 <= weight(p)) + continue; + for (q = p->item->parents; q; q = q->next) { + if (q->item->object.flags & UNINTERESTING) + continue; + if (0 <= weight(q)) + break; + } + if (!q) + continue; + + /* + * weight for p is unknown but q is known. + * add one for p itself if p is to be counted, + * otherwise inherit it from q directly. + */ + if (!revs.prune_fn || (flags & TREECHANGE)) { + weight_set(p, weight(q)+1); + counted++; + show_list("bisection 2 count one", + counted, nr, list); + } + else + weight_set(p, weight(q)); + + /* Does it happen to be at exactly half-way? */ + distance = weight(p); + if (halfway(p, distance, nr)) { + p->next = NULL; + *reaches = distance; + free(weights); + return p; + } + } + } + + show_list("bisection 2 counted all", counted, nr, list); + + /* Then find the best one */ + counted = -1; + best = list; + for (p = list; p; p = p->next) { + unsigned flags = p->item->object.flags; + + if (revs.prune_fn && !(flags & TREECHANGE)) + continue; + distance = weight(p); if (nr - distance < distance) distance = nr - distance; - if (distance > closest) { + if (distance > counted) { best = p; - closest = distance; + counted = distance; + *reaches = weight(p); } } if (best) best->next = NULL; + free(weights); return best; } @@ -226,6 +462,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) struct commit_list *list; int i; int read_from_stdin = 0; + int bisect_show_vars = 0; git_config(git_default_config); init_revisions(&revs, prefix); @@ -248,6 +485,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) bisect_list = 1; continue; } + if (!strcmp(arg, "--bisect-vars")) { + bisect_list = 1; + bisect_show_vars = 1; + continue; + } if (!strcmp(arg, "--stdin")) { if (read_from_stdin++) die("--stdin given twice?"); @@ -286,8 +528,39 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (revs.tree_objects) mark_edges_uninteresting(revs.commits, &revs, show_edge); - if (bisect_list) - revs.commits = find_bisection(revs.commits); + if (bisect_list) { + int reaches = reaches, all = all; + + revs.commits = find_bisection(revs.commits, &reaches, &all); + if (bisect_show_vars) { + int cnt; + if (!revs.commits) + return 1; + /* + * revs.commits can reach "reaches" commits among + * "all" commits. If it is good, then there are + * (all-reaches) commits left to be bisected. + * On the other hand, if it is bad, then the set + * to bisect is "reaches". + * A bisect set of size N has (N-1) commits further + * to test, as we already know one bad one. + */ + cnt = all-reaches; + if (cnt < reaches) + cnt = reaches; + 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), + cnt - 1, + all - reaches - 1, + reaches - 1, + all); + return 0; + } + } traverse_commit_list(&revs, show_commit, show_object); diff --git a/builtin-rm.c b/builtin-rm.c index bf42003a7e..4a0bd93c8b 100644 --- a/builtin-rm.c +++ b/builtin-rm.c @@ -10,7 +10,7 @@ #include "tree-walk.h" static const char builtin_rm_usage[] = -"git-rm [-f] [-n] [-r] [--cached] [--] <file>..."; +"git-rm [-f] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <file>..."; static struct { int nr, alloc; @@ -104,13 +104,14 @@ static struct lock_file lock_file; int cmd_rm(int argc, const char **argv, const char *prefix) { int i, newfd; - int show_only = 0, force = 0, index_only = 0, recursive = 0; + int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0; + int ignore_unmatch = 0; const char **pathspec; char *seen; git_config(git_default_config); - newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); + newfd = hold_locked_index(&lock_file, 1); if (read_cache() < 0) die("index file corrupt"); @@ -132,6 +133,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix) force = 1; else if (!strcmp(arg, "-r")) recursive = 1; + else if (!strcmp(arg, "--quiet")) + quiet = 1; + else if (!strcmp(arg, "--ignore-unmatch")) + ignore_unmatch = 1; else usage(builtin_rm_usage); } @@ -153,14 +158,24 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (pathspec) { const char *match; + int seen_any = 0; for (i = 0; (match = pathspec[i]) != NULL ; i++) { - if (!seen[i]) - die("pathspec '%s' did not match any files", - match); + if (!seen[i]) { + if (!ignore_unmatch) { + die("pathspec '%s' did not match any files", + match); + } + } + else { + seen_any = 1; + } if (!recursive && seen[i] == MATCHED_RECURSIVELY) die("not removing '%s' recursively without -r", *match ? match : "."); } + + if (! seen_any) + exit(0); } /* @@ -168,7 +183,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) * must match; but the file can already been removed, since * this sequence is a natural "novice" way: * - * rm F; git fm F + * rm F; git rm F * * Further, if HEAD commit exists, "diff-index --cached" must * report no changes unless forced. @@ -187,7 +202,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix) */ for (i = 0; i < list.nr; i++) { const char *path = list.name[i]; - printf("rm '%s'\n", path); + if (!quiet) + printf("rm '%s'\n", path); if (remove_file_from_cache(path)) die("git-rm: unable to remove %s", path); @@ -220,7 +236,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || - close(newfd) || commit_lock_file(&lock_file)) + close(newfd) || commit_locked_index(&lock_file)) die("Unable to write new index file"); } diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 29343aefc8..3f93498bb7 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -4,6 +4,7 @@ #include "diff.h" #include "path-list.h" #include "revision.h" +#include "utf8.h" static const char shortlog_usage[] = "git-shortlog [-n] [-s] [<commit-id>... ]"; @@ -276,11 +277,64 @@ static void get_from_rev(struct rev_info *rev, struct path_list *list) } +static int parse_uint(char const **arg, int comma) +{ + unsigned long ul; + int ret; + char *endp; + + ul = strtoul(*arg, &endp, 10); + if (endp != *arg && *endp && *endp != comma) + return -1; + ret = (int) ul; + if (ret != ul) + return -1; + *arg = endp; + if (**arg) + (*arg)++; + return ret; +} + +static const char wrap_arg_usage[] = "-w[<width>[,<indent1>[,<indent2>]]]"; +#define DEFAULT_WRAPLEN 76 +#define DEFAULT_INDENT1 6 +#define DEFAULT_INDENT2 9 + +static void parse_wrap_args(const char *arg, int *in1, int *in2, int *wrap) +{ + arg += 2; /* skip -w */ + + *wrap = parse_uint(&arg, ','); + if (*wrap < 0) + die(wrap_arg_usage); + *in1 = parse_uint(&arg, ','); + if (*in1 < 0) + die(wrap_arg_usage); + *in2 = parse_uint(&arg, '\0'); + if (*in2 < 0) + die(wrap_arg_usage); + + if (!*wrap) + *wrap = DEFAULT_WRAPLEN; + if (!*in1) + *in1 = DEFAULT_INDENT1; + if (!*in2) + *in2 = DEFAULT_INDENT2; + if (*wrap && + ((*in1 && *wrap <= *in1) || + (*in2 && *wrap <= *in2))) + die(wrap_arg_usage); +} + int cmd_shortlog(int argc, const char **argv, const char *prefix) { struct rev_info rev; struct path_list list = { NULL, 0, 0, 1 }; int i, j, sort_by_number = 0, summary = 0; + int wrap_lines = 0; + int wrap = DEFAULT_WRAPLEN; + int in1 = DEFAULT_INDENT1; + int in2 = DEFAULT_INDENT2; /* since -n is a shadowed rev argument, parse our args first */ while (argc > 1) { @@ -289,6 +343,10 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[1], "-s") || !strcmp(argv[1], "--summary")) summary = 1; + else if (!prefixcmp(argv[1], "-w")) { + wrap_lines = 1; + parse_wrap_args(argv[1], &in1, &in2, &wrap); + } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) usage(shortlog_usage); else @@ -323,9 +381,18 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) printf("%s: %d\n", list.items[i].path, onelines->nr); } else { printf("%s (%d):\n", list.items[i].path, onelines->nr); - for (j = onelines->nr - 1; j >= 0; j--) - printf(" %s\n", onelines->items[j].path); - printf("\n"); + for (j = onelines->nr - 1; j >= 0; j--) { + const char *msg = onelines->items[j].path; + + if (wrap_lines) { + int col = print_wrapped_text(msg, in1, in2, wrap); + if (col != wrap) + putchar('\n'); + } + else + printf(" %s\n", msg); + } + putchar('\n'); } onelines->strdup_paths = 1; diff --git a/builtin-update-index.c b/builtin-update-index.c index 8659800eec..e5541df284 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -60,7 +60,7 @@ static int mark_valid(const char *path) return -1; } -static int add_file_to_cache(const char *path) +static int process_file(const char *path) { int size, namelen, option, status; struct cache_entry *ce; @@ -210,7 +210,7 @@ static void update_one(const char *path, const char *prefix, int prefix_length) report("remove '%s'", path); goto free_return; } - if (add_file_to_cache(p)) + if (process_file(p)) die("Unable to process file %s", path); report("add '%s'", path); free_return: @@ -499,7 +499,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) /* We can't free this memory, it becomes part of a linked list parsed atexit() */ lock_file = xcalloc(1, sizeof(struct lock_file)); - newfd = hold_lock_file_for_update(lock_file, get_index_file(), 0); + newfd = hold_locked_index(lock_file, 0); if (newfd < 0) lock_error = errno; @@ -665,7 +665,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) get_index_file(), strerror(lock_error)); } if (write_cache(newfd, active_cache, active_nr) || - close(newfd) || commit_lock_file(lock_file)) + close(newfd) || commit_locked_index(lock_file)) die("Unable to write new index file"); } diff --git a/builtin-write-tree.c b/builtin-write-tree.c index 90fc1cfcf4..c88bbd1b9b 100644 --- a/builtin-write-tree.c +++ b/builtin-write-tree.c @@ -18,7 +18,7 @@ int write_tree(unsigned char *sha1, int missing_ok, const char *prefix) /* We can't free this memory, it becomes part of a linked list parsed atexit() */ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - newfd = hold_lock_file_for_update(lock_file, get_index_file(), 0); + newfd = hold_locked_index(lock_file, 1); entries = read_cache(); if (entries < 0) @@ -128,7 +128,6 @@ static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned in extern struct cache_entry **active_cache; extern unsigned int active_nr, active_alloc, active_cache_changed; extern struct cache_tree *active_cache_tree; -extern int cache_errno; enum object_type { OBJ_BAD = -1, @@ -188,7 +187,7 @@ extern int add_cache_entry(struct cache_entry *ce, int option); extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really); extern int remove_cache_entry_at(int pos); extern int remove_file_from_cache(const char *path); -extern int add_file_to_index(const char *path, int verbose); +extern int add_file_to_cache(const char *path, int verbose); extern int ce_same_name(struct cache_entry *a, struct cache_entry *b); extern int ce_match_stat(struct cache_entry *ce, struct stat *st, int); extern int ce_modified(struct cache_entry *ce, struct stat *st, int); @@ -212,6 +211,11 @@ struct lock_file { }; extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); extern int commit_lock_file(struct lock_file *); + +extern int hold_locked_index(struct lock_file *, int); +extern int commit_locked_index(struct lock_file *); +extern void set_alternate_index_output(const char *); + extern void rollback_lock_file(struct lock_file *); extern int delete_ref(const char *, const unsigned char *sha1); @@ -428,7 +432,7 @@ extern unsigned char* use_pack(struct packed_git *, struct pack_window **, off_t extern void unuse_pack(struct pack_window **); extern struct packed_git *add_packed_git(const char *, int, int); extern uint32_t num_packed_objects(const struct packed_git *p); -extern int nth_packed_object_sha1(const struct packed_git *, uint32_t, unsigned char*); +extern const unsigned char *nth_packed_object_sha1(const struct packed_git *, uint32_t); extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *); extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *); extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); @@ -492,4 +496,7 @@ extern void trace_argv_printf(const char **argv, int count, const char *format, extern int convert_to_git(const char *path, char **bufp, unsigned long *sizep); extern int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep); +/* match-trees.c */ +void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int); + #endif /* CACHE_H */ @@ -4,6 +4,8 @@ #include "pkt-line.h" #include "utf8.h" #include "interpolate.h" +#include "diff.h" +#include "revision.h" int save_commit_buffer = 1; @@ -808,7 +810,8 @@ static long format_commit_message(const struct commit *commit, { "%Cgreen" }, /* green */ { "%Cblue" }, /* blue */ { "%Creset" }, /* reset color */ - { "%n" } /* newline */ + { "%n" }, /* newline */ + { "%m" }, /* left/right/bottom */ }; enum interp_index { IHASH = 0, IHASH_ABBREV, @@ -824,14 +827,15 @@ static long format_commit_message(const struct commit *commit, ISUBJECT, IBODY, IRED, IGREEN, IBLUE, IRESET_COLOR, - INEWLINE + INEWLINE, + ILEFT_RIGHT, }; struct commit_list *p; char parents[1024]; int i; enum { HEADER, SUBJECT, BODY } state; - if (INEWLINE + 1 != ARRAY_SIZE(table)) + if (ILEFT_RIGHT + 1 != ARRAY_SIZE(table)) die("invalid interp table!"); /* these are independent of the commit */ @@ -852,6 +856,12 @@ static long format_commit_message(const struct commit *commit, interp_set_entry(table, ITREE_ABBREV, find_unique_abbrev(commit->tree->object.sha1, DEFAULT_ABBREV)); + interp_set_entry(table, ILEFT_RIGHT, + (commit->object.flags & BOUNDARY) + ? "-" + : (commit->object.flags & SYMMETRIC_LEFT) + ? "<" + : ">"); parents[1] = 0; for (i = 0, p = commit->parents; @@ -3,6 +3,7 @@ #include "object.h" #include "tree.h" +#include "decorate.h" struct commit_list { struct commit *item; @@ -21,6 +22,13 @@ struct commit { extern int save_commit_buffer; extern const char *commit_type; +/* While we can decorate any object with a name, it's only used for commits.. */ +extern struct decoration name_decoration; +struct name_decoration { + struct name_decoration *next; + char name[1]; +}; + struct commit *lookup_commit(const unsigned char *sha1); struct commit *lookup_commit_reference(const unsigned char *sha1); struct commit *lookup_commit_reference_gently(const unsigned char *sha1, diff --git a/config.mak.in b/config.mak.in index 9a578405d8..eb9d7a5549 100644 --- a/config.mak.in +++ b/config.mak.in @@ -6,6 +6,7 @@ CFLAGS = @CFLAGS@ AR = @AR@ TAR = @TAR@ #INSTALL = @INSTALL@ # needs install-sh or install.sh in sources +TCLTK_PATH = @TCLTK_PATH@ prefix = @prefix@ exec_prefix = @exec_prefix@ diff --git a/configure.ac b/configure.ac index 3a8e778def..50d2b85ace 100644 --- a/configure.ac +++ b/configure.ac @@ -75,6 +75,14 @@ GIT_ARG_SET_PATH(shell) # Define PERL_PATH to provide path to Perl. GIT_ARG_SET_PATH(perl) # +# Declare the with-tcltk/without-tcltk options. +AC_ARG_WITH(tcltk, +AS_HELP_STRING([--with-tcltk],[use Tcl/Tk GUI (default is YES)]) +AS_HELP_STRING([],[ARG is the full path to the Tcl/Tk interpreter.]) +AS_HELP_STRING([],[Bare --with-tcltk will make the GUI part only if]) +AS_HELP_STRING([],[Tcl/Tk interpreter will be found in a system.]),\ +GIT_PARSE_WITH(tcltk)) +# ## Checks for programs. @@ -84,6 +92,22 @@ AC_PROG_CC([cc gcc]) #AC_PROG_INSTALL # needs install-sh or install.sh in sources AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) +# TCLTK_PATH will be set to some value if we want Tcl/Tk +# or will be empty otherwise. +if test -z "$NO_TCLTK"; then + if test "$with_tcltk" = ""; then + # No Tcl/Tk switches given. Do not check for Tcl/Tk, use bare 'wish'. + TCLTK_PATH=wish + AC_SUBST(TCLTK_PATH) + elif test "$with_tcltk" = "yes"; then + # Tcl/Tk check requested. + AC_CHECK_PROGS(TCLTK_PATH, [wish], ) + else + AC_MSG_RESULT([Using Tcl/Tk interpreter $with_tcltk]) + TCLTK_PATH="$with_tcltk" + AC_SUBST(TCLTK_PATH) + fi +fi ## Checks for libraries. AC_MSG_NOTICE([CHECKS for libraries]) diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el index 64ad50b327..bb671d561e 100644 --- a/contrib/emacs/git-blame.el +++ b/contrib/emacs/git-blame.el @@ -8,8 +8,8 @@ ;; License: GPL ;; Keywords: git, version control, release management ;; -;; Compatibility: Emacs21 - +;; Compatibility: Emacs21, Emacs22 and EmacsCVS +;; Git 1.5 and up ;; This file is *NOT* part of GNU Emacs. ;; This file is distributed under the same terms as GNU Emacs. @@ -61,8 +61,9 @@ ;;; Compatibility: ;; -;; It requires GNU Emacs 21. If you'are using Emacs 20, try -;; changing this: +;; It requires GNU Emacs 21 or later and Git 1.5.0 and up +;; +;; If you'are using Emacs 20, try changing this: ;; ;; (overlay-put ovl 'face (list :background ;; (cdr (assq 'color (cddddr info))))) @@ -77,30 +78,51 @@ ;; ;;; Code: -(require 'cl) ; to use `push', `pop' - -(defun color-scale (l) - (let* ((colors ()) - r g b) - (setq r l) - (while r - (setq g l) - (while g - (setq b l) - (while b - (push (concat "#" (car r) (car g) (car b)) colors) - (pop b)) - (pop g)) - (pop r)) - colors)) +(eval-when-compile (require 'cl)) ; to use `push', `pop' + + +(defun git-blame-color-scale (&rest elements) + "Given a list, returns a list of triples formed with each +elements of the list. + +a b => bbb bba bab baa abb aba aaa aab" + (let (result) + (dolist (a elements) + (dolist (b elements) + (dolist (c elements) + (setq result (cons (format "#%s%s%s" a b c) result))))) + result)) + +;; (git-blame-color-scale "0c" "04" "24" "1c" "2c" "34" "14" "3c") => +;; ("#3c3c3c" "#3c3c14" "#3c3c34" "#3c3c2c" "#3c3c1c" "#3c3c24" +;; "#3c3c04" "#3c3c0c" "#3c143c" "#3c1414" "#3c1434" "#3c142c" ...) + +(defmacro git-blame-random-pop (l) + "Select a random element from L and returns it. Also remove +selected element from l." + ;; only works on lists with unique elements + `(let ((e (elt ,l (random (length ,l))))) + (setq ,l (remove e ,l)) + e)) (defvar git-blame-dark-colors - (color-scale '("0c" "04" "24" "1c" "2c" "34" "14" "3c"))) + (git-blame-color-scale "0c" "04" "24" "1c" "2c" "34" "14" "3c") + "*List of colors (format #RGB) to use in a dark environment. + +To check out the list, evaluate (list-colors-display git-blame-dark-colors).") (defvar git-blame-light-colors - (color-scale '("c4" "d4" "cc" "dc" "f4" "e4" "fc" "ec"))) + (git-blame-color-scale "c4" "d4" "cc" "dc" "f4" "e4" "fc" "ec") + "*List of colors (format #RGB) to use in a light environment. + +To check out the list, evaluate (list-colors-display git-blame-light-colors).") -(defvar git-blame-ancient-color "dark green") +(defvar git-blame-colors '() + "Colors used by git-blame. The list is built once when activating git-blame +minor mode.") + +(defvar git-blame-ancient-color "dark green" + "*Color to be used for ancient commit.") (defvar git-blame-autoupdate t "*Automatically update the blame display while editing") @@ -125,41 +147,64 @@ "A queue of update requests") (make-variable-buffer-local 'git-blame-update-queue) +;; FIXME: docstrings +(defvar git-blame-file nil) +(defvar git-blame-current nil) + (defvar git-blame-mode nil) (make-variable-buffer-local 'git-blame-mode) -(unless (assq 'git-blame-mode minor-mode-alist) - (setq minor-mode-alist - (cons (list 'git-blame-mode " blame") - minor-mode-alist))) + +(defvar git-blame-mode-line-string " blame" + "String to display on the mode line when git-blame is active.") + +(or (assq 'git-blame-mode minor-mode-alist) + (setq minor-mode-alist + (cons '(git-blame-mode git-blame-mode-line-string) minor-mode-alist))) ;;;###autoload (defun git-blame-mode (&optional arg) - "Minor mode for displaying Git blame" + "Toggle minor mode for displaying Git blame + +With prefix ARG, turn the mode on if ARG is positive." (interactive "P") - (if arg - (setq git-blame-mode (eq arg 1)) - (setq git-blame-mode (not git-blame-mode))) + (cond + ((null arg) + (if git-blame-mode (git-blame-mode-off) (git-blame-mode-on))) + ((> (prefix-numeric-value arg) 0) (git-blame-mode-on)) + (t (git-blame-mode-off)))) + +(defun git-blame-mode-on () + "Turn on git-blame mode. + +See also function `git-blame-mode'." (make-local-variable 'git-blame-colors) (if git-blame-autoupdate (add-hook 'after-change-functions 'git-blame-after-change nil t) (remove-hook 'after-change-functions 'git-blame-after-change t)) (git-blame-cleanup) - (if git-blame-mode - (progn - (let ((bgmode (cdr (assoc 'background-mode (frame-parameters))))) - (if (eq bgmode 'dark) - (setq git-blame-colors git-blame-dark-colors) - (setq git-blame-colors git-blame-light-colors))) - (setq git-blame-cache (make-hash-table :test 'equal)) - (git-blame-run)) - (cancel-timer git-blame-idle-timer))) + (let ((bgmode (cdr (assoc 'background-mode (frame-parameters))))) + (if (eq bgmode 'dark) + (setq git-blame-colors git-blame-dark-colors) + (setq git-blame-colors git-blame-light-colors))) + (setq git-blame-cache (make-hash-table :test 'equal)) + (setq git-blame-mode t) + (git-blame-run)) + +(defun git-blame-mode-off () + "Turn off git-blame mode. + +See also function `git-blame-mode'." + (git-blame-cleanup) + (if git-blame-idle-timer (cancel-timer git-blame-idle-timer)) + (setq git-blame-mode nil)) ;;;###autoload (defun git-reblame () "Recalculate all blame information in the current buffer" - (unless git-blame-mode - (error "git-blame is not active")) (interactive) + (unless git-blame-mode + (error "Git-blame is not active")) + (git-blame-cleanup) (git-blame-run)) @@ -275,7 +320,6 @@ (t nil))) - (defun git-blame-new-commit (hash src-line res-line num-lines) (save-excursion (set-buffer git-blame-file) @@ -283,9 +327,11 @@ (inhibit-point-motion-hooks t) (inhibit-modification-hooks t)) (when (not info) - (let ((color (pop git-blame-colors))) - (unless color - (setq color git-blame-ancient-color)) + ;; Assign a random color to each new commit info + ;; Take care not to select the same color multiple times + (let ((color (if git-blame-colors + (git-blame-random-pop git-blame-colors) + git-blame-ancient-color))) (setq info (list hash src-line res-line num-lines (git-describe-commit hash) (cons 'color color)))) diff --git a/decorate.c b/decorate.c new file mode 100644 index 0000000000..396b41311a --- /dev/null +++ b/decorate.c @@ -0,0 +1,89 @@ +/* + * decorate.c - decorate a git object with some arbitrary + * data. + */ +#include "cache.h" +#include "object.h" +#include "decorate.h" + +static unsigned int hash_obj(struct object *obj, unsigned int n) +{ + unsigned int hash = *(unsigned int *)obj->sha1; + return hash % n; +} + +static void *insert_decoration(struct decoration *n, struct object *base, void *decoration) +{ + int size = n->size; + struct object_decoration *hash = n->hash; + int j = hash_obj(base, size); + + while (hash[j].base) { + if (hash[j].base == base) { + void *old = hash[j].decoration; + hash[j].decoration = decoration; + return old; + } + j++; + if (++j >= size) + j = 0; + } + hash[j].base = base; + hash[j].decoration = decoration; + n->nr++; + return NULL; +} + +static void grow_decoration(struct decoration *n) +{ + int i; + int old_size = n->size; + struct object_decoration *old_hash; + + old_size = n->size; + old_hash = n->hash; + + n->size = (old_size + 1000) * 3 / 2; + n->hash = xcalloc(n->size, sizeof(struct object_decoration)); + n->nr = 0; + + for (i = 0; i < old_size; i++) { + struct object *base = old_hash[i].base; + void *decoration = old_hash[i].decoration; + + if (!base) + continue; + insert_decoration(n, base, decoration); + } + free(old_hash); +} + +/* Add a decoration pointer, return any old one */ +void *add_decoration(struct decoration *n, struct object *obj, void *decoration) +{ + int nr = n->nr + 1; + + if (nr > n->size * 2 / 3) + grow_decoration(n); + return insert_decoration(n, obj, decoration); +} + +/* Lookup a decoration pointer */ +void *lookup_decoration(struct decoration *n, struct object *obj) +{ + int j; + + /* nothing to lookup */ + if (!n->size) + return NULL; + j = hash_obj(obj, n->size); + for (;;) { + struct object_decoration *ref = n->hash + j; + if (ref->base == obj) + return ref->decoration; + if (!ref->base) + return NULL; + if (++j == n->size) + j = 0; + } +} diff --git a/decorate.h b/decorate.h new file mode 100644 index 0000000000..1fa4ad9beb --- /dev/null +++ b/decorate.h @@ -0,0 +1,18 @@ +#ifndef DECORATE_H +#define DECORATE_H + +struct object_decoration { + struct object *base; + void *decoration; +}; + +struct decoration { + const char *name; + unsigned int size, nr; + struct object_decoration *hash; +}; + +extern void *add_decoration(struct decoration *n, struct object *obj, void *decoration); +extern void *lookup_decoration(struct decoration *n, struct object *obj); + +#endif @@ -811,7 +811,12 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) if (data->files[i]->is_binary) { show_name(prefix, name, len, reset, set); - printf(" Bin\n"); + printf(" Bin "); + printf("%s%d%s", del_c, deleted, reset); + printf(" -> "); + printf("%s%d%s", add_c, added, reset); + printf(" bytes"); + printf("\n"); goto free_diffstat_file; } else if (data->files[i]->is_unmerged) { @@ -1185,9 +1190,11 @@ static void builtin_diffstat(const char *name_a, const char *name_b, if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) die("unable to read files to diff"); - if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2)) + if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2)) { data->is_binary = 1; - else { + data->added = mf2.size; + data->deleted = mf1.size; + } else { /* Crazy xdl interfaces.. */ xpparam_t xpp; xdemitconf_t xecfg; @@ -8,6 +8,11 @@ #include "cache.h" #include "dir.h" +struct path_simplify { + int len; + const char *path; +}; + int common_prefix(const char **pathspec) { const char *path, *slash, *next; @@ -293,6 +298,31 @@ static int dir_exists(const char *dirname, int len) } /* + * This is an inexact early pruning of any recursive directory + * reading - if the path cannot possibly be in the pathspec, + * return true, and we'll skip it early. + */ +static int simplify_away(const char *path, int pathlen, const struct path_simplify *simplify) +{ + if (simplify) { + for (;;) { + const char *match = simplify->path; + int len = simplify->len; + + if (!match) + break; + if (len > pathlen) + len = pathlen; + if (!memcmp(path, match, len)) + return 0; + simplify++; + } + return 1; + } + return 0; +} + +/* * Read a directory tree. We currently ignore anything but * directories, regular files and symlinks. That's because git * doesn't handle them at all yet. Maybe that will change some @@ -301,7 +331,7 @@ static int dir_exists(const char *dirname, int len) * Also, we ignore the name ".git" (even if it is not a directory). * That likely will not change. */ -static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only) +static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only, const struct path_simplify *simplify) { DIR *fdir = opendir(path); int contents = 0; @@ -324,6 +354,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co continue; len = strlen(de->d_name); memcpy(fullname + baselen, de->d_name, len+1); + if (simplify_away(fullname, baselen + len, simplify)) + continue; if (excluded(dir, fullname) != dir->show_ignored) { if (!dir->show_ignored || DTYPE(de) != DT_DIR) { continue; @@ -350,13 +382,13 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co if (dir->hide_empty_directories && !read_directory_recursive(dir, fullname, fullname, - baselen + len, 1)) + baselen + len, 1, simplify)) continue; break; } contents += read_directory_recursive(dir, - fullname, fullname, baselen + len, 0); + fullname, fullname, baselen + len, 0, simplify); continue; case DT_REG: case DT_LNK: @@ -386,8 +418,61 @@ static int cmp_name(const void *p1, const void *p2) e2->name, e2->len); } -int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen) +/* + * Return the length of the "simple" part of a path match limiter. + */ +static int simple_length(const char *match) { + const char special[256] = { + [0] = 1, ['?'] = 1, + ['\\'] = 1, ['*'] = 1, + ['['] = 1 + }; + int len = -1; + + for (;;) { + unsigned char c = *match++; + len++; + if (special[c]) + return len; + } +} + +static struct path_simplify *create_simplify(const char **pathspec) +{ + int nr, alloc = 0; + struct path_simplify *simplify = NULL; + + if (!pathspec) + return NULL; + + for (nr = 0 ; ; nr++) { + const char *match; + if (nr >= alloc) { + alloc = alloc_nr(alloc); + simplify = xrealloc(simplify, alloc * sizeof(*simplify)); + } + match = *pathspec++; + if (!match) + break; + simplify[nr].path = match; + simplify[nr].len = simple_length(match); + } + simplify[nr].path = NULL; + simplify[nr].len = 0; + return simplify; +} + +static void free_simplify(struct path_simplify *simplify) +{ + if (simplify) + free(simplify); +} + +int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen, const char **pathspec) +{ + struct path_simplify *simplify = create_simplify(pathspec); + /* * Make sure to do the per-directory exclude for all the * directories leading up to our base. @@ -414,7 +499,8 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i } } - read_directory_recursive(dir, path, base, baselen, 0); + read_directory_recursive(dir, path, base, baselen, 0, simplify); + free_simplify(simplify); qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name); return dir->nr; } @@ -48,7 +48,7 @@ extern int common_prefix(const char **pathspec); #define MATCHED_EXACTLY 3 extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen); -extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen); +extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen, const char **pathspec); extern int push_exclude_per_directory(struct dir_struct *, const char *, int); extern void pop_exclude_per_directory(struct dir_struct *, int); diff --git a/git-bisect.sh b/git-bisect.sh index 11313a7949..1cd456173d 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -1,15 +1,24 @@ #!/bin/sh USAGE='[start|bad|good|next|reset|visualize|replay|log|run]' -LONG_USAGE='git bisect start [<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 next find next bisection to test and check it out. -git bisect reset [<branch>] finish bisection search and go back to branch. -git bisect visualize show bisect status in gitk. -git bisect replay <logfile> replay bisection log. -git bisect log show bisect log. -git bisect run <cmd>... use <cmd>... to automatically bisect.' +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 next + find next bisection to test and check it out. +git bisect reset [<branch>] + finish bisection search and go back to branch. +git bisect visualize + show bisect status in gitk. +git bisect replay <logfile> + replay bisection log. +git bisect log + show bisect log. +git bisect run <cmd>... + use <cmd>... to automatically bisect.' . git-sh-setup require_work_tree @@ -70,14 +79,45 @@ bisect_start() { # # Get rid of any old bisect state # - rm -f "$GIT_DIR/refs/heads/bisect" - rm -rf "$GIT_DIR/refs/bisect/" + bisect_clean_state mkdir "$GIT_DIR/refs/bisect" - { - printf "git-bisect start" - sq "$@" - } >"$GIT_DIR/BISECT_LOG" + + # + # Check for one bad and then some good revisions. + # + has_double_dash=0 + for arg; do + case "$arg" in --) has_double_dash=1; break ;; esac + done + orig_args=$(sq "$@") + bad_seen=0 + while [ $# -gt 0 ]; do + arg="$1" + case "$arg" in + --) + shift + break + ;; + *) + 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 + ;; + esac + done + sq "$@" >"$GIT_DIR/BISECT_NAMES" + echo "git-bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" + bisect_auto_next } bisect_bad() { @@ -90,12 +130,17 @@ bisect_bad() { *) usage ;; esac || exit - echo "$rev" >"$GIT_DIR/refs/bisect/bad" - echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG" + 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_good() { bisect_autostart case "$#" in @@ -106,35 +151,54 @@ bisect_good() { for rev in $revs do rev=$(git-rev-parse --verify "$rev^{commit}") || exit - echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev" - echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG" + 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() { - next_ok=no - test -f "$GIT_DIR/refs/bisect/bad" && - case "$(cd "$GIT_DIR" && echo refs/bisect/good-*)" in - refs/bisect/good-\*) ;; - *) next_ok=yes ;; - esac - case "$next_ok,$1" in - no,) false ;; - no,fail) - THEN='' - test -d "$GIT_DIR/refs/bisect" || { - echo >&2 'You need to start by "git bisect start".' - THEN='then ' - } - echo >&2 'You '$THEN'need to give me at least one good' \ - 'and one bad revisions.' - echo >&2 '(You can use "git bisect bad" and' \ - '"git bisect good" for that.)' - exit 1 ;; + missing_good= missing_bad= + git show-ref -q --verify refs/bisect/bad || missing_bad=t + test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t + + case "$missing_good,$missing_bad,$1" in + ,,*) + : have both good and bad - ok + ;; + *,) + # do not have both but not asked to fail - just report. + false + ;; + t,,good) + # have bad but not good. we could bisect although + # this is less optimum. + echo >&2 'Warning: bisecting only with a bad commit.' + if test -t 0 + then + printf >&2 'Are you sure [Y/n]? ' + case "$(read yesno)" in [Nn]*) exit 1 ;; esac + fi + : bisect without good... + ;; *) - true ;; + THEN='' + test -d "$GIT_DIR/refs/bisect" || { + echo >&2 'You need to start by "git bisect start".' + THEN='then ' + } + echo >&2 'You '$THEN'need to give me at least one good' \ + 'and one bad revisions.' + echo >&2 '(You can use "git bisect bad" and' \ + '"git bisect good" for that.)' + exit 1 ;; esac } @@ -145,27 +209,32 @@ bisect_auto_next() { bisect_next() { case "$#" in 0) ;; *) usage ;; esac bisect_autostart - bisect_next_check fail + bisect_next_check good + bad=$(git-rev-parse --verify refs/bisect/bad) && - good=$(git-rev-parse --sq --revs-only --not \ - $(cd "$GIT_DIR" && ls refs/bisect/good-*)) && - rev=$(eval "git-rev-list --bisect $good $bad -- $(cat "$GIT_DIR/BISECT_NAMES")") || exit - if [ -z "$rev" ]; then - echo "$bad was both good and bad" - exit 1 + good=$(git for-each-ref --format='^%(objectname)' \ + "refs/bisect/good-*" | tr '[\012]' ' ') && + eval="git-rev-list --bisect-vars $good $bad --" && + eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" && + eval=$(eval "$eval") && + eval "$eval" || exit + + if [ -z "$bisect_rev" ]; then + echo "$bad was both good and bad" + exit 1 fi - if [ "$rev" = "$bad" ]; then - echo "$rev is first bad commit" - git-diff-tree --pretty $rev - exit 0 + if [ "$bisect_rev" = "$bad" ]; then + echo "$bisect_rev is first bad commit" + git-diff-tree --pretty $bisect_rev + exit 0 fi - nr=$(eval "git-rev-list $rev $good -- $(cat $GIT_DIR/BISECT_NAMES)" | wc -l) || exit - echo "Bisecting: $nr revisions left to test after this" - echo "$rev" > "$GIT_DIR/refs/heads/new-bisect" + + 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 mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" && GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect - git-show-branch "$rev" + git-show-branch "$bisect_rev" } bisect_visualize() { @@ -190,14 +259,19 @@ bisect_reset() { usage ;; esac if git checkout "$branch"; then - rm -fr "$GIT_DIR/refs/bisect" - rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name" - rm -f "$GIT_DIR/BISECT_LOG" - rm -f "$GIT_DIR/BISECT_NAMES" - rm -f "$GIT_DIR/BISECT_RUN" + rm -f "$GIT_DIR/head-name" + bisect_clean_state fi } +bisect_clean_state() { + rm -fr "$GIT_DIR/refs/bisect" + rm -f "$GIT_DIR/refs/heads/bisect" + rm -f "$GIT_DIR/BISECT_LOG" + rm -f "$GIT_DIR/BISECT_NAMES" + rm -f "$GIT_DIR/BISECT_RUN" +} + bisect_replay () { test -r "$1" || { echo >&2 "cannot read $1 for replaying" diff --git a/git-checkout.sh b/git-checkout.sh index a7390e808c..deb0a9a3c7 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -170,7 +170,7 @@ describe_detached_head () { } } -if test -z "$branch$newbranch" && test "$new" != "$old" +if test -z "$branch$newbranch" && test "$new_name" != "$old_name" then detached="$new" if test -n "$oldbranch" && test -z "$quiet" @@ -180,7 +180,7 @@ If you want to create a new branch from this checkout, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b <new_branch_name>" fi -elif test -z "$oldbranch" +elif test -z "$oldbranch" && test "$new" != "$old" then describe_detached_head 'Previous HEAD position was' "$old" fi diff --git a/git-commit.sh b/git-commit.sh index 292cf967e3..f28fc24224 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -370,8 +370,8 @@ t,) # the same way. if test -z "$initial_commit" then - cp "$THIS_INDEX" "$TMP_INDEX" - GIT_INDEX_FILE="$TMP_INDEX" git-read-tree -i -m HEAD + GIT_INDEX_FILE="$THIS_INDEX" \ + git-read-tree --index-output="$TMP_INDEX" -i -m HEAD else rm -f "$TMP_INDEX" fi || exit @@ -649,8 +649,9 @@ then fi if test -z "$quiet" then + commit=`git-diff-tree --always --shortstat --pretty="format:%h: %s"\ + --summary --root HEAD --` echo "Created${initial_commit:+ initial} commit $commit" - git-diff-tree --shortstat --summary --root --no-commit-id HEAD -- fi fi diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 25816c5a21..087e3abaef 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -91,7 +91,9 @@ $log->debug("Temporary directory is '$TEMP_DIR'"); # if we are called with a pserver argument, # deal with the authentication cat before entering the # main loop +$state->{method} = 'ext'; if (@ARGV && $ARGV[0] eq 'pserver') { + $state->{method} = 'pserver'; my $line = <STDIN>; chomp $line; unless( $line eq 'BEGIN AUTH REQUEST') { die "E Do not understand $line - expecting BEGIN AUTH REQUEST\n"; @@ -181,11 +183,18 @@ sub req_Root } foreach my $line ( @gitvars ) { - next unless ( $line =~ /^(.*?)\.(.*?)=(.*)$/ ); - $cfg->{$1}{$2} = $3; + next unless ( $line =~ /^(.*?)\.(.*?)(?:\.(.*?))?=(.*)$/ ); + unless ($3) { + $cfg->{$1}{$2} = $4; + } else { + $cfg->{$1}{$2}{$3} = $4; + } } - unless ( defined ( $cfg->{gitcvs}{enabled} ) and $cfg->{gitcvs}{enabled} =~ /^\s*(1|true|yes)\s*$/i ) + unless ( ($cfg->{gitcvs}{$state->{method}}{enabled} + and $cfg->{gitcvs}{$state->{method}}{enabled} =~ /^\s*(1|true|yes)\s*$/i) + or ($cfg->{gitcvs}{enabled} + and $cfg->{gitcvs}{enabled} =~ /^\s*(1|true|yes)\s*$/i) ) { print "E GITCVS emulation needs to be enabled on this repo\n"; print "E the repo config file needs a [gitcvs] section added, and the parameter 'enabled' set to 1\n"; @@ -194,9 +203,10 @@ sub req_Root return 0; } - if ( defined ( $cfg->{gitcvs}{logfile} ) ) + my $logfile = $cfg->{gitcvs}{$state->{method}}{logfile} || $cfg->{gitcvs}{logfile}; + if ( $logfile ) { - $log->setfile($cfg->{gitcvs}{logfile}); + $log->setfile($logfile); } else { $log->nofile(); } @@ -350,12 +360,52 @@ sub req_add argsplit("add"); + my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log); + $updater->update(); + + argsfromdir($updater); + my $addcount = 0; foreach my $filename ( @{$state->{args}} ) { $filename = filecleanup($filename); + my $meta = $updater->getmeta($filename); + my $wrev = revparse($filename); + + if ($wrev && $meta && ($wrev < 0)) + { + # previously removed file, add back + $log->info("added file $filename was previously removed, send 1.$meta->{revision}"); + + print "MT +updated\n"; + print "MT text U \n"; + print "MT fname $filename\n"; + print "MT newline\n"; + print "MT -updated\n"; + + unless ( $state->{globaloptions}{-n} ) + { + my ( $filepart, $dirpart ) = filenamesplit($filename,1); + + print "Created $dirpart\n"; + print $state->{CVSROOT} . "/$state->{module}/$filename\n"; + + # this is an "entries" line + my $kopts = kopts_from_path($filepart); + $log->debug("/$filepart/1.$meta->{revision}//$kopts/"); + print "/$filepart/1.$meta->{revision}//$kopts/\n"; + # permissions + $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}"); + print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n"; + # transmit file + transmitfile($meta->{filehash}); + } + + next; + } + unless ( defined ( $state->{entries}{$filename}{modified_filename} ) ) { print "E cvs add: nothing known about `$filename'\n"; @@ -1027,7 +1077,7 @@ sub req_ci $log->info("req_ci : " . ( defined($data) ? $data : "[NULL]" )); - if ( @ARGV && $ARGV[0] eq 'pserver') + if ( $state->{method} eq 'pserver') { print "error 1 pserver access cannot commit\n"; exit; @@ -2132,25 +2182,40 @@ sub new bless $self, $class; - $self->{dbdir} = $config . "/"; - die "Database dir '$self->{dbdir}' isn't a directory" unless ( defined($self->{dbdir}) and -d $self->{dbdir} ); - $self->{module} = $module; - $self->{file} = $self->{dbdir} . "/gitcvs.$module.sqlite"; - $self->{git_path} = $config . "/"; $self->{log} = $log; die "Git repo '$self->{git_path}' doesn't exist" unless ( -d $self->{git_path} ); - $self->{dbh} = DBI->connect("dbi:SQLite:dbname=" . $self->{file},"",""); + $self->{dbdriver} = $cfg->{gitcvs}{$state->{method}}{dbdriver} || + $cfg->{gitcvs}{dbdriver} || "SQLite"; + $self->{dbname} = $cfg->{gitcvs}{$state->{method}}{dbname} || + $cfg->{gitcvs}{dbname} || "%Ggitcvs.%m.sqlite"; + $self->{dbuser} = $cfg->{gitcvs}{$state->{method}}{dbuser} || + $cfg->{gitcvs}{dbuser} || ""; + $self->{dbpass} = $cfg->{gitcvs}{$state->{method}}{dbpass} || + $cfg->{gitcvs}{dbpass} || ""; + my %mapping = ( m => $module, + a => $state->{method}, + u => getlogin || getpwuid($<) || $<, + G => $self->{git_path}, + g => mangle_dirname($self->{git_path}), + ); + $self->{dbname} =~ s/%([mauGg])/$mapping{$1}/eg; + $self->{dbuser} =~ s/%([mauGg])/$mapping{$1}/eg; + + die "Invalid char ':' in dbdriver" if $self->{dbdriver} =~ /:/; + die "Invalid char ';' in dbname" if $self->{dbname} =~ /;/; + $self->{dbh} = DBI->connect("dbi:$self->{dbdriver}:dbname=$self->{dbname}", + $self->{dbuser}, + $self->{dbpass}); + die "Error connecting to database\n" unless defined $self->{dbh}; $self->{tables} = {}; - foreach my $table ( $self->{dbh}->tables ) + foreach my $table ( keys %{$self->{dbh}->table_info(undef,undef,undef,'TABLE')->fetchall_hashref('TABLE_NAME')} ) { - $table =~ s/^"//; - $table =~ s/"$//; $self->{tables}{$table} = 1; } @@ -2848,5 +2913,19 @@ sub safe_pipe_capture { return wantarray ? @output : join('',@output); } +=head2 mangle_dirname + +create a string from a directory name that is suitable to use as +part of a filename, mainly by converting all chars except \w.- to _ + +=cut +sub mangle_dirname { + my $dirname = shift; + return unless defined $dirname; + + $dirname =~ s/[^\w.-]/_/g; + + return $dirname; +} 1; diff --git a/git-fetch.sh b/git-fetch.sh index fd70696b74..b04bd553f8 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -26,6 +26,7 @@ keep= shallow_depth= no_progress= test -t 1 || no_progress=--no-progress +quiet= while case "$#" in 0) break ;; esac do case "$1" in @@ -56,6 +57,9 @@ do --update-head-o|--update-head-ok) update_head_ok=t ;; + -q|--q|--qu|--qui|--quie|--quiet) + quiet=--quiet + ;; -v|--verbose) verbose=Yes ;; @@ -173,8 +177,8 @@ fetch_all_at_once () { git-bundle unbundle "$remote" $rref || echo failed "$remote" else - git-fetch-pack --thin $exec $keep $shallow_depth $no_progress \ - "$remote" $rref || + git-fetch-pack --thin $exec $keep $shallow_depth \ + $quiet $no_progress "$remote" $rref || echo failed "$remote" fi ) | @@ -248,7 +252,8 @@ fetch_per_ref () { expr "z$head" : "z$_x40\$" >/dev/null || die "No such ref $remote_name at $remote" echo >&2 "Fetching $remote_name from $remote using $proto" - git-http-fetch -v -a "$head" "$remote" || exit + case "$quiet" in '') v=-v ;; *) v= ;; esac + git-http-fetch $v -a "$head" "$remote" || exit ;; rsync://*) test -n "$shallow_depth" && @@ -257,8 +262,9 @@ fetch_per_ref () { rsync -L -q "$remote/$remote_name" "$TMP_HEAD" || exit 1 head=$(git-rev-parse --verify TMP_HEAD) rm -f "$TMP_HEAD" + case "$quiet" in '') v=-v ;; *) v= ;; esac test "$rsync_slurped_objects" || { - rsync -av --ignore-existing --exclude info \ + rsync -a $v --ignore-existing --exclude info \ "$remote/objects/" "$GIT_OBJECT_DIRECTORY/" || exit # Look at objects/info/alternates for rsync -- http will diff --git a/git-gui/Makefile b/git-gui/Makefile index b82789ead6..b29d7d1e68 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@ -28,6 +28,8 @@ ifndef V QUIET_BUILT_IN = @echo ' ' BUILTIN $@; endif +TCLTK_PATH ?= wish + ifeq ($(findstring $(MAKEFLAGS),s),s) QUIET_GEN = QUIET_BUILT_IN = @@ -36,10 +38,12 @@ endif DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(QUIET_GEN)rm -f $@ $@+ && \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|^exec wish "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' \ -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \ $@.sh >$@+ && \ chmod +x $@+ && \ diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 7cbc977ea2..94067cc5f7 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -242,6 +242,8 @@ proc error_popup {msg} { if {[reponame] ne {}} { append title " ([reponame])" } + option add *Dialog.msg.font font_ui + option add *Button.font font_ui set cmd [list tk_messageBox \ -icon error \ -type ok \ @@ -258,6 +260,8 @@ proc warn_popup {msg} { if {[reponame] ne {}} { append title " ([reponame])" } + option add *Dialog.msg.font font_ui + option add *Button.font font_ui set cmd [list tk_messageBox \ -icon warning \ -type ok \ @@ -274,6 +278,8 @@ proc info_popup {msg {parent .}} { if {[reponame] ne {}} { append title " ([reponame])" } + option add *Dialog.msg.font font_ui + option add *Button.font font_ui tk_messageBox \ -parent $parent \ -icon info \ @@ -287,6 +293,8 @@ proc ask_popup {msg} { if {[reponame] ne {}} { append title " ([reponame])" } + option add *Dialog.msg.font font_ui + option add *Button.font font_ui return [tk_messageBox \ -parent . \ -icon question \ @@ -727,12 +735,9 @@ proc handle_empty_diff {} { [short_path $path] has no changes. -The modification date of this file was updated -by another application, but the content within -the file was not changed. +The modification date of this file was updated by another application, but the content within the file was not changed. -A rescan will be automatically started to find -other files which may have the same state." +A rescan will be automatically started to find other files which may have the same state." clear_diff display_file $path __ @@ -1033,8 +1038,7 @@ proc load_last_commit {} { if {[llength $PARENT] == 0} { error_popup {There is nothing to amend. -You are about to create the initial commit. -There is no commit before this to amend. +You are about to create the initial commit. There is no commit before this to amend. } return } @@ -1043,10 +1047,7 @@ There is no commit before this to amend. if {$curType eq {merge}} { error_popup {Cannot amend while merging. -You are currently in the middle of a merge that -has not been fully completed. You cannot amend -the prior commit unless you first abort the -current merge activity. +You are currently in the middle of a merge that has not been fully completed. You cannot amend the prior commit unless you first abort the current merge activity. } return } @@ -1136,9 +1137,7 @@ proc commit_tree {} { } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { info_popup {Last scanned state does not match repository state. -Another Git program has modified this repository -since the last scan. A rescan must be performed -before another commit can be created. +Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created. The rescan will be automatically started now. } @@ -1159,8 +1158,7 @@ The rescan will be automatically started now. U? { error_popup "Unmerged files cannot be committed. -File [short_path $path] has merge conflicts. -You must resolve them and add the file before committing. +File [short_path $path] has merge conflicts. You must resolve them and add the file before committing. " unlock_index return @@ -1276,8 +1274,7 @@ proc commit_committree {fd_wt curHEAD msg} { if {$tree_id eq $old_tree} { info_popup {No changes to commit. -No files were modified by this commit and it -was not a merge commit. +No files were modified by this commit and it was not a merge commit. A rescan will be automatically started now. } @@ -2116,7 +2113,10 @@ proc do_create_branch {} { -value head \ -variable create_branch_revtype \ -font font_ui - eval tk_optionMenu $w.from.head_m create_branch_head $all_heads + set lbranchm [eval tk_optionMenu $w.from.head_m create_branch_head \ + $all_heads] + $lbranchm configure -font font_ui + $w.from.head_m configure -font font_ui grid $w.from.head_r $w.from.head_m -sticky w set all_trackings [all_tracking_branches] if {$all_trackings ne {}} { @@ -2126,9 +2126,11 @@ proc do_create_branch {} { -value tracking \ -variable create_branch_revtype \ -font font_ui - eval tk_optionMenu $w.from.tracking_m \ + set tbranchm [eval tk_optionMenu $w.from.tracking_m \ create_branch_trackinghead \ - $all_trackings + $all_trackings] + $tbranchm configure -font font_ui + $w.from.tracking_m configure -font font_ui grid $w.from.tracking_r $w.from.tracking_m -sticky w } set all_tags [load_all_tags] @@ -2139,9 +2141,11 @@ proc do_create_branch {} { -value tag \ -variable create_branch_revtype \ -font font_ui - eval tk_optionMenu $w.from.tag_m \ + set tagsm [eval tk_optionMenu $w.from.tag_m \ create_branch_tag \ - $all_tags + $all_tags] + $tagsm configure -font font_ui + $w.from.tag_m configure -font font_ui grid $w.from.tag_r $w.from.tag_m -sticky w } radiobutton $w.from.exp_r \ @@ -2335,7 +2339,11 @@ proc do_delete_branch {} { -value head \ -variable delete_branch_checktype \ -font font_ui - eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads + set mergedlocalm [eval tk_optionMenu $w.validate.head_m \ + delete_branch_head \ + $all_heads] + $mergedlocalm configure -font font_ui + $w.validate.head_m configure -font font_ui grid $w.validate.head_r $w.validate.head_m -sticky w set all_trackings [all_tracking_branches] if {$all_trackings ne {}} { @@ -2345,9 +2353,11 @@ proc do_delete_branch {} { -value tracking \ -variable delete_branch_checktype \ -font font_ui - eval tk_optionMenu $w.validate.tracking_m \ + set mergedtrackm [eval tk_optionMenu $w.validate.tracking_m \ delete_branch_trackinghead \ - $all_trackings + $all_trackings] + $mergedtrackm configure -font font_ui + $w.validate.tracking_m configure -font font_ui grid $w.validate.tracking_r $w.validate.tracking_m -sticky w } radiobutton $w.validate.always_r \ @@ -2382,9 +2392,7 @@ proc switch_branch {new_branch} { } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { info_popup {Last scanned state does not match repository state. -Another Git program has modified this repository -since the last scan. A rescan must be performed -before the current branch can be changed. +Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed. The rescan will be automatically started now. } @@ -2475,12 +2483,9 @@ Staying on branch '$current_branch'." if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} { error_popup "Failed to set current branch. -This working directory is only partially switched. -We successfully updated your files, but failed to -update an internal Git file. +This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file. -This should not have occurred. [appname] will now -close and give up. +This should not have occurred. [appname] will now close and give up. $err" do_quit @@ -2684,10 +2689,12 @@ proc do_push_anywhere {} { frame $w.buttons button $w.buttons.create -text Push \ -font font_ui \ + -default active \ -command [list start_push_anywhere_action $w] pack $w.buttons.create -side right button $w.buttons.cancel -text {Cancel} \ -font font_ui \ + -default normal \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 @@ -2721,7 +2728,10 @@ proc do_push_anywhere {} { -value remote \ -variable push_urltype \ -font font_ui - eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes + set remmenu [eval tk_optionMenu $w.dest.remote_m push_remote \ + $all_remotes] + $remmenu configure -font font_ui + $w.dest.remote_m configure -font font_ui grid $w.dest.remote_r $w.dest.remote_m -sticky w if {[lsearch -sorted -exact $all_remotes origin] != -1} { set push_remote origin @@ -2775,8 +2785,9 @@ proc do_push_anywhere {} { set push_thin 0 set push_tags 0 - bind $w <Visibility> "grab $w" + bind $w <Visibility> "grab $w; focus $w.buttons.create" bind $w <Key-Escape> "destroy $w" + bind $w <Key-Return> [list start_push_anywhere_action $w] wm title $w "[appname] ([reponame]): Push" tkwait window $w } @@ -2791,8 +2802,7 @@ proc can_merge {} { if {[string match amend* $commit_type]} { info_popup {Cannot merge while amending. -You must finish amending this commit before -starting any type of merge. +You must finish amending this commit before starting any type of merge. } return 0 } @@ -2806,9 +2816,7 @@ starting any type of merge. if {$commit_type ne $curType || $HEAD ne $curHEAD} { info_popup {Last scanned state does not match repository state. -Another Git program has modified this repository -since the last scan. A rescan must be performed -before a merge can be performed. +Another Git program has modified this repository since the last scan. A rescan must be performed before a merge can be performed. The rescan will be automatically started now. } @@ -2827,9 +2835,7 @@ The rescan will be automatically started now. File [short_path $path] has merge conflicts. -You must resolve them, add the file, and commit to -complete the current merge. Only then can you -begin another merge. +You must resolve them, add the file, and commit to complete the current merge. Only then can you begin another merge. " unlock_index return 0 @@ -2839,9 +2845,7 @@ begin another merge. File [short_path $path] is modified. -You should complete the current commit before -starting a merge. Doing so will help you abort -a failed merge, should the need arise. +You should complete the current commit before starting a merge. Doing so will help you abort a failed merge, should the need arise. " unlock_index return 0 @@ -2917,13 +2921,11 @@ proc finish_merge {revcnt w ok} { Your merge of $revcnt branches has failed. -There are file-level conflicts between the -branches which must be resolved manually. +There are file-level conflicts between the branches which must be resolved manually. The working directory will now be reset. -You can attempt this merge again -by merging only one branch at a time." $w +You can attempt this merge again by merging only one branch at a time." $w set fd [open "| git read-tree --reset -u HEAD" r] fconfigure $fd -blocking 0 -translation binary @@ -3036,8 +3038,7 @@ You must finish amending this commit. if {[ask_popup "Abort $op? -Aborting the current $op will cause -*ALL* uncommitted changes to be lost. +Aborting the current $op will cause *ALL* uncommitted changes to be lost. Continue with aborting the current $op?"] eq {yes}} { set fd [open "| git read-tree --reset -u HEAD" r] @@ -4109,6 +4110,7 @@ proc console_done {args} { if {[winfo exists $w]} { $w.m.s conf -background green -text {Success} $w.ok conf -state normal + focus $w.ok } } else { if {![winfo exists $w]} { @@ -4116,6 +4118,7 @@ proc console_done {args} { } $w.m.s conf -background red -text {Error: Command Failed} $w.ok conf -state normal + focus $w.ok } array unset console_cr $w @@ -4183,9 +4186,11 @@ proc do_stats {} { frame $w.buttons -border 1 button $w.buttons.close -text Close \ -font font_ui \ + -default active \ -command [list destroy $w] button $w.buttons.gc -text {Compress Database} \ -font font_ui \ + -default normal \ -command "destroy $w;do_gc" pack $w.buttons.close -side right pack $w.buttons.gc -side left @@ -4214,7 +4219,7 @@ proc do_stats {} { } pack $w.stat -pady 10 -padx 10 - bind $w <Visibility> "grab $w; focus $w" + bind $w <Visibility> "grab $w; focus $w.buttons.close" bind $w <Key-Escape> [list destroy $w] bind $w <Key-Return> [list destroy $w] wm title $w "[appname] ([reponame]): Database Statistics" @@ -4511,6 +4516,7 @@ proc do_about {} { frame $w.buttons button $w.buttons.close -text {Close} \ -font font_ui \ + -default active \ -command [list destroy $w] pack $w.buttons.close -side right pack $w.buttons -side bottom -fill x -pady 10 -padx 10 @@ -4556,8 +4562,9 @@ $copyright" \ clipboard append -format STRING -type STRING -- \[$w.vers cget -text\] " - bind $w <Visibility> "grab $w; focus $w" + bind $w <Visibility> "grab $w; focus $w.buttons.close" bind $w <Key-Escape> "destroy $w" + bind $w <Key-Return> "destroy $w" bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w" wm title $w "About [appname]" tkwait window $w @@ -4594,14 +4601,17 @@ proc do_options {} { frame $w.buttons button $w.buttons.restore -text {Restore Defaults} \ -font font_ui \ + -default normal \ -command do_restore_defaults pack $w.buttons.restore -side left button $w.buttons.save -text Save \ -font font_ui \ + -default active \ -command [list do_save_config $w] pack $w.buttons.save -side right button $w.buttons.cancel -text {Cancel} \ -font font_ui \ + -default normal \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 @@ -4688,9 +4698,11 @@ proc do_options {} { frame $w.global.$name label $w.global.$name.l -text "$text:" -font font_ui pack $w.global.$name.l -side left -anchor w -fill x - eval tk_optionMenu $w.global.$name.family \ + set fontmenu [eval tk_optionMenu $w.global.$name.family \ global_config_new(gui.$font^^family) \ - $all_fonts + $all_fonts] + $w.global.$name.family configure -font font_ui + $fontmenu configure -font font_ui spinbox $w.global.$name.size \ -textvariable global_config_new(gui.$font^^size) \ -from 2 -to 80 -increment 1 \ @@ -4702,8 +4714,9 @@ proc do_options {} { pack $w.global.$name -side top -anchor w -fill x } - bind $w <Visibility> "grab $w; focus $w" + bind $w <Visibility> "grab $w; focus $w.buttons.save" bind $w <Key-Escape> "destroy $w" + bind $w <Key-Return> [list do_save_config $w] wm title $w "[appname] ([reponame]): Options" tkwait window $w } @@ -5085,18 +5098,18 @@ set ui_comm {} # -- Menu Bar # menu .mbar -tearoff 0 -.mbar add cascade -label Repository -menu .mbar.repository -.mbar add cascade -label Edit -menu .mbar.edit +.mbar add cascade -label Repository -menu .mbar.repository -font font_ui +.mbar add cascade -label Edit -menu .mbar.edit -font font_ui if {[is_enabled branch]} { - .mbar add cascade -label Branch -menu .mbar.branch + .mbar add cascade -label Branch -menu .mbar.branch -font font_ui } if {[is_enabled multicommit] || [is_enabled singlecommit]} { - .mbar add cascade -label Commit -menu .mbar.commit + .mbar add cascade -label Commit -menu .mbar.commit -font font_ui } if {[is_enabled transport]} { - .mbar add cascade -label Merge -menu .mbar.merge - .mbar add cascade -label Fetch -menu .mbar.fetch - .mbar add cascade -label Push -menu .mbar.push + .mbar add cascade -label Merge -menu .mbar.merge -font font_ui + .mbar add cascade -label Fetch -menu .mbar.fetch -font font_ui + .mbar add cascade -label Push -menu .mbar.push -font font_ui } . configure -menu .mbar @@ -5372,7 +5385,7 @@ if {[is_MacOSX]} { # -- Help Menu # -.mbar add cascade -label Help -menu .mbar.help +.mbar add cascade -label Help -menu .mbar.help -font font_ui menu .mbar.help if {![is_MacOSX]} { @@ -5955,7 +5968,7 @@ unset i set file_lists($ui_index) [list] set file_lists($ui_workdir) [list] -wm title . "[appname] ([file normalize [file dirname [gitdir]]])" +wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]" focus -force $ui_comm # -- Warn the user about environmental problems. Cygwin's Tcl @@ -6034,9 +6047,7 @@ if {[is_enabled multicommit]} { if {[ask_popup \ "This repository currently has $objects_current loose objects. -To maintain optimal performance it is strongly -recommended that you compress the database -when more than $object_limit loose objects exist. +To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist. Compress the database now?"] eq yes} { do_gc diff --git a/git-lost-found.sh b/git-lost-found.sh index 9360804711..58570dff13 100755 --- a/git-lost-found.sh +++ b/git-lost-found.sh @@ -12,7 +12,7 @@ fi laf="$GIT_DIR/lost-found" rm -fr "$laf" && mkdir -p "$laf/commit" "$laf/other" || exit -git fsck --full | +git fsck --full --no-reflogs | while read dangling type sha1 do case "$dangling" in diff --git a/git-merge.sh b/git-merge.sh index fa4589173f..7ebbce4bdb 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -16,10 +16,10 @@ test -z "$(git ls-files -u)" || LF=' ' -all_strategies='recur recursive octopus resolve stupid ours' +all_strategies='recur recursive octopus resolve stupid ours subtree' default_twohead_strategies='recursive' default_octopus_strategies='octopus' -no_trivial_merge_strategies='ours' +no_trivial_merge_strategies='ours subtree' use_strategies= index_merge=t diff --git a/git-send-email.perl b/git-send-email.perl index 1278fcba46..d6b15480dc 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -446,9 +446,12 @@ sub send_message my ($name, $addr) = ($from =~ /^(.*?)(\s+<.*)/); $from = "\"$name\"$addr"; } + my $ccline = ""; + if ($cc ne '') { + $ccline = "\nCc: $cc"; + } my $header = "From: $from -To: $to -Cc: $cc +To: $to${ccline} Subject: $subject Date: $date Message-Id: $message_id diff --git a/git.spec.in b/git.spec.in index 46aee88fd1..f0746ed78c 100644 --- a/git.spec.in +++ b/git.spec.in @@ -1,4 +1,7 @@ # Pass --without docs to rpmbuild if you don't want the documentation + +%define python_path /usr/bin/python + Name: git Version: @@VERSION@@ Release: 1%{?dist} @@ -9,7 +12,7 @@ URL: http://kernel.org/pub/software/scm/git/ Source: http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel %{!?_without_docs:, xmlto, asciidoc > 6.0.3} BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -Requires: git-core, git-svn, git-cvs, git-arch, git-email, gitk, git-gui, perl-Git +Requires: git-core, git-svn, git-cvs, git-arch, git-email, gitk, git-gui, git-p4, perl-Git %description Git is a fast, scalable, distributed revision control system with an @@ -50,6 +53,13 @@ Requires: git-core = %{version}-%{release}, tla %description arch Git tools for importing Arch repositories. +%package p4 +Summary: Git tools for importing Perforce repositories +Group: Development/Tools +Requires: git-core = %{version}-%{release}, python +%description p4 +Git tools for importing Perforce repositories. + %package email Summary: Git tools for sending email Group: Development/Tools @@ -85,23 +95,23 @@ Perl interface to Git %setup -q %build -make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_OWN_SUBPROCESS_PY=YesPlease \ - prefix=%{_prefix} all %{!?_without_docs: doc} +make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_P4IMPORT=YesPlease \ + prefix=%{_prefix} PYTHON_PATH=%{python_path} all %{!?_without_docs: doc} %install rm -rf $RPM_BUILD_ROOT make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" DESTDIR=$RPM_BUILD_ROOT \ - WITH_OWN_SUBPROCESS_PY=YesPlease \ - prefix=%{_prefix} mandir=%{_mandir} INSTALLDIRS=vendor \ - install %{!?_without_docs: install-doc} + WITH_P4IMPORT=YesPlease prefix=%{_prefix} mandir=%{_mandir} \ + PYTHON_PATH=%{python_path} \ + INSTALLDIRS=vendor install %{!?_without_docs: install-doc} find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';' find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';' find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';' -(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "archimport|svn|cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files +(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "p4import|archimport|svn|cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files (find $RPM_BUILD_ROOT%{perl_vendorlib} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files %if %{!?_without_docs:1}0 -(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "archimport|svn|git-cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files +(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "p4import|archimport|svn|git-cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files %else rm -rf $RPM_BUILD_ROOT%{_mandir} %endif @@ -133,6 +143,13 @@ rm -rf $RPM_BUILD_ROOT %{!?_without_docs: %{_mandir}/man1/git-archimport.1*} %{!?_without_docs: %doc Documentation/git-archimport.html } +%files p4 +%defattr(-,root,root) +%doc Documentation/git-p4import.txt +%{_bindir}/git-p4import +%{!?_without_docs: %{_mandir}/man1/git-p4import.1*} +%{!?_without_docs: %doc Documentation/git-p4import.html } + %files email %defattr(-,root,root) %doc Documentation/*email*.txt @@ -167,6 +184,9 @@ rm -rf $RPM_BUILD_ROOT %{!?_without_docs: %doc Documentation/*.html } %changelog +* Tue Mar 27 2007 Eygene Ryabinkin <rea-git@codelabs.ru> +- Added the git-p4 package: Perforce import stuff. + * Mon Feb 13 2007 Nicolas Pitre <nico@cam.org> - Update core package description (Git isn't as stupid as it used to be) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 45ac9d7121..c48b35aa39 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -19,7 +19,7 @@ use File::Basename qw(basename); binmode STDOUT, ':utf8'; BEGIN { - CGI->compile() if $ENV{MOD_PERL}; + CGI->compile() if $ENV{'MOD_PERL'}; } our $cgi = new CGI; @@ -71,6 +71,10 @@ our $logo_label = "git homepage"; # source of projects list our $projects_list = "++GITWEB_LIST++"; +# default order of projects list +# valid values are none, project, descr, owner, and age +our $default_projects_order = "project"; + # show repository only if this file exists # (only effective if this variable evaluates to true) our $export_ok = "++GITWEB_EXPORT_OK++"; @@ -176,8 +180,8 @@ our %feature = ( # projects matching $projname/*.git will not be shown in the main # projects list, instead a '+' mark will be added to $projname # there and a 'forks' view will be enabled for the project, listing - # all the forks. This feature is supported only if project list - # is taken from a directory, not file. + # all the forks. If project list is taken from a file, forks have + # to be listed after the main project. # To enable system wide have in $GITWEB_CONFIG # $feature{'forks'}{'default'} = [1]; @@ -1047,6 +1051,8 @@ sub git_get_projects_list { $filter ||= ''; $filter =~ s/\.git$//; + my ($check_forks) = gitweb_check_feature('forks'); + if (-d $projects_list) { # search in directory my $dir = $projects_list . ($filter ? "/$filter" : ''); @@ -1054,8 +1060,6 @@ sub git_get_projects_list { $dir =~ s!/+$!!; my $pfxlen = length("$dir"); - my ($check_forks) = gitweb_check_feature('forks'); - File::Find::find({ follow_fast => 1, # follow symbolic links dangling_symlinks => 0, # ignore dangling symlinks, silently @@ -1081,7 +1085,9 @@ sub git_get_projects_list { # 'git%2Fgit.git Linus+Torvalds' # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' + my %paths; open my ($fd), $projects_list or return; + PROJECT: while (my $line = <$fd>) { chomp $line; my ($path, $owner) = split ' ', $line; @@ -1094,11 +1100,27 @@ sub git_get_projects_list { # looking for forks; my $pfx = substr($path, 0, length($filter)); if ($pfx ne $filter) { - next; + next PROJECT; } my $sfx = substr($path, length($filter)); if ($sfx !~ /^\/.*\.git$/) { - next; + next PROJECT; + } + } elsif ($check_forks) { + PATH: + foreach my $filter (keys %paths) { + # looking for forks; + my $pfx = substr($path, 0, length($filter)); + if ($pfx ne $filter) { + next PATH; + } + my $sfx = substr($path, length($filter)); + if ($sfx !~ /^\/.*\.git$/) { + next PATH; + } + # is a fork, don't include it in + # the list + next PROJECT; } } if (check_export_ok("$projectroot/$path")) { @@ -1106,12 +1128,13 @@ sub git_get_projects_list { path => $path, owner => to_utf8($owner), }; - push @list, $pr + push @list, $pr; + (my $forks_path = $path) =~ s/\.git$//; + $paths{$forks_path}++; } } close $fd; } - @list = sort {$a->{'path'} cmp $b->{'path'}} @list; return @list; } @@ -1800,7 +1823,7 @@ EOF $cgi->hidden(-name => "a") . "\n" . $cgi->hidden(-name => "h") . "\n" . $cgi->popup_menu(-name => 'st', -default => 'commit', - -values => ['commit', 'author', 'committer', 'pickaxe']) . + -values => ['commit', 'author', 'committer', 'pickaxe']) . $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) . " search:\n", $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . @@ -1870,16 +1893,16 @@ sub git_print_page_nav { my %arg = map { $_ => {action=>$_} } @navs; if (defined $head) { for (qw(commit commitdiff)) { - $arg{$_}{hash} = $head; + $arg{$_}{'hash'} = $head; } if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) { for (qw(shortlog log)) { - $arg{$_}{hash} = $head; + $arg{$_}{'hash'} = $head; } } } - $arg{tree}{hash} = $treehead if defined $treehead; - $arg{tree}{hash_base} = $treebase if defined $treebase; + $arg{'tree'}{'hash'} = $treehead if defined $treehead; + $arg{'tree'}{'hash_base'} = $treebase if defined $treebase; print "<div class=\"page_nav\">\n" . (join " | ", @@ -1927,9 +1950,9 @@ sub git_print_header_div { my ($action, $title, $hash, $hash_base) = @_; my %args = (); - $args{action} = $action; - $args{hash} = $hash if $hash; - $args{hash_base} = $hash_base if $hash_base; + $args{'action'} = $action; + $args{'hash'} = $hash if $hash; + $args{'hash_base'} = $hash_base if $hash_base; print "<div class=\"header\">\n" . $cgi->a({-href => href(%args), -class => "title"}, @@ -2598,7 +2621,7 @@ sub git_project_list_body { push @projects, $pr; } - $order ||= "project"; + $order ||= $default_projects_order; $from = 0 unless defined $from; $to = $#projects if (!defined $to || $#projects < $to); @@ -2957,7 +2980,7 @@ sub git_search_grep_body { sub git_project_list { my $order = $cgi->param('o'); - if (defined $order && $order !~ m/project|descr|owner|age/) { + if (defined $order && $order !~ m/none|project|descr|owner|age/) { die_error(undef, "Unknown order parameter"); } @@ -2980,7 +3003,7 @@ sub git_project_list { sub git_forks { my $order = $cgi->param('o'); - if (defined $order && $order !~ m/project|descr|owner|age/) { + if (defined $order && $order !~ m/none|project|descr|owner|age/) { die_error(undef, "Unknown order parameter"); } @@ -3095,7 +3118,7 @@ sub git_summary { git_project_list_body(\@forklist, undef, 0, 15, $#forklist <= 15 ? undef : $cgi->a({-href => href(action=>"forks")}, "..."), - 'noheader'); + 'noheader'); } git_footer_html(); @@ -3202,7 +3225,7 @@ HTML my $rev = substr($full_rev, 0, 8); my $author = $meta->{'author'}; my %date = parse_date($meta->{'author-time'}, - $meta->{'author-tz'}); + $meta->{'author-tz'}); my $date = $date{'iso-tz'}; if ($group_size) { $current_color = ++$current_color % $num_colors; @@ -3214,9 +3237,9 @@ HTML print " rowspan=\"$group_size\"" if ($group_size > 1); print ">"; print $cgi->a({-href => href(action=>"commit", - hash=>$full_rev, - file_name=>$file_name)}, - esc_html($rev)); + hash=>$full_rev, + file_name=>$file_name)}, + esc_html($rev)); print "</td>\n"; } open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^") @@ -3225,13 +3248,13 @@ HTML close $dd; chomp($parent_commit); my $blamed = href(action => 'blame', - file_name => $meta->{'filename'}, - hash_base => $parent_commit); + file_name => $meta->{'filename'}, + hash_base => $parent_commit); print "<td class=\"linenr\">"; print $cgi->a({ -href => "$blamed#l$orig_lineno", - -id => "l$lineno", - -class => "linenr" }, - esc_html($lineno)); + -id => "l$lineno", + -class => "linenr" }, + esc_html($lineno)); print "</td>"; print "<td class=\"pre\">" . esc_html($data) . "</td>\n"; print "</tr>\n"; @@ -3621,7 +3644,7 @@ sub git_snapshot { my $name = $project; $name =~ s/\047/\047\\\047\047/g; open my $fd, "-|", - "$git archive --format=tar --prefix=\'$name\'/ $hash | $command" + "$git archive --format=tar --prefix=\'$name\'/ $hash | $command" or die_error(undef, "Execute git-tar-tree failed"); binmode STDOUT, ':raw'; print <$fd>; @@ -3734,7 +3757,7 @@ sub git_commit { # difftree output is not printed for merges open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id", @diff_opts, $parent, $hash, "--" - or die_error(undef, "Open git-diff-tree failed"); + or die_error(undef, "Open git-diff-tree failed"); @difftree = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading git-diff-tree failed"); } @@ -4306,13 +4329,13 @@ sub git_search { if ($page > 0) { $paging_nav .= $cgi->a({-href => href(action=>"search", hash=>$hash, - searchtext=>$searchtext, searchtype=>$searchtype)}, - "first"); + searchtext=>$searchtext, searchtype=>$searchtype)}, + "first"); $paging_nav .= " ⋅ " . $cgi->a({-href => href(action=>"search", hash=>$hash, - searchtext=>$searchtext, searchtype=>$searchtype, - page=>$page-1), - -accesskey => "p", -title => "Alt-p"}, "prev"); + searchtext=>$searchtext, searchtype=>$searchtype, + page=>$page-1), + -accesskey => "p", -title => "Alt-p"}, "prev"); } else { $paging_nav .= "first"; $paging_nav .= " ⋅ prev"; @@ -4320,9 +4343,9 @@ sub git_search { if ($#commitlist >= 100) { $paging_nav .= " ⋅ " . $cgi->a({-href => href(action=>"search", hash=>$hash, - searchtext=>$searchtext, searchtype=>$searchtype, - page=>$page+1), - -accesskey => "n", -title => "Alt-n"}, "next"); + searchtext=>$searchtext, searchtype=>$searchtype, + page=>$page+1), + -accesskey => "n", -title => "Alt-n"}, "next"); } else { $paging_nav .= " ⋅ next"; } @@ -4330,9 +4353,9 @@ sub git_search { if ($#commitlist >= 100) { $next_link = $cgi->a({-href => href(action=>"search", hash=>$hash, - searchtext=>$searchtext, searchtype=>$searchtype, - page=>$page+1), - -accesskey => "n", -title => "Alt-n"}, "next"); + searchtext=>$searchtext, searchtype=>$searchtype, + page=>$page+1), + -accesskey => "n", -title => "Alt-n"}, "next"); } git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav); @@ -9,10 +9,10 @@ static char git_default_date[50]; -static void copy_gecos(struct passwd *w, char *name, int sz) +static void copy_gecos(const struct passwd *w, char *name, size_t sz) { char *src, *dst; - int len, nlen; + size_t len, nlen; nlen = strlen(w->pw_name); @@ -43,13 +43,13 @@ static void copy_gecos(struct passwd *w, char *name, int sz) } -static void copy_email(struct passwd *pw) +static void copy_email(const struct passwd *pw) { /* * Make up a fake email address * (name + '@' + hostname [+ '.' + domainname]) */ - int len = strlen(pw->pw_name); + size_t len = strlen(pw->pw_name); if (len > sizeof(git_default_email)/2) die("Your sysadmin must hate you!"); memcpy(git_default_email, pw->pw_name, len); @@ -95,9 +95,9 @@ static void setup_ident(void) datestamp(git_default_date, sizeof(git_default_date)); } -static int add_raw(char *buf, int size, int offset, const char *str) +static int add_raw(char *buf, size_t size, int offset, const char *str) { - int len = strlen(str); + size_t len = strlen(str); if (offset + len > size) return size; memcpy(buf + offset, str, len); @@ -131,9 +131,9 @@ static int crud(unsigned char c) * Copy over a string to the destination, but avoid special * characters ('\n', '<' and '>') and remove crud at the end */ -static int copy(char *buf, int size, int offset, const char *src) +static int copy(char *buf, size_t size, int offset, const char *src) { - int i, len; + size_t i, len; unsigned char c; /* Remove crud from the beginning.. */ diff --git a/lockfile.c b/lockfile.c index 4824f4dc02..bed6b21daf 100644 --- a/lockfile.c +++ b/lockfile.c @@ -4,6 +4,7 @@ #include "cache.h" static struct lock_file *lock_file_list; +static const char *alternate_index_output; static void remove_lock_file(void) { @@ -65,6 +66,27 @@ int commit_lock_file(struct lock_file *lk) return i; } +int hold_locked_index(struct lock_file *lk, int die_on_error) +{ + return hold_lock_file_for_update(lk, get_index_file(), die_on_error); +} + +void set_alternate_index_output(const char *name) +{ + alternate_index_output = name; +} + +int commit_locked_index(struct lock_file *lk) +{ + if (alternate_index_output) { + int result = rename(lk->filename, alternate_index_output); + lk->filename[0] = 0; + return result; + } + else + return commit_lock_file(lk); +} + void rollback_lock_file(struct lock_file *lk) { if (lk->filename[0]) diff --git a/log-tree.c b/log-tree.c index 8797aa14c4..300b733560 100644 --- a/log-tree.c +++ b/log-tree.c @@ -4,6 +4,8 @@ #include "log-tree.h" #include "reflog-walk.h" +struct decoration name_decoration = { "object names" }; + static void show_parents(struct commit *commit, int abbrev) { struct commit_list *p; @@ -13,6 +15,23 @@ static void show_parents(struct commit *commit, int abbrev) } } +static void show_decorations(struct commit *commit) +{ + const char *prefix; + struct name_decoration *decoration; + + decoration = lookup_decoration(&name_decoration, &commit->object); + if (!decoration) + return; + prefix = " ("; + while (decoration) { + printf("%s%s", prefix, decoration->name); + prefix = ", "; + decoration = decoration->next; + } + putchar(')'); +} + /* * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches * Signed-off-by: and Acked-by: lines. @@ -136,6 +155,7 @@ void show_log(struct rev_info *opt, const char *sep) fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout); if (opt->parents) show_parents(commit, abbrev_commit); + show_decorations(commit); putchar(opt->diffopt.line_termination); return; } @@ -165,14 +185,20 @@ void show_log(struct rev_info *opt, const char *sep) if (opt->total > 0) { static char buffer[64]; snprintf(buffer, sizeof(buffer), - "Subject: [PATCH %0*d/%d] ", + "Subject: [%s %0*d/%d] ", + opt->subject_prefix, digits_in_number(opt->total), opt->nr, opt->total); subject = buffer; - } else if (opt->total == 0) - subject = "Subject: [PATCH] "; - else + } else if (opt->total == 0) { + static char buffer[256]; + snprintf(buffer, sizeof(buffer), + "Subject: [%s] ", + opt->subject_prefix); + subject = buffer; + } else { subject = "Subject: "; + } printf("From %s Mon Sep 17 00:00:00 2001\n", sha1); if (opt->message_id) @@ -234,6 +260,7 @@ void show_log(struct rev_info *opt, const char *sep) printf(" (from %s)", diff_unique_abbrev(parent->object.sha1, abbrev_commit)); + show_decorations(commit); printf("%s", diff_get_color(opt->diffopt.color_diff, DIFF_RESET)); putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n'); diff --git a/match-trees.c b/match-trees.c new file mode 100644 index 0000000000..23cafe47b4 --- /dev/null +++ b/match-trees.c @@ -0,0 +1,304 @@ +#include "cache.h" +#include "tree.h" +#include "tree-walk.h" + +static int score_missing(unsigned mode, const char *path) +{ + int score; + + if (S_ISDIR(mode)) + score = -1000; + else if (S_ISLNK(mode)) + score = -500; + else + score = -50; + return score; +} + +static int score_differs(unsigned mode1, unsigned mode2, const char *path) +{ + int score; + + if (S_ISDIR(mode1) != S_ISDIR(mode2)) + score = -100; + else if (S_ISLNK(mode1) != S_ISLNK(mode2)) + score = -50; + else + score = -5; + return score; +} + +static int score_matches(unsigned mode1, unsigned mode2, const char *path) +{ + int score; + + /* Heh, we found SHA-1 collisions between different kind of objects */ + if (S_ISDIR(mode1) != S_ISDIR(mode2)) + score = -100; + else if (S_ISLNK(mode1) != S_ISLNK(mode2)) + score = -50; + + else if (S_ISDIR(mode1)) + score = 1000; + else if (S_ISLNK(mode1)) + score = 500; + else + score = 250; + return score; +} + +/* + * Inspect two trees, and give a score that tells how similar they are. + */ +static int score_trees(const unsigned char *hash1, const unsigned char *hash2) +{ + struct tree_desc one; + struct tree_desc two; + void *one_buf, *two_buf; + int score = 0; + enum object_type type; + unsigned long size; + + one_buf = read_sha1_file(hash1, &type, &size); + if (!one_buf) + die("unable to read tree (%s)", sha1_to_hex(hash1)); + if (type != OBJ_TREE) + die("%s is not a tree", sha1_to_hex(hash1)); + init_tree_desc(&one, one_buf, size); + two_buf = read_sha1_file(hash2, &type, &size); + if (!two_buf) + die("unable to read tree (%s)", sha1_to_hex(hash2)); + if (type != OBJ_TREE) + die("%s is not a tree", sha1_to_hex(hash2)); + init_tree_desc(&two, two_buf, size); + while (one.size | two.size) { + const unsigned char *elem1 = elem1; + const unsigned char *elem2 = elem2; + const char *path1 = path1; + const char *path2 = path2; + unsigned mode1 = mode1; + unsigned mode2 = mode2; + int cmp; + + if (one.size) + elem1 = tree_entry_extract(&one, &path1, &mode1); + if (two.size) + elem2 = tree_entry_extract(&two, &path2, &mode2); + + if (!one.size) { + /* two has more entries */ + score += score_missing(mode2, path2); + update_tree_entry(&two); + continue; + } + if (!two.size) { + /* two lacks this entry */ + score += score_missing(mode1, path1); + update_tree_entry(&one); + continue; + } + cmp = base_name_compare(path1, strlen(path1), mode1, + path2, strlen(path2), mode2); + if (cmp < 0) { + /* path1 does not appear in two */ + score += score_missing(mode1, path1); + update_tree_entry(&one); + continue; + } + else if (cmp > 0) { + /* path2 does not appear in one */ + score += score_missing(mode2, path2); + update_tree_entry(&two); + continue; + } + else if (hashcmp(elem1, elem2)) + /* they are different */ + score += score_differs(mode1, mode2, path1); + else + /* same subtree or blob */ + score += score_matches(mode1, mode2, path1); + update_tree_entry(&one); + update_tree_entry(&two); + } + free(one_buf); + free(two_buf); + return score; +} + +/* + * Match one itself and its subtrees with two and pick the best match. + */ +static void match_trees(const unsigned char *hash1, + const unsigned char *hash2, + int *best_score, + char **best_match, + char *base, + int recurse_limit) +{ + struct tree_desc one; + void *one_buf; + enum object_type type; + unsigned long size; + + one_buf = read_sha1_file(hash1, &type, &size); + if (!one_buf) + die("unable to read tree (%s)", sha1_to_hex(hash1)); + if (type != OBJ_TREE) + die("%s is not a tree", sha1_to_hex(hash1)); + init_tree_desc(&one, one_buf, size); + + while (one.size) { + const char *path; + const unsigned char *elem; + unsigned mode; + int score; + + elem = tree_entry_extract(&one, &path, &mode); + if (!S_ISDIR(mode)) + goto next; + score = score_trees(elem, hash2); + if (*best_score < score) { + char *newpath; + newpath = xmalloc(strlen(base) + strlen(path) + 1); + sprintf(newpath, "%s%s", base, path); + free(*best_match); + *best_match = newpath; + *best_score = score; + } + if (recurse_limit) { + char *newbase; + newbase = xmalloc(strlen(base) + strlen(path) + 2); + sprintf(newbase, "%s%s/", base, path); + match_trees(elem, hash2, best_score, best_match, + newbase, recurse_limit - 1); + free(newbase); + } + + next: + update_tree_entry(&one); + } + free(one_buf); +} + +/* + * A tree "hash1" has a subdirectory at "prefix". Come up with a + * tree object by replacing it with another tree "hash2". + */ +static int splice_tree(const unsigned char *hash1, + char *prefix, + const unsigned char *hash2, + unsigned char *result) +{ + char *subpath; + int toplen; + char *buf; + unsigned long sz; + struct tree_desc desc; + unsigned char *rewrite_here; + const unsigned char *rewrite_with; + unsigned char subtree[20]; + enum object_type type; + int status; + + subpath = strchr(prefix, '/'); + if (!subpath) + toplen = strlen(prefix); + else { + toplen = subpath - prefix; + subpath++; + } + + buf = read_sha1_file(hash1, &type, &sz); + if (!buf) + die("cannot read tree %s", sha1_to_hex(hash1)); + init_tree_desc(&desc, buf, sz); + + rewrite_here = NULL; + while (desc.size) { + const char *name; + unsigned mode; + const unsigned char *sha1; + + sha1 = tree_entry_extract(&desc, &name, &mode); + if (strlen(name) == toplen && + !memcmp(name, prefix, toplen)) { + if (!S_ISDIR(mode)) + die("entry %s in tree %s is not a tree", + name, sha1_to_hex(hash1)); + rewrite_here = (unsigned char *) sha1; + break; + } + update_tree_entry(&desc); + } + if (!rewrite_here) + die("entry %.*s not found in tree %s", + toplen, prefix, sha1_to_hex(hash1)); + if (subpath) { + status = splice_tree(rewrite_here, subpath, hash2, subtree); + if (status) + return status; + rewrite_with = subtree; + } + else + rewrite_with = hash2; + hashcpy(rewrite_here, rewrite_with); + status = write_sha1_file(buf, sz, tree_type, result); + free(buf); + return status; +} + +/* + * We are trying to come up with a merge between one and two that + * results in a tree shape similar to one. The tree two might + * correspond to a subtree of one, in which case it needs to be + * shifted down by prefixing otherwise empty directories. On the + * other hand, it could cover tree one and we might need to pick a + * subtree of it. + */ +void shift_tree(const unsigned char *hash1, + const unsigned char *hash2, + unsigned char *shifted, + int depth_limit) +{ + char *add_prefix; + char *del_prefix; + int add_score, del_score; + + add_score = del_score = score_trees(hash1, hash2); + add_prefix = xcalloc(1, 1); + del_prefix = xcalloc(1, 1); + + /* + * See if one's subtree resembles two; if so we need to prefix + * two with a few fake trees to match the prefix. + */ + match_trees(hash1, hash2, &add_score, &add_prefix, "", depth_limit); + + /* + * See if two's subtree resembles one; if so we need to + * pick only subtree of two. + */ + match_trees(hash2, hash1, &del_score, &del_prefix, "", depth_limit); + + /* Assume we do not have to do any shifting */ + hashcpy(shifted, hash2); + + if (add_score < del_score) { + /* We need to pick a subtree of two */ + unsigned mode; + + if (!*del_prefix) + return; + + if (get_tree_entry(hash2, del_prefix, shifted, &mode)) + die("cannot find path %s in tree %s", + del_prefix, sha1_to_hex(hash2)); + return; + } + + if (!*add_prefix) + return; + + splice_tree(hash1, add_prefix, hash2, shifted); +} + diff --git a/merge-recursive.c b/merge-recursive.c index e1aebd7727..595b0226ac 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -16,6 +16,22 @@ #include "path-list.h" #include "xdiff-interface.h" +static int subtree_merge; + +static struct tree *shift_tree_object(struct tree *one, struct tree *two) +{ + unsigned char shifted[20]; + + /* + * NEEDSWORK: this limits the recursion depth to hardcoded + * value '2' to avoid excessive overhead. + */ + shift_tree(one->object.sha1, two->object.sha1, shifted, 2); + if (!hashcmp(two->object.sha1, shifted)) + return two; + return lookup_tree(shifted); +} + /* * A virtual commit has * - (const char *)commit->util set to the name, and @@ -221,7 +237,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, struct cache_entry *ce; ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh); if (!ce) - return error("cache_addinfo failed: %s", strerror(cache_errno)); + return error("addinfo_cache failed for path '%s'", path); return add_cache_entry(ce, options); } @@ -580,9 +596,31 @@ static void update_file_flags(const unsigned char *sha, if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) { int fd; - if (mkdir_p(path, 0777)) - die("failed to create path %s: %s", path, strerror(errno)); - unlink(path); + int status; + const char *msg = "failed to create path '%s'%s"; + + status = mkdir_p(path, 0777); + if (status) { + if (status == -3) { + /* something else exists */ + error(msg, path, ": perhaps a D/F conflict?"); + update_wd = 0; + goto update_index; + } + die(msg, path, ""); + } + if (unlink(path)) { + if (errno == EISDIR) { + /* something else exists */ + error(msg, path, ": perhaps a D/F conflict?"); + update_wd = 0; + goto update_index; + } + if (errno != ENOENT) + die("failed to unlink %s " + "in preparation to update: %s", + path, strerror(errno)); + } if (mode & 0100) mode = 0777; else @@ -604,6 +642,7 @@ static void update_file_flags(const unsigned char *sha, die("do not know what to do with %06o %s '%s'", mode, sha1_to_hex(sha), path); } + update_index: if (update_cache) add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD); } @@ -1002,9 +1041,9 @@ static int process_renames(struct path_list *a_renames, return clean_merge; } -static unsigned char *has_sha(const unsigned char *sha) +static unsigned char *stage_sha(const unsigned char *sha, unsigned mode) { - return is_null_sha1(sha) ? NULL: (unsigned char *)sha; + return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha; } /* Per entry merge function */ @@ -1017,12 +1056,12 @@ static int process_entry(const char *path, struct stage_data *entry, print_index_entry("\tpath: ", entry); */ int clean_merge = 1; - unsigned char *o_sha = has_sha(entry->stages[1].sha); - unsigned char *a_sha = has_sha(entry->stages[2].sha); - unsigned char *b_sha = has_sha(entry->stages[3].sha); unsigned o_mode = entry->stages[1].mode; unsigned a_mode = entry->stages[2].mode; unsigned b_mode = entry->stages[3].mode; + unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode); + unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode); + unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode); if (o_sha && (!a_sha || !b_sha)) { /* Case A: Deleted in one */ @@ -1123,6 +1162,12 @@ static int process_entry(const char *path, struct stage_data *entry, update_file_flags(mfi.sha, mfi.mode, path, 0 /* update_cache */, 1 /* update_working_directory */); } + } else if (!o_sha && !a_sha && !b_sha) { + /* + * this entry was deleted altogether. a_mode == 0 means + * we had that path and want to actively remove it. + */ + remove_file(1, path, !a_mode); } else die("Fatal merge failure, shouldn't happen."); @@ -1137,6 +1182,12 @@ static int merge_trees(struct tree *head, struct tree **result) { int code, clean; + + if (subtree_merge) { + merge = shift_tree_object(head, merge); + common = shift_tree_object(head, common); + } + if (sha_eq(common->object.sha1, merge->object.sha1)) { output(0, "Already uptodate!"); *result = head; @@ -1342,6 +1393,13 @@ int main(int argc, char *argv[]) struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); int index_fd; + if (argv[0]) { + int namelen = strlen(argv[0]); + if (8 < namelen && + !strcmp(argv[0] + namelen - 8, "-subtree")) + subtree_merge = 1; + } + git_config(merge_config); if (getenv("GIT_MERGE_VERBOSITY")) verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10); @@ -1378,7 +1436,7 @@ int main(int argc, char *argv[]) if (show(3)) printf("Merging %s with %s\n", branch1, branch2); - index_fd = hold_lock_file_for_update(lock, get_index_file(), 1); + index_fd = hold_locked_index(lock, 1); for (i = 0; i < bases_count; i++) { struct commit *ancestor = get_ref(bases[i]); @@ -1388,7 +1446,7 @@ int main(int argc, char *argv[]) if (active_cache_changed && (write_cache(index_fd, active_cache, active_nr) || - close(index_fd) || commit_lock_file(lock))) + close(index_fd) || commit_locked_index(lock))) die ("unable to write %s", get_index_file()); return clean ? 0: 1; diff --git a/object-refs.c b/object-refs.c index 98ea10005a..022e8d841c 100644 --- a/object-refs.c +++ b/object-refs.c @@ -1,75 +1,20 @@ #include "cache.h" #include "object.h" +#include "decorate.h" int track_object_refs = 0; -static unsigned int refs_hash_size, nr_object_refs; -static struct object_refs **refs_hash; +static struct decoration ref_decorate; -static unsigned int hash_obj(struct object *obj, unsigned int n) +struct object_refs *lookup_object_refs(struct object *base) { - unsigned int hash = *(unsigned int *)obj->sha1; - return hash % n; + return lookup_decoration(&ref_decorate, base); } -static void insert_ref_hash(struct object_refs *ref, struct object_refs **hash, unsigned int size) +static void add_object_refs(struct object *obj, struct object_refs *refs) { - int j = hash_obj(ref->base, size); - - while (hash[j]) { - j++; - if (j >= size) - j = 0; - } - hash[j] = ref; -} - -static void grow_refs_hash(void) -{ - int i; - int new_hash_size = (refs_hash_size + 1000) * 3 / 2; - struct object_refs **new_hash; - - new_hash = xcalloc(new_hash_size, sizeof(struct object_refs *)); - for (i = 0; i < refs_hash_size; i++) { - struct object_refs *ref = refs_hash[i]; - if (!ref) - continue; - insert_ref_hash(ref, new_hash, new_hash_size); - } - free(refs_hash); - refs_hash = new_hash; - refs_hash_size = new_hash_size; -} - -static void add_object_refs(struct object *obj, struct object_refs *ref) -{ - int nr = nr_object_refs + 1; - - if (nr > refs_hash_size * 2 / 3) - grow_refs_hash(); - ref->base = obj; - insert_ref_hash(ref, refs_hash, refs_hash_size); - nr_object_refs = nr; -} - -struct object_refs *lookup_object_refs(struct object *obj) -{ - struct object_refs *ref; - int j; - - /* nothing to lookup */ - if (!refs_hash_size) - return NULL; - j = hash_obj(obj, refs_hash_size); - while ((ref = refs_hash[j]) != NULL) { - if (ref->base == obj) - break; - j++; - if (j >= refs_hash_size) - j = 0; - } - return ref; + if (add_decoration(&ref_decorate, obj, refs)) + die("object %s tried to add refs twice!", sha1_to_hex(obj->sha1)); } struct object_refs *alloc_object_refs(unsigned count) @@ -8,7 +8,6 @@ struct object_list { struct object_refs { unsigned count; - struct object *base; struct object *ref[FLEX_ARRAY]; /* more */ }; diff --git a/pack-check.c b/pack-check.c index d9883225ea..f58083d11e 100644 --- a/pack-check.c +++ b/pack-check.c @@ -42,13 +42,14 @@ static int verify_packfile(struct packed_git *p, */ nr_objects = num_packed_objects(p); for (i = 0, err = 0; i < nr_objects; i++) { - unsigned char sha1[20]; + const unsigned char *sha1; void *data; enum object_type type; unsigned long size; off_t offset; - if (nth_packed_object_sha1(p, i, sha1)) + sha1 = nth_packed_object_sha1(p, i); + if (!sha1) die("internal error pack-check nth-packed-object"); offset = find_pack_entry_one(sha1, p); if (!offset) @@ -82,14 +83,16 @@ static void show_pack_info(struct packed_git *p) memset(chain_histogram, 0, sizeof(chain_histogram)); for (i = 0; i < nr_objects; i++) { - unsigned char sha1[20], base_sha1[20]; + const unsigned char *sha1; + unsigned char base_sha1[20]; const char *type; unsigned long size; unsigned long store_size; off_t offset; unsigned int delta_chain_length; - if (nth_packed_object_sha1(p, i, sha1)) + sha1 = nth_packed_object_sha1(p, i); + if (!sha1) die("internal error pack-check nth-packed-object"); offset = find_pack_entry_one(sha1, p); if (!offset) diff --git a/patch-ids.c b/patch-ids.c new file mode 100644 index 0000000000..a288fac992 --- /dev/null +++ b/patch-ids.c @@ -0,0 +1,192 @@ +#include "cache.h" +#include "diff.h" +#include "commit.h" +#include "patch-ids.h" + +static int commit_patch_id(struct commit *commit, struct diff_options *options, + unsigned char *sha1) +{ + if (commit->parents) + diff_tree_sha1(commit->parents->item->object.sha1, + commit->object.sha1, "", options); + else + diff_root_tree_sha1(commit->object.sha1, "", options); + diffcore_std(options); + return diff_flush_patch_id(options, sha1); +} + +static uint32_t take2(const unsigned char *id) +{ + return ((id[0] << 8) | id[1]); +} + +/* + * Conventional binary search loop looks like this: + * + * do { + * int mi = (lo + hi) / 2; + * int cmp = "entry pointed at by mi" minus "target"; + * if (!cmp) + * return (mi is the wanted one) + * if (cmp > 0) + * hi = mi; "mi is larger than target" + * else + * lo = mi+1; "mi is smaller than target" + * } while (lo < hi); + * + * The invariants are: + * + * - When entering the loop, lo points at a slot that is never + * above the target (it could be at the target), hi points at a + * slot that is guaranteed to be above the target (it can never + * be at the target). + * + * - We find a point 'mi' between lo and hi (mi could be the same + * as lo, but never can be the same as hi), and check if it hits + * the target. There are three cases: + * + * - if it is a hit, we are happy. + * + * - if it is strictly higher than the target, we update hi with + * it. + * + * - if it is strictly lower than the target, we update lo to be + * one slot after it, because we allow lo to be at the target. + * + * When choosing 'mi', we do not have to take the "middle" but + * anywhere in between lo and hi, as long as lo <= mi < hi is + * satisfied. When we somehow know that the distance between the + * target and lo is much shorter than the target and hi, we could + * pick mi that is much closer to lo than the midway. + */ +static int patch_pos(struct patch_id **table, int nr, const unsigned char *id) +{ + int hi = nr; + int lo = 0; + int mi = 0; + + if (!nr) + return -1; + + if (nr != 1) { + unsigned lov, hiv, miv, ofs; + + for (ofs = 0; ofs < 18; ofs += 2) { + lov = take2(table[0]->patch_id + ofs); + hiv = take2(table[nr-1]->patch_id + ofs); + miv = take2(id + ofs); + if (miv < lov) + return -1; + if (hiv < miv) + return -1 - nr; + if (lov != hiv) { + /* + * At this point miv could be equal + * to hiv (but id could still be higher); + * the invariant of (mi < hi) should be + * kept. + */ + mi = (nr-1) * (miv - lov) / (hiv - lov); + if (lo <= mi && mi < hi) + break; + die("oops"); + } + } + if (18 <= ofs) + die("cannot happen -- lo and hi are identical"); + } + + do { + int cmp; + cmp = hashcmp(table[mi]->patch_id, id); + if (!cmp) + return mi; + if (cmp > 0) + hi = mi; + else + lo = mi + 1; + mi = (hi + lo) / 2; + } while (lo < hi); + return -lo-1; +} + +#define BUCKET_SIZE 190 /* 190 * 21 = 3990, with slop close enough to 4K */ +struct patch_id_bucket { + struct patch_id_bucket *next; + int nr; + struct patch_id bucket[BUCKET_SIZE]; +}; + +int init_patch_ids(struct patch_ids *ids) +{ + memset(ids, 0, sizeof(*ids)); + diff_setup(&ids->diffopts); + ids->diffopts.recursive = 1; + if (diff_setup_done(&ids->diffopts) < 0) + return error("diff_setup_done failed"); + return 0; +} + +int free_patch_ids(struct patch_ids *ids) +{ + struct patch_id_bucket *next, *patches; + + free(ids->table); + for (patches = ids->patches; patches; patches = next) { + next = patches->next; + free(patches); + } + return 0; +} + +static struct patch_id *add_commit(struct commit *commit, + struct patch_ids *ids, + int no_add) +{ + struct patch_id_bucket *bucket; + struct patch_id *ent; + unsigned char sha1[20]; + int pos; + + if (commit_patch_id(commit, &ids->diffopts, sha1)) + return NULL; + pos = patch_pos(ids->table, ids->nr, sha1); + if (0 <= pos) + return ids->table[pos]; + if (no_add) + return NULL; + + pos = -1 - pos; + + bucket = ids->patches; + if (!bucket || (BUCKET_SIZE <= bucket->nr)) { + bucket = xcalloc(1, sizeof(*bucket)); + bucket->next = ids->patches; + ids->patches = bucket; + } + ent = &bucket->bucket[bucket->nr++]; + hashcpy(ent->patch_id, sha1); + + if (ids->alloc <= ids->nr) { + ids->alloc = alloc_nr(ids->nr); + ids->table = xrealloc(ids->table, sizeof(ent) * ids->alloc); + } + if (pos < ids->nr) + memmove(ids->table + pos + 1, ids->table + pos, + sizeof(ent) * (ids->nr - pos)); + ids->nr++; + ids->table[pos] = ent; + return ids->table[pos]; +} + +struct patch_id *has_commit_patch_id(struct commit *commit, + struct patch_ids *ids) +{ + return add_commit(commit, ids, 1); +} + +struct patch_id *add_commit_patch_id(struct commit *commit, + struct patch_ids *ids) +{ + return add_commit(commit, ids, 0); +} diff --git a/patch-ids.h b/patch-ids.h new file mode 100644 index 0000000000..c8c7ca110a --- /dev/null +++ b/patch-ids.h @@ -0,0 +1,21 @@ +#ifndef PATCH_IDS_H +#define PATCH_IDS_H + +struct patch_id { + unsigned char patch_id[20]; + char seen; +}; + +struct patch_ids { + struct diff_options diffopts; + int nr, alloc; + struct patch_id **table; + struct patch_id_bucket *patches; +}; + +int init_patch_ids(struct patch_ids *); +int free_patch_ids(struct patch_ids *); +struct patch_id *add_commit_patch_id(struct commit *, struct patch_ids *); +struct patch_id *has_commit_patch_id(struct commit *, struct patch_ids *); + +#endif /* PATCH_IDS_H */ diff --git a/read-cache.c b/read-cache.c index 6339a278da..54573ce2ee 100644 --- a/read-cache.c +++ b/read-cache.c @@ -24,8 +24,6 @@ unsigned int active_nr, active_alloc, active_cache_changed; struct cache_tree *active_cache_tree; -int cache_errno; - static void *cache_mmap; static size_t cache_mmap_size; @@ -327,7 +325,7 @@ int remove_file_from_cache(const char *path) return 0; } -int add_file_to_index(const char *path, int verbose) +int add_file_to_cache(const char *path, int verbose) { int size, namelen; struct stat st; @@ -487,6 +485,8 @@ static int has_file_name(const struct cache_entry *ce, int pos, int ok_to_replac continue; if (p->name[len] != '/') continue; + if (!ce_stage(p) && !p->ce_mode) + continue; retval = -1; if (!ok_to_replace) break; @@ -519,26 +519,37 @@ static int has_dir_name(const struct cache_entry *ce, int pos, int ok_to_replace pos = cache_name_pos(name, ntohs(create_ce_flags(len, stage))); if (pos >= 0) { - retval = -1; - if (!ok_to_replace) - break; - remove_cache_entry_at(pos); - continue; + /* + * Found one, but not so fast. This could + * be a marker that says "I was here, but + * I am being removed". Such an entry is + * not a part of the resulting tree, and + * it is Ok to have a directory at the same + * path. + */ + if (stage || active_cache[pos]->ce_mode) { + retval = -1; + if (!ok_to_replace) + break; + remove_cache_entry_at(pos); + continue; + } } + else + pos = -pos-1; /* * Trivial optimization: if we find an entry that * already matches the sub-directory, then we know * we're ok, and we can exit. */ - pos = -pos-1; while (pos < active_nr) { struct cache_entry *p = active_cache[pos]; if ((ce_namelen(p) <= len) || (p->name[len] != '/') || memcmp(p->name, name, len)) break; /* not our subdirectory */ - if (ce_stage(p) == stage) + if (ce_stage(p) == stage && (stage || p->ce_mode)) /* p is at the same stage as our entry, and * is a subdirectory of what we are looking * at, so we cannot have conflicts at our @@ -562,12 +573,21 @@ static int has_dir_name(const struct cache_entry *ce, int pos, int ok_to_replace */ static int check_file_directory_conflict(const struct cache_entry *ce, int pos, int ok_to_replace) { + int retval; + + /* + * When ce is an "I am going away" entry, we allow it to be added + */ + if (!ce_stage(ce) && !ce->ce_mode) + return 0; + /* * We check if the path is a sub-path of a subsequent pathname * first, since removing those will not change the position - * in the array + * in the array. */ - int retval = has_file_name(ce, pos, ok_to_replace); + retval = has_file_name(ce, pos, ok_to_replace); + /* * Then check if the path might have a clashing sub-directory * before it. @@ -643,14 +663,15 @@ int add_cache_entry(struct cache_entry *ce, int option) * For example, you'd want to do this after doing a "git-read-tree", * to link up the stat cache details with the proper files. */ -struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really) +static struct cache_entry *refresh_cache_ent(struct cache_entry *ce, int really, int *err) { struct stat st; struct cache_entry *updated; int changed, size; if (lstat(ce->name, &st) < 0) { - cache_errno = errno; + if (err) + *err = errno; return NULL; } @@ -664,7 +685,8 @@ struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really) } if (ce_modified(ce, &st, really)) { - cache_errno = EINVAL; + if (err) + *err = EINVAL; return NULL; } @@ -696,6 +718,8 @@ int refresh_cache(unsigned int flags) for (i = 0; i < active_nr; i++) { struct cache_entry *ce, *new; + int cache_errno = 0; + ce = active_cache[i]; if (ce_stage(ce)) { while ((i < active_nr) && @@ -709,7 +733,7 @@ int refresh_cache(unsigned int flags) continue; } - new = refresh_cache_entry(ce, really); + new = refresh_cache_ent(ce, really, &cache_errno); if (new == ce) continue; if (!new) { @@ -737,6 +761,11 @@ int refresh_cache(unsigned int flags) return has_errors; } +struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really) +{ + return refresh_cache_ent(ce, really, NULL); +} + static int verify_hdr(struct cache_header *hdr, unsigned long size) { SHA_CTX c; diff --git a/revision.c b/revision.c index 486393cb08..ce70f48ce0 100644 --- a/revision.c +++ b/revision.c @@ -8,6 +8,7 @@ #include "revision.h" #include "grep.h" #include "reflog-walk.h" +#include "patch-ids.h" static char *path_name(struct name_path *path, const char *name) { @@ -422,6 +423,86 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st } } +static void cherry_pick_list(struct commit_list *list) +{ + struct commit_list *p; + int left_count = 0, right_count = 0; + int left_first; + struct patch_ids ids; + + /* First count the commits on the left and on the right */ + for (p = list; p; p = p->next) { + struct commit *commit = p->item; + unsigned flags = commit->object.flags; + if (flags & BOUNDARY) + ; + else if (flags & SYMMETRIC_LEFT) + left_count++; + else + right_count++; + } + + left_first = left_count < right_count; + init_patch_ids(&ids); + + /* Compute patch-ids for one side */ + for (p = list; p; p = p->next) { + struct commit *commit = p->item; + unsigned flags = commit->object.flags; + + if (flags & BOUNDARY) + continue; + /* + * If we have fewer left, left_first is set and we omit + * commits on the right branch in this loop. If we have + * fewer right, we skip the left ones. + */ + if (left_first != !!(flags & SYMMETRIC_LEFT)) + continue; + commit->util = add_commit_patch_id(commit, &ids); + } + + /* Check the other side */ + for (p = list; p; p = p->next) { + struct commit *commit = p->item; + struct patch_id *id; + unsigned flags = commit->object.flags; + + if (flags & BOUNDARY) + continue; + /* + * If we have fewer left, left_first is set and we omit + * commits on the left branch in this loop. + */ + if (left_first == !!(flags & SYMMETRIC_LEFT)) + continue; + + /* + * Have we seen the same patch id? + */ + id = has_commit_patch_id(commit, &ids); + if (!id) + continue; + id->seen = 1; + commit->object.flags |= SHOWN; + } + + /* Now check the original side for seen ones */ + for (p = list; p; p = p->next) { + struct commit *commit = p->item; + struct patch_id *ent; + + ent = commit->util; + if (!ent) + continue; + if (ent->seen) + commit->object.flags |= SHOWN; + commit->util = NULL; + } + + free_patch_ids(&ids); +} + static void limit_list(struct rev_info *revs) { struct commit_list *list = revs->commits; @@ -449,6 +530,9 @@ static void limit_list(struct rev_info *revs) continue; p = &commit_list_insert(commit, p)->next; } + if (revs->cherry_pick) + cherry_pick_list(newlist); + revs->commits = newlist; } @@ -567,6 +651,7 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->min_age = -1; revs->skip_count = -1; revs->max_count = -1; + revs->subject_prefix = "PATCH"; revs->prune_fn = NULL; revs->prune_data = NULL; @@ -913,6 +998,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->left_right = 1; continue; } + if (!strcmp(arg, "--cherry-pick")) { + revs->cherry_pick = 1; + continue; + } if (!strcmp(arg, "--objects")) { revs->tag_objects = 1; revs->tree_objects = 1; diff --git a/revision.h b/revision.h index 55e6b531ce..8a02618428 100644 --- a/revision.h +++ b/revision.h @@ -47,6 +47,7 @@ struct rev_info { left_right:1, parents:1, reverse:1, + cherry_pick:1, first_parent_only:1; /* Diff flags */ @@ -78,6 +79,7 @@ struct rev_info { const char *add_signoff; const char *extra_headers; const char *log_reencode; + const char *subject_prefix; int no_inline; /* Filter by commit log message */ diff --git a/sha1_file.c b/sha1_file.c index 9c26038420..4304fe9bbc 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1532,15 +1532,14 @@ uint32_t num_packed_objects(const struct packed_git *p) return (uint32_t)((p->index_size - 20 - 20 - 4*256) / 24); } -int nth_packed_object_sha1(const struct packed_git *p, uint32_t n, - unsigned char* sha1) +const unsigned char *nth_packed_object_sha1(const struct packed_git *p, + uint32_t n) { const unsigned char *index = p->index_data; index += 4 * 256; if (num_packed_objects(p) <= n) - return -1; - hashcpy(sha1, index + 24 * n + 4); - return 0; + return NULL; + return index + 24 * n + 4; } off_t find_pack_entry_one(const unsigned char *sha1, diff --git a/sha1_name.c b/sha1_name.c index bede0e5b06..267ea3f3ed 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -71,7 +71,7 @@ static int match_sha(unsigned len, const unsigned char *a, const unsigned char * static int find_short_packed_object(int len, const unsigned char *match, unsigned char *sha1) { struct packed_git *p; - unsigned char found_sha1[20]; + const unsigned char *found_sha1 = NULL; int found = 0; prepare_packed_git(); @@ -80,10 +80,10 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne uint32_t first = 0, last = num; while (first < last) { uint32_t mid = (first + last) / 2; - unsigned char now[20]; + const unsigned char *now; int cmp; - nth_packed_object_sha1(p, mid, now); + now = nth_packed_object_sha1(p, mid); cmp = hashcmp(match, now); if (!cmp) { first = mid; @@ -96,14 +96,14 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne last = mid; } if (first < num) { - unsigned char now[20], next[20]; - nth_packed_object_sha1(p, first, now); + const unsigned char *now, *next; + now = nth_packed_object_sha1(p, first); if (match_sha(len, match, now)) { - if (nth_packed_object_sha1(p, first+1, next) || - !match_sha(len, match, next)) { + next = nth_packed_object_sha1(p, first+1); + if (!next|| !match_sha(len, match, next)) { /* unique within this pack */ if (!found) { - hashcpy(found_sha1, now); + found_sha1 = now; found++; } else if (hashcmp(found_sha1, now)) { diff --git a/t/diff-lib.sh b/t/diff-lib.sh index 4624fe654c..4624fe654c 100755..100644 --- a/t/diff-lib.sh +++ b/t/diff-lib.sh diff --git a/t/lib-read-tree-m-3way.sh b/t/lib-read-tree-m-3way.sh index d195603dfa..d195603dfa 100755..100644 --- a/t/lib-read-tree-m-3way.sh +++ b/t/lib-read-tree-m-3way.sh diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh index e26a36cf0f..de4e5eb61f 100755 --- a/t/t1000-read-tree-m-3way.sh +++ b/t/t1000-read-tree-m-3way.sh @@ -184,7 +184,7 @@ checked. 9 exists O!=A missing no merge must match A and be up-to-date, if exists. ------------------------------------------------------------------ - 10 exists O==A missing remove ditto + 10 exists O==A missing no merge must match A ------------------------------------------------------------------ 11 exists O!=A O!=B no merge must match A and be A!=B up-to-date, if exists. diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh new file mode 100755 index 0000000000..aef92b9b92 --- /dev/null +++ b/t/t3030-merge-recursive.sh @@ -0,0 +1,528 @@ +#!/bin/sh + +test_description='merge-recursive backend test' + +. ./test-lib.sh + +test_expect_success 'setup 1' ' + + echo hello >a && + o0=$(git hash-object a) && + cp a b && + cp a A && + mkdir d && + cp a d/e && + + test_tick && + git add a b A d/e && + git commit -m initial && + c0=$(git rev-parse --verify HEAD) && + git branch side && + git branch df-1 && + git branch df-2 && + git branch df-3 && + git branch remove && + + echo hello >>a && + cp a d/e && + o1=$(git hash-object a) && + + git add a d/e && + + test_tick && + git commit -m "master modifies a and d/e" && + c1=$(git rev-parse --verify HEAD) && + ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( + echo "100644 blob $o0 A" + echo "100644 blob $o1 a" + echo "100644 blob $o0 b" + echo "100644 blob $o1 d/e" + echo "100644 $o0 0 A" + echo "100644 $o1 0 a" + echo "100644 $o0 0 b" + echo "100644 $o1 0 d/e" + ) >expected && + git diff -u expected actual +' + +test_expect_success 'setup 2' ' + + rm -rf [Aabd] && + git checkout side && + ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( + echo "100644 blob $o0 A" + echo "100644 blob $o0 a" + echo "100644 blob $o0 b" + echo "100644 blob $o0 d/e" + echo "100644 $o0 0 A" + echo "100644 $o0 0 a" + echo "100644 $o0 0 b" + echo "100644 $o0 0 d/e" + ) >expected && + git diff -u expected actual && + + echo goodbye >>a && + o2=$(git hash-object a) && + + git add a && + + test_tick && + git commit -m "side modifies a" && + c2=$(git rev-parse --verify HEAD) && + ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( + echo "100644 blob $o0 A" + echo "100644 blob $o2 a" + echo "100644 blob $o0 b" + echo "100644 blob $o0 d/e" + echo "100644 $o0 0 A" + echo "100644 $o2 0 a" + echo "100644 $o0 0 b" + echo "100644 $o0 0 d/e" + ) >expected && + git diff -u expected actual +' + +test_expect_success 'setup 3' ' + + rm -rf [Aabd] && + git checkout df-1 && + ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( + echo "100644 blob $o0 A" + echo "100644 blob $o0 a" + echo "100644 blob $o0 b" + echo "100644 blob $o0 d/e" + echo "100644 $o0 0 A" + echo "100644 $o0 0 a" + echo "100644 $o0 0 b" + echo "100644 $o0 0 d/e" + ) >expected && + git diff -u expected actual && + + rm -f b && mkdir b && echo df-1 >b/c && git add b/c && + o3=$(git hash-object b/c) && + + test_tick && + git commit -m "df-1 makes b/c" && + c3=$(git rev-parse --verify HEAD) && + ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( + echo "100644 blob $o0 A" + echo "100644 blob $o0 a" + echo "100644 blob $o3 b/c" + echo "100644 blob $o0 d/e" + echo "100644 $o0 0 A" + echo "100644 $o0 0 a" + echo "100644 $o3 0 b/c" + echo "100644 $o0 0 d/e" + ) >expected && + git diff -u expected actual +' + +test_expect_success 'setup 4' ' + + rm -rf [Aabd] && + git checkout df-2 && + ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( + echo "100644 blob $o0 A" + echo "100644 blob $o0 a" + echo "100644 blob $o0 b" + echo "100644 blob $o0 d/e" + echo "100644 $o0 0 A" + echo "100644 $o0 0 a" + echo "100644 $o0 0 b" + echo "100644 $o0 0 d/e" + ) >expected && + git diff -u expected actual && + + rm -f a && mkdir a && echo df-2 >a/c && git add a/c && + o4=$(git hash-object a/c) && + + test_tick && + git commit -m "df-2 makes a/c" && + c4=$(git rev-parse --verify HEAD) && + ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( + echo "100644 blob $o0 A" + echo "100644 blob $o4 a/c" + echo "100644 blob $o0 b" + echo "100644 blob $o0 d/e" + echo "100644 $o0 0 A" + echo "100644 $o4 0 a/c" + echo "100644 $o0 0 b" + echo "100644 $o0 0 d/e" + ) >expected && + git diff -u expected actual +' + +test_expect_success 'setup 5' ' + + rm -rf [Aabd] && + git checkout remove && + ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( + echo "100644 blob $o0 A" + echo "100644 blob $o0 a" + echo "100644 blob $o0 b" + echo "100644 blob $o0 d/e" + echo "100644 $o0 0 A" + echo "100644 $o0 0 a" + echo "100644 $o0 0 b" + echo "100644 $o0 0 d/e" + ) >expected && + git diff -u expected actual && + + rm -f b && + echo remove-conflict >a && + + git add a && + git rm b && + o5=$(git hash-object a) && + + test_tick && + git commit -m "remove removes b and modifies a" && + c5=$(git rev-parse --verify HEAD) && + ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( + echo "100644 blob $o0 A" + echo "100644 blob $o5 a" + echo "100644 blob $o0 d/e" + echo "100644 $o0 0 A" + echo "100644 $o5 0 a" + echo "100644 $o0 0 d/e" + ) >expected && + git diff -u expected actual + +' + +test_expect_success 'setup 6' ' + + rm -rf [Aabd] && + git checkout df-3 && + ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( + echo "100644 blob $o0 A" + echo "100644 blob $o0 a" + echo "100644 blob $o0 b" + echo "100644 blob $o0 d/e" + echo "100644 $o0 0 A" + echo "100644 $o0 0 a" + echo "100644 $o0 0 b" + echo "100644 $o0 0 d/e" + ) >expected && + git diff -u expected actual && + + rm -fr d && echo df-3 >d && git add d && + o6=$(git hash-object d) && + + test_tick && + git commit -m "df-3 makes d" && + c6=$(git rev-parse --verify HEAD) && + ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( + echo "100644 blob $o0 A" + echo "100644 blob $o0 a" + echo "100644 blob $o0 b" + echo "100644 blob $o6 d" + echo "100644 $o0 0 A" + echo "100644 $o0 0 a" + echo "100644 $o0 0 b" + echo "100644 $o6 0 d" + ) >expected && + git diff -u expected actual +' + +test_expect_success 'merge-recursive simple' ' + + rm -fr [Aabd] && + git checkout -f "$c2" && + + git-merge-recursive "$c0" -- "$c2" "$c1" + status=$? + case "$status" in + 1) + : happy + ;; + *) + echo >&2 "why status $status!!!" + false + ;; + esac +' + +test_expect_success 'merge-recursive result' ' + + git ls-files -s >actual && + ( + echo "100644 $o0 0 A" + echo "100644 $o0 1 a" + echo "100644 $o2 2 a" + echo "100644 $o1 3 a" + echo "100644 $o0 0 b" + echo "100644 $o1 0 d/e" + ) >expected && + git diff -u expected actual + +' + +test_expect_success 'merge-recursive remove conflict' ' + + rm -fr [Aabd] && + git checkout -f "$c1" && + + git-merge-recursive "$c0" -- "$c1" "$c5" + status=$? + case "$status" in + 1) + : happy + ;; + *) + echo >&2 "why status $status!!!" + false + ;; + esac +' + +test_expect_success 'merge-recursive remove conflict' ' + + git ls-files -s >actual && + ( + echo "100644 $o0 0 A" + echo "100644 $o0 1 a" + echo "100644 $o1 2 a" + echo "100644 $o5 3 a" + echo "100644 $o1 0 d/e" + ) >expected && + git diff -u expected actual + +' + +test_expect_success 'merge-recursive d/f simple' ' + rm -fr [Aabd] && + git reset --hard && + git checkout -f "$c1" && + + git-merge-recursive "$c0" -- "$c1" "$c3" +' + +test_expect_success 'merge-recursive result' ' + + git ls-files -s >actual && + ( + echo "100644 $o0 0 A" + echo "100644 $o1 0 a" + echo "100644 $o3 0 b/c" + echo "100644 $o1 0 d/e" + ) >expected && + git diff -u expected actual + +' + +test_expect_success 'merge-recursive d/f conflict' ' + + rm -fr [Aabd] && + git reset --hard && + git checkout -f "$c1" && + + git-merge-recursive "$c0" -- "$c1" "$c4" + status=$? + case "$status" in + 1) + : happy + ;; + *) + echo >&2 "why status $status!!!" + false + ;; + esac +' + +test_expect_success 'merge-recursive d/f conflict result' ' + + git ls-files -s >actual && + ( + echo "100644 $o0 0 A" + echo "100644 $o0 1 a" + echo "100644 $o1 2 a" + echo "100644 $o4 0 a/c" + echo "100644 $o0 0 b" + echo "100644 $o1 0 d/e" + ) >expected && + git diff -u expected actual + +' + +test_expect_success 'merge-recursive d/f conflict the other way' ' + + rm -fr [Aabd] && + git reset --hard && + git checkout -f "$c4" && + + git-merge-recursive "$c0" -- "$c4" "$c1" + status=$? + case "$status" in + 1) + : happy + ;; + *) + echo >&2 "why status $status!!!" + false + ;; + esac +' + +test_expect_success 'merge-recursive d/f conflict result the other way' ' + + git ls-files -s >actual && + ( + echo "100644 $o0 0 A" + echo "100644 $o0 1 a" + echo "100644 $o1 3 a" + echo "100644 $o4 0 a/c" + echo "100644 $o0 0 b" + echo "100644 $o1 0 d/e" + ) >expected && + git diff -u expected actual + +' + +test_expect_success 'merge-recursive d/f conflict' ' + + rm -fr [Aabd] && + git reset --hard && + git checkout -f "$c1" && + + git-merge-recursive "$c0" -- "$c1" "$c6" + status=$? + case "$status" in + 1) + : happy + ;; + *) + echo >&2 "why status $status!!!" + false + ;; + esac +' + +test_expect_success 'merge-recursive d/f conflict result' ' + + git ls-files -s >actual && + ( + echo "100644 $o0 0 A" + echo "100644 $o1 0 a" + echo "100644 $o0 0 b" + echo "100644 $o6 3 d" + echo "100644 $o0 1 d/e" + echo "100644 $o1 2 d/e" + ) >expected && + git diff -u expected actual + +' + +test_expect_success 'merge-recursive d/f conflict' ' + + rm -fr [Aabd] && + git reset --hard && + git checkout -f "$c6" && + + git-merge-recursive "$c0" -- "$c6" "$c1" + status=$? + case "$status" in + 1) + : happy + ;; + *) + echo >&2 "why status $status!!!" + false + ;; + esac +' + +test_expect_success 'merge-recursive d/f conflict result' ' + + git ls-files -s >actual && + ( + echo "100644 $o0 0 A" + echo "100644 $o1 0 a" + echo "100644 $o0 0 b" + echo "100644 $o6 2 d" + echo "100644 $o0 1 d/e" + echo "100644 $o1 3 d/e" + ) >expected && + git diff -u expected actual + +' + +test_expect_success 'reset and 3-way merge' ' + + git reset --hard "$c2" && + git read-tree -m "$c0" "$c2" "$c1" + +' + +test_expect_success 'reset and bind merge' ' + + git reset --hard master && + git read-tree --prefix=M/ master && + git ls-files -s >actual && + ( + echo "100644 $o0 0 A" + echo "100644 $o0 0 M/A" + echo "100644 $o1 0 M/a" + echo "100644 $o0 0 M/b" + echo "100644 $o1 0 M/d/e" + echo "100644 $o1 0 a" + echo "100644 $o0 0 b" + echo "100644 $o1 0 d/e" + ) >expected && + git diff -u expected actual && + + git read-tree --prefix=a1/ master && + git ls-files -s >actual && + ( + echo "100644 $o0 0 A" + echo "100644 $o0 0 M/A" + echo "100644 $o1 0 M/a" + echo "100644 $o0 0 M/b" + echo "100644 $o1 0 M/d/e" + echo "100644 $o1 0 a" + echo "100644 $o0 0 a1/A" + echo "100644 $o1 0 a1/a" + echo "100644 $o0 0 a1/b" + echo "100644 $o1 0 a1/d/e" + echo "100644 $o0 0 b" + echo "100644 $o1 0 d/e" + ) >expected && + git diff -u expected actual + + git read-tree --prefix=z/ master && + git ls-files -s >actual && + ( + echo "100644 $o0 0 A" + echo "100644 $o0 0 M/A" + echo "100644 $o1 0 M/a" + echo "100644 $o0 0 M/b" + echo "100644 $o1 0 M/d/e" + echo "100644 $o1 0 a" + echo "100644 $o0 0 a1/A" + echo "100644 $o1 0 a1/a" + echo "100644 $o0 0 a1/b" + echo "100644 $o1 0 a1/d/e" + echo "100644 $o0 0 b" + echo "100644 $o1 0 d/e" + echo "100644 $o0 0 z/A" + echo "100644 $o1 0 z/a" + echo "100644 $o0 0 z/b" + echo "100644 $o1 0 z/d/e" + ) >expected && + git diff -u expected actual + +' + +test_done + diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index e31cf93a00..0a97b75288 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -84,6 +84,30 @@ test_expect_success \ 'When the rm in "git-rm -f" fails, it should not remove the file from the index' \ 'git-ls-files --error-unmatch baz' +test_expect_success 'Remove nonexistent file with --ignore-unmatch' ' + git rm --ignore-unmatch nonexistent +' + +test_expect_success '"rm" command printed' ' + echo frotz > test-file && + git add test-file && + git commit -m "add file for rm test" && + git rm test-file > rm-output && + test `egrep "^rm " rm-output | wc -l` = 1 && + rm -f test-file rm-output && + git commit -m "remove file from rm test" +' + +test_expect_success '"rm" command suppressed with --quiet' ' + echo frotz > test-file && + git add test-file && + git commit -m "add file for rm --quiet test" && + git rm --quiet test-file > rm-output && + test `wc -l < rm-output` = 0 && + rm -f test-file rm-output && + git commit -m "remove file from rm --quiet test" +' + # Now, failure cases. test_expect_success 'Re-add foo and baz' ' git add foo baz && @@ -154,4 +178,8 @@ test_expect_success 'Recursive with -r -f' ' ! test -d frotz ' +test_expect_failure 'Remove nonexistent file returns nonzero exit status' ' + git rm nonexistent +' + test_done diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 488e075c16..8f4c29a6b5 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -241,6 +241,7 @@ format-patch --attach --stdout initial..master format-patch --inline --stdout initial..side format-patch --inline --stdout initial..master^ format-patch --inline --stdout initial..master +format-patch --inline --stdout --subject-prefix=TESTCASE initial..master diff --abbrev initial..side diff -r initial..side diff --git a/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master b/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master new file mode 100644 index 0000000000..a8093be7ca --- /dev/null +++ b/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master @@ -0,0 +1,164 @@ +$ git format-patch --inline --stdout --subject-prefix=TESTCASE initial..master +From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001 +From: A U Thor <author@example.com> +Date: Mon, 26 Jun 2006 00:01:00 +0000 +Subject: [TESTCASE] Second +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n" + +This is a multi-part message in MIME format. +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/plain; charset=UTF-8; format=fixed +Content-Transfer-Encoding: 8bit + + +This is the second commit. +--- + dir/sub | 2 ++ + file0 | 3 +++ + file2 | 3 --- + 3 files changed, 5 insertions(+), 3 deletions(-) + delete mode 100644 file2 +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" +Content-Transfer-Encoding: 8bit +Content-Disposition: inline; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" + +diff --git a/dir/sub b/dir/sub +index 35d242b..8422d40 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++C ++D +diff --git a/file0 b/file0 +index 01e79c3..b414108 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++4 ++5 ++6 +diff --git a/file2 b/file2 +deleted file mode 100644 +index 01e79c3..0000000 +--- a/file2 ++++ /dev/null +@@ -1,3 +0,0 @@ +-1 +-2 +-3 + +--------------g-i-t--v-e-r-s-i-o-n-- + + + +From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001 +From: A U Thor <author@example.com> +Date: Mon, 26 Jun 2006 00:02:00 +0000 +Subject: [TESTCASE] Third +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n" + +This is a multi-part message in MIME format. +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/plain; charset=UTF-8; format=fixed +Content-Transfer-Encoding: 8bit + +--- + dir/sub | 2 ++ + file1 | 3 +++ + 2 files changed, 5 insertions(+), 0 deletions(-) + create mode 100644 file1 +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff" +Content-Transfer-Encoding: 8bit +Content-Disposition: inline; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff" + +diff --git a/dir/sub b/dir/sub +index 8422d40..cead32e 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -2,3 +2,5 @@ A + B + C + D ++E ++F +diff --git a/file1 b/file1 +new file mode 100644 +index 0000000..b1e6722 +--- /dev/null ++++ b/file1 +@@ -0,0 +1,3 @@ ++A ++B ++C + +--------------g-i-t--v-e-r-s-i-o-n-- + + + +From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001 +From: A U Thor <author@example.com> +Date: Mon, 26 Jun 2006 00:03:00 +0000 +Subject: [TESTCASE] Side +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n" + +This is a multi-part message in MIME format. +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/plain; charset=UTF-8; format=fixed +Content-Transfer-Encoding: 8bit + +--- + dir/sub | 2 ++ + file0 | 3 +++ + file3 | 4 ++++ + 3 files changed, 9 insertions(+), 0 deletions(-) + create mode 100644 file3 +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff" +Content-Transfer-Encoding: 8bit +Content-Disposition: inline; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff" + +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 0000000..7289e35 +--- /dev/null ++++ b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 + +--------------g-i-t--v-e-r-s-i-o-n-- + + +$ diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh new file mode 100755 index 0000000000..c27e39cb6f --- /dev/null +++ b/t/t4201-shortlog.sh @@ -0,0 +1,50 @@ +#!/bin/sh +# +# Copyright (c) 2006 Johannes E. Schindelin +# + +test_description='git-shortlog +' + +. ./test-lib.sh + +echo 1 > a1 +git add a1 +tree=$(git write-tree) +commit=$( (echo "Test"; echo) | git commit-tree $tree ) +git update-ref HEAD $commit + +echo 2 > a1 +git commit -m "This is a very, very long first line for the commit message to see if it is wrapped correctly" a1 + +# test if the wrapping is still valid when replacing all i's by treble clefs. +echo 3 > a1 +git commit -m "$(echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | sed "s/i/1234/g" | tr 1234 '\360\235\204\236')" a1 + +# now fsck up the utf8 +git repo-config i18n.commitencoding non-utf-8 +echo 4 > a1 +git commit -m "$(echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | sed "s/i/1234/g" | tr 1234 '\370\235\204\236')" a1 + +echo 5 > a1 +git commit -m "a 12 34 56 78" a1 + +git shortlog -w HEAD > out + +cat > expect << EOF +A U Thor (5): + Test + This is a very, very long first line for the commit message to see if + it is wrapped correctly + Th๐s ๐s a very, very long f๐rst l๐ne for the comm๐t message to see ๐f + ๐t ๐s wrapped correctly + Th๘s ๘s a very, very long f๘rst l๘ne for the comm๘t + message to see ๘f ๘t ๘s wrapped correctly + a 12 34 + 56 78 + +EOF + +test_expect_success 'shortlog wrapping' 'diff -u expect out' + +test_done diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index b4359df795..e223c074f0 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -50,8 +50,16 @@ test_expect_success \ git-commit-tree $treeid </dev/null)' test_expect_success \ + 'git-archive' \ + 'git-archive HEAD >b.tar' + +test_expect_success \ 'git-tar-tree' \ - 'git-tar-tree HEAD >b.tar' + 'git-tar-tree HEAD >b2.tar' + +test_expect_success \ + 'git-archive vs. git-tar-tree' \ + 'diff b.tar b2.tar' test_expect_success \ 'validate file modification time' \ diff --git a/t/t6002-rev-list-bisect.sh b/t/t6002-rev-list-bisect.sh index 7831e3461c..fcb3302764 100755 --- a/t/t6002-rev-list-bisect.sh +++ b/t/t6002-rev-list-bisect.sh @@ -163,7 +163,7 @@ test_sequence() # the bisection point is the head - this is the bad point. # -test_output_expect_success "--bisect l5 ^root" 'git-rev-list $_bisect_option l5 ^root' <<EOF +test_output_expect_success "$_bisect_option l5 ^root" 'git-rev-list $_bisect_option l5 ^root' <<EOF c3 EOF diff --git a/t/t6004-rev-list-path-optim.sh b/t/t6004-rev-list-path-optim.sh index 5182dbb158..761f09b1e5 100755 --- a/t/t6004-rev-list-path-optim.sh +++ b/t/t6004-rev-list-path-optim.sh @@ -7,7 +7,8 @@ test_description='git-rev-list trivial path optimization test' test_expect_success setup ' echo Hello > a && git add a && -git commit -m "Initial commit" a +git commit -m "Initial commit" a && +initial=$(git rev-parse --verify HEAD) ' test_expect_success path-optimization ' @@ -16,4 +17,35 @@ test_expect_success path-optimization ' test $(git-rev-list $commit -- . | wc -l) = 1 ' +test_expect_success 'further setup' ' + git checkout -b side && + echo Irrelevant >c && + git add c && + git commit -m "Side makes an irrelevant commit" && + echo "More Irrelevancy" >c && + git add c && + git commit -m "Side makes another irrelevant commit" && + echo Bye >a && + git add a && + git commit -m "Side touches a" && + side=$(git rev-parse --verify HEAD) && + echo "Yet more Irrelevancy" >c && + git add c && + git commit -m "Side makes yet another irrelevant commit" && + git checkout master && + echo Another >b && + git add b && + git commit -m "Master touches b" && + git merge side && + echo Touched >b && + git add b && + git commit -m "Master touches b again" +' + +test_expect_success 'path optimization 2' ' + ( echo "$side"; echo "$initial" ) >expected && + git rev-list HEAD -- a >actual && + diff -u expected actual +' + test_done diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index c76fccfb5a..c76fccfb5a 100644..100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh index a398556137..a398556137 100644..100755 --- a/t/t6024-recursive-merge.sh +++ b/t/t6024-recursive-merge.sh diff --git a/t/t6025-merge-symlinks.sh b/t/t6025-merge-symlinks.sh index 3c1a6972bd..3c1a6972bd 100644..100755 --- a/t/t6025-merge-symlinks.sh +++ b/t/t6025-merge-symlinks.sh diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh new file mode 100755 index 0000000000..13e9379236 --- /dev/null +++ b/t/t6030-bisect-porcelain.sh @@ -0,0 +1,102 @@ +#!/bin/sh +# +# Copyright (c) 2007 Christian Couder +# +test_description='Tests git-bisect functionality' + +exec </dev/null + +. ./test-lib.sh + +add_line_into_file() +{ + _line=$1 + _file=$2 + + if [ -f "$_file" ]; then + echo "$_line" >> $_file || return $? + MSG="Add <$_line> into <$_file>." + else + echo "$_line" > $_file || return $? + git add $_file || return $? + MSG="Create file <$_file> with <$_line> inside." + fi + + git-commit -m "$MSG" $_file +} + +HASH1= +HASH3= +HASH4= + +test_expect_success \ + 'set up basic repo with 1 file (hello) and 4 commits' \ + 'add_line_into_file "1: Hello World" hello && + add_line_into_file "2: A new day for git" hello && + add_line_into_file "3: Another new day for git" hello && + add_line_into_file "4: Ciao for now" hello && + HASH1=$(git rev-list HEAD | tail -1) && + HASH3=$(git rev-list HEAD | head -2 | tail -1) && + HASH4=$(git rev-list HEAD | head -1)' + +test_expect_success 'bisect starts with only one bad' ' + git bisect reset && + git bisect start && + git bisect bad $HASH4 && + git bisect next +' + +test_expect_success 'bisect does not start with only one good' ' + git bisect reset && + git bisect start && + git bisect good $HASH1 || return 1 + + if git bisect next + then + echo Oops, should have failed. + false + else + : + fi +' + +test_expect_success 'bisect start with one bad and good' ' + git bisect reset && + git bisect start && + git bisect good $HASH1 && + git bisect bad $HASH4 && + git bisect next +' + +# We want to automatically find the commit that +# introduced "Another" into hello. +test_expect_success \ + '"git bisect run" simple case' \ + 'echo "#"\!"/bin/sh" > test_script.sh && + echo "grep Another hello > /dev/null" >> test_script.sh && + echo "test \$? -ne 0" >> test_script.sh && + chmod +x test_script.sh && + git bisect start && + git bisect good $HASH1 && + git bisect bad $HASH4 && + git bisect run ./test_script.sh > my_bisect_log.txt && + grep "$HASH3 is first bad commit" my_bisect_log.txt && + git bisect reset' + +# We want to automatically find the commit that +# introduced "Ciao" into hello. +test_expect_success \ + '"git bisect run" with more complex "git bisect start"' \ + 'echo "#"\!"/bin/sh" > test_script.sh && + echo "grep Ciao hello > /dev/null" >> test_script.sh && + echo "test \$? -ne 0" >> test_script.sh && + chmod +x test_script.sh && + git bisect start $HASH4 $HASH1 && + git bisect run ./test_script.sh > my_bisect_log.txt && + grep "$HASH4 is first bad commit" my_bisect_log.txt && + git bisect reset' + +# +# +test_done + diff --git a/t/t6030-bisect-run.sh b/t/t6030-bisect-run.sh deleted file mode 100755 index 39c72283b5..0000000000 --- a/t/t6030-bisect-run.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2007 Christian Couder -# -test_description='Tests git-bisect run functionality' - -. ./test-lib.sh - -add_line_into_file() -{ - _line=$1 - _file=$2 - - if [ -f "$_file" ]; then - echo "$_line" >> $_file || return $? - MSG="Add <$_line> into <$_file>." - else - echo "$_line" > $_file || return $? - git add $_file || return $? - MSG="Create file <$_file> with <$_line> inside." - fi - - git-commit -m "$MSG" $_file -} - -HASH1= -HASH3= -HASH4= - -test_expect_success \ - 'set up basic repo with 1 file (hello) and 4 commits' \ - 'add_line_into_file "1: Hello World" hello && - add_line_into_file "2: A new day for git" hello && - add_line_into_file "3: Another new day for git" hello && - add_line_into_file "4: Ciao for now" hello && - HASH1=$(git rev-list HEAD | tail -1) && - HASH3=$(git rev-list HEAD | head -2 | tail -1) && - HASH4=$(git rev-list HEAD | head -1)' - -# We want to automatically find the commit that -# introduced "Another" into hello. -test_expect_success \ - 'git bisect run simple case' \ - 'echo "#!/bin/sh" > test_script.sh && - echo "grep Another hello > /dev/null" >> test_script.sh && - echo "test \$? -ne 0" >> test_script.sh && - chmod +x test_script.sh && - git bisect start && - git bisect good $HASH1 && - git bisect bad $HASH4 && - git bisect run ./test_script.sh > my_bisect_log.txt && - grep "$HASH3 is first bad commit" my_bisect_log.txt' - -# -# -test_done - diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 867bbd26cb..5fa6a45577 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -3,7 +3,20 @@ # Copyright (c) 2006 Junio C Hamano # -test_description='git-checkout tests.' +test_description='git-checkout tests. + +Creates master, forks renamer and side branches from it. +Test switching across them. + + ! [master] Initial A one, A two + * [renamer] Renamer R one->uno, M two + ! [side] Side M one, D two, A three + --- + + [side] Side M one, D two, A three + * [renamer] Renamer R one->uno, M two + +*+ [master] Initial A one, A two + +' . ./test-lib.sh @@ -129,4 +142,52 @@ test_expect_success 'checkout -m with merge conflict' ' ! test -s current ' +test_expect_success 'checkout to detach HEAD' ' + + git checkout -f renamer && git clean && + git checkout renamer^ && + H=$(git rev-parse --verify HEAD) && + M=$(git show-ref -s --verify refs/heads/master) && + test "z$H" = "z$M" && + if git symbolic-ref HEAD >/dev/null 2>&1 + then + echo "OOPS, HEAD is still symbolic???" + false + else + : happy + fi +' + +test_expect_success 'checkout to detach HEAD with branchname^' ' + + git checkout -f master && git clean && + git checkout renamer^ && + H=$(git rev-parse --verify HEAD) && + M=$(git show-ref -s --verify refs/heads/master) && + test "z$H" = "z$M" && + if git symbolic-ref HEAD >/dev/null 2>&1 + then + echo "OOPS, HEAD is still symbolic???" + false + else + : happy + fi +' + +test_expect_success 'checkout to detach HEAD with HEAD^0' ' + + git checkout -f master && git clean && + git checkout HEAD^0 && + H=$(git rev-parse --verify HEAD) && + M=$(git show-ref -s --verify refs/heads/master) && + test "z$H" = "z$M" && + if git symbolic-ref HEAD >/dev/null 2>&1 + then + echo "OOPS, HEAD is still symbolic???" + false + else + : happy + fi +' + test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index c0754747fb..c0754747fb 100755..100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh diff --git a/test-match-trees.c b/test-match-trees.c new file mode 100644 index 0000000000..a3c4688778 --- /dev/null +++ b/test-match-trees.c @@ -0,0 +1,24 @@ +#include "cache.h" +#include "tree.h" + +int main(int ac, char **av) +{ + unsigned char hash1[20], hash2[20], shifted[20]; + struct tree *one, *two; + + if (get_sha1(av[1], hash1)) + die("cannot parse %s as an object name", av[1]); + if (get_sha1(av[2], hash2)) + die("cannot parse %s as an object name", av[2]); + one = parse_tree_indirect(hash1); + if (!one) + die("not a treeish %s", av[1]); + two = parse_tree_indirect(hash2); + if (!two) + die("not a treeish %s", av[2]); + + shift_tree(one->object.sha1, two->object.sha1, shifted, -1); + printf("shifted: %s\n", sha1_to_hex(shifted)); + + exit(0); +} diff --git a/unpack-trees.c b/unpack-trees.c index ee10eea24c..5139481358 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -70,7 +70,6 @@ static int entcmp(const char *name1, int dir1, const char *name2, int dir2) static int unpack_trees_rec(struct tree_entry_list **posns, int len, const char *base, struct unpack_trees_options *o, - int *indpos, struct tree_entry_list *df_conflict_list) { int baselen = strlen(base); @@ -100,7 +99,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, cache_name = NULL; /* Check the cache */ - if (o->merge && *indpos < active_nr) { + if (o->merge && o->pos < active_nr) { /* This is a bit tricky: */ /* If the index has a subdirectory (with * contents) as the first name, it'll get a @@ -118,7 +117,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, * file case. */ - cache_name = active_cache[*indpos]->name; + cache_name = active_cache[o->pos]->name; if (strlen(cache_name) > baselen && !memcmp(cache_name, base, baselen)) { cache_name += baselen; @@ -158,8 +157,8 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, if (cache_name && !strcmp(cache_name, first)) { any_files = 1; - src[0] = active_cache[*indpos]; - remove_cache_entry_at(*indpos); + src[0] = active_cache[o->pos]; + remove_cache_entry_at(o->pos); } for (i = 0; i < len; i++) { @@ -228,7 +227,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, #if DBRT_DEBUG > 1 printf("Added %d entries\n", ret); #endif - *indpos += ret; + o->pos += ret; } else { for (i = 0; i < src_size; i++) { if (src[i]) { @@ -244,7 +243,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, newbase[baselen + pathlen] = '/'; newbase[baselen + pathlen + 1] = '\0'; if (unpack_trees_rec(subposns, len, newbase, o, - indpos, df_conflict_list)) { + df_conflict_list)) { retval = -1; goto leave_directory; } @@ -375,7 +374,6 @@ static void check_updates(struct cache_entry **src, int nr, int unpack_trees(struct object_list *trees, struct unpack_trees_options *o) { - int indpos = 0; unsigned len = object_list_length(trees); struct tree_entry_list **posns; int i; @@ -404,7 +402,7 @@ int unpack_trees(struct object_list *trees, struct unpack_trees_options *o) posn = posn->next; } if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "", - o, &indpos, &df_conflict_list)) + o, &df_conflict_list)) return -1; } @@ -467,6 +465,64 @@ static void invalidate_ce_path(struct cache_entry *ce) cache_tree_invalidate_path(active_cache_tree, ce->name); } +static int verify_clean_subdirectory(const char *path, const char *action, + struct unpack_trees_options *o) +{ + /* + * we are about to extract "path"; we would not want to lose + * anything in the existing directory there. + */ + int namelen; + int pos, i; + struct dir_struct d; + char *pathbuf; + int cnt = 0; + + /* + * First let's make sure we do not have a local modification + * in that directory. + */ + namelen = strlen(path); + pos = cache_name_pos(path, namelen); + if (0 <= pos) + return cnt; /* we have it as nondirectory */ + pos = -pos - 1; + for (i = pos; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + int len = ce_namelen(ce); + if (len < namelen || + strncmp(path, ce->name, namelen) || + ce->name[namelen] != '/') + break; + /* + * ce->name is an entry in the subdirectory. + */ + if (!ce_stage(ce)) { + verify_uptodate(ce, o); + ce->ce_mode = 0; + } + cnt++; + } + + /* + * Then we need to make sure that we do not lose a locally + * present file that is not ignored. + */ + pathbuf = xmalloc(namelen + 2); + memcpy(pathbuf, path, namelen); + strcpy(pathbuf+namelen, "/"); + + memset(&d, 0, sizeof(d)); + if (o->dir) + d.exclude_per_dir = o->dir->exclude_per_dir; + i = read_directory(&d, path, pathbuf, namelen+1, NULL); + if (i) + die("Updating '%s' would lose untracked files in it", + path); + free(pathbuf); + return cnt; +} + /* * We do not want to remove or overwrite a working tree file that * is not tracked, unless it is ignored. @@ -478,9 +534,62 @@ static void verify_absent(const char *path, const char *action, if (o->index_only || o->reset || !o->update) return; - if (!lstat(path, &st) && !(o->dir && excluded(o->dir, path))) + + if (!lstat(path, &st)) { + int cnt; + + if (o->dir && excluded(o->dir, path)) + /* + * path is explicitly excluded, so it is Ok to + * overwrite it. + */ + return; + if (S_ISDIR(st.st_mode)) { + /* + * We are checking out path "foo" and + * found "foo/." in the working tree. + * This is tricky -- if we have modified + * files that are in "foo/" we would lose + * it. + */ + cnt = verify_clean_subdirectory(path, action, o); + + /* + * If this removed entries from the index, + * what that means is: + * + * (1) the caller unpack_trees_rec() saw path/foo + * in the index, and it has not removed it because + * it thinks it is handling 'path' as blob with + * D/F conflict; + * (2) we will return "ok, we placed a merged entry + * in the index" which would cause o->pos to be + * incremented by one; + * (3) however, original o->pos now has 'path/foo' + * marked with "to be removed". + * + * We need to increment it by the number of + * deleted entries here. + */ + o->pos += cnt; + return; + } + + /* + * The previous round may already have decided to + * delete this path, which is in a subdirectory that + * is being replaced with a blob. + */ + cnt = cache_name_pos(path, strlen(path)); + if (0 <= cnt) { + struct cache_entry *ce = active_cache[cnt]; + if (!ce_stage(ce) && !ce->ce_mode) + return; + } + die("Untracked working tree file '%s' " "would be %s by merge.", path, action); + } } static int merged_entry(struct cache_entry *merge, struct cache_entry *old, @@ -525,7 +634,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old, return 1; } -static int keep_entry(struct cache_entry *ce) +static int keep_entry(struct cache_entry *ce, struct unpack_trees_options *o) { add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); return 1; @@ -556,7 +665,6 @@ int threeway_merge(struct cache_entry **stages, int count; int head_match = 0; int remote_match = 0; - const char *path = NULL; int df_conflict_head = 0; int df_conflict_remote = 0; @@ -566,13 +674,10 @@ int threeway_merge(struct cache_entry **stages, int i; for (i = 1; i < o->head_idx; i++) { - if (!stages[i]) + if (!stages[i] || stages[i] == o->df_conflict_entry) any_anc_missing = 1; - else { - if (!path) - path = stages[i]->name; + else no_anc_exists = 0; - } } index = stages[0]; @@ -588,13 +693,6 @@ int threeway_merge(struct cache_entry **stages, remote = NULL; } - if (!path && index) - path = index->name; - if (!path && head) - path = head->name; - if (!path && remote) - path = remote->name; - /* First, if there's a #16 situation, note that to prevent #13 * and #14. */ @@ -646,6 +744,23 @@ int threeway_merge(struct cache_entry **stages, if (o->aggressive) { int head_deleted = !head && !df_conflict_head; int remote_deleted = !remote && !df_conflict_remote; + const char *path = NULL; + + if (index) + path = index->name; + else if (head) + path = head->name; + else if (remote) + path = remote->name; + else { + for (i = 1; i < o->head_idx; i++) { + if (stages[i] && stages[i] != o->df_conflict_entry) { + path = stages[i]->name; + break; + } + } + } + /* * Deleted in both. * Deleted in one and unchanged in the other. @@ -677,12 +792,12 @@ int threeway_merge(struct cache_entry **stages, o->nontrivial_merge = 1; - /* #2, #3, #4, #6, #7, #9, #11. */ + /* #2, #3, #4, #6, #7, #9, #10, #11. */ count = 0; if (!head_match || !remote_match) { for (i = 1; i < o->head_idx; i++) { - if (stages[i]) { - keep_entry(stages[i]); + if (stages[i] && stages[i] != o->df_conflict_entry) { + keep_entry(stages[i], o); count++; break; } @@ -695,8 +810,8 @@ int threeway_merge(struct cache_entry **stages, show_stage_entry(stderr, "remote ", stages[remote_match]); } #endif - if (head) { count += keep_entry(head); } - if (remote) { count += keep_entry(remote); } + if (head) { count += keep_entry(head, o); } + if (remote) { count += keep_entry(remote, o); } return count; } @@ -713,12 +828,18 @@ int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o) { struct cache_entry *current = src[0]; - struct cache_entry *oldtree = src[1], *newtree = src[2]; + struct cache_entry *oldtree = src[1]; + struct cache_entry *newtree = src[2]; if (o->merge_size != 2) return error("Cannot do a twoway merge of %d trees", o->merge_size); + if (oldtree == o->df_conflict_entry) + oldtree = NULL; + if (newtree == o->df_conflict_entry) + newtree = NULL; + if (current) { if ((!oldtree && !newtree) || /* 4 and 5 */ (!oldtree && newtree && @@ -726,9 +847,9 @@ int twoway_merge(struct cache_entry **src, (oldtree && newtree && same(oldtree, newtree)) || /* 14 and 15 */ (oldtree && newtree && - !same(oldtree, newtree) && /* 18 and 19*/ + !same(oldtree, newtree) && /* 18 and 19 */ same(current, newtree))) { - return keep_entry(current); + return keep_entry(current, o); } else if (oldtree && !newtree && same(current, oldtree)) { /* 10 or 11 */ @@ -774,7 +895,7 @@ int bind_merge(struct cache_entry **src, if (a && old) die("Entry '%s' overlaps. Cannot bind.", a->name); if (!a) - return keep_entry(old); + return keep_entry(old, o); else return merged_entry(a, NULL, o); } @@ -804,7 +925,7 @@ int oneway_merge(struct cache_entry **src, ce_match_stat(old, &st, 1)) old->ce_flags |= htons(CE_UPDATE); } - return keep_entry(old); + return keep_entry(old, o); } return merged_entry(a, old, o); } diff --git a/unpack-trees.h b/unpack-trees.h index 191f7442f1..fee7da4382 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -16,6 +16,7 @@ struct unpack_trees_options { int verbose_update; int aggressive; const char *prefix; + int pos; struct dir_struct *dir; merge_fn_t fn; diff --git a/wt-status.c b/wt-status.c index a25632bc87..a0559905a0 100644 --- a/wt-status.c +++ b/wt-status.c @@ -260,7 +260,7 @@ static void wt_status_print_untracked(struct wt_status *s) if (file_exists(x)) add_excludes_from_file(&dir, x); - read_directory(&dir, ".", "", 0); + read_directory(&dir, ".", "", 0, NULL); for(i = 0; i < dir.nr; i++) { /* check for matching entry, which is unmerged; lifted from * builtin-ls-files:show_other_files */ |