diff options
55 files changed, 3596 insertions, 2500 deletions
diff --git a/.gitignore b/.gitignore index b5959d6311..7906909b30 100644 --- a/.gitignore +++ b/.gitignore @@ -123,6 +123,7 @@ git-write-tree git-core-*/?* test-date test-delta +test-dump-cache-tree common-cmds.h *.tar.gz *.dsc diff --git a/Documentation/Makefile b/Documentation/Makefile index f4cbf7e159..c1af22ce04 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -79,7 +79,7 @@ clean: asciidoc -b xhtml11 -d manpage -f asciidoc.conf $< %.1 %.7 : %.xml - xmlto man $< + xmlto -m callouts.xsl man $< %.xml : %.txt asciidoc -b docbook -d manpage -f asciidoc.conf $< diff --git a/Documentation/callouts.xsl b/Documentation/callouts.xsl new file mode 100644 index 0000000000..ad03755d8f --- /dev/null +++ b/Documentation/callouts.xsl @@ -0,0 +1,16 @@ +<!-- callout.xsl: converts asciidoc callouts to man page format --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> +<xsl:template match="co"> + <xsl:value-of select="concat('\fB(',substring-after(@id,'-'),')\fR')"/> +</xsl:template> +<xsl:template match="calloutlist"> + <xsl:text>.sp </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> +</xsl:template> +<xsl:template match="callout"> + <xsl:value-of select="concat('\fB',substring-after(@arearefs,'-'),'. \fR')"/> + <xsl:apply-templates/> + <xsl:text>.br </xsl:text> +</xsl:template> +</xsl:stylesheet> diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt index 3ab9b916c2..4b56370937 100644 --- a/Documentation/everyday.txt +++ b/Documentation/everyday.txt @@ -61,7 +61,8 @@ $ git prune $ git count-objects <2> $ git repack <3> $ git prune <4> - +------------ ++ <1> running without "--full" is usually cheap and assures the repository health reasonably well. <2> check how many loose objects there are and how much @@ -69,17 +70,16 @@ diskspace is wasted by not repacking. <3> without "-a" repacks incrementally. repacking every 4-5MB of loose objects accumulation may be a good rule of thumb. <4> after repack, prune removes the duplicate loose objects. ------------- Repack a small project into single pack.:: + ------------ $ git repack -a -d <1> $ git prune - +------------ ++ <1> pack all the objects reachable from the refs into one pack and remove unneeded other packs ------------- Individual Developer (Standalone)[[Individual Developer (Standalone)]] @@ -129,10 +129,10 @@ $ git-init-db $ git add . <1> $ git commit -m 'import of frotz source tree.' $ git tag v2.43 <2> - +------------ ++ <1> add everything under the current directory. <2> make a lightweight, unannotated tag. ------------- Create a topic branch and develop.:: + @@ -153,7 +153,8 @@ $ git checkout master <9> $ git pull . alsa-audio <10> $ git log --since='3 days ago' <11> $ git log v2.43.. curses/ <12> - +------------ ++ <1> create a new topic branch. <2> revert your botched changes in "curses/ux_audio_oss.c". <3> you need to tell git if you added a new file; removal and @@ -170,7 +171,6 @@ you originally wrote. combined and include --max-count=10 (show 10 commits), --until='2005-12-10'. <12> view only the changes that touch what's in curses/ directory, since v2.43 tag. ------------- Individual Developer (Participant)[[Individual Developer (Participant)]] @@ -208,7 +208,8 @@ $ git pull git://git.kernel.org/pub/.../jgarzik/libata-dev.git ALL <5> $ git reset --hard ORIG_HEAD <6> $ git prune <7> $ git fetch --tags <8> - +------------ ++ <1> repeat as needed. <2> extract patches from your branch for e-mail submission. <3> "pull" fetches from "origin" by default and merges into the @@ -221,7 +222,6 @@ area we are interested in. <7> garbage collect leftover objects from reverted pull. <8> from time to time, obtain official tags from the "origin" and store them under .git/refs/tags/. ------------- Push into another repository.:: @@ -239,7 +239,8 @@ satellite$ git push origin <4> mothership$ cd frotz mothership$ git checkout master mothership$ git pull . satellite <5> - +------------ ++ <1> mothership machine has a frotz repository under your home directory; clone from it to start a repository on the satellite machine. @@ -252,7 +253,6 @@ to local "origin" branch. mothership machine. You could use this as a back-up method. <5> on mothership machine, merge the work done on the satellite machine into the master branch. ------------- Branch off of a specific tag.:: + @@ -262,12 +262,12 @@ $ edit/compile/test; git commit -a $ git checkout master $ git format-patch -k -m --stdout v2.6.14..private2.6.14 | git am -3 -k <2> - +------------ ++ <1> create a private branch based on a well known (but somewhat behind) tag. <2> forward port all changes in private2.6.14 branch to master branch without a formal "merging". ------------- Integrator[[Integrator]] @@ -317,7 +317,8 @@ $ git tag -s -m 'GIT 0.99.9x' v0.99.9x <10> $ git fetch ko && git show-branch master maint 'tags/ko-*' <11> $ git push ko <12> $ git push ko v0.99.9x <13> - +------------ ++ <1> see what I was in the middle of doing, if any. <2> see what topic branches I have and think about how ready they are. @@ -346,7 +347,6 @@ In the output from "git show-branch", "master" should have everything "ko-master" has. <12> push out the bleeding edge. <13> push the tag out, too. ------------- Repository Administration[[Repository Administration]] @@ -367,7 +367,6 @@ example of managing a shared central repository. Examples ~~~~~~~~ - Run git-daemon to serve /pub/scm from inetd.:: + ------------ @@ -388,13 +387,13 @@ cindy:x:1002:1002::/home/cindy:/usr/bin/git-shell david:x:1003:1003::/home/david:/usr/bin/git-shell $ grep git /etc/shells <2> /usr/bin/git-shell - +------------ ++ <1> log-in shell is set to /usr/bin/git-shell, which does not allow anything but "git push" and "git pull". The users should get an ssh access to the machine. <2> in many distributions /etc/shells needs to list what is used as the login shell. ------------- CVS-style shared repository.:: + @@ -419,7 +418,8 @@ $ cat info/allowed-users <4> refs/heads/master alice\|cindy refs/heads/doc-update bob refs/tags/v[0-9]* david - +------------ ++ <1> place the developers into the same git group. <2> and make the shared repository writable by the group. <3> use update-hook example by Carl from Documentation/howto/ @@ -427,7 +427,6 @@ for branch policy control. <4> alice and cindy can push into master, only bob can push into doc-update. david is the release manager and is the only person who can create and push version tags. ------------- HTTP server to support dumb protocol transfer.:: + @@ -435,7 +434,7 @@ HTTP server to support dumb protocol transfer.:: dev$ git update-server-info <1> dev$ ftp user@isp.example.com <2> ftp> cp -r .git /home/user/myproject.git - +------------ ++ <1> make sure your info/refs and objects/info/packs are up-to-date <2> upload to public HTTP server hosted by your ISP. ------------- diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 71ecd858aa..72fb2f89b4 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -3,22 +3,27 @@ git-branch(1) NAME ---- -git-branch - Create a new branch, or remove an old one +git-branch - List, create, or delete branches. SYNOPSIS -------- [verse] -'git-branch' [[-f] <branchname> [<start-point>]] -'git-branch' (-d | -D) <branchname> +'git-branch' [-r] +'git-branch' [-f] <branchname> [<start-point>] +'git-branch' (-d | -D) <branchname>... DESCRIPTION ----------- -If no argument is provided, show available branches and mark current -branch with star. Otherwise, create a new branch of name <branchname>. -If a starting point is also specified, that will be where the branch is -created, otherwise it will be created at the current HEAD. +With no arguments given (or just `-r`) a list of available branches +will be shown, the current branch will be highlighted with an asterisk. -With a `-d` or `-D` option, `<branchname>` will be deleted. +In its second form, a new branch named <branchname> will be created. +It will start out with a head equal to the one given as <start-point>. +If no <start-point> is given, the branch will be created with a head +equal to that of the currently checked out branch. + +With a `-d` or `-D` option, `<branchname>` will be deleted. You may +specify more than one branch for deletion. OPTIONS @@ -30,40 +35,56 @@ OPTIONS Delete a branch irrespective of its index status. -f:: - Force a reset of <branchname> to <start-point> (or current head). + Force the creation of a new branch even if it means deleting + a branch that already exists with the same name. + +-r:: + List only the "remote" branches. <branchname>:: The name of the branch to create or delete. <start-point>:: - Where to create the branch; defaults to HEAD. This - option has no meaning with -d and -D. + The new branch will be created with a HEAD equal to this. It may + be given as a branch name, a commit-id, or a tag. If this option + is omitted, the current branch is assumed. + Examples -~~~~~~~~ +-------- Start development off of a known tag:: + ------------ $ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6 $ cd my2.6 -$ git branch my2.6.14 v2.6.14 <1> +$ git branch my2.6.14 v2.6.14 <1> $ git checkout my2.6.14 - -<1> These two steps are the same as "checkout -b my2.6.14 v2.6.14". ------------ ++ +<1> This step and the next one could be combined into a single step with +"checkout -b my2.6.14 v2.6.14". Delete unneeded branch:: + ------------ $ git clone git://git.kernel.org/.../git.git my.git $ cd my.git -$ git branch -D todo <1> - +$ git branch -D todo <1> +------------ ++ <1> delete todo branch even if the "master" branch does not have all commits from todo branch. ------------- + + +Notes +----- + +If you are creating a branch that you want to immediately checkout, it's +easier to use the git checkout command with its `-b` option to create +a branch and check it out with a single command. + Author ------ diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 985bb2f827..095128906a 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -66,19 +66,19 @@ the `Makefile` to two revisions back, deletes hello.c by mistake, and gets it back from the index. + ------------ -$ git checkout master <1> -$ git checkout master~2 Makefile <2> +$ git checkout master <1> +$ git checkout master~2 Makefile <2> $ rm -f hello.c -$ git checkout hello.c <3> - +$ git checkout hello.c <3> +------------ ++ <1> switch branch <2> take out a file out of other commit -<3> or "git checkout -- hello.c", as in the next example. ------------- +<3> restore hello.c from HEAD of current branch + -If you have an unfortunate branch that is named `hello.c`, the -last step above would be confused as an instruction to switch to -that branch. You should instead write: +If you have an unfortunate branch that is named `hello.c`, this +step would be confused as an instruction to switch to that branch. +You should instead write: + ------------ $ git checkout -- hello.c diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 890931c891..7267bcd7a0 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -46,40 +46,41 @@ EXAMPLES Various ways to check your working tree:: + ------------ -$ git diff <1> -$ git diff --cached <2> -$ git diff HEAD <3> - +$ git diff <1> +$ git diff --cached <2> +$ git diff HEAD <3> +------------ ++ <1> changes in the working tree since your last git-update-index. <2> changes between the index and your last commit; what you would be committing if you run "git commit" without "-a" option. <3> changes in the working tree since your last commit; what you would be committing if you run "git commit -a" ------------- Comparing with arbitrary commits:: + ------------ -$ git diff test <1> -$ git diff HEAD -- ./test <2> -$ git diff HEAD^ HEAD <3> - +$ git diff test <1> +$ git diff HEAD -- ./test <2> +$ git diff HEAD^ HEAD <3> +------------ ++ <1> instead of using the tip of the current branch, compare with the tip of "test" branch. <2> instead of comparing with the tip of "test" branch, compare with the tip of the current branch, but limit the comparison to the file "test". <3> compare the version before the last commit and the last commit. ------------- Limiting the diff output:: + ------------ -$ git diff --diff-filter=MRC <1> -$ git diff --name-status -r <2> -$ git diff arch/i386 include/asm-i386 <3> - +$ git diff --diff-filter=MRC <1> +$ git diff --name-status -r <2> +$ git diff arch/i386 include/asm-i386 <3> +------------ ++ <1> show only modification, rename and copy, but not addition nor deletion. <2> show only names and the nature of change, but not actual @@ -88,18 +89,17 @@ which in turn also disables recursive behaviour, so without -r you would only see the directory name if there is a change in a file in a subdirectory. <3> limit diff output to named subtrees. ------------- Munging the diff output:: + ------------ -$ git diff --find-copies-harder -B -C <1> -$ git diff -R <2> - +$ git diff --find-copies-harder -B -C <1> +$ git diff -R <2> +------------ ++ <1> spend extra cycles to find renames, copies and complete rewrites (very expensive). <2> output diff in reverse. ------------- Author diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt index aeb1115af9..8a150d861f 100644 --- a/Documentation/git-init-db.txt +++ b/Documentation/git-init-db.txt @@ -60,12 +60,12 @@ Start a new git repository for an existing code base:: + ---------------- $ cd /path/to/my/codebase -$ git-init-db <1> -$ git-add . <2> - +$ git-init-db <1> +$ git-add . <2> +---------------- ++ <1> prepare /path/to/my/codebase/.git directory <2> add all existing file to the index ----------------- Author diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 76cb894caa..af378ffcf9 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -14,13 +14,12 @@ DESCRIPTION ----------- Shows the commit logs. -The command takes options applicable to the gitlink::git-rev-list[1] +The command takes options applicable to the gitlink:git-rev-list[1] command to control what is shown and how, and options applicable to -the gitlink::git-diff-tree[1] commands to control how the change +the gitlink:git-diff-tree[1] commands to control how the change each commit introduces are shown. -This manual page describes only the most frequently used -options. +This manual page describes only the most frequently used options. OPTIONS diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 4a7e67a4d2..1b482abecd 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -3,38 +3,54 @@ git-rebase(1) NAME ---- -git-rebase - Rebase local commits to new upstream head +git-rebase - Rebase local commits to a new head SYNOPSIS -------- 'git-rebase' [--onto <newbase>] <upstream> [<branch>] +'git-rebase' --continue + +'git-rebase' --abort + DESCRIPTION ----------- -git-rebase applies to <upstream> (or optionally to <newbase>) commits -from <branch> that do not appear in <upstream>. When <branch> is not -specified it defaults to the current branch (HEAD). +git-rebase replaces <branch> with a new branch of the same name. When +the --onto option is provided the new branch starts out with a HEAD equal +to <newbase>, otherwise it is equal to <upstream>. It then attempts to +create a new commit for each commit from the original <branch> that does +not exist in the <upstream> branch. -When git-rebase is complete, <branch> will be updated to point to the -newly created line of commit objects, so the previous line will not be -accessible unless there are other references to it already. +It is possible that a merge failure will prevent this process from being +completely automatic. You will have to resolve any such merge failure +and run `git rebase --continue`. If you can not resolve the merge +failure, running `git rebase --abort` will restore the original <branch> +and remove the working files found in the .dotest directory. + +Note that if <branch> is not specified on the command line, the currently +checked out branch is used. Assume the following history exists and the current branch is "topic": +------------ A---B---C topic / D---E---F---G master +------------ From this point, the result of either of the following commands: + git-rebase master git-rebase master topic would be: +------------ A'--B'--C' topic / D---E---F---G master +------------ While, starting from the same point, the result of either of the following commands: @@ -44,21 +60,33 @@ commands: would be: +------------ A'--B'--C' topic / D---E---F---G master +------------ In case of conflict, git-rebase will stop at the first problematic commit -and leave conflict markers in the tree. After resolving the conflict manually -and updating the index with the desired resolution, you can continue the -rebasing process with +and leave conflict markers in the tree. You can use git diff to locate +the markers (<<<<<<) and make edits to resolve the conflict. For each +file you edit, you need to tell git that the conflict has been resolved, +typically this would be done with + + + git update-index <filename> + + +After resolving the conflict manually and updating the index with the +desired resolution, you can continue the rebasing process with + + + git rebase --continue - git am --resolved --3way Alternatively, you can undo the git-rebase with - git reset --hard ORIG_HEAD - rm -r .dotest + + git rebase --abort OPTIONS ------- @@ -73,6 +101,28 @@ OPTIONS <branch>:: Working branch; defaults to HEAD. +--continue:: + Restart the rebasing process after having resolved a merge conflict. + +--abort:: + Restore the original branch and abort the rebase operation. + +NOTES +----- +When you rebase a branch, you are changing its history in a way that +will cause problems for anyone who already has a copy of the branch +in their repository and tries to pull updates from you. You should +understand the implications of using 'git rebase' on a repository that +you share. + +When the git rebase command is run, it will first execute a "pre-rebase" +hook if one exists. You can use this hook to do sanity checks and +reject the rebase if it isn't appropriate. Please see the template +pre-rebase hook script for an example. + +You must be in the top directory of your project to start (or continue) +a rebase. Upon completion, <branch> will be the current branch. + Author ------ Written by Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt index 71f96bdd10..566cfa1836 100644 --- a/Documentation/git-repo-config.txt +++ b/Documentation/git-repo-config.txt @@ -15,6 +15,7 @@ SYNOPSIS 'git-repo-config' [type] --get-all name [value_regex] 'git-repo-config' [type] --unset name [value_regex] 'git-repo-config' [type] --unset-all name [value_regex] +'git-repo-config' -l | --list DESCRIPTION ----------- @@ -64,6 +65,9 @@ OPTIONS --unset-all:: Remove all matching lines from .git/config. +-l, --list:: + List all variables set in .git/config. + EXAMPLE ------- diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index b7b9798bf9..ebcfe5edb7 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -49,10 +49,11 @@ Undo a commit and redo:: + ------------ $ git commit ... -$ git reset --soft HEAD^ <1> -$ edit <2> -$ git commit -a -c ORIG_HEAD <3> - +$ git reset --soft HEAD^ <1> +$ edit <2> +$ git commit -a -c ORIG_HEAD <3> +------------ ++ <1> This is most often done when you remembered what you just committed is incomplete, or you misspelled your commit message, or both. Leaves working tree as it was before "reset". @@ -60,43 +61,43 @@ message, or both. Leaves working tree as it was before "reset". <3> "reset" copies the old head to .git/ORIG_HEAD; redo the commit by starting with its log message. If you do not need to edit the message further, you can give -C option instead. ------------- Undo commits permanently:: + ------------ $ git commit ... -$ git reset --hard HEAD~3 <1> - +$ git reset --hard HEAD~3 <1> +------------ ++ <1> The last three commits (HEAD, HEAD^, and HEAD~2) were bad and you do not want to ever see them again. Do *not* do this if you have already given these commits to somebody else. ------------- Undo a commit, making it a topic branch:: + ------------ -$ git branch topic/wip <1> -$ git reset --hard HEAD~3 <2> -$ git checkout topic/wip <3> - +$ git branch topic/wip <1> +$ git reset --hard HEAD~3 <2> +$ git checkout topic/wip <3> +------------ ++ <1> You have made some commits, but realize they were premature to be in the "master" branch. You want to continue polishing them in a topic branch, so create "topic/wip" branch off of the current HEAD. <2> Rewind the master branch to get rid of those three commits. <3> Switch to "topic/wip" branch and keep working. ------------- Undo update-index:: + ------------ -$ edit <1> +$ edit <1> $ git-update-index frotz.c filfre.c -$ mailx <2> -$ git reset <3> -$ git pull git://info.example.com/ nitfol <4> - +$ mailx <2> +$ git reset <3> +$ git pull git://info.example.com/ nitfol <4> +------------ ++ <1> you are happily working on something, and find the changes in these files are in good order. You do not want to see them when you run "git diff", because you plan to work on other files @@ -109,12 +110,11 @@ index changes for these two files. Your changes in working tree remain there. <4> then you can pull and merge, leaving frotz.c and filfre.c changes still in the working tree. ------------- Undo a merge or pull:: + ------------ -$ git pull <1> +$ git pull <1> Trying really trivial in-index merge... fatal: Merge requires file-level merging Nope. @@ -122,20 +122,19 @@ Nope. Auto-merging nitfol CONFLICT (content): Merge conflict in nitfol Automatic merge failed/prevented; fix up by hand -$ git reset --hard <2> - +$ git reset --hard <2> +$ git pull . topic/branch <3> +Updating from 41223... to 13134... +Fast forward +$ git reset --hard ORIG_HEAD <4> +------------ ++ <1> try to update from the upstream resulted in a lot of conflicts; you were not ready to spend a lot of time merging right now, so you decide to do that later. <2> "pull" has not made merge commit, so "git reset --hard" which is a synonym for "git reset --hard HEAD" clears the mess from the index file and the working tree. - -$ git pull . topic/branch <3> -Updating from 41223... to 13134... -Fast forward -$ git reset --hard ORIG_HEAD <4> - <3> merge a topic branch into the current branch, which resulted in a fast forward. <4> but you decided that the topic branch is not ready for public @@ -143,7 +142,6 @@ consumption yet. "pull" or "merge" always leaves the original tip of the current branch in ORIG_HEAD, so resetting hard to it brings your index file and the working tree back to that state, and resets the tip of the branch to that commit. ------------- Interrupted workflow:: + @@ -155,21 +153,21 @@ need to get to the other branch for a quick bugfix. ------------ $ git checkout feature ;# you were working in "feature" branch and $ work work work ;# got interrupted -$ git commit -a -m 'snapshot WIP' <1> +$ git commit -a -m 'snapshot WIP' <1> $ git checkout master $ fix fix fix $ git commit ;# commit with real log $ git checkout feature -$ git reset --soft HEAD^ ;# go back to WIP state <2> -$ git reset <3> - +$ git reset --soft HEAD^ ;# go back to WIP state <2> +$ git reset <3> +------------ ++ <1> This commit will get blown away so a throw-away log message is OK. <2> This removes the 'WIP' commit from the commit history, and sets your working tree to the state just before you made that snapshot. -<3> After <2>, the index file still has all the WIP changes you - committed in <1>. This sets it to the last commit you were - basing the WIP changes on. ------------- +<3> At this point the index file still has all the WIP changes you + committed as 'snapshot WIP'. This updates the index to show your + WIP files as uncommitted. Author ------ diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 0a1b0ad56d..d4137fc87e 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -247,34 +247,33 @@ To update and refresh only the files already checked out: $ git-checkout-index -n -f -a && git-update-index --ignore-missing --refresh ---------------- -On an inefficient filesystem with `core.ignorestat` set: - +On an inefficient filesystem with `core.ignorestat` set:: ++ ------------ -$ git update-index --really-refresh <1> -$ git update-index --no-assume-unchanged foo.c <2> -$ git diff --name-only <3> +$ git update-index --really-refresh <1> +$ git update-index --no-assume-unchanged foo.c <2> +$ git diff --name-only <3> $ edit foo.c -$ git diff --name-only <4> +$ git diff --name-only <4> M foo.c -$ git update-index foo.c <5> -$ git diff --name-only <6> +$ git update-index foo.c <5> +$ git diff --name-only <6> $ edit foo.c -$ git diff --name-only <7> -$ git update-index --no-assume-unchanged foo.c <8> -$ git diff --name-only <9> +$ git diff --name-only <7> +$ git update-index --no-assume-unchanged foo.c <8> +$ git diff --name-only <9> M foo.c - -<1> forces lstat(2) to set "assume unchanged" bits for paths - that match index. +------------ ++ +<1> forces lstat(2) to set "assume unchanged" bits for paths that match index. <2> mark the path to be edited. <3> this does lstat(2) and finds index matches the path. -<4> this does lstat(2) and finds index does not match the path. +<4> this does lstat(2) and finds index does *not* match the path. <5> registering the new version to index sets "assume unchanged" bit. <6> and it is assumed unchanged. <7> even after you edit it. <8> you can tell about the change after the fact. <9> now it checks with lstat(2) and finds it has been changed. ------------- Configuration diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt index 379571eef0..a5b1a0dbab 100644 --- a/Documentation/git-var.txt +++ b/Documentation/git-var.txt @@ -19,7 +19,8 @@ OPTIONS -l:: Cause the logical variables to be listed. In addition, all the variables of the git configuration file .git/config are listed - as well. + as well. (However, the configuration variables listing functionality + is deprecated in favor of `git-repo-config -l`.) EXAMPLE -------- @@ -199,12 +199,12 @@ LIB_H = \ tree-walk.h log-tree.h DIFF_OBJS = \ - diff-lib.o diffcore-break.o diffcore-order.o \ + diff.o diff-lib.o diffcore-break.o diffcore-order.o \ diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \ diffcore-delta.o log-tree.o LIB_OBJS = \ - blob.o commit.o connect.o csum-file.o \ + blob.o commit.o connect.o csum-file.o cache-tree.o \ date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \ object.o pack-check.o patch-delta.o path.o pkt-line.o \ quote.o read-cache.o refs.o run-command.o \ @@ -214,7 +214,7 @@ LIB_OBJS = \ $(DIFF_OBJS) BUILTIN_OBJS = \ - builtin-log.o builtin-help.o + builtin-log.o builtin-help.o builtin-count.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz @@ -575,12 +575,12 @@ $(patsubst git-%$X,%.o,$(PROGRAMS)): $(GITLIBS) $(DIFF_OBJS): diffcore.h $(LIB_FILE): $(LIB_OBJS) - $(AR) rcs $@ $(LIB_OBJS) + rm -f $@ && $(AR) rcs $@ $(LIB_OBJS) XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o $(XDIFF_LIB): $(XDIFF_OBJS) - $(AR) rcs $@ $(XDIFF_OBJS) + rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS) doc: @@ -611,6 +611,9 @@ test-date$X: test-date.c date.o ctype.o test-delta$X: test-delta.c diff-delta.o patch-delta.o $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^ +test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS) + $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) + check: for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done @@ -8,6 +8,7 @@ */ #include <fnmatch.h> #include "cache.h" +#include "cache-tree.h" #include "quote.h" #include "blob.h" @@ -1717,6 +1718,7 @@ static void remove_file(struct patch *patch) if (write_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); } unlink(patch->old_name); } @@ -1813,8 +1815,9 @@ static void create_file(struct patch *patch) if (!mode) mode = S_IFREG | 0644; - create_one_file(path, mode, buf, size); + create_one_file(path, mode, buf, size); add_index_file(path, mode, buf, size); + cache_tree_invalidate_path(active_cache_tree, path); } static void write_out_one_result(struct patch *patch) diff --git a/builtin-count.c b/builtin-count.c new file mode 100644 index 0000000000..0256369d5b --- /dev/null +++ b/builtin-count.c @@ -0,0 +1,123 @@ +/* + * Builtin "git count-objects". + * + * Copyright (c) 2006 Junio C Hamano + */ + +#include "cache.h" +#include "builtin.h" + +static const char count_objects_usage[] = "git-count-objects [-v]"; + +static void count_objects(DIR *d, char *path, int len, int verbose, + unsigned long *loose, + unsigned long *loose_size, + unsigned long *packed_loose, + unsigned long *garbage) +{ + struct dirent *ent; + while ((ent = readdir(d)) != NULL) { + char hex[41]; + unsigned char sha1[20]; + const char *cp; + int bad = 0; + + if ((ent->d_name[0] == '.') && + (ent->d_name[1] == 0 || + ((ent->d_name[1] == '.') && (ent->d_name[2] == 0)))) + continue; + for (cp = ent->d_name; *cp; cp++) { + int ch = *cp; + if (('0' <= ch && ch <= '9') || + ('a' <= ch && ch <= 'f')) + continue; + bad = 1; + break; + } + if (cp - ent->d_name != 38) + bad = 1; + else { + struct stat st; + memcpy(path + len + 3, ent->d_name, 38); + path[len + 2] = '/'; + path[len + 41] = 0; + if (lstat(path, &st) || !S_ISREG(st.st_mode)) + bad = 1; + else + (*loose_size) += st.st_blocks; + } + if (bad) { + if (verbose) { + error("garbage found: %.*s/%s", + len + 2, path, ent->d_name); + (*garbage)++; + } + continue; + } + (*loose)++; + if (!verbose) + continue; + memcpy(hex, path+len, 2); + memcpy(hex+2, ent->d_name, 38); + hex[40] = 0; + if (get_sha1_hex(hex, sha1)) + die("internal error"); + if (has_sha1_pack(sha1)) + (*packed_loose)++; + } +} + +int cmd_count_objects(int ac, const char **av, char **ep) +{ + int i; + int verbose = 0; + const char *objdir = get_object_directory(); + int len = strlen(objdir); + char *path = xmalloc(len + 50); + unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0; + unsigned long loose_size = 0; + + for (i = 1; i < ac; i++) { + const char *arg = av[i]; + if (*arg != '-') + break; + else if (!strcmp(arg, "-v")) + verbose = 1; + else + usage(count_objects_usage); + } + + /* we do not take arguments other than flags for now */ + if (i < ac) + usage(count_objects_usage); + memcpy(path, objdir, len); + if (len && objdir[len-1] != '/') + path[len++] = '/'; + for (i = 0; i < 256; i++) { + DIR *d; + sprintf(path + len, "%02x", i); + d = opendir(path); + if (!d) + continue; + count_objects(d, path, len, verbose, + &loose, &loose_size, &packed_loose, &garbage); + closedir(d); + } + if (verbose) { + struct packed_git *p; + for (p = packed_git; p; p = p->next) { + if (!p->pack_local) + continue; + packed += num_packed_objects(p); + } + printf("count: %lu\n", loose); + printf("size: %lu\n", loose_size / 2); + printf("in-pack: %lu\n", packed); + printf("prune-packable: %lu\n", packed_loose); + printf("garbage: %lu\n", garbage); + } + else + printf("%lu objects, %lu kilobytes\n", + loose, loose_size / 2); + return 0; +} diff --git a/builtin-help.c b/builtin-help.c index 10a59cc403..7470faa566 100644 --- a/builtin-help.c +++ b/builtin-help.c @@ -3,6 +3,7 @@ * * Builtin help-related commands (help, usage, version) */ +#include <sys/ioctl.h> #include "cache.h" #include "builtin.h" #include "exec_cmd.h" diff --git a/builtin-log.c b/builtin-log.c index 69f2911cb4..a39aed6d86 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -9,6 +9,7 @@ #include "diff.h" #include "revision.h" #include "log-tree.h" +#include "builtin.h" static int cmd_log_wc(int argc, const char **argv, char **envp, struct rev_info *rev) @@ -67,3 +68,41 @@ int cmd_log(int argc, const char **argv, char **envp) rev.diffopt.recursive = 1; return cmd_log_wc(argc, argv, envp, &rev); } + +int cmd_format_patch(int argc, const char **argv, char **envp) +{ + struct commit *commit; + struct commit **list = NULL; + struct rev_info rev; + int nr = 0; + + init_revisions(&rev); + rev.commit_format = CMIT_FMT_EMAIL; + rev.verbose_header = 1; + rev.diff = 1; + rev.diffopt.with_raw = 0; + rev.diffopt.with_stat = 1; + rev.combine_merges = 0; + rev.ignore_merges = 1; + rev.diffopt.output_format = DIFF_FORMAT_PATCH; + argc = setup_revisions(argc, argv, &rev, "HEAD"); + + prepare_revision_walk(&rev); + while ((commit = get_revision(&rev)) != NULL) { + nr++; + list = realloc(list, nr * sizeof(list[0])); + list[nr - 1] = commit; + } + while (0 <= --nr) { + int shown; + commit = list[nr]; + shown = log_tree_commit(&rev, commit); + free(commit->buffer); + commit->buffer = NULL; + if (shown) + printf("-- \n%s\n\n", git_version_string); + } + free(list); + return 0; +} + @@ -19,5 +19,7 @@ extern int cmd_version(int argc, const char **argv, char **envp); extern int cmd_whatchanged(int argc, const char **argv, char **envp); extern int cmd_show(int argc, const char **argv, char **envp); extern int cmd_log(int argc, const char **argv, char **envp); +extern int cmd_format_patch(int argc, const char **argv, char **envp); +extern int cmd_count_objects(int argc, const char **argv, char **envp); #endif diff --git a/cache-tree.c b/cache-tree.c new file mode 100644 index 0000000000..28b78f88ef --- /dev/null +++ b/cache-tree.c @@ -0,0 +1,518 @@ +#include "cache.h" +#include "tree.h" +#include "cache-tree.h" + +#define DEBUG 0 + +struct cache_tree *cache_tree(void) +{ + struct cache_tree *it = xcalloc(1, sizeof(struct cache_tree)); + it->entry_count = -1; + return it; +} + +void cache_tree_free(struct cache_tree **it_p) +{ + int i; + struct cache_tree *it = *it_p; + + if (!it) + return; + for (i = 0; i < it->subtree_nr; i++) + if (it->down[i]) + cache_tree_free(&it->down[i]->cache_tree); + free(it->down); + free(it); + *it_p = NULL; +} + +static int subtree_name_cmp(const char *one, int onelen, + const char *two, int twolen) +{ + if (onelen < twolen) + return -1; + if (twolen < onelen) + return 1; + return memcmp(one, two, onelen); +} + +static int subtree_pos(struct cache_tree *it, const char *path, int pathlen) +{ + struct cache_tree_sub **down = it->down; + int lo, hi; + lo = 0; + hi = it->subtree_nr; + while (lo < hi) { + int mi = (lo + hi) / 2; + struct cache_tree_sub *mdl = down[mi]; + int cmp = subtree_name_cmp(path, pathlen, + mdl->name, mdl->namelen); + if (!cmp) + return mi; + if (cmp < 0) + hi = mi; + else + lo = mi + 1; + } + return -lo-1; +} + +static struct cache_tree_sub *find_subtree(struct cache_tree *it, + const char *path, + int pathlen, + int create) +{ + struct cache_tree_sub *down; + int pos = subtree_pos(it, path, pathlen); + if (0 <= pos) + return it->down[pos]; + if (!create) + return NULL; + + pos = -pos-1; + if (it->subtree_alloc <= it->subtree_nr) { + it->subtree_alloc = alloc_nr(it->subtree_alloc); + it->down = xrealloc(it->down, it->subtree_alloc * + sizeof(*it->down)); + } + it->subtree_nr++; + + down = xmalloc(sizeof(*down) + pathlen + 1); + down->cache_tree = NULL; + down->namelen = pathlen; + memcpy(down->name, path, pathlen); + down->name[pathlen] = 0; + + if (pos < it->subtree_nr) + memmove(it->down + pos + 1, + it->down + pos, + sizeof(down) * (it->subtree_nr - pos - 1)); + it->down[pos] = down; + return down; +} + +struct cache_tree_sub *cache_tree_sub(struct cache_tree *it, const char *path) +{ + int pathlen = strlen(path); + return find_subtree(it, path, pathlen, 1); +} + +void cache_tree_invalidate_path(struct cache_tree *it, const char *path) +{ + /* a/b/c + * ==> invalidate self + * ==> find "a", have it invalidate "b/c" + * a + * ==> invalidate self + * ==> if "a" exists as a subtree, remove it. + */ + const char *slash; + int namelen; + struct cache_tree_sub *down; + + if (!it) + return; + slash = strchr(path, '/'); + it->entry_count = -1; + if (!slash) { + int pos; + namelen = strlen(path); + pos = subtree_pos(it, path, namelen); + if (0 <= pos) { + cache_tree_free(&it->down[pos]->cache_tree); + free(it->down[pos]); + /* 0 1 2 3 4 5 + * ^ ^subtree_nr = 6 + * pos + * move 4 and 5 up one place (2 entries) + * 2 = 6 - 3 - 1 = subtree_nr - pos - 1 + */ + memmove(it->down+pos, it->down+pos+1, + sizeof(struct cache_tree_sub *) * + (it->subtree_nr - pos - 1)); + it->subtree_nr--; + } + return; + } + namelen = slash - path; + down = find_subtree(it, path, namelen, 0); + if (down) + cache_tree_invalidate_path(down->cache_tree, slash + 1); +} + +static int verify_cache(struct cache_entry **cache, + int entries) +{ + int i, funny; + + /* Verify that the tree is merged */ + funny = 0; + for (i = 0; i < entries; i++) { + struct cache_entry *ce = cache[i]; + if (ce_stage(ce)) { + if (10 < ++funny) { + fprintf(stderr, "...\n"); + break; + } + fprintf(stderr, "%s: unmerged (%s)\n", + ce->name, sha1_to_hex(ce->sha1)); + } + } + if (funny) + return -1; + + /* Also verify that the cache does not have path and path/file + * at the same time. At this point we know the cache has only + * stage 0 entries. + */ + funny = 0; + for (i = 0; i < entries - 1; i++) { + /* path/file always comes after path because of the way + * the cache is sorted. Also path can appear only once, + * which means conflicting one would immediately follow. + */ + const char *this_name = cache[i]->name; + const char *next_name = cache[i+1]->name; + int this_len = strlen(this_name); + if (this_len < strlen(next_name) && + strncmp(this_name, next_name, this_len) == 0 && + next_name[this_len] == '/') { + if (10 < ++funny) { + fprintf(stderr, "...\n"); + break; + } + fprintf(stderr, "You have both %s and %s\n", + this_name, next_name); + } + } + if (funny) + return -1; + return 0; +} + +static void discard_unused_subtrees(struct cache_tree *it) +{ + struct cache_tree_sub **down = it->down; + int nr = it->subtree_nr; + int dst, src; + for (dst = src = 0; src < nr; src++) { + struct cache_tree_sub *s = down[src]; + if (s->used) + down[dst++] = s; + else { + cache_tree_free(&s->cache_tree); + free(s); + it->subtree_nr--; + } + } +} + +int cache_tree_fully_valid(struct cache_tree *it) +{ + int i; + if (!it) + return 0; + if (it->entry_count < 0 || !has_sha1_file(it->sha1)) + return 0; + for (i = 0; i < it->subtree_nr; i++) { + if (!cache_tree_fully_valid(it->down[i]->cache_tree)) + return 0; + } + return 1; +} + +static int update_one(struct cache_tree *it, + struct cache_entry **cache, + int entries, + const char *base, + int baselen, + int missing_ok, + int dryrun) +{ + unsigned long size, offset; + char *buffer; + int i; + + if (0 <= it->entry_count && has_sha1_file(it->sha1)) + return it->entry_count; + + /* + * We first scan for subtrees and update them; we start by + * marking existing subtrees -- the ones that are unmarked + * should not be in the result. + */ + for (i = 0; i < it->subtree_nr; i++) + it->down[i]->used = 0; + + /* + * Find the subtrees and update them. + */ + for (i = 0; i < entries; i++) { + struct cache_entry *ce = cache[i]; + struct cache_tree_sub *sub; + const char *path, *slash; + int pathlen, sublen, subcnt; + + path = ce->name; + pathlen = ce_namelen(ce); + if (pathlen <= baselen || memcmp(base, path, baselen)) + break; /* at the end of this level */ + + slash = strchr(path + baselen, '/'); + if (!slash) + continue; + /* + * a/bbb/c (base = a/, slash = /c) + * ==> + * path+baselen = bbb/c, sublen = 3 + */ + sublen = slash - (path + baselen); + sub = find_subtree(it, path + baselen, sublen, 1); + if (!sub->cache_tree) + sub->cache_tree = cache_tree(); + subcnt = update_one(sub->cache_tree, + cache + i, entries - i, + path, + baselen + sublen + 1, + missing_ok, + dryrun); + i += subcnt - 1; + sub->used = 1; + } + + discard_unused_subtrees(it); + + /* + * Then write out the tree object for this level. + */ + size = 8192; + buffer = xmalloc(size); + offset = 0; + + for (i = 0; i < entries; i++) { + struct cache_entry *ce = cache[i]; + struct cache_tree_sub *sub; + const char *path, *slash; + int pathlen, entlen; + const unsigned char *sha1; + unsigned mode; + + path = ce->name; + pathlen = ce_namelen(ce); + if (pathlen <= baselen || memcmp(base, path, baselen)) + break; /* at the end of this level */ + + slash = strchr(path + baselen, '/'); + if (slash) { + entlen = slash - (path + baselen); + sub = find_subtree(it, path + baselen, entlen, 0); + if (!sub) + die("cache-tree.c: '%.*s' in '%s' not found", + entlen, path + baselen, path); + i += sub->cache_tree->entry_count - 1; + sha1 = sub->cache_tree->sha1; + mode = S_IFDIR; + } + else { + sha1 = ce->sha1; + mode = ntohl(ce->ce_mode); + entlen = pathlen - baselen; + } + if (!missing_ok && !has_sha1_file(sha1)) + return error("invalid object %s", sha1_to_hex(sha1)); + + if (!ce->ce_mode) + continue; /* entry being removed */ + + if (size < offset + entlen + 100) { + size = alloc_nr(offset + entlen + 100); + buffer = xrealloc(buffer, size); + } + offset += sprintf(buffer + offset, + "%o %.*s", mode, entlen, path + baselen); + buffer[offset++] = 0; + memcpy(buffer + offset, sha1, 20); + offset += 20; + +#if DEBUG + fprintf(stderr, "cache-tree %o %.*s\n", + mode, entlen, path + baselen); +#endif + } + + if (dryrun) { + unsigned char hdr[200]; + int hdrlen; + write_sha1_file_prepare(buffer, offset, tree_type, it->sha1, + hdr, &hdrlen); + } + else + write_sha1_file(buffer, offset, tree_type, it->sha1); + free(buffer); + it->entry_count = i; +#if DEBUG + fprintf(stderr, "cache-tree (%d ent, %d subtree) %s\n", + it->entry_count, it->subtree_nr, + sha1_to_hex(it->sha1)); +#endif + return i; +} + +int cache_tree_update(struct cache_tree *it, + struct cache_entry **cache, + int entries, + int missing_ok, + int dryrun) +{ + int i; + i = verify_cache(cache, entries); + if (i) + return i; + i = update_one(it, cache, entries, "", 0, missing_ok, dryrun); + if (i < 0) + return i; + return 0; +} + +static void *write_one(struct cache_tree *it, + char *path, + int pathlen, + char *buffer, + unsigned long *size, + unsigned long *offset) +{ + int i; + + /* One "cache-tree" entry consists of the following: + * path (NUL terminated) + * entry_count, subtree_nr ("%d %d\n") + * tree-sha1 (missing if invalid) + * subtree_nr "cache-tree" entries for subtrees. + */ + if (*size < *offset + pathlen + 100) { + *size = alloc_nr(*offset + pathlen + 100); + buffer = xrealloc(buffer, *size); + } + *offset += sprintf(buffer + *offset, "%.*s%c%d %d\n", + pathlen, path, 0, + it->entry_count, it->subtree_nr); + +#if DEBUG + if (0 <= it->entry_count) + fprintf(stderr, "cache-tree <%.*s> (%d ent, %d subtree) %s\n", + pathlen, path, it->entry_count, it->subtree_nr, + sha1_to_hex(it->sha1)); + else + fprintf(stderr, "cache-tree <%.*s> (%d subtree) invalid\n", + pathlen, path, it->subtree_nr); +#endif + + if (0 <= it->entry_count) { + memcpy(buffer + *offset, it->sha1, 20); + *offset += 20; + } + for (i = 0; i < it->subtree_nr; i++) { + struct cache_tree_sub *down = it->down[i]; + if (i) { + struct cache_tree_sub *prev = it->down[i-1]; + if (subtree_name_cmp(down->name, down->namelen, + prev->name, prev->namelen) <= 0) + die("fatal - unsorted cache subtree"); + } + buffer = write_one(down->cache_tree, down->name, down->namelen, + buffer, size, offset); + } + return buffer; +} + +void *cache_tree_write(struct cache_tree *root, unsigned long *size_p) +{ + char path[PATH_MAX]; + unsigned long size = 8192; + char *buffer = xmalloc(size); + + *size_p = 0; + path[0] = 0; + return write_one(root, path, 0, buffer, &size, size_p); +} + +static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) +{ + const char *buf = *buffer; + unsigned long size = *size_p; + struct cache_tree *it; + int i, subtree_nr; + + it = NULL; + /* skip name, but make sure name exists */ + while (size && *buf) { + size--; + buf++; + } + if (!size) + goto free_return; + buf++; size--; + it = cache_tree(); + if (sscanf(buf, "%d %d\n", &it->entry_count, &subtree_nr) != 2) + goto free_return; + while (size && *buf && *buf != '\n') { + size--; + buf++; + } + if (!size) + goto free_return; + buf++; size--; + if (0 <= it->entry_count) { + if (size < 20) + goto free_return; + memcpy(it->sha1, buf, 20); + buf += 20; + size -= 20; + } + +#if DEBUG + if (0 <= it->entry_count) + fprintf(stderr, "cache-tree <%s> (%d ent, %d subtree) %s\n", + *buffer, it->entry_count, subtree_nr, + sha1_to_hex(it->sha1)); + else + fprintf(stderr, "cache-tree <%s> (%d subtrees) invalid\n", + *buffer, subtree_nr); +#endif + + /* + * Just a heuristic -- we do not add directories that often but + * we do not want to have to extend it immediately when we do, + * hence +2. + */ + it->subtree_alloc = subtree_nr + 2; + it->down = xcalloc(it->subtree_alloc, sizeof(struct cache_tree_sub *)); + for (i = 0; i < subtree_nr; i++) { + /* read each subtree */ + struct cache_tree *sub; + struct cache_tree_sub *subtree; + const char *name = buf; + + sub = read_one(&buf, &size); + if (!sub) + goto free_return; + subtree = cache_tree_sub(it, name); + subtree->cache_tree = sub; + } + if (subtree_nr != it->subtree_nr) + die("cache-tree: internal error"); + *buffer = buf; + *size_p = size; + return it; + + free_return: + cache_tree_free(&it); + return NULL; +} + +struct cache_tree *cache_tree_read(const char *buffer, unsigned long size) +{ + if (buffer[0]) + return NULL; /* not the whole tree */ + return read_one(&buffer, &size); +} diff --git a/cache-tree.h b/cache-tree.h new file mode 100644 index 0000000000..72c64801f5 --- /dev/null +++ b/cache-tree.h @@ -0,0 +1,31 @@ +#ifndef CACHE_TREE_H +#define CACHE_TREE_H + +struct cache_tree; +struct cache_tree_sub { + struct cache_tree *cache_tree; + int namelen; + int used; + char name[FLEX_ARRAY]; +}; + +struct cache_tree { + int entry_count; /* negative means "invalid" */ + unsigned char sha1[20]; + int subtree_nr; + int subtree_alloc; + struct cache_tree_sub **down; +}; + +struct cache_tree *cache_tree(void); +void cache_tree_free(struct cache_tree **); +void cache_tree_invalidate_path(struct cache_tree *, const char *); +struct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *); + +void *cache_tree_write(struct cache_tree *root, unsigned long *size_p); +struct cache_tree *cache_tree_read(const char *buffer, unsigned long size); + +int cache_tree_fully_valid(struct cache_tree *); +int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int); + +#endif @@ -114,6 +114,7 @@ static inline unsigned int create_ce_mode(unsigned int mode) extern struct cache_entry **active_cache; extern unsigned int active_nr, active_alloc, active_cache_changed; +extern struct cache_tree *active_cache_tree; #define GIT_DIR_ENVIRONMENT "GIT_DIR" #define DEFAULT_GIT_DIR_ENVIRONMENT ".git" @@ -134,6 +135,8 @@ extern const char *setup_git_directory_gently(int *); extern const char *setup_git_directory(void); extern const char *prefix_path(const char *prefix, int len, const char *path); extern const char *prefix_filename(const char *prefix, int len, const char *path); +extern void verify_filename(const char *prefix, const char *name); +extern void verify_non_filename(const char *prefix, const char *name); #define alloc_nr(x) (((x)+16)*3/2) diff --git a/checkout-index.c b/checkout-index.c index dd6a2d86fe..e56c354f8c 100644 --- a/checkout-index.c +++ b/checkout-index.c @@ -39,6 +39,7 @@ #include "cache.h" #include "strbuf.h" #include "quote.h" +#include "cache-tree.h" #define CHECKOUT_ALL 4 static const char *prefix; diff --git a/commit-tree.c b/commit-tree.c index 2d8651894a..bad72e89e8 100644 --- a/commit-tree.c +++ b/commit-tree.c @@ -45,14 +45,13 @@ static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...) static void check_valid(unsigned char *sha1, const char *expect) { - void *buf; char type[20]; - unsigned long size; - buf = read_sha1_file(sha1, type, &size); - if (!buf || strcmp(type, expect)) - die("%s is not a valid '%s' object", sha1_to_hex(sha1), expect); - free(buf); + if (sha1_object_info(sha1, type, NULL)) + die("%s is not a valid object", sha1_to_hex(sha1)); + if (expect && strcmp(type, expect)) + die("%s is not a valid '%s' object", sha1_to_hex(sha1), + expect); } /* @@ -92,7 +91,7 @@ int main(int argc, char **argv) git_config(git_default_config); - if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0) + if (argc < 2 || get_sha1(argv[1], tree_sha1) < 0) usage(commit_tree_usage); check_valid(tree_sha1, tree_type); @@ -36,6 +36,8 @@ enum cmit_fmt get_commit_format(const char *arg) return CMIT_FMT_FULL; if (!strcmp(arg, "=fuller")) return CMIT_FMT_FULLER; + if (!strcmp(arg, "=email")) + return CMIT_FMT_EMAIL; if (!strcmp(arg, "=oneline")) return CMIT_FMT_ONELINE; die("invalid --pretty format"); @@ -428,6 +430,10 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c time = strtoul(date, &date, 10); tz = strtol(date, NULL, 10); + if (fmt == CMIT_FMT_EMAIL) { + what = "From"; + filler = ""; + } ret = sprintf(buf, "%s: %.*s%.*s\n", what, (fmt == CMIT_FMT_FULLER) ? 4 : 0, filler, namelen, line); @@ -435,6 +441,9 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c case CMIT_FMT_MEDIUM: ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz)); break; + case CMIT_FMT_EMAIL: + ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz)); + break; case CMIT_FMT_FULLER: ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz)); break; @@ -445,10 +454,12 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c return ret; } -static int is_empty_line(const char *line, int len) +static int is_empty_line(const char *line, int *len_p) { + int len = *len_p; while (len && isspace(line[len-1])) len--; + *len_p = len; return !len; } @@ -457,7 +468,8 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com struct commit_list *parent = commit->parents; int offset; - if ((fmt == CMIT_FMT_ONELINE) || !parent || !parent->next) + if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) || + !parent || !parent->next) return 0; offset = sprintf(buf, "Merge:"); @@ -480,9 +492,15 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit { int hdr = 1, body = 0; unsigned long offset = 0; - int indent = (fmt == CMIT_FMT_ONELINE) ? 0 : 4; + int indent = 4; int parents_shown = 0; const char *msg = commit->buffer; + const char *subject = NULL; + + if (fmt == CMIT_FMT_EMAIL) + subject = "Subject: [PATCH] "; + if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL) + indent = 0; for (;;) { const char *line = msg; @@ -506,7 +524,7 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit if (hdr) { if (linelen == 1) { hdr = 0; - if (fmt != CMIT_FMT_ONELINE) + if ((fmt != CMIT_FMT_ONELINE) && !subject) buf[offset++] = '\n'; continue; } @@ -544,20 +562,29 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit continue; } - if (is_empty_line(line, linelen)) { + if (is_empty_line(line, &linelen)) { if (!body) continue; + if (subject) + continue; if (fmt == CMIT_FMT_SHORT) break; } else { body = 1; } + if (subject) { + int slen = strlen(subject); + memcpy(buf + offset, subject, slen); + offset += slen; + } memset(buf + offset, ' ', indent); memcpy(buf + offset + indent, line, linelen); offset += linelen + indent; + buf[offset++] = '\n'; if (fmt == CMIT_FMT_ONELINE) break; + subject = NULL; } while (offset && isspace(buf[offset-1])) offset--; @@ -45,6 +45,7 @@ enum cmit_fmt { CMIT_FMT_FULL, CMIT_FMT_FULLER, CMIT_FMT_ONELINE, + CMIT_FMT_EMAIL, CMIT_FMT_UNSPECIFIED, }; diff --git a/diff-files.c b/diff-files.c index ffbef48b2e..b9d193d506 100644 --- a/diff-files.c +++ b/diff-files.c @@ -12,203 +12,43 @@ static const char diff_files_usage[] = "git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]" COMMON_DIFF_OPTIONS_HELP; -static struct rev_info rev; -static int silent = 0; -static int diff_unmerged_stage = 2; -static int combine_merges = 0; -static int dense_combined_merges = 0; - -static void show_unmerge(const char *path) -{ - diff_unmerge(&rev.diffopt, path); -} - -static void show_file(int pfx, struct cache_entry *ce) -{ - diff_addremove(&rev.diffopt, pfx, ntohl(ce->ce_mode), - ce->sha1, ce->name, NULL); -} - -static void show_modified(int oldmode, int mode, - const unsigned char *old_sha1, const unsigned char *sha1, - char *path) -{ - diff_change(&rev.diffopt, oldmode, mode, old_sha1, sha1, path, NULL); -} - int main(int argc, const char **argv) { - const char **pathspec; - const char *prefix = setup_git_directory(); - int entries, i; + struct rev_info rev; + int silent = 0; git_config(git_diff_config); - diff_setup(&rev.diffopt); + init_revisions(&rev); + rev.abbrev = 0; + + argc = setup_revisions(argc, argv, &rev, NULL); while (1 < argc && argv[1][0] == '-') { - if (!strcmp(argv[1], "--")) { - argv++; - argc--; - break; - } - if (!strcmp(argv[1], "-0")) - diff_unmerged_stage = 0; - else if (!strcmp(argv[1], "-1")) - diff_unmerged_stage = 1; - else if (!strcmp(argv[1], "-2")) - diff_unmerged_stage = 2; - else if (!strcmp(argv[1], "-3")) - diff_unmerged_stage = 3; - else if (!strcmp(argv[1], "--base")) - diff_unmerged_stage = 1; + if (!strcmp(argv[1], "--base")) + rev.max_count = 1; else if (!strcmp(argv[1], "--ours")) - diff_unmerged_stage = 2; + rev.max_count = 2; else if (!strcmp(argv[1], "--theirs")) - diff_unmerged_stage = 3; + rev.max_count = 3; else if (!strcmp(argv[1], "-q")) silent = 1; - else if (!strcmp(argv[1], "-r")) - ; /* no-op */ - else if (!strcmp(argv[1], "-s")) - ; /* no-op */ - else if (!strcmp(argv[1], "-c")) - combine_merges = 1; - else if (!strcmp(argv[1], "--cc")) - dense_combined_merges = combine_merges = 1; - else { - int diff_opt_cnt; - diff_opt_cnt = diff_opt_parse(&rev.diffopt, - argv+1, argc-1); - if (diff_opt_cnt < 0) - usage(diff_files_usage); - else if (diff_opt_cnt) { - argv += diff_opt_cnt; - argc -= diff_opt_cnt; - continue; - } - else - usage(diff_files_usage); - } + else + usage(diff_files_usage); argv++; argc--; } - if (dense_combined_merges) - rev.diffopt.output_format = DIFF_FORMAT_PATCH; - - /* Find the directory, and set up the pathspec */ - pathspec = get_pathspec(prefix, argv + 1); - entries = read_cache(); - - if (diff_setup_done(&rev.diffopt) < 0) + /* + * Make sure there are NO revision (i.e. pending object) parameter, + * rev.max_count is reasonable (0 <= n <= 3), + * there is no other revision filtering parameters. + */ + if (rev.pending_objects || + rev.min_age != -1 || rev.max_age != -1) usage(diff_files_usage); - - /* At this point, if argc == 1, then we are doing everything. - * Otherwise argv[1] .. argv[argc-1] have the explicit paths. + /* + * Backward compatibility wart - "diff-files -s" used to + * defeat the common diff option "-s" which asked for + * DIFF_FORMAT_NO_OUTPUT. */ - if (entries < 0) { - perror("read_cache"); - exit(1); - } - - for (i = 0; i < entries; i++) { - struct stat st; - unsigned int oldmode, newmode; - struct cache_entry *ce = active_cache[i]; - int changed; - - if (!ce_path_match(ce, pathspec)) - continue; - - if (ce_stage(ce)) { - struct { - struct combine_diff_path p; - struct combine_diff_parent filler[5]; - } combine; - int num_compare_stages = 0; - - combine.p.next = NULL; - combine.p.len = ce_namelen(ce); - combine.p.path = xmalloc(combine.p.len + 1); - memcpy(combine.p.path, ce->name, combine.p.len); - combine.p.path[combine.p.len] = 0; - combine.p.mode = 0; - memset(combine.p.sha1, 0, 20); - memset(&combine.p.parent[0], 0, - sizeof(combine.filler)); - - while (i < entries) { - struct cache_entry *nce = active_cache[i]; - int stage; - - if (strcmp(ce->name, nce->name)) - break; - - /* Stage #2 (ours) is the first parent, - * stage #3 (theirs) is the second. - */ - stage = ce_stage(nce); - if (2 <= stage) { - int mode = ntohl(nce->ce_mode); - num_compare_stages++; - memcpy(combine.p.parent[stage-2].sha1, - nce->sha1, 20); - combine.p.parent[stage-2].mode = - canon_mode(mode); - combine.p.parent[stage-2].status = - DIFF_STATUS_MODIFIED; - } - - /* diff against the proper unmerged stage */ - if (stage == diff_unmerged_stage) - ce = nce; - i++; - } - /* - * Compensate for loop update - */ - i--; - - if (combine_merges && num_compare_stages == 2) { - show_combined_diff(&combine.p, 2, - dense_combined_merges, - &rev); - free(combine.p.path); - continue; - } - free(combine.p.path); - - /* - * Show the diff for the 'ce' if we found the one - * from the desired stage. - */ - show_unmerge(ce->name); - if (ce_stage(ce) != diff_unmerged_stage) - continue; - } - - if (lstat(ce->name, &st) < 0) { - if (errno != ENOENT && errno != ENOTDIR) { - perror(ce->name); - continue; - } - if (silent) - continue; - show_file('-', ce); - continue; - } - changed = ce_match_stat(ce, &st, 0); - if (!changed && !rev.diffopt.find_copies_harder) - continue; - oldmode = ntohl(ce->ce_mode); - - newmode = canon_mode(st.st_mode); - if (!trust_executable_bit && - S_ISREG(newmode) && S_ISREG(oldmode) && - ((newmode ^ oldmode) == 0111)) - newmode = oldmode; - show_modified(oldmode, newmode, - ce->sha1, (changed ? null_sha1 : ce->sha1), - ce->name); - } - diffcore_std(&rev.diffopt); - diff_flush(&rev.diffopt); - return 0; + if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT) + rev.diffopt.output_format = DIFF_FORMAT_RAW; + return run_diff_files(&rev, silent); } diff --git a/diff-index.c b/diff-index.c index e376d65f80..8c9f60173b 100644 --- a/diff-index.c +++ b/diff-index.c @@ -1,166 +1,7 @@ #include "cache.h" -#include "tree.h" #include "diff.h" - -static int cached_only = 0; -static int match_nonexisting = 0; -static struct diff_options diff_options; - -/* A file entry went away or appeared */ -static void show_file(const char *prefix, - struct cache_entry *ce, - unsigned char *sha1, unsigned int mode) -{ - diff_addremove(&diff_options, prefix[0], ntohl(mode), - sha1, ce->name, NULL); -} - -static int get_stat_data(struct cache_entry *ce, - unsigned char ** sha1p, unsigned int *modep) -{ - unsigned char *sha1 = ce->sha1; - unsigned int mode = ce->ce_mode; - - if (!cached_only) { - static unsigned char no_sha1[20]; - int changed; - struct stat st; - if (lstat(ce->name, &st) < 0) { - if (errno == ENOENT && match_nonexisting) { - *sha1p = sha1; - *modep = mode; - return 0; - } - return -1; - } - changed = ce_match_stat(ce, &st, 0); - if (changed) { - mode = create_ce_mode(st.st_mode); - if (!trust_executable_bit && S_ISREG(st.st_mode)) - mode = ce->ce_mode; - sha1 = no_sha1; - } - } - - *sha1p = sha1; - *modep = mode; - return 0; -} - -static void show_new_file(struct cache_entry *new) -{ - unsigned char *sha1; - unsigned int mode; - - /* New file in the index: it might actually be different in - * the working copy. - */ - if (get_stat_data(new, &sha1, &mode) < 0) - return; - - show_file("+", new, sha1, mode); -} - -static int show_modified(struct cache_entry *old, - struct cache_entry *new, - int report_missing) -{ - unsigned int mode, oldmode; - unsigned char *sha1; - - if (get_stat_data(new, &sha1, &mode) < 0) { - if (report_missing) - show_file("-", old, old->sha1, old->ce_mode); - return -1; - } - - oldmode = old->ce_mode; - if (mode == oldmode && !memcmp(sha1, old->sha1, 20) && - !diff_options.find_copies_harder) - return 0; - - mode = ntohl(mode); - oldmode = ntohl(oldmode); - - diff_change(&diff_options, oldmode, mode, - old->sha1, sha1, old->name, NULL); - return 0; -} - -static int diff_cache(struct cache_entry **ac, int entries, const char **pathspec) -{ - while (entries) { - struct cache_entry *ce = *ac; - int same = (entries > 1) && ce_same_name(ce, ac[1]); - - if (!ce_path_match(ce, pathspec)) - goto skip_entry; - - switch (ce_stage(ce)) { - case 0: - /* No stage 1 entry? That means it's a new file */ - if (!same) { - show_new_file(ce); - break; - } - /* Show difference between old and new */ - show_modified(ac[1], ce, 1); - break; - case 1: - /* No stage 3 (merge) entry? That means it's been deleted */ - if (!same) { - show_file("-", ce, ce->sha1, ce->ce_mode); - break; - } - /* We come here with ce pointing at stage 1 - * (original tree) and ac[1] pointing at stage - * 3 (unmerged). show-modified with - * report-missing set to false does not say the - * file is deleted but reports true if work - * tree does not have it, in which case we - * fall through to report the unmerged state. - * Otherwise, we show the differences between - * the original tree and the work tree. - */ - if (!cached_only && !show_modified(ce, ac[1], 0)) - break; - /* fallthru */ - case 3: - diff_unmerge(&diff_options, ce->name); - break; - - default: - die("impossible cache entry stage"); - } - -skip_entry: - /* - * Ignore all the different stages for this file, - * we've handled the relevant cases now. - */ - do { - ac++; - entries--; - } while (entries && ce_same_name(ce, ac[0])); - } - return 0; -} - -/* - * This turns all merge entries into "stage 3". That guarantees that - * when we read in the new tree (into "stage 1"), we won't lose sight - * of the fact that we had unmerged entries. - */ -static void mark_merge_entries(void) -{ - int i; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (!ce_stage(ce)) - continue; - ce->ce_flags |= htons(CE_STAGEMASK); - } -} +#include "commit.h" +#include "revision.h" static const char diff_cache_usage[] = "git-diff-index [-m] [--cached] " @@ -169,85 +10,29 @@ COMMON_DIFF_OPTIONS_HELP; int main(int argc, const char **argv) { - const char *tree_name = NULL; - unsigned char sha1[20]; - const char *prefix = setup_git_directory(); - const char **pathspec = NULL; - struct tree *tree; - int ret; - int allow_options = 1; + struct rev_info rev; + int cached = 0; int i; git_config(git_diff_config); - diff_setup(&diff_options); + init_revisions(&rev); + rev.abbrev = 0; + + argc = setup_revisions(argc, argv, &rev, NULL); for (i = 1; i < argc; i++) { const char *arg = argv[i]; - int diff_opt_cnt; - - if (!allow_options || *arg != '-') { - if (tree_name) - break; - tree_name = arg; - continue; - } - if (!strcmp(arg, "--")) { - allow_options = 0; - continue; - } - if (!strcmp(arg, "-r")) { - /* We accept the -r flag just to look like git-diff-tree */ - continue; - } - if (!strcmp(arg, "--cc")) - /* - * I _think_ "diff-index --cached HEAD" with an - * unmerged index could show something else - * later, but pretend --cc is the same as -p for - * now. "git diff" uses --cc by default. - */ - argv[i] = arg = "-p"; - diff_opt_cnt = diff_opt_parse(&diff_options, argv + i, - argc - i); - if (diff_opt_cnt < 0) + if (!strcmp(arg, "--cached")) + cached = 1; + else usage(diff_cache_usage); - else if (diff_opt_cnt) { - i += diff_opt_cnt - 1; - continue; - } - - if (!strcmp(arg, "-m")) { - match_nonexisting = 1; - continue; - } - if (!strcmp(arg, "--cached")) { - cached_only = 1; - continue; - } - usage(diff_cache_usage); } - - pathspec = get_pathspec(prefix, argv + i); - - if (diff_setup_done(&diff_options) < 0) - usage(diff_cache_usage); - - if (!tree_name || get_sha1(tree_name, sha1)) + /* + * Make sure there is one revision (i.e. pending object), + * and there is no revision filtering parameters. + */ + if (!rev.pending_objects || rev.pending_objects->next || + rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) usage(diff_cache_usage); - - read_cache(); - - mark_merge_entries(); - - tree = parse_tree_indirect(sha1); - if (!tree) - die("bad tree object %s", tree_name); - if (read_tree(tree, 1, pathspec)) - die("unable to read tree object %s", tree_name); - - ret = diff_cache(active_cache, active_nr, pathspec); - - diffcore_std(&diff_options); - diff_flush(&diff_options); - return ret; + return run_diff_index(&rev, cached); } diff --git a/diff-lib.c b/diff-lib.c index 13b216f273..2183b41b03 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -1,1795 +1,344 @@ /* * Copyright (C) 2005 Junio C Hamano */ -#include <sys/types.h> -#include <sys/wait.h> -#include <signal.h> #include "cache.h" #include "quote.h" +#include "commit.h" #include "diff.h" #include "diffcore.h" -#include "xdiff-interface.h" +#include "revision.h" -static int use_size_cache; - -int diff_rename_limit_default = -1; - -int git_diff_config(const char *var, const char *value) -{ - if (!strcmp(var, "diff.renamelimit")) { - diff_rename_limit_default = git_config_int(var, value); - return 0; - } - - return git_default_config(var, value); -} - -static char *quote_one(const char *str) -{ - int needlen; - char *xp; - - if (!str) - return NULL; - needlen = quote_c_style(str, NULL, NULL, 0); - if (!needlen) - return strdup(str); - xp = xmalloc(needlen + 1); - quote_c_style(str, xp, NULL, 0); - return xp; -} - -static char *quote_two(const char *one, const char *two) -{ - int need_one = quote_c_style(one, NULL, NULL, 1); - int need_two = quote_c_style(two, NULL, NULL, 1); - char *xp; - - if (need_one + need_two) { - if (!need_one) need_one = strlen(one); - if (!need_two) need_one = strlen(two); - - xp = xmalloc(need_one + need_two + 3); - xp[0] = '"'; - quote_c_style(one, xp + 1, NULL, 1); - quote_c_style(two, xp + need_one + 1, NULL, 1); - strcpy(xp + need_one + need_two + 1, "\""); - return xp; - } - need_one = strlen(one); - need_two = strlen(two); - xp = xmalloc(need_one + need_two + 1); - strcpy(xp, one); - strcpy(xp + need_one, two); - return xp; -} - -static const char *external_diff(void) -{ - static const char *external_diff_cmd = NULL; - static int done_preparing = 0; - - if (done_preparing) - return external_diff_cmd; - external_diff_cmd = getenv("GIT_EXTERNAL_DIFF"); - done_preparing = 1; - return external_diff_cmd; -} - -#define TEMPFILE_PATH_LEN 50 - -static struct diff_tempfile { - const char *name; /* filename external diff should read from */ - char hex[41]; - char mode[10]; - char tmp_path[TEMPFILE_PATH_LEN]; -} diff_temp[2]; - -static int count_lines(const char *data, int size) -{ - int count, ch, completely_empty = 1, nl_just_seen = 0; - count = 0; - while (0 < size--) { - ch = *data++; - if (ch == '\n') { - count++; - nl_just_seen = 1; - completely_empty = 0; - } - else { - nl_just_seen = 0; - completely_empty = 0; - } - } - if (completely_empty) - return 0; - if (!nl_just_seen) - count++; /* no trailing newline */ - return count; -} - -static void print_line_count(int count) -{ - switch (count) { - case 0: - printf("0,0"); - break; - case 1: - printf("1"); - break; - default: - printf("1,%d", count); - break; - } -} - -static void copy_file(int prefix, const char *data, int size) -{ - int ch, nl_just_seen = 1; - while (0 < size--) { - ch = *data++; - if (nl_just_seen) - putchar(prefix); - putchar(ch); - if (ch == '\n') - nl_just_seen = 1; - else - nl_just_seen = 0; - } - if (!nl_just_seen) - printf("\n\\ No newline at end of file\n"); -} +/* + * diff-files + */ -static void emit_rewrite_diff(const char *name_a, - const char *name_b, - struct diff_filespec *one, - struct diff_filespec *two) +int run_diff_files(struct rev_info *revs, int silent_on_removed) { - int lc_a, lc_b; - diff_populate_filespec(one, 0); - diff_populate_filespec(two, 0); - lc_a = count_lines(one->data, one->size); - lc_b = count_lines(two->data, two->size); - printf("--- %s\n+++ %s\n@@ -", name_a, name_b); - print_line_count(lc_a); - printf(" +"); - print_line_count(lc_b); - printf(" @@\n"); - if (lc_a) - copy_file('-', one->data, one->size); - if (lc_b) - copy_file('+', two->data, two->size); -} + int entries, i; + int diff_unmerged_stage = revs->max_count; -static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) -{ - if (!DIFF_FILE_VALID(one)) { - mf->ptr = ""; /* does not matter */ - mf->size = 0; - return 0; - } - else if (diff_populate_filespec(one, 0)) + if (diff_unmerged_stage < 0) + diff_unmerged_stage = 2; + entries = read_cache(); + if (entries < 0) { + perror("read_cache"); return -1; - mf->ptr = one->data; - mf->size = one->size; - return 0; -} - -struct emit_callback { - const char **label_path; -}; - -static int fn_out(void *priv, mmbuffer_t *mb, int nbuf) -{ - int i; - struct emit_callback *ecbdata = priv; - - if (ecbdata->label_path[0]) { - printf("--- %s\n", ecbdata->label_path[0]); - printf("+++ %s\n", ecbdata->label_path[1]); - ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; } - for (i = 0; i < nbuf; i++) - if (!fwrite(mb[i].ptr, mb[i].size, 1, stdout)) - return -1; - return 0; -} - -static char *pprint_rename(const char *a, const char *b) -{ - const char *old = a; - const char *new = b; - char *name = NULL; - int pfx_length, sfx_length; - int len_a = strlen(a); - int len_b = strlen(b); - - /* Find common prefix */ - pfx_length = 0; - while (*old && *new && *old == *new) { - if (*old == '/') - pfx_length = old - a + 1; - old++; - new++; - } - - /* Find common suffix */ - old = a + len_a; - new = b + len_b; - sfx_length = 0; - while (a <= old && b <= new && *old == *new) { - if (*old == '/') - sfx_length = len_a - (old - a); - old--; - new--; - } - - /* - * pfx{mid-a => mid-b}sfx - * {pfx-a => pfx-b}sfx - * pfx{sfx-a => sfx-b} - * name-a => name-b - */ - if (pfx_length + sfx_length) { - name = xmalloc(len_a + len_b - pfx_length - sfx_length + 7); - sprintf(name, "%.*s{%.*s => %.*s}%s", - pfx_length, a, - len_a - pfx_length - sfx_length, a + pfx_length, - len_b - pfx_length - sfx_length, b + pfx_length, - a + len_a - sfx_length); - } - else { - name = xmalloc(len_a + len_b + 5); - sprintf(name, "%s => %s", a, b); - } - return name; -} - -struct diffstat_t { - struct xdiff_emit_state xm; - - int nr; - int alloc; - struct diffstat_file { - char *name; - unsigned is_unmerged:1; - unsigned is_binary:1; - unsigned is_renamed:1; - unsigned int added, deleted; - } **files; -}; - -static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, - const char *name_a, - const char *name_b) -{ - struct diffstat_file *x; - x = xcalloc(sizeof (*x), 1); - if (diffstat->nr == diffstat->alloc) { - diffstat->alloc = alloc_nr(diffstat->alloc); - diffstat->files = xrealloc(diffstat->files, - diffstat->alloc * sizeof(x)); - } - diffstat->files[diffstat->nr++] = x; - if (name_b) { - x->name = pprint_rename(name_a, name_b); - x->is_renamed = 1; - } - else - x->name = strdup(name_a); - return x; -} - -static void diffstat_consume(void *priv, char *line, unsigned long len) -{ - struct diffstat_t *diffstat = priv; - struct diffstat_file *x = diffstat->files[diffstat->nr - 1]; - - if (line[0] == '+') - x->added++; - else if (line[0] == '-') - x->deleted++; -} - -static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; -static const char minuses[]= "----------------------------------------------------------------------"; - -static void show_stats(struct diffstat_t* data) -{ - char *prefix = ""; - int i, len, add, del, total, adds = 0, dels = 0; - int max, max_change = 0, max_len = 0; - int total_files = data->nr; - - if (data->nr == 0) - return; - - for (i = 0; i < data->nr; i++) { - struct diffstat_file *file = data->files[i]; - - len = strlen(file->name); - if (max_len < len) - max_len = len; + for (i = 0; i < entries; i++) { + struct stat st; + unsigned int oldmode, newmode; + struct cache_entry *ce = active_cache[i]; + int changed; - if (file->is_binary || file->is_unmerged) + if (!ce_path_match(ce, revs->prune_data)) continue; - if (max_change < file->added + file->deleted) - max_change = file->added + file->deleted; - } - for (i = 0; i < data->nr; i++) { - char *name = data->files[i]->name; - int added = data->files[i]->added; - int deleted = data->files[i]->deleted; + if (ce_stage(ce)) { + struct { + struct combine_diff_path p; + struct combine_diff_parent filler[5]; + } combine; + int num_compare_stages = 0; + + combine.p.next = NULL; + combine.p.len = ce_namelen(ce); + combine.p.path = xmalloc(combine.p.len + 1); + memcpy(combine.p.path, ce->name, combine.p.len); + combine.p.path[combine.p.len] = 0; + combine.p.mode = 0; + memset(combine.p.sha1, 0, 20); + memset(&combine.p.parent[0], 0, + sizeof(combine.filler)); + + while (i < entries) { + struct cache_entry *nce = active_cache[i]; + int stage; + + if (strcmp(ce->name, nce->name)) + break; + + /* Stage #2 (ours) is the first parent, + * stage #3 (theirs) is the second. + */ + stage = ce_stage(nce); + if (2 <= stage) { + int mode = ntohl(nce->ce_mode); + num_compare_stages++; + memcpy(combine.p.parent[stage-2].sha1, + nce->sha1, 20); + combine.p.parent[stage-2].mode = + canon_mode(mode); + combine.p.parent[stage-2].status = + DIFF_STATUS_MODIFIED; + } + + /* diff against the proper unmerged stage */ + if (stage == diff_unmerged_stage) + ce = nce; + i++; + } + /* + * Compensate for loop update + */ + i--; - if (0 < (len = quote_c_style(name, NULL, NULL, 0))) { - char *qname = xmalloc(len + 1); - quote_c_style(name, qname, NULL, 0); - free(name); - data->files[i]->name = name = qname; - } + if (revs->combine_merges && num_compare_stages == 2) { + show_combined_diff(&combine.p, 2, + revs->dense_combined_merges, + revs); + free(combine.p.path); + continue; + } + free(combine.p.path); - /* - * "scale" the filename - */ - len = strlen(name); - max = max_len; - if (max > 50) - max = 50; - if (len > max) { - char *slash; - prefix = "..."; - max -= 3; - name += len - max; - slash = strchr(name, '/'); - if (slash) - name = slash; + /* + * Show the diff for the 'ce' if we found the one + * from the desired stage. + */ + diff_unmerge(&revs->diffopt, ce->name); + if (ce_stage(ce) != diff_unmerged_stage) + continue; } - len = max; - /* - * scale the add/delete - */ - max = max_change; - if (max + len > 70) - max = 70 - len; - - if (data->files[i]->is_binary) { - printf(" %s%-*s | Bin\n", prefix, len, name); - goto free_diffstat_file; - } - else if (data->files[i]->is_unmerged) { - printf(" %s%-*s | Unmerged\n", prefix, len, name); - goto free_diffstat_file; - } - else if (!data->files[i]->is_renamed && - (added + deleted == 0)) { - total_files--; - goto free_diffstat_file; + if (lstat(ce->name, &st) < 0) { + if (errno != ENOENT && errno != ENOTDIR) { + perror(ce->name); + continue; + } + if (silent_on_removed) + continue; + diff_addremove(&revs->diffopt, '-', ntohl(ce->ce_mode), + ce->sha1, ce->name, NULL); + continue; } + changed = ce_match_stat(ce, &st, 0); + if (!changed && !revs->diffopt.find_copies_harder) + continue; + oldmode = ntohl(ce->ce_mode); - add = added; - del = deleted; - total = add + del; - adds += add; - dels += del; + newmode = canon_mode(st.st_mode); + if (!trust_executable_bit && + S_ISREG(newmode) && S_ISREG(oldmode) && + ((newmode ^ oldmode) == 0111)) + newmode = oldmode; + diff_change(&revs->diffopt, oldmode, newmode, + ce->sha1, (changed ? null_sha1 : ce->sha1), + ce->name, NULL); - if (max_change > 0) { - total = (total * max + max_change / 2) / max_change; - add = (add * max + max_change / 2) / max_change; - del = total - add; - } - printf(" %s%-*s |%5d %.*s%.*s\n", prefix, - len, name, added + deleted, - add, pluses, del, minuses); - free_diffstat_file: - free(data->files[i]->name); - free(data->files[i]); } - free(data->files); - printf(" %d files changed, %d insertions(+), %d deletions(-)\n", - total_files, adds, dels); -} - -#define FIRST_FEW_BYTES 8000 -static int mmfile_is_binary(mmfile_t *mf) -{ - long sz = mf->size; - if (FIRST_FEW_BYTES < sz) - sz = FIRST_FEW_BYTES; - if (memchr(mf->ptr, 0, sz)) - return 1; + diffcore_std(&revs->diffopt); + diff_flush(&revs->diffopt); return 0; } -static void builtin_diff(const char *name_a, - const char *name_b, - struct diff_filespec *one, - struct diff_filespec *two, - const char *xfrm_msg, - int complete_rewrite) -{ - mmfile_t mf1, mf2; - const char *lbl[2]; - char *a_one, *b_two; - - a_one = quote_two("a/", name_a); - b_two = quote_two("b/", name_b); - lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null"; - lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null"; - printf("diff --git %s %s\n", a_one, b_two); - if (lbl[0][0] == '/') { - /* /dev/null */ - printf("new file mode %06o\n", two->mode); - if (xfrm_msg && xfrm_msg[0]) - puts(xfrm_msg); - } - else if (lbl[1][0] == '/') { - printf("deleted file mode %06o\n", one->mode); - if (xfrm_msg && xfrm_msg[0]) - puts(xfrm_msg); - } - else { - if (one->mode != two->mode) { - printf("old mode %06o\n", one->mode); - printf("new mode %06o\n", two->mode); - } - if (xfrm_msg && xfrm_msg[0]) - puts(xfrm_msg); - /* - * we do not run diff between different kind - * of objects. - */ - if ((one->mode ^ two->mode) & S_IFMT) - goto free_ab_and_return; - if (complete_rewrite) { - emit_rewrite_diff(name_a, name_b, one, two); - goto free_ab_and_return; - } - } - - 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)) - printf("Binary files %s and %s differ\n", lbl[0], lbl[1]); - else { - /* Crazy xdl interfaces.. */ - const char *diffopts = getenv("GIT_DIFF_OPTS"); - xpparam_t xpp; - xdemitconf_t xecfg; - xdemitcb_t ecb; - struct emit_callback ecbdata; - - ecbdata.label_path = lbl; - xpp.flags = XDF_NEED_MINIMAL; - xecfg.ctxlen = 3; - xecfg.flags = XDL_EMIT_FUNCNAMES; - if (!diffopts) - ; - else if (!strncmp(diffopts, "--unified=", 10)) - xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10); - else if (!strncmp(diffopts, "-u", 2)) - xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10); - ecb.outf = fn_out; - ecb.priv = &ecbdata; - xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); - } - - free_ab_and_return: - free(a_one); - free(b_two); - return; -} - -static void builtin_diffstat(const char *name_a, const char *name_b, - struct diff_filespec *one, - struct diff_filespec *two, - struct diffstat_t *diffstat) -{ - mmfile_t mf1, mf2; - struct diffstat_file *data; - - data = diffstat_add(diffstat, name_a, name_b); - - if (!one || !two) { - data->is_unmerged = 1; - return; - } - - 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)) - data->is_binary = 1; - else { - /* Crazy xdl interfaces.. */ - xpparam_t xpp; - xdemitconf_t xecfg; - xdemitcb_t ecb; - - xpp.flags = XDF_NEED_MINIMAL; - xecfg.ctxlen = 0; - xecfg.flags = 0; - ecb.outf = xdiff_outf; - ecb.priv = diffstat; - xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); - } -} - -struct diff_filespec *alloc_filespec(const char *path) -{ - int namelen = strlen(path); - struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1); - - memset(spec, 0, sizeof(*spec)); - spec->path = (char *)(spec + 1); - memcpy(spec->path, path, namelen+1); - return spec; -} - -void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, - unsigned short mode) -{ - if (mode) { - spec->mode = canon_mode(mode); - memcpy(spec->sha1, sha1, 20); - spec->sha1_valid = !!memcmp(sha1, null_sha1, 20); - } -} - /* - * Given a name and sha1 pair, if the dircache tells us the file in - * the work tree has that object contents, return true, so that - * prepare_temp_file() does not have to inflate and extract. + * diff-index */ -static int work_tree_matches(const char *name, const unsigned char *sha1) -{ - struct cache_entry *ce; - struct stat st; - int pos, len; - - /* We do not read the cache ourselves here, because the - * benchmark with my previous version that always reads cache - * shows that it makes things worse for diff-tree comparing - * two linux-2.6 kernel trees in an already checked out work - * tree. This is because most diff-tree comparisons deal with - * only a small number of files, while reading the cache is - * expensive for a large project, and its cost outweighs the - * savings we get by not inflating the object to a temporary - * file. Practically, this code only helps when we are used - * by diff-cache --cached, which does read the cache before - * calling us. - */ - if (!active_cache) - return 0; - len = strlen(name); - pos = cache_name_pos(name, len); - if (pos < 0) - return 0; - ce = active_cache[pos]; - if ((lstat(name, &st) < 0) || - !S_ISREG(st.st_mode) || /* careful! */ - ce_match_stat(ce, &st, 0) || - memcmp(sha1, ce->sha1, 20)) - return 0; - /* we return 1 only when we can stat, it is a regular file, - * stat information matches, and sha1 recorded in the cache - * matches. I.e. we know the file in the work tree really is - * the same as the <name, sha1> pair. - */ - return 1; -} - -static struct sha1_size_cache { - unsigned char sha1[20]; - unsigned long size; -} **sha1_size_cache; -static int sha1_size_cache_nr, sha1_size_cache_alloc; - -static struct sha1_size_cache *locate_size_cache(unsigned char *sha1, - int find_only, - unsigned long size) +/* A file entry went away or appeared */ +static void diff_index_show_file(struct rev_info *revs, + const char *prefix, + struct cache_entry *ce, + unsigned char *sha1, unsigned int mode) { - int first, last; - struct sha1_size_cache *e; - - first = 0; - last = sha1_size_cache_nr; - while (last > first) { - int cmp, next = (last + first) >> 1; - e = sha1_size_cache[next]; - cmp = memcmp(e->sha1, sha1, 20); - if (!cmp) - return e; - if (cmp < 0) { - last = next; - continue; - } - first = next+1; - } - /* not found */ - if (find_only) - return NULL; - /* insert to make it at "first" */ - if (sha1_size_cache_alloc <= sha1_size_cache_nr) { - sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc); - sha1_size_cache = xrealloc(sha1_size_cache, - sha1_size_cache_alloc * - sizeof(*sha1_size_cache)); - } - sha1_size_cache_nr++; - if (first < sha1_size_cache_nr) - memmove(sha1_size_cache + first + 1, sha1_size_cache + first, - (sha1_size_cache_nr - first - 1) * - sizeof(*sha1_size_cache)); - e = xmalloc(sizeof(struct sha1_size_cache)); - sha1_size_cache[first] = e; - memcpy(e->sha1, sha1, 20); - e->size = size; - return e; + diff_addremove(&revs->diffopt, prefix[0], ntohl(mode), + sha1, ce->name, NULL); } -/* - * While doing rename detection and pickaxe operation, we may need to - * grab the data for the blob (or file) for our own in-core comparison. - * diff_filespec has data and size fields for this purpose. - */ -int diff_populate_filespec(struct diff_filespec *s, int size_only) +static int get_stat_data(struct cache_entry *ce, + unsigned char **sha1p, + unsigned int *modep, + int cached, int match_missing) { - int err = 0; - if (!DIFF_FILE_VALID(s)) - die("internal error: asking to populate invalid file."); - if (S_ISDIR(s->mode)) - return -1; - - if (!use_size_cache) - size_only = 0; + unsigned char *sha1 = ce->sha1; + unsigned int mode = ce->ce_mode; - if (s->data) - return err; - if (!s->sha1_valid || - work_tree_matches(s->path, s->sha1)) { + if (!cached) { + static unsigned char no_sha1[20]; + int changed; struct stat st; - int fd; - if (lstat(s->path, &st) < 0) { - if (errno == ENOENT) { - err_empty: - err = -1; - empty: - s->data = ""; - s->size = 0; - return err; - } - } - s->size = st.st_size; - if (!s->size) - goto empty; - if (size_only) - return 0; - if (S_ISLNK(st.st_mode)) { - int ret; - s->data = xmalloc(s->size); - s->should_free = 1; - ret = readlink(s->path, s->data, s->size); - if (ret < 0) { - free(s->data); - goto err_empty; - } - return 0; - } - fd = open(s->path, O_RDONLY); - if (fd < 0) - goto err_empty; - s->data = mmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0); - close(fd); - if (s->data == MAP_FAILED) - goto err_empty; - s->should_munmap = 1; - } - else { - char type[20]; - struct sha1_size_cache *e; - - if (size_only) { - e = locate_size_cache(s->sha1, 1, 0); - if (e) { - s->size = e->size; + if (lstat(ce->name, &st) < 0) { + if (errno == ENOENT && match_missing) { + *sha1p = sha1; + *modep = mode; return 0; } - if (!sha1_object_info(s->sha1, type, &s->size)) - locate_size_cache(s->sha1, 0, s->size); - } - else { - s->data = read_sha1_file(s->sha1, type, &s->size); - s->should_free = 1; - } - } - return 0; -} - -void diff_free_filespec_data(struct diff_filespec *s) -{ - if (s->should_free) - free(s->data); - else if (s->should_munmap) - munmap(s->data, s->size); - s->should_free = s->should_munmap = 0; - s->data = NULL; - free(s->cnt_data); - s->cnt_data = NULL; -} - -static void prep_temp_blob(struct diff_tempfile *temp, - void *blob, - unsigned long size, - const unsigned char *sha1, - int mode) -{ - int fd; - - fd = git_mkstemp(temp->tmp_path, TEMPFILE_PATH_LEN, ".diff_XXXXXX"); - if (fd < 0) - die("unable to create temp-file"); - if (write(fd, blob, size) != size) - die("unable to write temp-file"); - close(fd); - temp->name = temp->tmp_path; - strcpy(temp->hex, sha1_to_hex(sha1)); - temp->hex[40] = 0; - sprintf(temp->mode, "%06o", mode); -} - -static void prepare_temp_file(const char *name, - struct diff_tempfile *temp, - struct diff_filespec *one) -{ - if (!DIFF_FILE_VALID(one)) { - not_a_valid_file: - /* A '-' entry produces this for file-2, and - * a '+' entry produces this for file-1. - */ - temp->name = "/dev/null"; - strcpy(temp->hex, "."); - strcpy(temp->mode, "."); - return; - } - - if (!one->sha1_valid || - work_tree_matches(name, one->sha1)) { - struct stat st; - if (lstat(name, &st) < 0) { - if (errno == ENOENT) - goto not_a_valid_file; - die("stat(%s): %s", name, strerror(errno)); - } - if (S_ISLNK(st.st_mode)) { - int ret; - char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */ - if (sizeof(buf) <= st.st_size) - die("symlink too long: %s", name); - ret = readlink(name, buf, st.st_size); - if (ret < 0) - die("readlink(%s)", name); - prep_temp_blob(temp, buf, st.st_size, - (one->sha1_valid ? - one->sha1 : null_sha1), - (one->sha1_valid ? - one->mode : S_IFLNK)); + return -1; } - else { - /* we can borrow from the file in the work tree */ - temp->name = name; - if (!one->sha1_valid) - strcpy(temp->hex, sha1_to_hex(null_sha1)); - else - strcpy(temp->hex, sha1_to_hex(one->sha1)); - /* Even though we may sometimes borrow the - * contents from the work tree, we always want - * one->mode. mode is trustworthy even when - * !(one->sha1_valid), as long as - * DIFF_FILE_VALID(one). - */ - sprintf(temp->mode, "%06o", one->mode); + changed = ce_match_stat(ce, &st, 0); + if (changed) { + mode = create_ce_mode(st.st_mode); + if (!trust_executable_bit && S_ISREG(st.st_mode)) + mode = ce->ce_mode; + sha1 = no_sha1; } - return; } - else { - if (diff_populate_filespec(one, 0)) - die("cannot read data blob for %s", one->path); - prep_temp_blob(temp, one->data, one->size, - one->sha1, one->mode); - } -} -static void remove_tempfile(void) -{ - int i; - - for (i = 0; i < 2; i++) - if (diff_temp[i].name == diff_temp[i].tmp_path) { - unlink(diff_temp[i].name); - diff_temp[i].name = NULL; - } -} - -static void remove_tempfile_on_signal(int signo) -{ - remove_tempfile(); - signal(SIGINT, SIG_DFL); - raise(signo); + *sha1p = sha1; + *modep = mode; + return 0; } -static int spawn_prog(const char *pgm, const char **arg) +static void show_new_file(struct rev_info *revs, + struct cache_entry *new, + int cached, int match_missing) { - pid_t pid; - int status; - - fflush(NULL); - pid = fork(); - if (pid < 0) - die("unable to fork"); - if (!pid) { - execvp(pgm, (char *const*) arg); - exit(255); - } - - while (waitpid(pid, &status, 0) < 0) { - if (errno == EINTR) - continue; - return -1; - } + unsigned char *sha1; + unsigned int mode; - /* Earlier we did not check the exit status because - * diff exits non-zero if files are different, and - * we are not interested in knowing that. It was a - * mistake which made it harder to quit a diff-* - * session that uses the git-apply-patch-script as - * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF - * should also exit non-zero only when it wants to - * abort the entire diff-* session. + /* New file in the index: it might actually be different in + * the working copy. */ - if (WIFEXITED(status) && !WEXITSTATUS(status)) - return 0; - return -1; -} - -/* An external diff command takes: - * - * diff-cmd name infile1 infile1-sha1 infile1-mode \ - * infile2 infile2-sha1 infile2-mode [ rename-to ] - * - */ -static void run_external_diff(const char *pgm, - const char *name, - const char *other, - struct diff_filespec *one, - struct diff_filespec *two, - const char *xfrm_msg, - int complete_rewrite) -{ - const char *spawn_arg[10]; - struct diff_tempfile *temp = diff_temp; - int retval; - static int atexit_asked = 0; - const char *othername; - const char **arg = &spawn_arg[0]; - - othername = (other? other : name); - if (one && two) { - prepare_temp_file(name, &temp[0], one); - prepare_temp_file(othername, &temp[1], two); - if (! atexit_asked && - (temp[0].name == temp[0].tmp_path || - temp[1].name == temp[1].tmp_path)) { - atexit_asked = 1; - atexit(remove_tempfile); - } - signal(SIGINT, remove_tempfile_on_signal); - } - - if (one && two) { - *arg++ = pgm; - *arg++ = name; - *arg++ = temp[0].name; - *arg++ = temp[0].hex; - *arg++ = temp[0].mode; - *arg++ = temp[1].name; - *arg++ = temp[1].hex; - *arg++ = temp[1].mode; - if (other) { - *arg++ = other; - *arg++ = xfrm_msg; - } - } else { - *arg++ = pgm; - *arg++ = name; - } - *arg = NULL; - retval = spawn_prog(pgm, spawn_arg); - remove_tempfile(); - if (retval) { - fprintf(stderr, "external diff died, stopping at %s.\n", name); - exit(1); - } -} - -static void run_diff_cmd(const char *pgm, - const char *name, - const char *other, - struct diff_filespec *one, - struct diff_filespec *two, - const char *xfrm_msg, - int complete_rewrite) -{ - if (pgm) { - run_external_diff(pgm, name, other, one, two, xfrm_msg, - complete_rewrite); + if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) return; - } - if (one && two) - builtin_diff(name, other ? other : name, - one, two, xfrm_msg, complete_rewrite); - else - printf("* Unmerged path %s\n", name); -} -static void diff_fill_sha1_info(struct diff_filespec *one) -{ - if (DIFF_FILE_VALID(one)) { - if (!one->sha1_valid) { - struct stat st; - if (lstat(one->path, &st) < 0) - die("stat %s", one->path); - if (index_path(one->sha1, one->path, &st, 0)) - die("cannot hash %s\n", one->path); - } - } - else - memset(one->sha1, 0, 20); + diff_index_show_file(revs, "+", new, sha1, mode); } -static void run_diff(struct diff_filepair *p, struct diff_options *o) +static int show_modified(struct rev_info *revs, + struct cache_entry *old, + struct cache_entry *new, + int report_missing, + int cached, int match_missing) { - const char *pgm = external_diff(); - char msg[PATH_MAX*2+300], *xfrm_msg; - struct diff_filespec *one; - struct diff_filespec *two; - const char *name; - const char *other; - char *name_munged, *other_munged; - int complete_rewrite = 0; - int len; - - if (DIFF_PAIR_UNMERGED(p)) { - /* unmerged */ - run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, 0); - return; - } - - name = p->one->path; - other = (strcmp(name, p->two->path) ? p->two->path : NULL); - name_munged = quote_one(name); - other_munged = quote_one(other); - one = p->one; two = p->two; - - diff_fill_sha1_info(one); - diff_fill_sha1_info(two); - - len = 0; - switch (p->status) { - case DIFF_STATUS_COPIED: - len += snprintf(msg + len, sizeof(msg) - len, - "similarity index %d%%\n" - "copy from %s\n" - "copy to %s\n", - (int)(0.5 + p->score * 100.0/MAX_SCORE), - name_munged, other_munged); - break; - case DIFF_STATUS_RENAMED: - len += snprintf(msg + len, sizeof(msg) - len, - "similarity index %d%%\n" - "rename from %s\n" - "rename to %s\n", - (int)(0.5 + p->score * 100.0/MAX_SCORE), - name_munged, other_munged); - break; - case DIFF_STATUS_MODIFIED: - if (p->score) { - len += snprintf(msg + len, sizeof(msg) - len, - "dissimilarity index %d%%\n", - (int)(0.5 + p->score * - 100.0/MAX_SCORE)); - complete_rewrite = 1; - break; - } - /* fallthru */ - default: - /* nothing */ - ; - } - - if (memcmp(one->sha1, two->sha1, 20)) { - char one_sha1[41]; - int abbrev = o->full_index ? 40 : DEFAULT_ABBREV; - memcpy(one_sha1, sha1_to_hex(one->sha1), 41); - - len += snprintf(msg + len, sizeof(msg) - len, - "index %.*s..%.*s", - abbrev, one_sha1, abbrev, - sha1_to_hex(two->sha1)); - if (one->mode == two->mode) - len += snprintf(msg + len, sizeof(msg) - len, - " %06o", one->mode); - len += snprintf(msg + len, sizeof(msg) - len, "\n"); - } - - if (len) - msg[--len] = 0; - xfrm_msg = len ? msg : NULL; + unsigned int mode, oldmode; + unsigned char *sha1; - if (!pgm && - DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) && - (S_IFMT & one->mode) != (S_IFMT & two->mode)) { - /* a filepair that changes between file and symlink - * needs to be split into deletion and creation. - */ - struct diff_filespec *null = alloc_filespec(two->path); - run_diff_cmd(NULL, name, other, one, null, xfrm_msg, 0); - free(null); - null = alloc_filespec(one->path); - run_diff_cmd(NULL, name, other, null, two, xfrm_msg, 0); - free(null); - } - else - run_diff_cmd(pgm, name, other, one, two, xfrm_msg, - complete_rewrite); - - free(name_munged); - free(other_munged); -} - -static void run_diffstat(struct diff_filepair *p, struct diff_options *o, - struct diffstat_t *diffstat) -{ - const char *name; - const char *other; - - if (DIFF_PAIR_UNMERGED(p)) { - /* unmerged */ - builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat); - return; - } - - name = p->one->path; - other = (strcmp(name, p->two->path) ? p->two->path : NULL); - - diff_fill_sha1_info(p->one); - diff_fill_sha1_info(p->two); - - builtin_diffstat(name, other, p->one, p->two, diffstat); -} - -void diff_setup(struct diff_options *options) -{ - memset(options, 0, sizeof(*options)); - options->output_format = DIFF_FORMAT_RAW; - options->line_termination = '\n'; - options->break_opt = -1; - options->rename_limit = -1; - - options->change = diff_change; - options->add_remove = diff_addremove; -} - -int diff_setup_done(struct diff_options *options) -{ - if ((options->find_copies_harder && - options->detect_rename != DIFF_DETECT_COPY) || - (0 <= options->rename_limit && !options->detect_rename)) + if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) { + if (report_missing) + diff_index_show_file(revs, "-", old, + old->sha1, old->ce_mode); return -1; - - /* - * These cases always need recursive; we do not drop caller-supplied - * recursive bits for other formats here. - */ - if ((options->output_format == DIFF_FORMAT_PATCH) || - (options->output_format == DIFF_FORMAT_DIFFSTAT)) - options->recursive = 1; - - if (options->detect_rename && options->rename_limit < 0) - options->rename_limit = diff_rename_limit_default; - if (options->setup & DIFF_SETUP_USE_CACHE) { - if (!active_cache) - /* read-cache does not die even when it fails - * so it is safe for us to do this here. Also - * it does not smudge active_cache or active_nr - * when it fails, so we do not have to worry about - * cleaning it up ourselves either. - */ - read_cache(); } - if (options->setup & DIFF_SETUP_USE_SIZE_CACHE) - use_size_cache = 1; - if (options->abbrev <= 0 || 40 < options->abbrev) - options->abbrev = 40; /* full */ - - return 0; -} -int diff_opt_parse(struct diff_options *options, const char **av, int ac) -{ - const char *arg = av[0]; - if (!strcmp(arg, "-p") || !strcmp(arg, "-u")) - options->output_format = DIFF_FORMAT_PATCH; - else if (!strcmp(arg, "--patch-with-raw")) { - options->output_format = DIFF_FORMAT_PATCH; - options->with_raw = 1; - } - else if (!strcmp(arg, "--stat")) - options->output_format = DIFF_FORMAT_DIFFSTAT; - else if (!strcmp(arg, "--patch-with-stat")) { - options->output_format = DIFF_FORMAT_PATCH; - options->with_stat = 1; - } - else if (!strcmp(arg, "-z")) - options->line_termination = 0; - else if (!strncmp(arg, "-l", 2)) - options->rename_limit = strtoul(arg+2, NULL, 10); - else if (!strcmp(arg, "--full-index")) - options->full_index = 1; - else if (!strcmp(arg, "--name-only")) - options->output_format = DIFF_FORMAT_NAME; - else if (!strcmp(arg, "--name-status")) - options->output_format = DIFF_FORMAT_NAME_STATUS; - else if (!strcmp(arg, "-R")) - options->reverse_diff = 1; - else if (!strncmp(arg, "-S", 2)) - options->pickaxe = arg + 2; - else if (!strcmp(arg, "-s")) - options->output_format = DIFF_FORMAT_NO_OUTPUT; - else if (!strncmp(arg, "-O", 2)) - options->orderfile = arg + 2; - else if (!strncmp(arg, "--diff-filter=", 14)) - options->filter = arg + 14; - else if (!strcmp(arg, "--pickaxe-all")) - options->pickaxe_opts = DIFF_PICKAXE_ALL; - else if (!strcmp(arg, "--pickaxe-regex")) - options->pickaxe_opts = DIFF_PICKAXE_REGEX; - else if (!strncmp(arg, "-B", 2)) { - if ((options->break_opt = - diff_scoreopt_parse(arg)) == -1) - return -1; - } - else if (!strncmp(arg, "-M", 2)) { - if ((options->rename_score = - diff_scoreopt_parse(arg)) == -1) - return -1; - options->detect_rename = DIFF_DETECT_RENAME; - } - else if (!strncmp(arg, "-C", 2)) { - if ((options->rename_score = - diff_scoreopt_parse(arg)) == -1) - return -1; - options->detect_rename = DIFF_DETECT_COPY; - } - else if (!strcmp(arg, "--find-copies-harder")) - options->find_copies_harder = 1; - else if (!strcmp(arg, "--abbrev")) - options->abbrev = DEFAULT_ABBREV; - else if (!strncmp(arg, "--abbrev=", 9)) { - options->abbrev = strtoul(arg + 9, NULL, 10); - if (options->abbrev < MINIMUM_ABBREV) - options->abbrev = MINIMUM_ABBREV; - else if (40 < options->abbrev) - options->abbrev = 40; - } - else + oldmode = old->ce_mode; + if (mode == oldmode && !memcmp(sha1, old->sha1, 20) && + !revs->diffopt.find_copies_harder) return 0; - return 1; -} - -static int parse_num(const char **cp_p) -{ - unsigned long num, scale; - int ch, dot; - const char *cp = *cp_p; - - num = 0; - scale = 1; - dot = 0; - for(;;) { - ch = *cp; - if ( !dot && ch == '.' ) { - scale = 1; - dot = 1; - } else if ( ch == '%' ) { - scale = dot ? scale*100 : 100; - cp++; /* % is always at the end */ - break; - } else if ( ch >= '0' && ch <= '9' ) { - if ( scale < 100000 ) { - scale *= 10; - num = (num*10) + (ch-'0'); - } - } else { - break; - } - cp++; - } - *cp_p = cp; - - /* user says num divided by scale and we say internally that - * is MAX_SCORE * num / scale. - */ - return (num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale); -} - -int diff_scoreopt_parse(const char *opt) -{ - int opt1, opt2, cmd; - - if (*opt++ != '-') - return -1; - cmd = *opt++; - if (cmd != 'M' && cmd != 'C' && cmd != 'B') - return -1; /* that is not a -M, -C nor -B option */ - - opt1 = parse_num(&opt); - if (cmd != 'B') - opt2 = 0; - else { - if (*opt == 0) - opt2 = 0; - else if (*opt != '/') - return -1; /* we expect -B80/99 or -B80 */ - else { - opt++; - opt2 = parse_num(&opt); - } - } - if (*opt != 0) - return -1; - return opt1 | (opt2 << 16); -} - -struct diff_queue_struct diff_queued_diff; - -void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp) -{ - if (queue->alloc <= queue->nr) { - queue->alloc = alloc_nr(queue->alloc); - queue->queue = xrealloc(queue->queue, - sizeof(dp) * queue->alloc); - } - queue->queue[queue->nr++] = dp; -} - -struct diff_filepair *diff_queue(struct diff_queue_struct *queue, - struct diff_filespec *one, - struct diff_filespec *two) -{ - struct diff_filepair *dp = xmalloc(sizeof(*dp)); - dp->one = one; - dp->two = two; - dp->score = 0; - dp->status = 0; - dp->source_stays = 0; - dp->broken_pair = 0; - if (queue) - diff_q(queue, dp); - return dp; -} - -void diff_free_filepair(struct diff_filepair *p) -{ - diff_free_filespec_data(p->one); - diff_free_filespec_data(p->two); - free(p->one); - free(p->two); - free(p); -} - -/* This is different from find_unique_abbrev() in that - * it stuffs the result with dots for alignment. - */ -const char *diff_unique_abbrev(const unsigned char *sha1, int len) -{ - int abblen; - const char *abbrev; - if (len == 40) - return sha1_to_hex(sha1); - - abbrev = find_unique_abbrev(sha1, len); - if (!abbrev) - return sha1_to_hex(sha1); - abblen = strlen(abbrev); - if (abblen < 37) { - static char hex[41]; - if (len < abblen && abblen <= len + 2) - sprintf(hex, "%s%.*s", abbrev, len+3-abblen, ".."); - else - sprintf(hex, "%s...", abbrev); - return hex; - } - return sha1_to_hex(sha1); -} - -static void diff_flush_raw(struct diff_filepair *p, - int line_termination, - int inter_name_termination, - struct diff_options *options, - int output_format) -{ - int two_paths; - char status[10]; - int abbrev = options->abbrev; - const char *path_one, *path_two; - - path_one = p->one->path; - path_two = p->two->path; - if (line_termination) { - path_one = quote_one(path_one); - path_two = quote_one(path_two); - } - - if (p->score) - sprintf(status, "%c%03d", p->status, - (int)(0.5 + p->score * 100.0/MAX_SCORE)); - else { - status[0] = p->status; - status[1] = 0; - } - switch (p->status) { - case DIFF_STATUS_COPIED: - case DIFF_STATUS_RENAMED: - two_paths = 1; - break; - case DIFF_STATUS_ADDED: - case DIFF_STATUS_DELETED: - two_paths = 0; - break; - default: - two_paths = 0; - break; - } - if (output_format != DIFF_FORMAT_NAME_STATUS) { - printf(":%06o %06o %s ", - p->one->mode, p->two->mode, - diff_unique_abbrev(p->one->sha1, abbrev)); - printf("%s ", - diff_unique_abbrev(p->two->sha1, abbrev)); - } - printf("%s%c%s", status, inter_name_termination, path_one); - if (two_paths) - printf("%c%s", inter_name_termination, path_two); - putchar(line_termination); - if (path_one != p->one->path) - free((void*)path_one); - if (path_two != p->two->path) - free((void*)path_two); -} - -static void diff_flush_name(struct diff_filepair *p, - int inter_name_termination, - int line_termination) -{ - char *path = p->two->path; - - if (line_termination) - path = quote_one(p->two->path); - else - path = p->two->path; - printf("%s%c", path, line_termination); - if (p->two->path != path) - free(path); -} - -int diff_unmodified_pair(struct diff_filepair *p) -{ - /* This function is written stricter than necessary to support - * the currently implemented transformers, but the idea is to - * let transformers to produce diff_filepairs any way they want, - * and filter and clean them up here before producing the output. - */ - struct diff_filespec *one, *two; - if (DIFF_PAIR_UNMERGED(p)) - return 0; /* unmerged is interesting */ + mode = ntohl(mode); + oldmode = ntohl(oldmode); - one = p->one; - two = p->two; - - /* deletion, addition, mode or type change - * and rename are all interesting. - */ - if (DIFF_FILE_VALID(one) != DIFF_FILE_VALID(two) || - DIFF_PAIR_MODE_CHANGED(p) || - strcmp(one->path, two->path)) - return 0; - - /* both are valid and point at the same path. that is, we are - * dealing with a change. - */ - if (one->sha1_valid && two->sha1_valid && - !memcmp(one->sha1, two->sha1, sizeof(one->sha1))) - return 1; /* no change */ - if (!one->sha1_valid && !two->sha1_valid) - return 1; /* both look at the same file on the filesystem. */ + diff_change(&revs->diffopt, oldmode, mode, + old->sha1, sha1, old->name, NULL); return 0; } -static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o) +static int diff_cache(struct rev_info *revs, + struct cache_entry **ac, int entries, + const char **pathspec, + int cached, int match_missing) { - if (diff_unmodified_pair(p)) - return; + while (entries) { + struct cache_entry *ce = *ac; + int same = (entries > 1) && ce_same_name(ce, ac[1]); - if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) || - (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode))) - return; /* no tree diffs in patch format */ + if (!ce_path_match(ce, pathspec)) + goto skip_entry; - run_diff(p, o); -} - -static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o, - struct diffstat_t *diffstat) -{ - if (diff_unmodified_pair(p)) - return; - - if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) || - (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode))) - return; /* no tree diffs in patch format */ - - run_diffstat(p, o, diffstat); -} - -int diff_queue_is_empty(void) -{ - struct diff_queue_struct *q = &diff_queued_diff; - int i; - for (i = 0; i < q->nr; i++) - if (!diff_unmodified_pair(q->queue[i])) - return 0; - return 1; -} - -#if DIFF_DEBUG -void diff_debug_filespec(struct diff_filespec *s, int x, const char *one) -{ - fprintf(stderr, "queue[%d] %s (%s) %s %06o %s\n", - x, one ? one : "", - s->path, - DIFF_FILE_VALID(s) ? "valid" : "invalid", - s->mode, - s->sha1_valid ? sha1_to_hex(s->sha1) : ""); - fprintf(stderr, "queue[%d] %s size %lu flags %d\n", - x, one ? one : "", - s->size, s->xfrm_flags); -} - -void diff_debug_filepair(const struct diff_filepair *p, int i) -{ - diff_debug_filespec(p->one, i, "one"); - diff_debug_filespec(p->two, i, "two"); - fprintf(stderr, "score %d, status %c stays %d broken %d\n", - p->score, p->status ? p->status : '?', - p->source_stays, p->broken_pair); -} - -void diff_debug_queue(const char *msg, struct diff_queue_struct *q) -{ - int i; - if (msg) - fprintf(stderr, "%s\n", msg); - fprintf(stderr, "q->nr = %d\n", q->nr); - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - diff_debug_filepair(p, i); - } -} -#endif - -static void diff_resolve_rename_copy(void) -{ - int i, j; - struct diff_filepair *p, *pp; - struct diff_queue_struct *q = &diff_queued_diff; - - diff_debug_queue("resolve-rename-copy", q); - - for (i = 0; i < q->nr; i++) { - p = q->queue[i]; - p->status = 0; /* undecided */ - if (DIFF_PAIR_UNMERGED(p)) - p->status = DIFF_STATUS_UNMERGED; - else if (!DIFF_FILE_VALID(p->one)) - p->status = DIFF_STATUS_ADDED; - else if (!DIFF_FILE_VALID(p->two)) - p->status = DIFF_STATUS_DELETED; - else if (DIFF_PAIR_TYPE_CHANGED(p)) - p->status = DIFF_STATUS_TYPE_CHANGED; - - /* from this point on, we are dealing with a pair - * whose both sides are valid and of the same type, i.e. - * either in-place edit or rename/copy edit. - */ - else if (DIFF_PAIR_RENAME(p)) { - if (p->source_stays) { - p->status = DIFF_STATUS_COPIED; - continue; + switch (ce_stage(ce)) { + case 0: + /* No stage 1 entry? That means it's a new file */ + if (!same) { + show_new_file(revs, ce, cached, match_missing); + break; } - /* See if there is some other filepair that - * copies from the same source as us. If so - * we are a copy. Otherwise we are either a - * copy if the path stays, or a rename if it - * does not, but we already handled "stays" case. + /* Show difference between old and new */ + show_modified(revs,ac[1], ce, 1, + cached, match_missing); + break; + case 1: + /* No stage 3 (merge) entry? + * That means it's been deleted. */ - for (j = i + 1; j < q->nr; j++) { - pp = q->queue[j]; - if (strcmp(pp->one->path, p->one->path)) - continue; /* not us */ - if (!DIFF_PAIR_RENAME(pp)) - continue; /* not a rename/copy */ - /* pp is a rename/copy from the same source */ - p->status = DIFF_STATUS_COPIED; + if (!same) { + diff_index_show_file(revs, "-", ce, + ce->sha1, ce->ce_mode); break; } - if (!p->status) - p->status = DIFF_STATUS_RENAMED; - } - else if (memcmp(p->one->sha1, p->two->sha1, 20) || - p->one->mode != p->two->mode) - p->status = DIFF_STATUS_MODIFIED; - else { - /* This is a "no-change" entry and should not - * happen anymore, but prepare for broken callers. + /* We come here with ce pointing at stage 1 + * (original tree) and ac[1] pointing at stage + * 3 (unmerged). show-modified with + * report-missing set to false does not say the + * file is deleted but reports true if work + * tree does not have it, in which case we + * fall through to report the unmerged state. + * Otherwise, we show the differences between + * the original tree and the work tree. */ - error("feeding unmodified %s to diffcore", - p->one->path); - p->status = DIFF_STATUS_UNKNOWN; - } - } - diff_debug_queue("resolve-rename-copy done", q); -} - -static void flush_one_pair(struct diff_filepair *p, - int diff_output_format, - struct diff_options *options, - struct diffstat_t *diffstat) -{ - int inter_name_termination = '\t'; - int line_termination = options->line_termination; - if (!line_termination) - inter_name_termination = 0; - - switch (p->status) { - case DIFF_STATUS_UNKNOWN: - break; - case 0: - die("internal error in diff-resolve-rename-copy"); - break; - default: - switch (diff_output_format) { - case DIFF_FORMAT_DIFFSTAT: - diff_flush_stat(p, options, diffstat); - break; - case DIFF_FORMAT_PATCH: - diff_flush_patch(p, options); - break; - case DIFF_FORMAT_RAW: - case DIFF_FORMAT_NAME_STATUS: - diff_flush_raw(p, line_termination, - inter_name_termination, - options, diff_output_format); - break; - case DIFF_FORMAT_NAME: - diff_flush_name(p, - inter_name_termination, - line_termination); - break; - case DIFF_FORMAT_NO_OUTPUT: + if (!cached && + !show_modified(revs, ce, ac[1], 0, + cached, match_missing)) + break; + /* fallthru */ + case 3: + diff_unmerge(&revs->diffopt, ce->name); break; - } - } -} -void diff_flush(struct diff_options *options) -{ - struct diff_queue_struct *q = &diff_queued_diff; - int i; - int diff_output_format = options->output_format; - struct diffstat_t *diffstat = NULL; - - if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) { - diffstat = xcalloc(sizeof (struct diffstat_t), 1); - diffstat->xm.consume = diffstat_consume; - } - - if (options->with_raw) { - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL); - } - putchar(options->line_termination); - } - if (options->with_stat) { - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options, - diffstat); + default: + die("impossible cache entry stage"); } - show_stats(diffstat); - free(diffstat); - diffstat = NULL; - putchar(options->line_termination); - } - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - flush_one_pair(p, diff_output_format, options, diffstat); - diff_free_filepair(p); - } - if (diffstat) { - show_stats(diffstat); - free(diffstat); +skip_entry: + /* + * Ignore all the different stages for this file, + * we've handled the relevant cases now. + */ + do { + ac++; + entries--; + } while (entries && ce_same_name(ce, ac[0])); } - - free(q->queue); - q->queue = NULL; - q->nr = q->alloc = 0; + return 0; } -static void diffcore_apply_filter(const char *filter) +/* + * This turns all merge entries into "stage 3". That guarantees that + * when we read in the new tree (into "stage 1"), we won't lose sight + * of the fact that we had unmerged entries. + */ +static void mark_merge_entries(void) { int i; - struct diff_queue_struct *q = &diff_queued_diff; - struct diff_queue_struct outq; - outq.queue = NULL; - outq.nr = outq.alloc = 0; - - if (!filter) - return; - - if (strchr(filter, DIFF_STATUS_FILTER_AON)) { - int found; - for (i = found = 0; !found && i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (((p->status == DIFF_STATUS_MODIFIED) && - ((p->score && - strchr(filter, DIFF_STATUS_FILTER_BROKEN)) || - (!p->score && - strchr(filter, DIFF_STATUS_MODIFIED)))) || - ((p->status != DIFF_STATUS_MODIFIED) && - strchr(filter, p->status))) - found++; - } - if (found) - return; - - /* otherwise we will clear the whole queue - * by copying the empty outq at the end of this - * function, but first clear the current entries - * in the queue. - */ - for (i = 0; i < q->nr; i++) - diff_free_filepair(q->queue[i]); - } - else { - /* Only the matching ones */ - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - - if (((p->status == DIFF_STATUS_MODIFIED) && - ((p->score && - strchr(filter, DIFF_STATUS_FILTER_BROKEN)) || - (!p->score && - strchr(filter, DIFF_STATUS_MODIFIED)))) || - ((p->status != DIFF_STATUS_MODIFIED) && - strchr(filter, p->status))) - diff_q(&outq, p); - else - diff_free_filepair(p); - } + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (!ce_stage(ce)) + continue; + ce->ce_flags |= htons(CE_STAGEMASK); } - free(q->queue); - *q = outq; -} - -void diffcore_std(struct diff_options *options) -{ - if (options->break_opt != -1) - diffcore_break(options->break_opt); - if (options->detect_rename) - diffcore_rename(options); - if (options->break_opt != -1) - diffcore_merge_broken(); - if (options->pickaxe) - diffcore_pickaxe(options->pickaxe, options->pickaxe_opts); - if (options->orderfile) - diffcore_order(options->orderfile); - diff_resolve_rename_copy(); - diffcore_apply_filter(options->filter); -} - - -void diffcore_std_no_resolve(struct diff_options *options) -{ - if (options->pickaxe) - diffcore_pickaxe(options->pickaxe, options->pickaxe_opts); - if (options->orderfile) - diffcore_order(options->orderfile); - diffcore_apply_filter(options->filter); } -void diff_addremove(struct diff_options *options, - int addremove, unsigned mode, - const unsigned char *sha1, - const char *base, const char *path) +int run_diff_index(struct rev_info *revs, int cached) { - char concatpath[PATH_MAX]; - struct diff_filespec *one, *two; + int ret; + struct object *ent; + struct tree *tree; + const char *tree_name; + int match_missing = 0; - /* This may look odd, but it is a preparation for - * feeding "there are unchanged files which should - * not produce diffs, but when you are doing copy - * detection you would need them, so here they are" - * entries to the diff-core. They will be prefixed - * with something like '=' or '*' (I haven't decided - * which but should not make any difference). - * Feeding the same new and old to diff_change() - * also has the same effect. - * Before the final output happens, they are pruned after - * merged into rename/copy pairs as appropriate. + /* + * Backward compatibility wart - "diff-index -m" does + * not mean "do not ignore merges", but totally different. */ - if (options->reverse_diff) - addremove = (addremove == '+' ? '-' : - addremove == '-' ? '+' : addremove); - - if (!path) path = ""; - sprintf(concatpath, "%s%s", base, path); - one = alloc_filespec(concatpath); - two = alloc_filespec(concatpath); - - if (addremove != '+') - fill_filespec(one, sha1, mode); - if (addremove != '-') - fill_filespec(two, sha1, mode); + if (!revs->ignore_merges) + match_missing = 1; - diff_queue(&diff_queued_diff, one, two); -} - -void diff_change(struct diff_options *options, - unsigned old_mode, unsigned new_mode, - const unsigned char *old_sha1, - const unsigned char *new_sha1, - const char *base, const char *path) -{ - char concatpath[PATH_MAX]; - struct diff_filespec *one, *two; - - if (options->reverse_diff) { - unsigned tmp; - const unsigned char *tmp_c; - tmp = old_mode; old_mode = new_mode; new_mode = tmp; - tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c; + if (read_cache() < 0) { + perror("read_cache"); + return -1; } - if (!path) path = ""; - sprintf(concatpath, "%s%s", base, path); - one = alloc_filespec(concatpath); - two = alloc_filespec(concatpath); - fill_filespec(one, old_sha1, old_mode); - fill_filespec(two, new_sha1, new_mode); - - diff_queue(&diff_queued_diff, one, two); -} - -void diff_unmerge(struct diff_options *options, - const char *path) -{ - struct diff_filespec *one, *two; - one = alloc_filespec(path); - two = alloc_filespec(path); - diff_queue(&diff_queued_diff, one, two); + mark_merge_entries(); + + ent = revs->pending_objects->item; + tree_name = revs->pending_objects->name; + tree = parse_tree_indirect(ent->sha1); + if (!tree) + return error("bad tree object %s", tree_name); + if (read_tree(tree, 1, revs->prune_data)) + return error("unable to read tree object %s", tree_name); + ret = diff_cache(revs, active_cache, active_nr, revs->prune_data, + cached, match_missing); + diffcore_std(&revs->diffopt); + diff_flush(&revs->diffopt); + return ret; } diff --git a/diff.c b/diff.c new file mode 100644 index 0000000000..6762fcee5a --- /dev/null +++ b/diff.c @@ -0,0 +1,1805 @@ +/* + * Copyright (C) 2005 Junio C Hamano + */ +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include "cache.h" +#include "quote.h" +#include "diff.h" +#include "diffcore.h" +#include "xdiff-interface.h" + +static int use_size_cache; + +int diff_rename_limit_default = -1; + +int git_diff_config(const char *var, const char *value) +{ + if (!strcmp(var, "diff.renamelimit")) { + diff_rename_limit_default = git_config_int(var, value); + return 0; + } + + return git_default_config(var, value); +} + +static char *quote_one(const char *str) +{ + int needlen; + char *xp; + + if (!str) + return NULL; + needlen = quote_c_style(str, NULL, NULL, 0); + if (!needlen) + return strdup(str); + xp = xmalloc(needlen + 1); + quote_c_style(str, xp, NULL, 0); + return xp; +} + +static char *quote_two(const char *one, const char *two) +{ + int need_one = quote_c_style(one, NULL, NULL, 1); + int need_two = quote_c_style(two, NULL, NULL, 1); + char *xp; + + if (need_one + need_two) { + if (!need_one) need_one = strlen(one); + if (!need_two) need_one = strlen(two); + + xp = xmalloc(need_one + need_two + 3); + xp[0] = '"'; + quote_c_style(one, xp + 1, NULL, 1); + quote_c_style(two, xp + need_one + 1, NULL, 1); + strcpy(xp + need_one + need_two + 1, "\""); + return xp; + } + need_one = strlen(one); + need_two = strlen(two); + xp = xmalloc(need_one + need_two + 1); + strcpy(xp, one); + strcpy(xp + need_one, two); + return xp; +} + +static const char *external_diff(void) +{ + static const char *external_diff_cmd = NULL; + static int done_preparing = 0; + + if (done_preparing) + return external_diff_cmd; + external_diff_cmd = getenv("GIT_EXTERNAL_DIFF"); + done_preparing = 1; + return external_diff_cmd; +} + +#define TEMPFILE_PATH_LEN 50 + +static struct diff_tempfile { + const char *name; /* filename external diff should read from */ + char hex[41]; + char mode[10]; + char tmp_path[TEMPFILE_PATH_LEN]; +} diff_temp[2]; + +static int count_lines(const char *data, int size) +{ + int count, ch, completely_empty = 1, nl_just_seen = 0; + count = 0; + while (0 < size--) { + ch = *data++; + if (ch == '\n') { + count++; + nl_just_seen = 1; + completely_empty = 0; + } + else { + nl_just_seen = 0; + completely_empty = 0; + } + } + if (completely_empty) + return 0; + if (!nl_just_seen) + count++; /* no trailing newline */ + return count; +} + +static void print_line_count(int count) +{ + switch (count) { + case 0: + printf("0,0"); + break; + case 1: + printf("1"); + break; + default: + printf("1,%d", count); + break; + } +} + +static void copy_file(int prefix, const char *data, int size) +{ + int ch, nl_just_seen = 1; + while (0 < size--) { + ch = *data++; + if (nl_just_seen) + putchar(prefix); + putchar(ch); + if (ch == '\n') + nl_just_seen = 1; + else + nl_just_seen = 0; + } + if (!nl_just_seen) + printf("\n\\ No newline at end of file\n"); +} + +static void emit_rewrite_diff(const char *name_a, + const char *name_b, + struct diff_filespec *one, + struct diff_filespec *two) +{ + int lc_a, lc_b; + diff_populate_filespec(one, 0); + diff_populate_filespec(two, 0); + lc_a = count_lines(one->data, one->size); + lc_b = count_lines(two->data, two->size); + printf("--- %s\n+++ %s\n@@ -", name_a, name_b); + print_line_count(lc_a); + printf(" +"); + print_line_count(lc_b); + printf(" @@\n"); + if (lc_a) + copy_file('-', one->data, one->size); + if (lc_b) + copy_file('+', two->data, two->size); +} + +static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) +{ + if (!DIFF_FILE_VALID(one)) { + mf->ptr = ""; /* does not matter */ + mf->size = 0; + return 0; + } + else if (diff_populate_filespec(one, 0)) + return -1; + mf->ptr = one->data; + mf->size = one->size; + return 0; +} + +struct emit_callback { + const char **label_path; +}; + +static int fn_out(void *priv, mmbuffer_t *mb, int nbuf) +{ + int i; + struct emit_callback *ecbdata = priv; + + if (ecbdata->label_path[0]) { + printf("--- %s\n", ecbdata->label_path[0]); + printf("+++ %s\n", ecbdata->label_path[1]); + ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; + } + for (i = 0; i < nbuf; i++) + if (!fwrite(mb[i].ptr, mb[i].size, 1, stdout)) + return -1; + return 0; +} + +static char *pprint_rename(const char *a, const char *b) +{ + const char *old = a; + const char *new = b; + char *name = NULL; + int pfx_length, sfx_length; + int len_a = strlen(a); + int len_b = strlen(b); + + /* Find common prefix */ + pfx_length = 0; + while (*old && *new && *old == *new) { + if (*old == '/') + pfx_length = old - a + 1; + old++; + new++; + } + + /* Find common suffix */ + old = a + len_a; + new = b + len_b; + sfx_length = 0; + while (a <= old && b <= new && *old == *new) { + if (*old == '/') + sfx_length = len_a - (old - a); + old--; + new--; + } + + /* + * pfx{mid-a => mid-b}sfx + * {pfx-a => pfx-b}sfx + * pfx{sfx-a => sfx-b} + * name-a => name-b + */ + if (pfx_length + sfx_length) { + name = xmalloc(len_a + len_b - pfx_length - sfx_length + 7); + sprintf(name, "%.*s{%.*s => %.*s}%s", + pfx_length, a, + len_a - pfx_length - sfx_length, a + pfx_length, + len_b - pfx_length - sfx_length, b + pfx_length, + a + len_a - sfx_length); + } + else { + name = xmalloc(len_a + len_b + 5); + sprintf(name, "%s => %s", a, b); + } + return name; +} + +struct diffstat_t { + struct xdiff_emit_state xm; + + int nr; + int alloc; + struct diffstat_file { + char *name; + unsigned is_unmerged:1; + unsigned is_binary:1; + unsigned is_renamed:1; + unsigned int added, deleted; + } **files; +}; + +static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, + const char *name_a, + const char *name_b) +{ + struct diffstat_file *x; + x = xcalloc(sizeof (*x), 1); + if (diffstat->nr == diffstat->alloc) { + diffstat->alloc = alloc_nr(diffstat->alloc); + diffstat->files = xrealloc(diffstat->files, + diffstat->alloc * sizeof(x)); + } + diffstat->files[diffstat->nr++] = x; + if (name_b) { + x->name = pprint_rename(name_a, name_b); + x->is_renamed = 1; + } + else + x->name = strdup(name_a); + return x; +} + +static void diffstat_consume(void *priv, char *line, unsigned long len) +{ + struct diffstat_t *diffstat = priv; + struct diffstat_file *x = diffstat->files[diffstat->nr - 1]; + + if (line[0] == '+') + x->added++; + else if (line[0] == '-') + x->deleted++; +} + +static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; +static const char minuses[]= "----------------------------------------------------------------------"; + +static void show_stats(struct diffstat_t* data) +{ + char *prefix = ""; + int i, len, add, del, total, adds = 0, dels = 0; + int max, max_change = 0, max_len = 0; + int total_files = data->nr; + + if (data->nr == 0) + return; + + for (i = 0; i < data->nr; i++) { + struct diffstat_file *file = data->files[i]; + + len = strlen(file->name); + if (max_len < len) + max_len = len; + + if (file->is_binary || file->is_unmerged) + continue; + if (max_change < file->added + file->deleted) + max_change = file->added + file->deleted; + } + + for (i = 0; i < data->nr; i++) { + char *name = data->files[i]->name; + int added = data->files[i]->added; + int deleted = data->files[i]->deleted; + + if (0 < (len = quote_c_style(name, NULL, NULL, 0))) { + char *qname = xmalloc(len + 1); + quote_c_style(name, qname, NULL, 0); + free(name); + data->files[i]->name = name = qname; + } + + /* + * "scale" the filename + */ + len = strlen(name); + max = max_len; + if (max > 50) + max = 50; + if (len > max) { + char *slash; + prefix = "..."; + max -= 3; + name += len - max; + slash = strchr(name, '/'); + if (slash) + name = slash; + } + len = max; + + /* + * scale the add/delete + */ + max = max_change; + if (max + len > 70) + max = 70 - len; + + if (data->files[i]->is_binary) { + printf(" %s%-*s | Bin\n", prefix, len, name); + goto free_diffstat_file; + } + else if (data->files[i]->is_unmerged) { + printf(" %s%-*s | Unmerged\n", prefix, len, name); + goto free_diffstat_file; + } + else if (!data->files[i]->is_renamed && + (added + deleted == 0)) { + total_files--; + goto free_diffstat_file; + } + + add = added; + del = deleted; + total = add + del; + adds += add; + dels += del; + + if (max_change > 0) { + total = (total * max + max_change / 2) / max_change; + add = (add * max + max_change / 2) / max_change; + del = total - add; + } + printf(" %s%-*s |%5d %.*s%.*s\n", prefix, + len, name, added + deleted, + add, pluses, del, minuses); + free_diffstat_file: + free(data->files[i]->name); + free(data->files[i]); + } + free(data->files); + printf(" %d files changed, %d insertions(+), %d deletions(-)\n", + total_files, adds, dels); +} + +#define FIRST_FEW_BYTES 8000 +static int mmfile_is_binary(mmfile_t *mf) +{ + long sz = mf->size; + if (FIRST_FEW_BYTES < sz) + sz = FIRST_FEW_BYTES; + if (memchr(mf->ptr, 0, sz)) + return 1; + return 0; +} + +static void builtin_diff(const char *name_a, + const char *name_b, + struct diff_filespec *one, + struct diff_filespec *two, + const char *xfrm_msg, + int complete_rewrite) +{ + mmfile_t mf1, mf2; + const char *lbl[2]; + char *a_one, *b_two; + + a_one = quote_two("a/", name_a); + b_two = quote_two("b/", name_b); + lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null"; + lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null"; + printf("diff --git %s %s\n", a_one, b_two); + if (lbl[0][0] == '/') { + /* /dev/null */ + printf("new file mode %06o\n", two->mode); + if (xfrm_msg && xfrm_msg[0]) + puts(xfrm_msg); + } + else if (lbl[1][0] == '/') { + printf("deleted file mode %06o\n", one->mode); + if (xfrm_msg && xfrm_msg[0]) + puts(xfrm_msg); + } + else { + if (one->mode != two->mode) { + printf("old mode %06o\n", one->mode); + printf("new mode %06o\n", two->mode); + } + if (xfrm_msg && xfrm_msg[0]) + puts(xfrm_msg); + /* + * we do not run diff between different kind + * of objects. + */ + if ((one->mode ^ two->mode) & S_IFMT) + goto free_ab_and_return; + if (complete_rewrite) { + emit_rewrite_diff(name_a, name_b, one, two); + goto free_ab_and_return; + } + } + + 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)) + printf("Binary files %s and %s differ\n", lbl[0], lbl[1]); + else { + /* Crazy xdl interfaces.. */ + const char *diffopts = getenv("GIT_DIFF_OPTS"); + xpparam_t xpp; + xdemitconf_t xecfg; + xdemitcb_t ecb; + struct emit_callback ecbdata; + + ecbdata.label_path = lbl; + xpp.flags = XDF_NEED_MINIMAL; + xecfg.ctxlen = 3; + xecfg.flags = XDL_EMIT_FUNCNAMES; + if (!diffopts) + ; + else if (!strncmp(diffopts, "--unified=", 10)) + xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10); + else if (!strncmp(diffopts, "-u", 2)) + xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10); + ecb.outf = fn_out; + ecb.priv = &ecbdata; + xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); + } + + free_ab_and_return: + free(a_one); + free(b_two); + return; +} + +static void builtin_diffstat(const char *name_a, const char *name_b, + struct diff_filespec *one, + struct diff_filespec *two, + struct diffstat_t *diffstat, + int complete_rewrite) +{ + mmfile_t mf1, mf2; + struct diffstat_file *data; + + data = diffstat_add(diffstat, name_a, name_b); + + if (!one || !two) { + data->is_unmerged = 1; + return; + } + if (complete_rewrite) { + diff_populate_filespec(one, 0); + diff_populate_filespec(two, 0); + data->deleted = count_lines(one->data, one->size); + data->added = count_lines(two->data, two->size); + return; + } + 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)) + data->is_binary = 1; + else { + /* Crazy xdl interfaces.. */ + xpparam_t xpp; + xdemitconf_t xecfg; + xdemitcb_t ecb; + + xpp.flags = XDF_NEED_MINIMAL; + xecfg.ctxlen = 0; + xecfg.flags = 0; + ecb.outf = xdiff_outf; + ecb.priv = diffstat; + xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); + } +} + +struct diff_filespec *alloc_filespec(const char *path) +{ + int namelen = strlen(path); + struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1); + + memset(spec, 0, sizeof(*spec)); + spec->path = (char *)(spec + 1); + memcpy(spec->path, path, namelen+1); + return spec; +} + +void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, + unsigned short mode) +{ + if (mode) { + spec->mode = canon_mode(mode); + memcpy(spec->sha1, sha1, 20); + spec->sha1_valid = !!memcmp(sha1, null_sha1, 20); + } +} + +/* + * Given a name and sha1 pair, if the dircache tells us the file in + * the work tree has that object contents, return true, so that + * prepare_temp_file() does not have to inflate and extract. + */ +static int work_tree_matches(const char *name, const unsigned char *sha1) +{ + struct cache_entry *ce; + struct stat st; + int pos, len; + + /* We do not read the cache ourselves here, because the + * benchmark with my previous version that always reads cache + * shows that it makes things worse for diff-tree comparing + * two linux-2.6 kernel trees in an already checked out work + * tree. This is because most diff-tree comparisons deal with + * only a small number of files, while reading the cache is + * expensive for a large project, and its cost outweighs the + * savings we get by not inflating the object to a temporary + * file. Practically, this code only helps when we are used + * by diff-cache --cached, which does read the cache before + * calling us. + */ + if (!active_cache) + return 0; + + len = strlen(name); + pos = cache_name_pos(name, len); + if (pos < 0) + return 0; + ce = active_cache[pos]; + if ((lstat(name, &st) < 0) || + !S_ISREG(st.st_mode) || /* careful! */ + ce_match_stat(ce, &st, 0) || + memcmp(sha1, ce->sha1, 20)) + return 0; + /* we return 1 only when we can stat, it is a regular file, + * stat information matches, and sha1 recorded in the cache + * matches. I.e. we know the file in the work tree really is + * the same as the <name, sha1> pair. + */ + return 1; +} + +static struct sha1_size_cache { + unsigned char sha1[20]; + unsigned long size; +} **sha1_size_cache; +static int sha1_size_cache_nr, sha1_size_cache_alloc; + +static struct sha1_size_cache *locate_size_cache(unsigned char *sha1, + int find_only, + unsigned long size) +{ + int first, last; + struct sha1_size_cache *e; + + first = 0; + last = sha1_size_cache_nr; + while (last > first) { + int cmp, next = (last + first) >> 1; + e = sha1_size_cache[next]; + cmp = memcmp(e->sha1, sha1, 20); + if (!cmp) + return e; + if (cmp < 0) { + last = next; + continue; + } + first = next+1; + } + /* not found */ + if (find_only) + return NULL; + /* insert to make it at "first" */ + if (sha1_size_cache_alloc <= sha1_size_cache_nr) { + sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc); + sha1_size_cache = xrealloc(sha1_size_cache, + sha1_size_cache_alloc * + sizeof(*sha1_size_cache)); + } + sha1_size_cache_nr++; + if (first < sha1_size_cache_nr) + memmove(sha1_size_cache + first + 1, sha1_size_cache + first, + (sha1_size_cache_nr - first - 1) * + sizeof(*sha1_size_cache)); + e = xmalloc(sizeof(struct sha1_size_cache)); + sha1_size_cache[first] = e; + memcpy(e->sha1, sha1, 20); + e->size = size; + return e; +} + +/* + * While doing rename detection and pickaxe operation, we may need to + * grab the data for the blob (or file) for our own in-core comparison. + * diff_filespec has data and size fields for this purpose. + */ +int diff_populate_filespec(struct diff_filespec *s, int size_only) +{ + int err = 0; + if (!DIFF_FILE_VALID(s)) + die("internal error: asking to populate invalid file."); + if (S_ISDIR(s->mode)) + return -1; + + if (!use_size_cache) + size_only = 0; + + if (s->data) + return err; + if (!s->sha1_valid || + work_tree_matches(s->path, s->sha1)) { + struct stat st; + int fd; + if (lstat(s->path, &st) < 0) { + if (errno == ENOENT) { + err_empty: + err = -1; + empty: + s->data = ""; + s->size = 0; + return err; + } + } + s->size = st.st_size; + if (!s->size) + goto empty; + if (size_only) + return 0; + if (S_ISLNK(st.st_mode)) { + int ret; + s->data = xmalloc(s->size); + s->should_free = 1; + ret = readlink(s->path, s->data, s->size); + if (ret < 0) { + free(s->data); + goto err_empty; + } + return 0; + } + fd = open(s->path, O_RDONLY); + if (fd < 0) + goto err_empty; + s->data = mmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + if (s->data == MAP_FAILED) + goto err_empty; + s->should_munmap = 1; + } + else { + char type[20]; + struct sha1_size_cache *e; + + if (size_only) { + e = locate_size_cache(s->sha1, 1, 0); + if (e) { + s->size = e->size; + return 0; + } + if (!sha1_object_info(s->sha1, type, &s->size)) + locate_size_cache(s->sha1, 0, s->size); + } + else { + s->data = read_sha1_file(s->sha1, type, &s->size); + s->should_free = 1; + } + } + return 0; +} + +void diff_free_filespec_data(struct diff_filespec *s) +{ + if (s->should_free) + free(s->data); + else if (s->should_munmap) + munmap(s->data, s->size); + s->should_free = s->should_munmap = 0; + s->data = NULL; + free(s->cnt_data); + s->cnt_data = NULL; +} + +static void prep_temp_blob(struct diff_tempfile *temp, + void *blob, + unsigned long size, + const unsigned char *sha1, + int mode) +{ + int fd; + + fd = git_mkstemp(temp->tmp_path, TEMPFILE_PATH_LEN, ".diff_XXXXXX"); + if (fd < 0) + die("unable to create temp-file"); + if (write(fd, blob, size) != size) + die("unable to write temp-file"); + close(fd); + temp->name = temp->tmp_path; + strcpy(temp->hex, sha1_to_hex(sha1)); + temp->hex[40] = 0; + sprintf(temp->mode, "%06o", mode); +} + +static void prepare_temp_file(const char *name, + struct diff_tempfile *temp, + struct diff_filespec *one) +{ + if (!DIFF_FILE_VALID(one)) { + not_a_valid_file: + /* A '-' entry produces this for file-2, and + * a '+' entry produces this for file-1. + */ + temp->name = "/dev/null"; + strcpy(temp->hex, "."); + strcpy(temp->mode, "."); + return; + } + + if (!one->sha1_valid || + work_tree_matches(name, one->sha1)) { + struct stat st; + if (lstat(name, &st) < 0) { + if (errno == ENOENT) + goto not_a_valid_file; + die("stat(%s): %s", name, strerror(errno)); + } + if (S_ISLNK(st.st_mode)) { + int ret; + char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */ + if (sizeof(buf) <= st.st_size) + die("symlink too long: %s", name); + ret = readlink(name, buf, st.st_size); + if (ret < 0) + die("readlink(%s)", name); + prep_temp_blob(temp, buf, st.st_size, + (one->sha1_valid ? + one->sha1 : null_sha1), + (one->sha1_valid ? + one->mode : S_IFLNK)); + } + else { + /* we can borrow from the file in the work tree */ + temp->name = name; + if (!one->sha1_valid) + strcpy(temp->hex, sha1_to_hex(null_sha1)); + else + strcpy(temp->hex, sha1_to_hex(one->sha1)); + /* Even though we may sometimes borrow the + * contents from the work tree, we always want + * one->mode. mode is trustworthy even when + * !(one->sha1_valid), as long as + * DIFF_FILE_VALID(one). + */ + sprintf(temp->mode, "%06o", one->mode); + } + return; + } + else { + if (diff_populate_filespec(one, 0)) + die("cannot read data blob for %s", one->path); + prep_temp_blob(temp, one->data, one->size, + one->sha1, one->mode); + } +} + +static void remove_tempfile(void) +{ + int i; + + for (i = 0; i < 2; i++) + if (diff_temp[i].name == diff_temp[i].tmp_path) { + unlink(diff_temp[i].name); + diff_temp[i].name = NULL; + } +} + +static void remove_tempfile_on_signal(int signo) +{ + remove_tempfile(); + signal(SIGINT, SIG_DFL); + raise(signo); +} + +static int spawn_prog(const char *pgm, const char **arg) +{ + pid_t pid; + int status; + + fflush(NULL); + pid = fork(); + if (pid < 0) + die("unable to fork"); + if (!pid) { + execvp(pgm, (char *const*) arg); + exit(255); + } + + while (waitpid(pid, &status, 0) < 0) { + if (errno == EINTR) + continue; + return -1; + } + + /* Earlier we did not check the exit status because + * diff exits non-zero if files are different, and + * we are not interested in knowing that. It was a + * mistake which made it harder to quit a diff-* + * session that uses the git-apply-patch-script as + * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF + * should also exit non-zero only when it wants to + * abort the entire diff-* session. + */ + if (WIFEXITED(status) && !WEXITSTATUS(status)) + return 0; + return -1; +} + +/* An external diff command takes: + * + * diff-cmd name infile1 infile1-sha1 infile1-mode \ + * infile2 infile2-sha1 infile2-mode [ rename-to ] + * + */ +static void run_external_diff(const char *pgm, + const char *name, + const char *other, + struct diff_filespec *one, + struct diff_filespec *two, + const char *xfrm_msg, + int complete_rewrite) +{ + const char *spawn_arg[10]; + struct diff_tempfile *temp = diff_temp; + int retval; + static int atexit_asked = 0; + const char *othername; + const char **arg = &spawn_arg[0]; + + othername = (other? other : name); + if (one && two) { + prepare_temp_file(name, &temp[0], one); + prepare_temp_file(othername, &temp[1], two); + if (! atexit_asked && + (temp[0].name == temp[0].tmp_path || + temp[1].name == temp[1].tmp_path)) { + atexit_asked = 1; + atexit(remove_tempfile); + } + signal(SIGINT, remove_tempfile_on_signal); + } + + if (one && two) { + *arg++ = pgm; + *arg++ = name; + *arg++ = temp[0].name; + *arg++ = temp[0].hex; + *arg++ = temp[0].mode; + *arg++ = temp[1].name; + *arg++ = temp[1].hex; + *arg++ = temp[1].mode; + if (other) { + *arg++ = other; + *arg++ = xfrm_msg; + } + } else { + *arg++ = pgm; + *arg++ = name; + } + *arg = NULL; + retval = spawn_prog(pgm, spawn_arg); + remove_tempfile(); + if (retval) { + fprintf(stderr, "external diff died, stopping at %s.\n", name); + exit(1); + } +} + +static void run_diff_cmd(const char *pgm, + const char *name, + const char *other, + struct diff_filespec *one, + struct diff_filespec *two, + const char *xfrm_msg, + int complete_rewrite) +{ + if (pgm) { + run_external_diff(pgm, name, other, one, two, xfrm_msg, + complete_rewrite); + return; + } + if (one && two) + builtin_diff(name, other ? other : name, + one, two, xfrm_msg, complete_rewrite); + else + printf("* Unmerged path %s\n", name); +} + +static void diff_fill_sha1_info(struct diff_filespec *one) +{ + if (DIFF_FILE_VALID(one)) { + if (!one->sha1_valid) { + struct stat st; + if (lstat(one->path, &st) < 0) + die("stat %s", one->path); + if (index_path(one->sha1, one->path, &st, 0)) + die("cannot hash %s\n", one->path); + } + } + else + memset(one->sha1, 0, 20); +} + +static void run_diff(struct diff_filepair *p, struct diff_options *o) +{ + const char *pgm = external_diff(); + char msg[PATH_MAX*2+300], *xfrm_msg; + struct diff_filespec *one; + struct diff_filespec *two; + const char *name; + const char *other; + char *name_munged, *other_munged; + int complete_rewrite = 0; + int len; + + if (DIFF_PAIR_UNMERGED(p)) { + /* unmerged */ + run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, 0); + return; + } + + name = p->one->path; + other = (strcmp(name, p->two->path) ? p->two->path : NULL); + name_munged = quote_one(name); + other_munged = quote_one(other); + one = p->one; two = p->two; + + diff_fill_sha1_info(one); + diff_fill_sha1_info(two); + + len = 0; + switch (p->status) { + case DIFF_STATUS_COPIED: + len += snprintf(msg + len, sizeof(msg) - len, + "similarity index %d%%\n" + "copy from %s\n" + "copy to %s\n", + (int)(0.5 + p->score * 100.0/MAX_SCORE), + name_munged, other_munged); + break; + case DIFF_STATUS_RENAMED: + len += snprintf(msg + len, sizeof(msg) - len, + "similarity index %d%%\n" + "rename from %s\n" + "rename to %s\n", + (int)(0.5 + p->score * 100.0/MAX_SCORE), + name_munged, other_munged); + break; + case DIFF_STATUS_MODIFIED: + if (p->score) { + len += snprintf(msg + len, sizeof(msg) - len, + "dissimilarity index %d%%\n", + (int)(0.5 + p->score * + 100.0/MAX_SCORE)); + complete_rewrite = 1; + break; + } + /* fallthru */ + default: + /* nothing */ + ; + } + + if (memcmp(one->sha1, two->sha1, 20)) { + char one_sha1[41]; + int abbrev = o->full_index ? 40 : DEFAULT_ABBREV; + memcpy(one_sha1, sha1_to_hex(one->sha1), 41); + + len += snprintf(msg + len, sizeof(msg) - len, + "index %.*s..%.*s", + abbrev, one_sha1, abbrev, + sha1_to_hex(two->sha1)); + if (one->mode == two->mode) + len += snprintf(msg + len, sizeof(msg) - len, + " %06o", one->mode); + len += snprintf(msg + len, sizeof(msg) - len, "\n"); + } + + if (len) + msg[--len] = 0; + xfrm_msg = len ? msg : NULL; + + if (!pgm && + DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) && + (S_IFMT & one->mode) != (S_IFMT & two->mode)) { + /* a filepair that changes between file and symlink + * needs to be split into deletion and creation. + */ + struct diff_filespec *null = alloc_filespec(two->path); + run_diff_cmd(NULL, name, other, one, null, xfrm_msg, 0); + free(null); + null = alloc_filespec(one->path); + run_diff_cmd(NULL, name, other, null, two, xfrm_msg, 0); + free(null); + } + else + run_diff_cmd(pgm, name, other, one, two, xfrm_msg, + complete_rewrite); + + free(name_munged); + free(other_munged); +} + +static void run_diffstat(struct diff_filepair *p, struct diff_options *o, + struct diffstat_t *diffstat) +{ + const char *name; + const char *other; + int complete_rewrite = 0; + + if (DIFF_PAIR_UNMERGED(p)) { + /* unmerged */ + builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, 0); + return; + } + + name = p->one->path; + other = (strcmp(name, p->two->path) ? p->two->path : NULL); + + diff_fill_sha1_info(p->one); + diff_fill_sha1_info(p->two); + + if (p->status == DIFF_STATUS_MODIFIED && p->score) + complete_rewrite = 1; + builtin_diffstat(name, other, p->one, p->two, diffstat, complete_rewrite); +} + +void diff_setup(struct diff_options *options) +{ + memset(options, 0, sizeof(*options)); + options->output_format = DIFF_FORMAT_RAW; + options->line_termination = '\n'; + options->break_opt = -1; + options->rename_limit = -1; + + options->change = diff_change; + options->add_remove = diff_addremove; +} + +int diff_setup_done(struct diff_options *options) +{ + if ((options->find_copies_harder && + options->detect_rename != DIFF_DETECT_COPY) || + (0 <= options->rename_limit && !options->detect_rename)) + return -1; + + /* + * These cases always need recursive; we do not drop caller-supplied + * recursive bits for other formats here. + */ + if ((options->output_format == DIFF_FORMAT_PATCH) || + (options->output_format == DIFF_FORMAT_DIFFSTAT)) + options->recursive = 1; + + if (options->detect_rename && options->rename_limit < 0) + options->rename_limit = diff_rename_limit_default; + if (options->setup & DIFF_SETUP_USE_CACHE) { + if (!active_cache) + /* read-cache does not die even when it fails + * so it is safe for us to do this here. Also + * it does not smudge active_cache or active_nr + * when it fails, so we do not have to worry about + * cleaning it up ourselves either. + */ + read_cache(); + } + if (options->setup & DIFF_SETUP_USE_SIZE_CACHE) + use_size_cache = 1; + if (options->abbrev <= 0 || 40 < options->abbrev) + options->abbrev = 40; /* full */ + + return 0; +} + +int diff_opt_parse(struct diff_options *options, const char **av, int ac) +{ + const char *arg = av[0]; + if (!strcmp(arg, "-p") || !strcmp(arg, "-u")) + options->output_format = DIFF_FORMAT_PATCH; + else if (!strcmp(arg, "--patch-with-raw")) { + options->output_format = DIFF_FORMAT_PATCH; + options->with_raw = 1; + } + else if (!strcmp(arg, "--stat")) + options->output_format = DIFF_FORMAT_DIFFSTAT; + else if (!strcmp(arg, "--patch-with-stat")) { + options->output_format = DIFF_FORMAT_PATCH; + options->with_stat = 1; + } + else if (!strcmp(arg, "-z")) + options->line_termination = 0; + else if (!strncmp(arg, "-l", 2)) + options->rename_limit = strtoul(arg+2, NULL, 10); + else if (!strcmp(arg, "--full-index")) + options->full_index = 1; + else if (!strcmp(arg, "--name-only")) + options->output_format = DIFF_FORMAT_NAME; + else if (!strcmp(arg, "--name-status")) + options->output_format = DIFF_FORMAT_NAME_STATUS; + else if (!strcmp(arg, "-R")) + options->reverse_diff = 1; + else if (!strncmp(arg, "-S", 2)) + options->pickaxe = arg + 2; + else if (!strcmp(arg, "-s")) + options->output_format = DIFF_FORMAT_NO_OUTPUT; + else if (!strncmp(arg, "-O", 2)) + options->orderfile = arg + 2; + else if (!strncmp(arg, "--diff-filter=", 14)) + options->filter = arg + 14; + else if (!strcmp(arg, "--pickaxe-all")) + options->pickaxe_opts = DIFF_PICKAXE_ALL; + else if (!strcmp(arg, "--pickaxe-regex")) + options->pickaxe_opts = DIFF_PICKAXE_REGEX; + else if (!strncmp(arg, "-B", 2)) { + if ((options->break_opt = + diff_scoreopt_parse(arg)) == -1) + return -1; + } + else if (!strncmp(arg, "-M", 2)) { + if ((options->rename_score = + diff_scoreopt_parse(arg)) == -1) + return -1; + options->detect_rename = DIFF_DETECT_RENAME; + } + else if (!strncmp(arg, "-C", 2)) { + if ((options->rename_score = + diff_scoreopt_parse(arg)) == -1) + return -1; + options->detect_rename = DIFF_DETECT_COPY; + } + else if (!strcmp(arg, "--find-copies-harder")) + options->find_copies_harder = 1; + else if (!strcmp(arg, "--abbrev")) + options->abbrev = DEFAULT_ABBREV; + else if (!strncmp(arg, "--abbrev=", 9)) { + options->abbrev = strtoul(arg + 9, NULL, 10); + if (options->abbrev < MINIMUM_ABBREV) + options->abbrev = MINIMUM_ABBREV; + else if (40 < options->abbrev) + options->abbrev = 40; + } + else + return 0; + return 1; +} + +static int parse_num(const char **cp_p) +{ + unsigned long num, scale; + int ch, dot; + const char *cp = *cp_p; + + num = 0; + scale = 1; + dot = 0; + for(;;) { + ch = *cp; + if ( !dot && ch == '.' ) { + scale = 1; + dot = 1; + } else if ( ch == '%' ) { + scale = dot ? scale*100 : 100; + cp++; /* % is always at the end */ + break; + } else if ( ch >= '0' && ch <= '9' ) { + if ( scale < 100000 ) { + scale *= 10; + num = (num*10) + (ch-'0'); + } + } else { + break; + } + cp++; + } + *cp_p = cp; + + /* user says num divided by scale and we say internally that + * is MAX_SCORE * num / scale. + */ + return (num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale); +} + +int diff_scoreopt_parse(const char *opt) +{ + int opt1, opt2, cmd; + + if (*opt++ != '-') + return -1; + cmd = *opt++; + if (cmd != 'M' && cmd != 'C' && cmd != 'B') + return -1; /* that is not a -M, -C nor -B option */ + + opt1 = parse_num(&opt); + if (cmd != 'B') + opt2 = 0; + else { + if (*opt == 0) + opt2 = 0; + else if (*opt != '/') + return -1; /* we expect -B80/99 or -B80 */ + else { + opt++; + opt2 = parse_num(&opt); + } + } + if (*opt != 0) + return -1; + return opt1 | (opt2 << 16); +} + +struct diff_queue_struct diff_queued_diff; + +void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp) +{ + if (queue->alloc <= queue->nr) { + queue->alloc = alloc_nr(queue->alloc); + queue->queue = xrealloc(queue->queue, + sizeof(dp) * queue->alloc); + } + queue->queue[queue->nr++] = dp; +} + +struct diff_filepair *diff_queue(struct diff_queue_struct *queue, + struct diff_filespec *one, + struct diff_filespec *two) +{ + struct diff_filepair *dp = xmalloc(sizeof(*dp)); + dp->one = one; + dp->two = two; + dp->score = 0; + dp->status = 0; + dp->source_stays = 0; + dp->broken_pair = 0; + if (queue) + diff_q(queue, dp); + return dp; +} + +void diff_free_filepair(struct diff_filepair *p) +{ + diff_free_filespec_data(p->one); + diff_free_filespec_data(p->two); + free(p->one); + free(p->two); + free(p); +} + +/* This is different from find_unique_abbrev() in that + * it stuffs the result with dots for alignment. + */ +const char *diff_unique_abbrev(const unsigned char *sha1, int len) +{ + int abblen; + const char *abbrev; + if (len == 40) + return sha1_to_hex(sha1); + + abbrev = find_unique_abbrev(sha1, len); + if (!abbrev) + return sha1_to_hex(sha1); + abblen = strlen(abbrev); + if (abblen < 37) { + static char hex[41]; + if (len < abblen && abblen <= len + 2) + sprintf(hex, "%s%.*s", abbrev, len+3-abblen, ".."); + else + sprintf(hex, "%s...", abbrev); + return hex; + } + return sha1_to_hex(sha1); +} + +static void diff_flush_raw(struct diff_filepair *p, + int line_termination, + int inter_name_termination, + struct diff_options *options, + int output_format) +{ + int two_paths; + char status[10]; + int abbrev = options->abbrev; + const char *path_one, *path_two; + + path_one = p->one->path; + path_two = p->two->path; + if (line_termination) { + path_one = quote_one(path_one); + path_two = quote_one(path_two); + } + + if (p->score) + sprintf(status, "%c%03d", p->status, + (int)(0.5 + p->score * 100.0/MAX_SCORE)); + else { + status[0] = p->status; + status[1] = 0; + } + switch (p->status) { + case DIFF_STATUS_COPIED: + case DIFF_STATUS_RENAMED: + two_paths = 1; + break; + case DIFF_STATUS_ADDED: + case DIFF_STATUS_DELETED: + two_paths = 0; + break; + default: + two_paths = 0; + break; + } + if (output_format != DIFF_FORMAT_NAME_STATUS) { + printf(":%06o %06o %s ", + p->one->mode, p->two->mode, + diff_unique_abbrev(p->one->sha1, abbrev)); + printf("%s ", + diff_unique_abbrev(p->two->sha1, abbrev)); + } + printf("%s%c%s", status, inter_name_termination, path_one); + if (two_paths) + printf("%c%s", inter_name_termination, path_two); + putchar(line_termination); + if (path_one != p->one->path) + free((void*)path_one); + if (path_two != p->two->path) + free((void*)path_two); +} + +static void diff_flush_name(struct diff_filepair *p, + int inter_name_termination, + int line_termination) +{ + char *path = p->two->path; + + if (line_termination) + path = quote_one(p->two->path); + else + path = p->two->path; + printf("%s%c", path, line_termination); + if (p->two->path != path) + free(path); +} + +int diff_unmodified_pair(struct diff_filepair *p) +{ + /* This function is written stricter than necessary to support + * the currently implemented transformers, but the idea is to + * let transformers to produce diff_filepairs any way they want, + * and filter and clean them up here before producing the output. + */ + struct diff_filespec *one, *two; + + if (DIFF_PAIR_UNMERGED(p)) + return 0; /* unmerged is interesting */ + + one = p->one; + two = p->two; + + /* deletion, addition, mode or type change + * and rename are all interesting. + */ + if (DIFF_FILE_VALID(one) != DIFF_FILE_VALID(two) || + DIFF_PAIR_MODE_CHANGED(p) || + strcmp(one->path, two->path)) + return 0; + + /* both are valid and point at the same path. that is, we are + * dealing with a change. + */ + if (one->sha1_valid && two->sha1_valid && + !memcmp(one->sha1, two->sha1, sizeof(one->sha1))) + return 1; /* no change */ + if (!one->sha1_valid && !two->sha1_valid) + return 1; /* both look at the same file on the filesystem. */ + return 0; +} + +static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o) +{ + if (diff_unmodified_pair(p)) + return; + + if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) || + (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode))) + return; /* no tree diffs in patch format */ + + run_diff(p, o); +} + +static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o, + struct diffstat_t *diffstat) +{ + if (diff_unmodified_pair(p)) + return; + + if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) || + (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode))) + return; /* no tree diffs in patch format */ + + run_diffstat(p, o, diffstat); +} + +int diff_queue_is_empty(void) +{ + struct diff_queue_struct *q = &diff_queued_diff; + int i; + for (i = 0; i < q->nr; i++) + if (!diff_unmodified_pair(q->queue[i])) + return 0; + return 1; +} + +#if DIFF_DEBUG +void diff_debug_filespec(struct diff_filespec *s, int x, const char *one) +{ + fprintf(stderr, "queue[%d] %s (%s) %s %06o %s\n", + x, one ? one : "", + s->path, + DIFF_FILE_VALID(s) ? "valid" : "invalid", + s->mode, + s->sha1_valid ? sha1_to_hex(s->sha1) : ""); + fprintf(stderr, "queue[%d] %s size %lu flags %d\n", + x, one ? one : "", + s->size, s->xfrm_flags); +} + +void diff_debug_filepair(const struct diff_filepair *p, int i) +{ + diff_debug_filespec(p->one, i, "one"); + diff_debug_filespec(p->two, i, "two"); + fprintf(stderr, "score %d, status %c stays %d broken %d\n", + p->score, p->status ? p->status : '?', + p->source_stays, p->broken_pair); +} + +void diff_debug_queue(const char *msg, struct diff_queue_struct *q) +{ + int i; + if (msg) + fprintf(stderr, "%s\n", msg); + fprintf(stderr, "q->nr = %d\n", q->nr); + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + diff_debug_filepair(p, i); + } +} +#endif + +static void diff_resolve_rename_copy(void) +{ + int i, j; + struct diff_filepair *p, *pp; + struct diff_queue_struct *q = &diff_queued_diff; + + diff_debug_queue("resolve-rename-copy", q); + + for (i = 0; i < q->nr; i++) { + p = q->queue[i]; + p->status = 0; /* undecided */ + if (DIFF_PAIR_UNMERGED(p)) + p->status = DIFF_STATUS_UNMERGED; + else if (!DIFF_FILE_VALID(p->one)) + p->status = DIFF_STATUS_ADDED; + else if (!DIFF_FILE_VALID(p->two)) + p->status = DIFF_STATUS_DELETED; + else if (DIFF_PAIR_TYPE_CHANGED(p)) + p->status = DIFF_STATUS_TYPE_CHANGED; + + /* from this point on, we are dealing with a pair + * whose both sides are valid and of the same type, i.e. + * either in-place edit or rename/copy edit. + */ + else if (DIFF_PAIR_RENAME(p)) { + if (p->source_stays) { + p->status = DIFF_STATUS_COPIED; + continue; + } + /* See if there is some other filepair that + * copies from the same source as us. If so + * we are a copy. Otherwise we are either a + * copy if the path stays, or a rename if it + * does not, but we already handled "stays" case. + */ + for (j = i + 1; j < q->nr; j++) { + pp = q->queue[j]; + if (strcmp(pp->one->path, p->one->path)) + continue; /* not us */ + if (!DIFF_PAIR_RENAME(pp)) + continue; /* not a rename/copy */ + /* pp is a rename/copy from the same source */ + p->status = DIFF_STATUS_COPIED; + break; + } + if (!p->status) + p->status = DIFF_STATUS_RENAMED; + } + else if (memcmp(p->one->sha1, p->two->sha1, 20) || + p->one->mode != p->two->mode) + p->status = DIFF_STATUS_MODIFIED; + else { + /* This is a "no-change" entry and should not + * happen anymore, but prepare for broken callers. + */ + error("feeding unmodified %s to diffcore", + p->one->path); + p->status = DIFF_STATUS_UNKNOWN; + } + } + diff_debug_queue("resolve-rename-copy done", q); +} + +static void flush_one_pair(struct diff_filepair *p, + int diff_output_format, + struct diff_options *options, + struct diffstat_t *diffstat) +{ + int inter_name_termination = '\t'; + int line_termination = options->line_termination; + if (!line_termination) + inter_name_termination = 0; + + switch (p->status) { + case DIFF_STATUS_UNKNOWN: + break; + case 0: + die("internal error in diff-resolve-rename-copy"); + break; + default: + switch (diff_output_format) { + case DIFF_FORMAT_DIFFSTAT: + diff_flush_stat(p, options, diffstat); + break; + case DIFF_FORMAT_PATCH: + diff_flush_patch(p, options); + break; + case DIFF_FORMAT_RAW: + case DIFF_FORMAT_NAME_STATUS: + diff_flush_raw(p, line_termination, + inter_name_termination, + options, diff_output_format); + break; + case DIFF_FORMAT_NAME: + diff_flush_name(p, + inter_name_termination, + line_termination); + break; + case DIFF_FORMAT_NO_OUTPUT: + break; + } + } +} + +void diff_flush(struct diff_options *options) +{ + struct diff_queue_struct *q = &diff_queued_diff; + int i; + int diff_output_format = options->output_format; + struct diffstat_t *diffstat = NULL; + + if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) { + diffstat = xcalloc(sizeof (struct diffstat_t), 1); + diffstat->xm.consume = diffstat_consume; + } + + if (options->with_raw) { + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL); + } + putchar(options->line_termination); + } + if (options->with_stat) { + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options, + diffstat); + } + show_stats(diffstat); + free(diffstat); + diffstat = NULL; + putchar(options->line_termination); + } + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + flush_one_pair(p, diff_output_format, options, diffstat); + diff_free_filepair(p); + } + + if (diffstat) { + show_stats(diffstat); + free(diffstat); + } + + free(q->queue); + q->queue = NULL; + q->nr = q->alloc = 0; +} + +static void diffcore_apply_filter(const char *filter) +{ + int i; + struct diff_queue_struct *q = &diff_queued_diff; + struct diff_queue_struct outq; + outq.queue = NULL; + outq.nr = outq.alloc = 0; + + if (!filter) + return; + + if (strchr(filter, DIFF_STATUS_FILTER_AON)) { + int found; + for (i = found = 0; !found && i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (((p->status == DIFF_STATUS_MODIFIED) && + ((p->score && + strchr(filter, DIFF_STATUS_FILTER_BROKEN)) || + (!p->score && + strchr(filter, DIFF_STATUS_MODIFIED)))) || + ((p->status != DIFF_STATUS_MODIFIED) && + strchr(filter, p->status))) + found++; + } + if (found) + return; + + /* otherwise we will clear the whole queue + * by copying the empty outq at the end of this + * function, but first clear the current entries + * in the queue. + */ + for (i = 0; i < q->nr; i++) + diff_free_filepair(q->queue[i]); + } + else { + /* Only the matching ones */ + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + + if (((p->status == DIFF_STATUS_MODIFIED) && + ((p->score && + strchr(filter, DIFF_STATUS_FILTER_BROKEN)) || + (!p->score && + strchr(filter, DIFF_STATUS_MODIFIED)))) || + ((p->status != DIFF_STATUS_MODIFIED) && + strchr(filter, p->status))) + diff_q(&outq, p); + else + diff_free_filepair(p); + } + } + free(q->queue); + *q = outq; +} + +void diffcore_std(struct diff_options *options) +{ + if (options->break_opt != -1) + diffcore_break(options->break_opt); + if (options->detect_rename) + diffcore_rename(options); + if (options->break_opt != -1) + diffcore_merge_broken(); + if (options->pickaxe) + diffcore_pickaxe(options->pickaxe, options->pickaxe_opts); + if (options->orderfile) + diffcore_order(options->orderfile); + diff_resolve_rename_copy(); + diffcore_apply_filter(options->filter); +} + + +void diffcore_std_no_resolve(struct diff_options *options) +{ + if (options->pickaxe) + diffcore_pickaxe(options->pickaxe, options->pickaxe_opts); + if (options->orderfile) + diffcore_order(options->orderfile); + diffcore_apply_filter(options->filter); +} + +void diff_addremove(struct diff_options *options, + int addremove, unsigned mode, + const unsigned char *sha1, + const char *base, const char *path) +{ + char concatpath[PATH_MAX]; + struct diff_filespec *one, *two; + + /* This may look odd, but it is a preparation for + * feeding "there are unchanged files which should + * not produce diffs, but when you are doing copy + * detection you would need them, so here they are" + * entries to the diff-core. They will be prefixed + * with something like '=' or '*' (I haven't decided + * which but should not make any difference). + * Feeding the same new and old to diff_change() + * also has the same effect. + * Before the final output happens, they are pruned after + * merged into rename/copy pairs as appropriate. + */ + if (options->reverse_diff) + addremove = (addremove == '+' ? '-' : + addremove == '-' ? '+' : addremove); + + if (!path) path = ""; + sprintf(concatpath, "%s%s", base, path); + one = alloc_filespec(concatpath); + two = alloc_filespec(concatpath); + + if (addremove != '+') + fill_filespec(one, sha1, mode); + if (addremove != '-') + fill_filespec(two, sha1, mode); + + diff_queue(&diff_queued_diff, one, two); +} + +void diff_change(struct diff_options *options, + unsigned old_mode, unsigned new_mode, + const unsigned char *old_sha1, + const unsigned char *new_sha1, + const char *base, const char *path) +{ + char concatpath[PATH_MAX]; + struct diff_filespec *one, *two; + + if (options->reverse_diff) { + unsigned tmp; + const unsigned char *tmp_c; + tmp = old_mode; old_mode = new_mode; new_mode = tmp; + tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c; + } + if (!path) path = ""; + sprintf(concatpath, "%s%s", base, path); + one = alloc_filespec(concatpath); + two = alloc_filespec(concatpath); + fill_filespec(one, old_sha1, old_mode); + fill_filespec(two, new_sha1, new_mode); + + diff_queue(&diff_queued_diff, one, two); +} + +void diff_unmerge(struct diff_options *options, + const char *path) +{ + struct diff_filespec *one, *two; + one = alloc_filespec(path); + two = alloc_filespec(path); + diff_queue(&diff_queued_diff, one, two); +} @@ -28,10 +28,11 @@ struct diff_options { with_raw:1, with_stat:1, tree_in_recursive:1, - full_index:1; + full_index:1, + silent_on_remove:1, + find_copies_harder:1; int break_opt; int detect_rename; - int find_copies_harder; int line_termination; int output_format; int pickaxe_opts; @@ -168,4 +169,8 @@ extern void diff_flush(struct diff_options*); extern const char *diff_unique_abbrev(const unsigned char *, int); +extern int run_diff_files(struct rev_info *revs, int silent_on_removed); + +extern int run_diff_index(struct rev_info *revs, int cached); + #endif /* DIFF_H */ diff --git a/dump-cache-tree.c b/dump-cache-tree.c new file mode 100644 index 0000000000..fbea263dd9 --- /dev/null +++ b/dump-cache-tree.c @@ -0,0 +1,65 @@ +#include "cache.h" +#include "tree.h" +#include "cache-tree.h" + + +static void dump_one(struct cache_tree *it, const char *pfx, const char *x) +{ + if (it->entry_count < 0) + printf("%-40s %s%s (%d subtrees)\n", + "invalid", x, pfx, it->subtree_nr); + else + printf("%s %s%s (%d entries, %d subtrees)\n", + sha1_to_hex(it->sha1), x, pfx, + it->entry_count, it->subtree_nr); +} + +static int dump_cache_tree(struct cache_tree *it, + struct cache_tree *ref, + const char *pfx) +{ + int i; + int errs = 0; + + if (!it) + return; + if (!ref) + die("internal error"); + + if (it->entry_count < 0) { + dump_one(it, pfx, ""); + dump_one(ref, pfx, "#(ref) "); + if (it->subtree_nr != ref->subtree_nr) + errs = 1; + } + else { + dump_one(it, pfx, ""); + if (memcmp(it->sha1, ref->sha1, 20) || + ref->entry_count != it->entry_count || + ref->subtree_nr != it->subtree_nr) { + dump_one(ref, pfx, "#(ref) "); + errs = 1; + } + } + + for (i = 0; i < it->subtree_nr; i++) { + char path[PATH_MAX]; + struct cache_tree_sub *down = it->down[i]; + struct cache_tree_sub *rdwn; + + rdwn = cache_tree_sub(ref, down->name); + sprintf(path, "%s%.*s/", pfx, down->namelen, down->name); + if (dump_cache_tree(down->cache_tree, rdwn->cache_tree, path)) + errs = 1; + } + return errs; +} + +int main(int ac, char **av) +{ + struct cache_tree *another = cache_tree(); + if (read_cache() < 0) + die("unable to read index file"); + cache_tree_update(another, active_cache, active_nr, 0, 1); + return dump_cache_tree(active_cache_tree, another, ""); +} diff --git a/fsck-objects.c b/fsck-objects.c index 59b25904cb..cc09143a92 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -8,6 +8,7 @@ #include "tag.h" #include "refs.h" #include "pack.h" +#include "cache-tree.h" #define REACHABLE 0x0001 @@ -438,6 +439,21 @@ static int fsck_head_link(void) return 0; } +static int fsck_cache_tree(struct cache_tree *it) +{ + int i; + int err = 0; + + if (0 <= it->entry_count) { + struct object *obj = parse_object(it->sha1); + if (obj->type != tree_type) + err |= objerror(obj, "non-tree in cache-tree"); + } + for (i = 0; i < it->subtree_nr; i++) + err |= fsck_cache_tree(it->down[i]->cache_tree); + return err; +} + int main(int argc, char **argv) { int i, heads; @@ -547,6 +563,8 @@ int main(int argc, char **argv) obj->used = 1; mark_reachable(obj, REACHABLE); } + if (active_cache_tree) + fsck_cache_tree(active_cache_tree); } check_connectivity(); @@ -376,6 +376,13 @@ do echo "No changes - did you forget update-index?" stop_here $this fi + unmerged=$(git-ls-files -u) + if test -n "$unmerged" + then + echo "You still have unmerged paths in your index" + echo "did you forget update-index?" + stop_here $this + fi apply_status=0 ;; esac diff --git a/git-annotate.perl b/git-annotate.perl index 9df72a1662..a6a7a482cd 100755 --- a/git-annotate.perl +++ b/git-annotate.perl @@ -10,9 +10,10 @@ use warnings; use strict; use Getopt::Long; use POSIX qw(strftime gmtime); +use File::Basename qw(basename dirname); sub usage() { - print STDERR 'Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ] + print STDERR "Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ] -l, --long Show long rev (Defaults off) -t, --time @@ -23,7 +24,7 @@ sub usage() { Use revs from revs-file instead of calling git-rev-list -h, --help This message. -'; +"; exit(1); } @@ -35,7 +36,7 @@ my $rc = GetOptions( "long|l" => \$longrev, "help|h" => \$help, "rename|r" => \$rename, "rev-file|S=s" => \$rev_file); -if (!$rc or $help) { +if (!$rc or $help or !@ARGV) { usage(); } @@ -208,6 +209,9 @@ sub find_parent_renames { while (my $change = <$patch>) { chomp $change; my $filename = <$patch>; + if (!defined $filename) { + next; + } chomp $filename; if ($change =~ m/^[AMD]$/ ) { diff --git a/git-branch.sh b/git-branch.sh index 663a3a370c..ebcc8989d8 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -1,6 +1,6 @@ #!/bin/sh -USAGE='[(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]]' +USAGE='[(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r' LONG_USAGE='If no arguments, show available branches and mark current branch with a star. If one argument, create a new branch <branchname> based off of current HEAD. If two arguments, create a new branch <branchname> based off of <start-point>.' diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 7d3f78e375..11d153c4cd 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -88,7 +88,7 @@ my $TEMP_DIR = tempdir( CLEANUP => 1 ); $log->debug("Temporary directory is '$TEMP_DIR'"); # if we are called with a pserver argument, -# deal with the authentication cat before entereing the +# deal with the authentication cat before entering the # main loop if (@ARGV && $ARGV[0] eq 'pserver') { my $line = <STDIN>; chomp $line; @@ -117,7 +117,7 @@ while (<STDIN>) { chomp; - # Check to see if we've seen this method, and call appropiate function. + # Check to see if we've seen this method, and call appropriate function. if ( /^([\w-]+)(?:\s+(.*))?$/ and defined($methods->{$1}) ) { # use the $methods hash to call the appropriate sub for this command @@ -171,11 +171,11 @@ sub req_Root return 0; } - my @gitvars = `git-var -l`; + my @gitvars = `git-repo-config -l`; if ($?) { - print "E problems executing git-var on the server -- this is not a git repository or the PATH is not set correcly.\n"; + print "E problems executing git-repo-config on the server -- this is not a git repository or the PATH is not set correctly.\n"; print "E \n"; - print "error 1 - problem executing git-var\n"; + print "error 1 - problem executing git-repo-config\n"; return 0; } foreach my $line ( @gitvars ) @@ -224,7 +224,7 @@ sub req_Globaloption sub req_Validresponses { my ( $cmd, $data ) = @_; - $log->debug("req_Validrepsonses : $data"); + $log->debug("req_Validresponses : $data"); # TODO : re-enable this, currently it's not particularly useful #$state->{validresponses} = [ split /\s+/, $data ]; @@ -733,7 +733,7 @@ sub req_update argsplit("update"); # - # It may just be a client exploring the available heads/modukles + # It may just be a client exploring the available heads/modules # in that case, list them as top level directories and leave it # at that. Eclipse uses this technique to offer you a list of # projects (heads in this case) to checkout. @@ -1731,7 +1731,7 @@ sub transmitfile } # This method takes a file name, and returns ( $dirpart, $filepart ) which -# refers to the directory porition and the file portion of the filename +# refers to the directory portion and the file portion of the filename # respectively sub filenamesplit { @@ -1790,7 +1790,7 @@ Log::Log4perl =head2 new Creates a new log object, optionally you can specify a filename here to -indicate the file to log to. If no log file is specified, you can specifiy one +indicate the file to log to. If no log file is specified, you can specify one later with method setfile, or indicate you no longer want logging with method nofile. @@ -2595,7 +2595,7 @@ sub in_array =head2 safe_pipe_capture -an alterative to `command` that allows input to be passed as an array +an alternative to `command` that allows input to be passed as an array to work around shell problems with weird characters in arguments =cut diff --git a/git-fetch.sh b/git-fetch.sh index 83143f82cf..280f62e4b7 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -270,14 +270,22 @@ fetch_main () { if [ -n "$GIT_SSL_NO_VERIFY" ]; then curl_extra_args="-k" fi - remote_name_quoted=$(perl -e ' + max_depth=5 + depth=0 + head="ref: $remote_name" + while (expr "z$head" : "zref:" && expr $depth \< $max_depth) >/dev/null + do + remote_name_quoted=$(perl -e ' my $u = $ARGV[0]; + $u =~ s/^ref:\s*//; $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg; print "$u"; - ' "$remote_name") - head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted") && + ' "$head") + head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted") + depth=$( expr \( $depth + 1 \) ) + done expr "z$head" : "z$_x40\$" >/dev/null || - die "Failed to fetch $remote_name from $remote" + die "Failed to fetch $remote_name from $remote" echo >&2 Fetching "$remote_name from $remote" using http git-http-fetch -v -a "$head" "$remote/" || exit ;; diff --git a/git-rebase.sh b/git-rebase.sh index 86dfe9cb96..9e259028e0 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -4,37 +4,51 @@ # USAGE='[--onto <newbase>] <upstream> [<branch>]' -LONG_USAGE='git-rebase applies to <upstream> (or optionally to <newbase>) commits -from <branch> that do not appear in <upstream>. When <branch> is not -specified it defaults to the current branch (HEAD). - -When git-rebase is complete, <branch> will be updated to point to the -newly created line of commit objects, so the previous line will not be -accessible unless there are other references to it already. - -Assuming the following history: - - A---B---C topic - / - D---E---F---G master - -The result of the following command: - - git-rebase --onto master~1 master topic - - would be: - - A'\''--B'\''--C'\'' topic - / - D---E---F---G master +LONG_USAGE='git-rebase replaces <branch> with a new branch of the +same name. When the --onto option is provided the new branch starts +out with a HEAD equal to <newbase>, otherwise it is equal to <upstream> +It then attempts to create a new commit for each commit from the original +<branch> that does not exist in the <upstream> branch. + +It is possible that a merge failure will prevent this process from being +completely automatic. You will have to resolve any such merge failure +and run git-rebase --continue. If you can not resolve the merge failure, +running git-rebase --abort will restore the original <branch> and remove +the working files found in the .dotest directory. + +Note that if <branch> is not specified on the command line, the +currently checked out branch is used. You must be in the top +directory of your project to start (or continue) a rebase. + +Example: git-rebase master~1 topic + + A---B---C topic A'\''--B'\''--C'\'' topic + / --> / + D---E---F---G master D---E---F---G master ' - . git-sh-setup unset newbase while case "$#" in 0) break ;; esac do case "$1" in + --continue) + diff=$(git-diff-files) + case "$diff" in + ?*) echo "You must edit all merge conflicts and then" + echo "mark them as resolved using git update-index" + exit 1 + ;; + esac + git am --resolved --3way + exit + ;; + --abort) + [ -d .dotest ] || die "No rebase in progress?" + git reset --hard ORIG_HEAD + rm -r .dotest + exit + ;; --onto) test 2 -le "$#" || usage newbase="$2" @@ -107,7 +121,7 @@ onto=$(git-rev-parse --verify "${onto_name}^0") || exit # Check if we are already based on $onto, but this should be # done only when upstream and onto are the same. -if test "$upstream" = "onto" +if test "$upstream" = "$onto" then mb=$(git-merge-base "$onto" "$branch") if test "$mb" = "$onto" @@ -8,7 +8,6 @@ #include <errno.h> #include <limits.h> #include <stdarg.h> -#include <sys/ioctl.h> #include "git-compat-util.h" #include "exec_cmd.h" @@ -47,6 +46,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "log", cmd_log }, { "whatchanged", cmd_whatchanged }, { "show", cmd_show }, + { "fmt-patch", cmd_format_patch }, + { "count-objects", cmd_count_objects }, }; int i; diff --git a/log-tree.c b/log-tree.c index 9634c4677f..aaf2b9423f 100644 --- a/log-tree.c +++ b/log-tree.c @@ -37,12 +37,20 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep) /* * Print header line of header.. */ - printf("%s%s", - opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ", - diff_unique_abbrev(commit->object.sha1, abbrev_commit)); - if (parent) - printf(" (from %s)", diff_unique_abbrev(parent->object.sha1, abbrev_commit)); - putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n'); + + if (opt->commit_format == CMIT_FMT_EMAIL) + printf("From %s Thu Apr 7 15:13:13 2005\n", + sha1_to_hex(commit->object.sha1)); + else { + printf("%s%s", + opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ", + diff_unique_abbrev(commit->object.sha1, abbrev_commit)); + if (parent) + printf(" (from %s)", + diff_unique_abbrev(parent->object.sha1, + abbrev_commit)); + putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n'); + } /* * And then the pretty-printed message itself @@ -152,15 +160,18 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log int log_tree_commit(struct rev_info *opt, struct commit *commit) { struct log_info log; + int shown; log.commit = commit; log.parent = NULL; opt->loginfo = &log; - if (!log_tree_diff(opt, commit, &log) && opt->loginfo && opt->always_show_header) { + shown = log_tree_diff(opt, commit, &log); + if (!shown && opt->loginfo && opt->always_show_header) { log.parent = NULL; show_log(opt, opt->loginfo, ""); + shown = 1; } opt->loginfo = NULL; - return 0; + return shown; } diff --git a/pack-check.c b/pack-check.c index 84ed90d369..e57587909e 100644 --- a/pack-check.c +++ b/pack-check.c @@ -29,12 +29,12 @@ static int verify_packfile(struct packed_git *p) pack_base = p->pack_base; SHA1_Update(&ctx, pack_base, pack_size - 20); SHA1_Final(sha1, &ctx); - if (memcmp(sha1, index_base + index_size - 40, 20)) - return error("Packfile %s SHA1 mismatch with idx", - p->pack_name); if (memcmp(sha1, pack_base + pack_size - 20, 20)) return error("Packfile %s SHA1 mismatch with itself", p->pack_name); + if (memcmp(sha1, index_base + index_size - 40, 20)) + return error("Packfile %s SHA1 mismatch with idx", + p->pack_name); /* Make sure everything reachable from idx is valid. Since we * have verified that nr_objects matches between idx and pack, diff --git a/read-cache.c b/read-cache.c index f97f92d90a..1f71d12578 100644 --- a/read-cache.c +++ b/read-cache.c @@ -4,11 +4,26 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "cache-tree.h" + +/* Index extensions. + * + * The first letter should be 'A'..'Z' for extensions that are not + * necessary for a correct operation (i.e. optimization data). + * When new extensions are added that _needs_ to be understood in + * order to correctly interpret the index file, pick character that + * is outside the range, to cause the reader to abort. + */ + +#define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) ) +#define CACHE_EXT_TREE 0x54524545 /* "TREE" */ struct cache_entry **active_cache = NULL; static time_t index_file_timestamp; unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0; +struct cache_tree *active_cache_tree = NULL; + /* * This only updates the "non-critical" parts of the directory * cache, ie the parts that aren't tracked by GIT, and only used @@ -513,6 +528,22 @@ static int verify_hdr(struct cache_header *hdr, unsigned long size) return 0; } +static int read_index_extension(const char *ext, void *data, unsigned long sz) +{ + switch (CACHE_EXT(ext)) { + case CACHE_EXT_TREE: + active_cache_tree = cache_tree_read(data, sz); + break; + default: + if (*ext < 'A' || 'Z' < *ext) + return error("index uses %.4s extension, which we do not understand", + ext); + fprintf(stderr, "ignoring %.4s extension\n", ext); + break; + } + return 0; +} + int read_cache(void) { int fd, i; @@ -561,6 +592,22 @@ int read_cache(void) active_cache[i] = ce; } index_file_timestamp = st.st_mtime; + while (offset <= size - 20 - 8) { + /* After an array of active_nr index entries, + * there can be arbitrary number of extended + * sections, each of which is prefixed with + * extension name (4-byte) and section length + * in 4-byte network byte order. + */ + unsigned long extsize; + memcpy(&extsize, map + offset + 4, 4); + extsize = ntohl(extsize); + if (read_index_extension(map + offset, + map + offset + 8, extsize) < 0) + goto unmap; + offset += 8; + offset += extsize; + } return active_nr; unmap: @@ -595,6 +642,17 @@ static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len) return 0; } +static int write_index_ext_header(SHA_CTX *context, int fd, + unsigned long ext, unsigned long sz) +{ + ext = htonl(ext); + sz = htonl(sz); + if ((ce_write(context, fd, &ext, 4) < 0) || + (ce_write(context, fd, &sz, 4) < 0)) + return -1; + return 0; +} + static int ce_flush(SHA_CTX *context, int fd) { unsigned int left = write_buffer_len; @@ -691,5 +749,19 @@ int write_cache(int newfd, struct cache_entry **cache, int entries) if (ce_write(&c, newfd, ce, ce_size(ce)) < 0) return -1; } + + /* Write extension data here */ + if (active_cache_tree) { + unsigned long sz; + void *data = cache_tree_write(active_cache_tree, &sz); + if (data && + !write_index_ext_header(&c, newfd, CACHE_EXT_TREE, sz) && + !ce_write(&c, newfd, data, sz)) + ; + else { + free(data); + return -1; + } + } return ce_flush(&c, newfd); } diff --git a/read-tree.c b/read-tree.c index 26f4f7e323..66c0120f13 100644 --- a/read-tree.c +++ b/read-tree.c @@ -9,6 +9,7 @@ #include "object.h" #include "tree.h" +#include "cache-tree.h" #include <sys/time.h> #include <signal.h> @@ -421,6 +422,12 @@ static void verify_uptodate(struct cache_entry *ce) die("Entry '%s' not uptodate. Cannot merge.", ce->name); } +static void invalidate_ce_path(struct cache_entry *ce) +{ + if (ce) + cache_tree_invalidate_path(active_cache_tree, ce->name); +} + static int merged_entry(struct cache_entry *merge, struct cache_entry *old) { merge->ce_flags |= htons(CE_UPDATE); @@ -436,6 +443,7 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old) *merge = *old; } else { verify_uptodate(old); + invalidate_ce_path(old); } } merge->ce_flags &= ~htons(CE_STAGEMASK); @@ -449,6 +457,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old) verify_uptodate(old); ce->ce_mode = 0; add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); + invalidate_ce_path(ce); return 1; } @@ -683,8 +692,10 @@ static int oneway_merge(struct cache_entry **src) return error("Cannot do a oneway merge of %d trees", merge_size); - if (!a) + if (!a) { + invalidate_ce_path(old); return 0; + } if (old && same(old, a)) { return keep_entry(old); } @@ -703,6 +714,7 @@ static int read_cache_unmerged(void) struct cache_entry *ce = active_cache[i]; if (ce_stage(ce)) { deleted++; + invalidate_ce_path(ce); continue; } if (deleted) @@ -713,6 +725,39 @@ static int read_cache_unmerged(void) return deleted; } +static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree) +{ + struct tree_entry_list *ent; + int cnt; + + memcpy(it->sha1, tree->object.sha1, 20); + for (cnt = 0, ent = tree->entries; ent; ent = ent->next) { + if (!ent->directory) + cnt++; + else { + struct cache_tree_sub *sub; + struct tree *subtree = (struct tree *)ent->item.tree; + if (!subtree->object.parsed) + parse_tree(subtree); + sub = cache_tree_sub(it, ent->name); + sub->cache_tree = cache_tree(); + prime_cache_tree_rec(sub->cache_tree, subtree); + cnt += sub->cache_tree->entry_count; + } + } + it->entry_count = cnt; +} + +static void prime_cache_tree(void) +{ + struct tree *tree = (struct tree *)trees->item; + if (!tree) + return; + active_cache_tree = cache_tree(); + prime_cache_tree_rec(active_cache_tree, tree); + +} + static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])"; static struct cache_file cache_file; @@ -814,10 +859,9 @@ int main(int argc, char **argv) fn = twoway_merge; break; case 3: - fn = threeway_merge; - break; default: fn = threeway_merge; + cache_tree_free(&active_cache_tree); break; } @@ -828,6 +872,18 @@ int main(int argc, char **argv) } unpack_trees(fn); + + /* + * When reading only one tree (either the most basic form, + * "-m ent" or "--reset ent" form), we can obtain a fully + * valid cache-tree because the index must match exactly + * what came from the tree. + */ + if (trees->item && (!merge || (stage == 2))) { + cache_tree_free(&active_cache_tree); + prime_cache_tree(); + } + if (write_cache(newfd, active_cache, active_nr) || commit_index_file(&cache_file)) die("unable to write new index file"); diff --git a/repo-config.c b/repo-config.c index c5ebb7668a..fa8aba7a1b 100644 --- a/repo-config.c +++ b/repo-config.c @@ -2,7 +2,7 @@ #include <regex.h> static const char git_config_set_usage[] = -"git-repo-config [ --bool | --int ] [--get | --get-all | --replace-all | --unset | --unset-all] name [value [value_regex]]"; +"git-repo-config [ --bool | --int ] [--get | --get-all | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list"; static char* key = NULL; static char* value = NULL; @@ -12,6 +12,15 @@ static int do_not_match = 0; static int seen = 0; static enum { T_RAW, T_INT, T_BOOL } type = T_RAW; +static int show_all_config(const char *key_, const char *value_) +{ + if (value_) + printf("%s=%s\n", key_, value_); + else + printf("%s\n", key_); + return 0; +} + static int show_config(const char* key_, const char* value_) { if (value_ == NULL) @@ -67,7 +76,7 @@ static int get_value(const char* key_, const char* regex_) } } - i = git_config(show_config); + git_config(show_config); if (value) { printf("%s\n", value); free(value); @@ -99,6 +108,9 @@ int main(int argc, const char **argv) argv++; } + if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l")) + return git_config(show_all_config); + switch (argc) { case 2: return get_value(argv[1], NULL); diff --git a/rev-parse.c b/rev-parse.c index 7f66ae2db8..62e16af33c 100644 --- a/rev-parse.c +++ b/rev-parse.c @@ -160,14 +160,6 @@ static int show_file(const char *arg) return 0; } -static void die_badfile(const char *arg) -{ - if (errno != ENOENT) - die("'%s': %s", arg, strerror(errno)); - die("'%s' is ambiguous - revision name or file/directory name?\n" - "Please put '--' before the list of filenames.", arg); -} - int main(int argc, char **argv) { int i, as_is = 0, verify = 0; @@ -177,14 +169,12 @@ int main(int argc, char **argv) git_config(git_default_config); for (i = 1; i < argc; i++) { - struct stat st; char *arg = argv[i]; char *dotdot; if (as_is) { if (show_file(arg) && as_is < 2) - if (lstat(arg, &st) < 0) - die_badfile(arg); + verify_filename(prefix, arg); continue; } if (!strcmp(arg,"-n")) { @@ -350,8 +340,7 @@ int main(int argc, char **argv) continue; if (verify) die("Needed a single revision"); - if (lstat(arg, &st) < 0) - die_badfile(arg); + verify_filename(prefix, arg); } show_default(); if (verify && revs_count != 1) diff --git a/revision.c b/revision.c index f9c7d15f56..b6ed0149a5 100644 --- a/revision.c +++ b/revision.c @@ -740,6 +740,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch include = get_reference(revs, next, sha1, flags); if (!exclude || !include) die("Invalid revision range %s..%s", arg, next); + + if (!seen_dashdash) { + *dotdot = '.'; + verify_non_filename(revs->prefix, arg); + } add_pending_object(revs, exclude, this); add_pending_object(revs, include, next); continue; @@ -752,20 +757,25 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch arg++; } if (get_sha1(arg, sha1) < 0) { - struct stat st; int j; if (seen_dashdash || local_flags) die("bad revision '%s'", arg); - /* If we didn't have a "--", all filenames must exist */ - for (j = i; j < argc; j++) { - if (lstat(argv[j], &st) < 0) - die("'%s': %s", argv[j], strerror(errno)); - } + /* If we didn't have a "--": + * (1) all filenames must exist; + * (2) all rev-args must not be interpretable + * as a valid filename. + * but the latter we have checked in the main loop. + */ + for (j = i; j < argc; j++) + verify_filename(revs->prefix, argv[j]); + revs->prune_data = get_pathspec(revs->prefix, argv + i); break; } + if (!seen_dashdash) + verify_non_filename(revs->prefix, arg); object = get_reference(revs, arg, sha1, flags ^ local_flags); add_pending_object(revs, object, arg); } @@ -62,6 +62,49 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) return path; } +/* + * Verify a filename that we got as an argument for a pathspec + * entry. Note that a filename that begins with "-" never verifies + * as true, because even if such a filename were to exist, we want + * it to be preceded by the "--" marker (or we want the user to + * use a format like "./-filename") + */ +void verify_filename(const char *prefix, const char *arg) +{ + const char *name; + struct stat st; + + if (*arg == '-') + die("bad flag '%s' used after filename", arg); + name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg; + if (!lstat(name, &st)) + return; + if (errno == ENOENT) + die("ambiguous argument '%s': unknown revision or path not in the working tree.\n" + "Use '--' to separate paths from revisions", arg); + die("'%s': %s", arg, strerror(errno)); +} + +/* + * Opposite of the above: the command line did not have -- marker + * and we parsed the arg as a refname. It should not be interpretable + * as a filename. + */ +void verify_non_filename(const char *prefix, const char *arg) +{ + const char *name; + struct stat st; + + if (*arg == '-') + return; /* flag */ + name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg; + if (!lstat(name, &st)) + die("ambiguous argument '%s': both revision and filename\n" + "Use '--' to separate filenames from revisions", arg); + if (errno != ENOENT) + die("'%s': %s", arg, strerror(errno)); +} + const char **get_pathspec(const char *prefix, const char **pathspec) { const char *entry = *pathspec; diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 6729a18266..cf33989b56 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -174,6 +174,27 @@ test_expect_success \ 'git-ls-tree -r output for a known tree.' \ 'diff current expected' +# But with -r -t we can have both. +test_expect_success \ + 'showing tree with git-ls-tree -r -t' \ + 'git-ls-tree -r -t $tree >current' +cat >expected <<\EOF +100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 +120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym +040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe path2 +100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2 +120000 blob d8ce161addc5173867a3c3c730924388daedbc38 path2/file2sym +040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3 +100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376 path3/file3 +120000 blob 8599103969b43aff7e430efea79ca4636466794f path3/file3sym +040000 tree 3c5e5399f3a333eddecce7a9b9465b63f65f51e2 path3/subp3 +100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f path3/subp3/file3 +120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym +EOF +test_expect_success \ + 'git-ls-tree -r output for a known tree.' \ + 'diff current expected' + ################################################################ rm .git/index test_expect_success \ @@ -205,4 +226,32 @@ test_expect_success \ 'no diff after checkout and git-update-index --refresh.' \ 'git-diff-files >current && cmp -s current /dev/null' +################################################################ +P=087704a96baf1c2d1c869a8b084481e121c88b5b +test_expect_success \ + 'git-commit-tree records the correct tree in a commit.' \ + 'commit0=$(echo NO | git-commit-tree $P) && + tree=$(git show --pretty=raw $commit0 | + sed -n -e "s/^tree //p" -e "/^author /q") && + test "z$tree" = "z$P"' + +test_expect_success \ + 'git-commit-tree records the correct parent in a commit.' \ + 'commit1=$(echo NO | git-commit-tree $P -p $commit0) && + parent=$(git show --pretty=raw $commit1 | + sed -n -e "s/^parent //p" -e "/^author /q") && + test "z$commit0" = "z$parent"' + +test_expect_success \ + 'git-commit-tree omits duplicated parent in a commit.' \ + 'commit2=$(echo NO | git-commit-tree $P -p $commit0 -p $commit0) && + parent=$(git show --pretty=raw $commit2 | + sed -n -e "s/^parent //p" -e "/^author /q" | + sort -u) && + test "z$commit0" = "z$parent" && + numparent=$(git show --pretty=raw $commit2 | + sed -n -e "s/^parent //p" -e "/^author /q" | + wc -l) && + test $numparent = 1' + test_done diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index d0ed24275e..75e4c9a886 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -37,7 +37,7 @@ compare_change () { } check_cache_at () { - clean_if_empty=`git-diff-files "$1"` + clean_if_empty=`git-diff-files -- "$1"` case "$clean_if_empty" in '') echo "$1: clean" ;; ?*) echo "$1: dirty" ;; diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh index 861ef4c0c6..4d175d8ea1 100755 --- a/t/t1002-read-tree-m-u-2way.sh +++ b/t/t1002-read-tree-m-u-2way.sh @@ -20,7 +20,7 @@ compare_change () { } check_cache_at () { - clean_if_empty=`git-diff-files "$1"` + clean_if_empty=`git-diff-files -- "$1"` case "$clean_if_empty" in '') echo "$1: clean" ;; ?*) echo "$1: dirty" ;; diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh index 8db329d7ff..9e1544df9d 100755 --- a/t/t4010-diff-pathspec.sh +++ b/t/t4010-diff-pathspec.sh @@ -28,7 +28,7 @@ cat >expected <<\EOF EOF test_expect_success \ 'limit to path should show nothing' \ - 'git-diff-index --cached $tree path >current && + 'git-diff-index --cached $tree -- path >current && compare_diff_raw current expected' cat >expected <<\EOF @@ -36,7 +36,7 @@ cat >expected <<\EOF EOF test_expect_success \ 'limit to path1 should show path1/file1' \ - 'git-diff-index --cached $tree path1 >current && + 'git-diff-index --cached $tree -- path1 >current && compare_diff_raw current expected' cat >expected <<\EOF @@ -44,7 +44,7 @@ cat >expected <<\EOF EOF test_expect_success \ 'limit to path1/ should show path1/file1' \ - 'git-diff-index --cached $tree path1/ >current && + 'git-diff-index --cached $tree -- path1/ >current && compare_diff_raw current expected' cat >expected <<\EOF @@ -52,14 +52,14 @@ cat >expected <<\EOF EOF test_expect_success \ 'limit to file0 should show file0' \ - 'git-diff-index --cached $tree file0 >current && + 'git-diff-index --cached $tree -- file0 >current && compare_diff_raw current expected' cat >expected <<\EOF EOF test_expect_success \ 'limit to file0/ should emit nothing.' \ - 'git-diff-index --cached $tree file0/ >current && + 'git-diff-index --cached $tree -- file0/ >current && compare_diff_raw current expected' test_done diff --git a/update-index.c b/update-index.c index facec8d915..1c1f13bd70 100644 --- a/update-index.c +++ b/update-index.c @@ -6,6 +6,7 @@ #include "cache.h" #include "strbuf.h" #include "quote.h" +#include "cache-tree.h" #include "tree-walk.h" /* @@ -71,6 +72,7 @@ static int mark_valid(const char *path) active_cache[pos]->ce_flags &= ~htons(CE_VALID); break; } + cache_tree_invalidate_path(active_cache_tree, path); active_cache_changed = 1; return 0; } @@ -84,6 +86,12 @@ static int add_file_to_cache(const char *path) struct stat st; status = lstat(path, &st); + + /* We probably want to do this in remove_file_from_cache() and + * add_cache_entry() instead... + */ + cache_tree_invalidate_path(active_cache_tree, path); + if (status < 0 || S_ISDIR(st.st_mode)) { /* When we used to have "path" and now we want to add * "path/file", we need a way to remove "path" before @@ -326,6 +334,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, return error("%s: cannot add to the index - missing --add option?", path); report("add '%s'", path); + cache_tree_invalidate_path(active_cache_tree, path); return 0; } @@ -350,6 +359,7 @@ static void chmod_path(int flip, const char *path) default: goto fail; } + cache_tree_invalidate_path(active_cache_tree, path); active_cache_changed = 1; report("chmod %cx '%s'", flip, path); return; @@ -371,6 +381,7 @@ static void update_one(const char *path, const char *prefix, int prefix_length) die("Unable to mark file %s", path); return; } + cache_tree_invalidate_path(active_cache_tree, path); if (force_remove) { if (remove_file_from_cache(p)) @@ -446,6 +457,7 @@ static void read_index_info(int line_termination) free(path_name); continue; } + cache_tree_invalidate_path(active_cache_tree, path_name); if (!mode) { /* mode == 0 means there is no such path -- remove */ @@ -550,6 +562,7 @@ static int unresolve_one(const char *path) goto free_return; } + cache_tree_invalidate_path(active_cache_tree, path); remove_file_from_cache(path); if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) { error("%s: cannot add our version to the index.", path); diff --git a/write-tree.c b/write-tree.c index dcad6e6670..7a4f691d8a 100644 --- a/write-tree.c +++ b/write-tree.c @@ -5,95 +5,21 @@ */ #include "cache.h" #include "tree.h" +#include "cache-tree.h" static int missing_ok = 0; -static int check_valid_sha1(unsigned char *sha1) -{ - int ret; - - /* If we were anal, we'd check that the sha1 of the contents actually matches */ - ret = has_sha1_file(sha1); - if (ret == 0) - perror(sha1_file_name(sha1)); - return ret ? 0 : -1; -} - -static int write_tree(struct cache_entry **cachep, int maxentries, const char *base, int baselen, unsigned char *returnsha1) -{ - unsigned char subdir_sha1[20]; - unsigned long size, offset; - char *buffer; - int nr; - - /* Guess at some random initial size */ - size = 8192; - buffer = xmalloc(size); - offset = 0; - - nr = 0; - while (nr < maxentries) { - struct cache_entry *ce = cachep[nr]; - const char *pathname = ce->name, *filename, *dirname; - int pathlen = ce_namelen(ce), entrylen; - unsigned char *sha1; - unsigned int mode; - - /* Did we hit the end of the directory? Return how many we wrote */ - if (baselen >= pathlen || memcmp(base, pathname, baselen)) - break; - - sha1 = ce->sha1; - mode = ntohl(ce->ce_mode); - - /* Do we have _further_ subdirectories? */ - filename = pathname + baselen; - dirname = strchr(filename, '/'); - if (dirname) { - int subdir_written; - - subdir_written = write_tree(cachep + nr, maxentries - nr, pathname, dirname-pathname+1, subdir_sha1); - nr += subdir_written; - - /* Now we need to write out the directory entry into this tree.. */ - mode = S_IFDIR; - pathlen = dirname - pathname; - - /* ..but the directory entry doesn't count towards the total count */ - nr--; - sha1 = subdir_sha1; - } - - if (!missing_ok && check_valid_sha1(sha1) < 0) - exit(1); - - entrylen = pathlen - baselen; - if (offset + entrylen + 100 > size) { - size = alloc_nr(offset + entrylen + 100); - buffer = xrealloc(buffer, size); - } - offset += sprintf(buffer + offset, "%o %.*s", mode, entrylen, filename); - buffer[offset++] = 0; - memcpy(buffer + offset, sha1, 20); - offset += 20; - nr++; - } - - write_sha1_file(buffer, offset, tree_type, returnsha1); - free(buffer); - return nr; -} - static const char write_tree_usage[] = "git-write-tree [--missing-ok]"; +static struct cache_file cache_file; + int main(int argc, char **argv) { - int i, funny; - int entries; - unsigned char sha1[20]; - + int entries, was_valid, newfd; + setup_git_directory(); + newfd = hold_index_file_for_update(&cache_file, get_index_file()); entries = read_cache(); if (argc == 2) { if (!strcmp(argv[1], "--missing-ok")) @@ -108,51 +34,26 @@ int main(int argc, char **argv) if (entries < 0) die("git-write-tree: error reading cache"); - /* Verify that the tree is merged */ - funny = 0; - for (i = 0; i < entries; i++) { - struct cache_entry *ce = active_cache[i]; - if (ce_stage(ce)) { - if (10 < ++funny) { - fprintf(stderr, "...\n"); - break; - } - fprintf(stderr, "%s: unmerged (%s)\n", ce->name, sha1_to_hex(ce->sha1)); + if (!active_cache_tree) + active_cache_tree = cache_tree(); + + was_valid = cache_tree_fully_valid(active_cache_tree); + if (!was_valid) { + if (cache_tree_update(active_cache_tree, + active_cache, active_nr, + missing_ok, 0) < 0) + die("git-write-tree: error building trees"); + if (0 <= newfd) { + if (!write_cache(newfd, active_cache, active_nr)) + commit_index_file(&cache_file); } - } - if (funny) - die("git-write-tree: not able to write tree"); - - /* Also verify that the cache does not have path and path/file - * at the same time. At this point we know the cache has only - * stage 0 entries. - */ - funny = 0; - for (i = 0; i < entries - 1; i++) { - /* path/file always comes after path because of the way - * the cache is sorted. Also path can appear only once, - * which means conflicting one would immediately follow. + /* Not being able to write is fine -- we are only interested + * in updating the cache-tree part, and if the next caller + * ends up using the old index with unupdated cache-tree part + * it misses the work we did here, but that is just a + * performance penalty and not a big deal. */ - const char *this_name = active_cache[i]->name; - const char *next_name = active_cache[i+1]->name; - int this_len = strlen(this_name); - if (this_len < strlen(next_name) && - strncmp(this_name, next_name, this_len) == 0 && - next_name[this_len] == '/') { - if (10 < ++funny) { - fprintf(stderr, "...\n"); - break; - } - fprintf(stderr, "You have both %s and %s\n", - this_name, next_name); - } } - if (funny) - die("git-write-tree: not able to write tree"); - - /* Ok, write it out */ - if (write_tree(active_cache, entries, "", 0, sha1) != entries) - die("git-write-tree: internal error"); - printf("%s\n", sha1_to_hex(sha1)); + printf("%s\n", sha1_to_hex(active_cache_tree->sha1)); return 0; } |