diff options
37 files changed, 2423 insertions, 2321 deletions
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-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,7 +199,7 @@ 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 @@ -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: 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" @@ -134,6 +134,7 @@ 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); #define alloc_nr(x) (((x)+16)*3/2) 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); 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 */ @@ -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-format-patch.sh b/git-format-patch.sh index c7133bc126..c077f44ca1 100755 --- a/git-format-patch.sh +++ b/git-format-patch.sh @@ -205,11 +205,10 @@ sub show_date { } my $t = $time + $minutes * 60; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($t); - return sprintf("%s %s %d %02d:%02d:%02d %d %+05d", - $weekday_names[$wday], - $month_names[$mon], - $mday, $hour, $min, $sec, - $year+1900, $tz); + return sprintf("%s, %d %s %d %02d:%02d:%02d %+05d", + $weekday_names[$wday], $mday, + $month_names[$mon], $year+1900, + $hour, $min, $sec, $tz); } print "From nobody Mon Sep 17 00:00:00 2001\n"; diff --git a/git-rebase.sh b/git-rebase.sh index 86dfe9cb96..f7b2b9401a 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -107,7 +107,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" 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/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..f2a9f25fe1 100644 --- a/revision.c +++ b/revision.c @@ -752,17 +752,15 @@ 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)); - } + for (j = i; j < argc; j++) + verify_filename(revs->prefix, argv[j]); + revs->prune_data = get_pathspec(revs->prefix, argv + i); break; } @@ -62,6 +62,29 @@ 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 filename\n" + "Use '--' to separate filenames from revisions", arg); + 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 |