From: Junio C Hamano Date: Sat, 29 Sep 2007 23:43:06 +0000 (-0700) Subject: Merge branch 'jb/remote-rm' X-Git-Tag: v1.5.4-rc0~408 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/471fa1ecd5c323807b8156956c4602c5966f3a2b?hp=683b56791b4e8a29ff9bc98c7faff69d7854d845 Merge branch 'jb/remote-rm' * jb/remote-rm: git-remote rm: add tests and minor fix-ups remote: document the 'rm' subcommand remote: add 'rm' subcommand --- diff --git a/.gitignore b/.gitignore index 63c918c667..e0b91befb9 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ git-clone git-commit git-commit-tree git-config -git-convert-objects git-count-objects git-cvsexportcommit git-cvsimport diff --git a/Documentation/RelNotes-1.5.3.2.txt b/Documentation/RelNotes-1.5.3.2.txt new file mode 100644 index 0000000000..4bbde3cab4 --- /dev/null +++ b/Documentation/RelNotes-1.5.3.2.txt @@ -0,0 +1,58 @@ +GIT v1.5.3.2 Release Notes +========================== + +Fixes since v1.5.3.1 +-------------------- + + * git-push sent thin packs by default, which was not good for + the public distribution server (no point in saving transfer + while pushing; no point in making the resulting pack less + optimum). + + * git-svn sometimes terminated with "Malformed network data" when + talking over svn:// protocol. + + * git-send-email re-issued the same message-id about 10% of the + time if you fired off 30 messages within a single second. + + * git-stash was not terminating the log message of commits it + internally creates with LF. + + * git-apply failed to check the size of the patch hunk when its + beginning part matched the remainder of the preimage exactly, + even though the preimage recorded in the hunk was much larger + (therefore the patch should not have applied), leading to a + segfault. + + * "git rm foo && git commit foo" complained that 'foo' needs to + be added first, instead of committing the removal, which was a + nonsense. + + * git grep -c said "/dev/null: 0". + + * git-add -u failed to recognize a blob whose type changed + between the index and the work tree. + + * The limit to rename detection has been tightened a lot to + reduce performance problems with a huge change. + + * cvsimport and svnimport barfed when the input tried to move + a tag. + + * "git apply -pN" did not chop the right number of directories. + + * "git svnimport" did not like SVN tags with funny characters in them. + + * git-gui 0.8.3, with assorted fixes, including: + + - font-chooser on X11 was unusable with large number of fonts; + - a diff that contained a deleted symlink made it barf; + - an untracked symbolic link to a directory made it fart; + - a file with % in its name made it vomit; + + +Documentation updates +--------------------- + +User manual has been somewhat restructured. I think the new +organization is much easier to read. diff --git a/Documentation/RelNotes-1.5.3.3.txt b/Documentation/RelNotes-1.5.3.3.txt new file mode 100644 index 0000000000..e91bd84162 --- /dev/null +++ b/Documentation/RelNotes-1.5.3.3.txt @@ -0,0 +1,37 @@ +GIT v1.5.3.3 Release Notes +========================== + +Fixes since v1.5.3.2 +-------------------- + + * git-quiltimport did not like it when a patch described in the + series file does not exist. + + * p4 importer missed executable bit in some cases. + + * The default shell on some FreeBSD did not execute the + argument parsing code correctly and made git unusable. + + * git-svn incorrectly spawned pager even when the user user + explicitly asked not to. + + * sample post-receive hook overquoted the envelope sender + value. + + * git-am got confused when the patch contained a change that is + only about type and not contents. + + * git-mergetool did not show our and their version of the + conflicted file when started from a subdirectory of the + project. + + * git-mergetool did not pass correct options when invoking diff3. + + * git-log sometimes invoked underlying "diff" machinery + unnecessarily. + +-- +exec >/var/tmp/1 +O=v1.5.3.2-29-gb7bb760 +echo O=`git describe refs/heads/maint` +git shortlog --no-merges $O..refs/heads/maint diff --git a/Documentation/RelNotes-1.5.4.txt b/Documentation/RelNotes-1.5.4.txt index 1df66af9ce..ceee857232 100644 --- a/Documentation/RelNotes-1.5.4.txt +++ b/Documentation/RelNotes-1.5.4.txt @@ -4,7 +4,22 @@ GIT v1.5.4 Release Notes Updates since v1.5.3 -------------------- + * git-reset is now built-in. + * git-send-email can optionally talk over ssmtp and use SMTP-AUTH. + + * git-rebase learned --whitespace option. + + * git-remote knows --mirror mode. + + * git-merge can call the "post-merge" hook. + + * git-pack-objects can optionally run deltification with multiple threads. + + * git-archive can optionally substitute keywords in files marked with + export-subst attribute. + + * Various Perforce importer updates. Fixes since v1.5.3 ------------------ @@ -12,3 +27,9 @@ Fixes since v1.5.3 All of the fixes in v1.5.3 maintenance series are included in this release, unless otherwise noted. +-- +exec >/var/tmp/1 +O=v1.5.3.2-99-ge4b2890 +echo O=`git describe refs/heads/master` +git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint + diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl index 4ee76eaf99..1061fd8bcd 100755 --- a/Documentation/cmd-list.perl +++ b/Documentation/cmd-list.perl @@ -94,7 +94,6 @@ sub format_one { git-commit mainporcelain git-commit-tree plumbingmanipulators git-config ancillarymanipulators -git-convert-objects ancillarymanipulators git-count-objects ancillaryinterrogators git-cvsexportcommit foreignscminterface git-cvsimport foreignscminterface diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt index 4b4fd9a506..6b2590d072 100644 --- a/Documentation/core-tutorial.txt +++ b/Documentation/core-tutorial.txt @@ -1459,7 +1459,8 @@ Although git is a truly distributed system, it is often convenient to organize your project with an informal hierarchy of developers. Linux kernel development is run this way. There is a nice illustration (page 17, "Merges to Mainline") in -link:http://tinyurl.com/a2jdg[Randy Dunlap's presentation]. +link:http://www.xenotime.net/linux/mentor/linux-mentoring-2006.pdf +[Randy Dunlap's presentation]. It should be stressed that this hierarchy is purely *informal*. There is nothing fundamental in git that enforces the "chain of diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 4c7e3a2f7f..c1c54bfe0b 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] - [--apply] [--no-add] [--index-info] [-R | --reverse] + [--apply] [--no-add] [--build-fake-ancestor ] [-R | --reverse] [--allow-binary-replacement | --binary] [--reject] [-z] [-pNUM] [-CNUM] [--inaccurate-eof] [--cached] [--whitespace=] @@ -63,12 +63,15 @@ OPTIONS cached data, apply the patch, and store the result in the index, without using the working tree. This implies '--index'. ---index-info:: +--build-fake-ancestor :: Newer git-diff output has embedded 'index information' for each blob to help identify the original version that the patch applies to. When this flag is given, and if - the original version of the blob is available locally, - outputs information about them to the standard output. + the original versions of the blobs is available locally, + builds a temporary index containing those blobs. ++ +When a pure mode change is encountered (which has no index information), +the information is read from the current index instead. -R, --reverse:: Apply the patch in reverse. diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt index 5051e2bada..0cc6511bdf 100644 --- a/Documentation/git-bundle.txt +++ b/Documentation/git-bundle.txt @@ -103,14 +103,20 @@ We set a tag in R1 (lastR2bundle) after the previous such transport, and move it afterwards to help build the bundle. in R1 on A: + +------------ $ git-bundle create mybundle master ^lastR2bundle $ git tag -f lastR2bundle master +------------ (move mybundle from A to B by some mechanism) in R2 on B: + +------------ $ git-bundle verify mybundle $ git-fetch mybundle refspec +------------ where refspec is refInBundle:localRef @@ -124,9 +130,11 @@ Also, with something like this in your config: You can first sneakernet the bundle file to ~/tmp/file.bdl and then these commands: +------------ $ git ls-remote bundle $ git fetch bundle $ git pull bundle +------------ would treat it as if it is talking with a remote side over the network. diff --git a/Documentation/git-convert-objects.txt b/Documentation/git-convert-objects.txt deleted file mode 100644 index 9718abf86d..0000000000 --- a/Documentation/git-convert-objects.txt +++ /dev/null @@ -1,28 +0,0 @@ -git-convert-objects(1) -====================== - -NAME ----- -git-convert-objects - Converts old-style git repository - - -SYNOPSIS --------- -'git-convert-objects' - -DESCRIPTION ------------ -Converts old-style git repository to the latest format - - -Author ------- -Written by Linus Torvalds - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list . - -GIT ---- -Part of the gitlink:git[7] suite diff --git a/Documentation/git-lost-found.txt b/Documentation/git-lost-found.txt index e48607f008..bc739117be 100644 --- a/Documentation/git-lost-found.txt +++ b/Documentation/git-lost-found.txt @@ -65,7 +65,7 @@ $ git rev-parse not-lost-anymore Author ------ -Written by Junio C Hamano 濱野 純 +Written by Junio C Hamano Documentation -------------- diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 144bc16ff2..eae49c4876 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -56,7 +56,7 @@ merge.verbosity:: message if conflicts were detected. Level 1 outputs only conflicts, 2 outputs conflicts and file changes. Level 5 and above outputs debugging information. The default is level 2. - Can be overriden by 'GIT_MERGE_VERBOSITY' environment variable. + Can be overridden by 'GIT_MERGE_VERBOSITY' environment variable. HOW MERGE WORKS diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 628f296ce1..5237ab0c04 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -25,16 +25,16 @@ is efficient to access. The packed archive format (.pack) is designed to be unpackable without having anything else, but for random access, accompanied with the pack index file (.idx). +Placing both in the pack/ subdirectory of $GIT_OBJECT_DIRECTORY (or +any of the directories on $GIT_ALTERNATE_OBJECT_DIRECTORIES) +enables git to read from such an archive. + 'git-unpack-objects' command can read the packed archive and expand the objects contained in the pack into "one-file one-object" format; this is typically done by the smart-pull commands when a pack is created on-the-fly for efficient network transport by their peers. -Placing both in the pack/ subdirectory of $GIT_OBJECT_DIRECTORY (or -any of the directories on $GIT_ALTERNATE_OBJECT_DIRECTORIES) -enables git to read from such an archive. - In a packed archive, an object is either stored as a compressed whole, or as a difference from some other object. The latter is often called a delta. diff --git a/Documentation/git-prune-packed.txt b/Documentation/git-prune-packed.txt index 3800edb7bb..9f85f3833e 100644 --- a/Documentation/git-prune-packed.txt +++ b/Documentation/git-prune-packed.txt @@ -13,7 +13,7 @@ SYNOPSIS DESCRIPTION ----------- -This program search the `$GIT_OBJECT_DIR` for all objects that currently +This program searches the `$GIT_OBJECT_DIR` for all objects that currently exist in a pack file as well as the independent object directories. All such extra objects are removed. diff --git a/Documentation/git.txt b/Documentation/git.txt index a7cd91acc1..cb59639f77 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -46,6 +46,7 @@ Documentation for older releases are available here: * link:v1.5.3/git.html[documentation for release 1.5.3] * release notes for + link:RelNotes-1.5.3.2.txt[1.5.3.2], link:RelNotes-1.5.3.1.txt[1.5.3.1]. * release notes for diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index d0e951ee6f..20cf8ff816 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -145,17 +145,6 @@ sign `$` upon checkout. Any byte sequence that begins with with `$Id$` upon check-in. -Interaction between checkin/checkout attributes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -In the check-in codepath, the worktree file is first converted -with `ident` (if specified), and then with `crlf` (again, if -specified and applicable). - -In the check-out codepath, the blob content is first converted -with `crlf`, and then `ident`. - - `filter` ^^^^^^^^ @@ -175,11 +164,10 @@ but makes the filter a no-op passthru. The content filtering is done to massage the content into a shape that is more convenient for the platform, filesystem, and the user to use. The keyword here is "more convenient" and not -"turning something unusable into usable". In other words, it is -"hanging yourself because we gave you a long rope" if your -project uses filtering mechanism in such a way that it makes -your project unusable unless the checkout is done with a -specific filter in effect. +"turning something unusable into usable". In other words, the +intent is that if someone unsets the filter driver definition, +or does not have the appropriate filter program, the project +should still be usable. Interaction between checkin/checkout attributes diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index 9c83095693..e8b8581f52 100644 --- a/Documentation/gitignore.txt +++ b/Documentation/gitignore.txt @@ -26,7 +26,7 @@ precedence, the last matching pattern decides the outcome): * Patterns read from a `.gitignore` file in the same directory as the path, or in any parent directory, with patterns in the - higher level files (up to the root) being overriden by those in + higher level files (up to the root) being overridden by those in lower level files down to the directory containing the file. These patterns match relative to the location of the `.gitignore` file. A project normally includes such diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt index c39edc57c4..58b9547596 100644 --- a/Documentation/hooks.txt +++ b/Documentation/hooks.txt @@ -87,6 +87,19 @@ parameter, and is invoked after a commit is made. This hook is meant primarily for notification, and cannot affect the outcome of `git-commit`. +post-merge +----------- + +This hook is invoked by `git-merge`, which happens when a `git pull` +is done on a local repository. The hook takes a single parameter, a status +flag specifying whether or not the merge being done was a squash merge. +This hook cannot affect the outcome of `git-merge`. + +This hook can be used in conjunction with a corresponding pre-commit hook to +save and restore any form of metadata associated with the working tree +(eg: permissions/ownership, ACLS, etc). See contrib/hooks/setgitperms.perl +for an example of how to do this. + [[pre-receive]] pre-receive ----------- diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index ecb2bf93f2..c7fdf25e27 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -369,6 +369,11 @@ shorthand: The full name is occasionally useful if, for example, there ever exists a tag and a branch with the same name. +(Newly created refs are actually stored in the .git/refs directory, +under the path given by their name. However, for efficiency reasons +they may also be packed together in a single file; see +gitlink:git-pack-refs[1]). + As another useful shortcut, the "HEAD" of a repository can be referred to just using the name of that repository. So, for example, "origin" is usually a shortcut for the HEAD branch in the repository "origin". @@ -2189,9 +2194,9 @@ test|release) git checkout $1 && git pull . origin ;; origin) - before=$(cat .git/refs/remotes/origin/master) + before=$(git rev-parse refs/remotes/origin/master) git fetch origin - after=$(cat .git/refs/remotes/origin/master) + after=$(git rev-parse refs/remotes/origin/master) if [ $before != $after ] then git log $before..$after | git shortlog @@ -2216,11 +2221,10 @@ usage() exit 1 } -if [ ! -f .git/refs/heads/"$1" ] -then +git show-ref -q --verify -- refs/heads/"$1" || { echo "Can't see branch <$1>" 1>&2 usage -fi +} case "$2" in test|release) @@ -2251,7 +2255,7 @@ then git log test..release fi -for branch in `ls .git/refs/heads` +for branch in `git show-ref --heads | sed 's|^.*/||'` do if [ $branch = test -o $branch = release ] then @@ -2852,8 +2856,7 @@ between two related tree objects, since it can ignore any entries with identical object names. (Note: in the presence of submodules, trees may also have commits as -entries. See gitlink:git-submodule[1] and gitlink:gitmodules.txt[1] -for partial documentation.) +entries. See <> for documentation.) Note that the files all have mode 644 or 755: git actually only pays attention to the executable bit. @@ -2946,7 +2949,7 @@ nLE/L9aUXdWeTFPron96DLA= See the gitlink:git-tag[1] command to learn how to create and verify tag objects. (Note that gitlink:git-tag[1] can also be used to create "lightweight tags", which are not tag objects at all, but just simple -references in .git/refs/tags/). +references whose names begin with "refs/tags/"). [[pack-files]] How git stores objects efficiently: pack files @@ -3155,6 +3158,241 @@ a tree which you are in the process of working on. If you blow the index away entirely, you generally haven't lost any information as long as you have the name of the tree that it described. +[[submodules]] +Submodules +========== + +Large projects are often composed of smaller, self-contained modules. For +example, an embedded Linux distribution's source tree would include every +piece of software in the distribution with some local modifications; a movie +player might need to build against a specific, known-working version of a +decompression library; several independent programs might all share the same +build scripts. + +With centralized revision control systems this is often accomplished by +including every module in one single repository. Developers can check out +all modules or only the modules they need to work with. They can even modify +files across several modules in a single commit while moving things around +or updating APIs and translations. + +Git does not allow partial checkouts, so duplicating this approach in Git +would force developers to keep a local copy of modules they are not +interested in touching. Commits in an enormous checkout would be slower +than you'd expect as Git would have to scan every directory for changes. +If modules have a lot of local history, clones would take forever. + +On the plus side, distributed revision control systems can much better +integrate with external sources. In a centralized model, a single arbitrary +snapshot of the external project is exported from its own revision control +and then imported into the local revision control on a vendor branch. All +the history is hidden. With distributed revision control you can clone the +entire external history and much more easily follow development and re-merge +local changes. + +Git's submodule support allows a repository to contain, as a subdirectory, a +checkout of an external project. Submodules maintain their own identity; +the submodule support just stores the submodule repository location and +commit ID, so other developers who clone the containing project +("superproject") can easily clone all the submodules at the same revision. +Partial checkouts of the superproject are possible: you can tell Git to +clone none, some or all of the submodules. + +The gitlink:git-submodule[1] command is available since Git 1.5.3. Users +with Git 1.5.2 can look up the submodule commits in the repository and +manually check them out; earlier versions won't recognize the submodules at +all. + +To see how submodule support works, create (for example) four example +repositories that can be used later as a submodule: + +------------------------------------------------- +$ mkdir ~/git +$ cd ~/git +$ for i in a b c d +do + mkdir $i + cd $i + git init + echo "module $i" > $i.txt + git add $i.txt + git commit -m "Initial commit, submodule $i" + cd .. +done +------------------------------------------------- + +Now create the superproject and add all the submodules: + +------------------------------------------------- +$ mkdir super +$ cd super +$ git init +$ for i in a b c d +do + git submodule add ~/git/$i +done +------------------------------------------------- + +NOTE: Do not use local URLs here if you plan to publish your superproject! + +See what files `git submodule` created: + +------------------------------------------------- +$ ls -a +. .. .git .gitmodules a b c d +------------------------------------------------- + +The `git submodule add` command does a couple of things: + +- It clones the submodule under the current directory and by default checks out + the master branch. +- It adds the submodule's clone path to the gitlink:gitmodules[5] file and + adds this file to the index, ready to be committed. +- It adds the submodule's current commit ID to the index, ready to be + committed. + +Commit the superproject: + +------------------------------------------------- +$ git commit -m "Add submodules a, b, c and d." +------------------------------------------------- + +Now clone the superproject: + +------------------------------------------------- +$ cd .. +$ git clone super cloned +$ cd cloned +------------------------------------------------- + +The submodule directories are there, but they're empty: + +------------------------------------------------- +$ ls -a a +. .. +$ git submodule status +-d266b9873ad50488163457f025db7cdd9683d88b a +-e81d457da15309b4fef4249aba9b50187999670d b +-c1536a972b9affea0f16e0680ba87332dc059146 c +-d96249ff5d57de5de093e6baff9e0aafa5276a74 d +------------------------------------------------- + +NOTE: The commit object names shown above would be different for you, but they +should match the HEAD commit object names of your repositories. You can check +it by running `git ls-remote ../a`. + +Pulling down the submodules is a two-step process. First run `git submodule +init` to add the submodule repository URLs to `.git/config`: + +------------------------------------------------- +$ git submodule init +------------------------------------------------- + +Now use `git submodule update` to clone the repositories and check out the +commits specified in the superproject: + +------------------------------------------------- +$ git submodule update +$ cd a +$ ls -a +. .. .git a.txt +------------------------------------------------- + +One major difference between `git submodule update` and `git submodule add` is +that `git submodule update` checks out a specific commit, rather than the tip +of a branch. It's like checking out a tag: the head is detached, so you're not +working on a branch. + +------------------------------------------------- +$ git branch +* (no branch) + master +------------------------------------------------- + +If you want to make a change within a submodule and you have a detached head, +then you should create or checkout a branch, make your changes, publish the +change within the submodule, and then update the superproject to reference the +new commit: + +------------------------------------------------- +$ git checkout master +------------------------------------------------- + +or + +------------------------------------------------- +$ git checkout -b fix-up +------------------------------------------------- + +then + +------------------------------------------------- +$ echo "adding a line again" >> a.txt +$ git commit -a -m "Updated the submodule from within the superproject." +$ git push +$ cd .. +$ git diff +diff --git a/a b/a +index d266b98..261dfac 160000 +--- a/a ++++ b/a +@@ -1 +1 @@ +-Subproject commit d266b9873ad50488163457f025db7cdd9683d88b ++Subproject commit 261dfac35cb99d380eb966e102c1197139f7fa24 +$ git add a +$ git commit -m "Updated submodule a." +$ git push +------------------------------------------------- + +You have to run `git submodule update` after `git pull` if you want to update +submodules, too. + +Pitfalls with submodules +------------------------ + +Always publish the submodule change before publishing the change to the +superproject that references it. If you forget to publish the submodule change, +others won't be able to clone the repository: + +------------------------------------------------- +$ cd ~/git/super/a +$ echo i added another line to this file >> a.txt +$ git commit -a -m "doing it wrong this time" +$ cd .. +$ git add a +$ git commit -m "Updated submodule a again." +$ git push +$ cd ~/git/cloned +$ git pull +$ git submodule update +error: pathspec '261dfac35cb99d380eb966e102c1197139f7fa24' did not match any file(s) known to git. +Did you forget to 'git add'? +Unable to checkout '261dfac35cb99d380eb966e102c1197139f7fa24' in submodule path 'a' +------------------------------------------------- + +You also should not rewind branches in a submodule beyond commits that were +ever recorded in any superproject. + +It's not safe to run `git submodule update` if you've made and committed +changes within a submodule without checking out a branch first. They will be +silently overwritten: + +------------------------------------------------- +$ cat a.txt +module a +$ echo line added from private2 >> a.txt +$ git commit -a -m "line added inside private2" +$ cd .. +$ git submodule update +Submodule path 'a': checked out 'd266b9873ad50488163457f025db7cdd9683d88b' +$ cd a +$ cat a.txt +module a +------------------------------------------------- + +NOTE: The changes are still visible in the submodule's reflog. + +This is not the case if you did not commit your changes. + [[low-level-operations]] Low-level git operations ======================== @@ -4071,5 +4309,3 @@ Write a chapter on using plumbing and writing scripts. Alternates, clone -reference, etc. git unpack-objects -r for recovery - -submodules diff --git a/Makefile b/Makefile index 0055eef24d..8db4dbe1d2 100644 --- a/Makefile +++ b/Makefile @@ -233,7 +233,7 @@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS = \ - git-convert-objects$X git-fetch-pack$X \ + git-fetch-pack$X \ git-hash-object$X git-index-pack$X git-local-fetch$X \ git-fast-import$X \ git-daemon$X \ @@ -805,7 +805,7 @@ perl/perl.mak: GIT-CFLAGS $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl $(QUIET_GEN)$(RM) $@ $@+ && \ - INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \ + INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C perl -s --no-print-directory instlibdir` && \ sed -e '1{' \ -e ' s|#!.*perl|#!$(PERL_PATH_SQ)|' \ -e ' h' \ diff --git a/builtin-apply.c b/builtin-apply.c index 86d89a4a7e..6777231c66 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -41,7 +41,7 @@ static int apply_in_reverse; static int apply_with_reject; static int apply_verbosely; static int no_add; -static int show_index_info; +static const char *fake_ancestor; static int line_termination = '\n'; static unsigned long p_context = ULONG_MAX; static const char apply_usage[] = @@ -2248,9 +2248,12 @@ static int get_current_sha1(const char *path, unsigned char *sha1) return 0; } -static void show_index_list(struct patch *list) +/* Build an index that contains the just the files needed for a 3way merge */ +static void build_fake_ancestor(struct patch *list, const char *filename) { struct patch *patch; + struct index_state result = { 0 }; + int fd; /* Once we start supporting the reverse patch, it may be * worth showing the new sha1 prefix, but until then... @@ -2258,11 +2261,12 @@ static void show_index_list(struct patch *list) for (patch = list; patch; patch = patch->next) { const unsigned char *sha1_ptr; unsigned char sha1[20]; + struct cache_entry *ce; const char *name; name = patch->old_name ? patch->old_name : patch->new_name; if (0 < patch->is_new) - sha1_ptr = null_sha1; + continue; else if (get_sha1(patch->old_sha1_prefix, sha1)) /* git diff has no index line for mode/type changes */ if (!patch->lines_added && !patch->lines_deleted) { @@ -2277,13 +2281,16 @@ static void show_index_list(struct patch *list) else sha1_ptr = sha1; - printf("%06o %s ",patch->old_mode, sha1_to_hex(sha1_ptr)); - if (line_termination && quote_c_style(name, NULL, NULL, 0)) - quote_c_style(name, NULL, stdout, 0); - else - fputs(name, stdout); - putchar(line_termination); + ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0); + if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD)) + die ("Could not add %s to temporary index", name); } + + fd = open(filename, O_WRONLY | O_CREAT, 0666); + if (fd < 0 || write_index(&result, fd) || close(fd)) + die ("Could not write temporary index to %s", filename); + + discard_index(&result); } static void stat_patch_list(struct patch *patch) @@ -2803,8 +2810,8 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof) if (apply && write_out_results(list, skipped_patch)) exit(1); - if (show_index_info) - show_index_list(list); + if (fake_ancestor) + build_fake_ancestor(list, fake_ancestor); if (diffstat) stat_patch_list(list); @@ -2912,9 +2919,11 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) apply = 1; continue; } - if (!strcmp(arg, "--index-info")) { + if (!strcmp(arg, "--build-fake-ancestor")) { apply = 0; - show_index_info = 1; + if (++i >= argc) + die ("need a filename"); + fake_ancestor = argv[i]; continue; } if (!strcmp(arg, "-z")) { diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 899a31d09a..3894633999 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -436,10 +436,10 @@ static struct commit_list *find_bisection(struct commit_list *list, /* Do the real work of finding bisection commit. */ best = do_find_bisection(list, nr, weights); - if (best) + if (best) { best->next = NULL; - - *reaches = weight(best); + *reaches = weight(best); + } free(weights); return best; diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index cad842af45..e760930740 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -299,7 +299,6 @@ __git_commands () check-attr) : plumbing;; check-ref-format) : plumbing;; commit-tree) : plumbing;; - convert-objects) : plumbing;; cvsexportcommit) : export;; cvsimport) : import;; cvsserver) : daemon;; diff --git a/contrib/convert-objects/convert-objects.c b/contrib/convert-objects/convert-objects.c new file mode 100644 index 0000000000..90e7900e6d --- /dev/null +++ b/contrib/convert-objects/convert-objects.c @@ -0,0 +1,329 @@ +#include "cache.h" +#include "blob.h" +#include "commit.h" +#include "tree.h" + +struct entry { + unsigned char old_sha1[20]; + unsigned char new_sha1[20]; + int converted; +}; + +#define MAXOBJECTS (1000000) + +static struct entry *convert[MAXOBJECTS]; +static int nr_convert; + +static struct entry * convert_entry(unsigned char *sha1); + +static struct entry *insert_new(unsigned char *sha1, int pos) +{ + struct entry *new = xcalloc(1, sizeof(struct entry)); + hashcpy(new->old_sha1, sha1); + memmove(convert + pos + 1, convert + pos, (nr_convert - pos) * sizeof(struct entry *)); + convert[pos] = new; + nr_convert++; + if (nr_convert == MAXOBJECTS) + die("you're kidding me - hit maximum object limit"); + return new; +} + +static struct entry *lookup_entry(unsigned char *sha1) +{ + int low = 0, high = nr_convert; + + while (low < high) { + int next = (low + high) / 2; + struct entry *n = convert[next]; + int cmp = hashcmp(sha1, n->old_sha1); + if (!cmp) + return n; + if (cmp < 0) { + high = next; + continue; + } + low = next+1; + } + return insert_new(sha1, low); +} + +static void convert_binary_sha1(void *buffer) +{ + struct entry *entry = convert_entry(buffer); + hashcpy(buffer, entry->new_sha1); +} + +static void convert_ascii_sha1(void *buffer) +{ + unsigned char sha1[20]; + struct entry *entry; + + if (get_sha1_hex(buffer, sha1)) + die("expected sha1, got '%s'", (char*) buffer); + entry = convert_entry(sha1); + memcpy(buffer, sha1_to_hex(entry->new_sha1), 40); +} + +static unsigned int convert_mode(unsigned int mode) +{ + unsigned int newmode; + + newmode = mode & S_IFMT; + if (S_ISREG(mode)) + newmode |= (mode & 0100) ? 0755 : 0644; + return newmode; +} + +static int write_subdirectory(void *buffer, unsigned long size, const char *base, int baselen, unsigned char *result_sha1) +{ + char *new = xmalloc(size); + unsigned long newlen = 0; + unsigned long used; + + used = 0; + while (size) { + int len = 21 + strlen(buffer); + char *path = strchr(buffer, ' '); + unsigned char *sha1; + unsigned int mode; + char *slash, *origpath; + + if (!path || strtoul_ui(buffer, 8, &mode)) + die("bad tree conversion"); + mode = convert_mode(mode); + path++; + if (memcmp(path, base, baselen)) + break; + origpath = path; + path += baselen; + slash = strchr(path, '/'); + if (!slash) { + newlen += sprintf(new + newlen, "%o %s", mode, path); + new[newlen++] = '\0'; + hashcpy((unsigned char*)new + newlen, (unsigned char *) buffer + len - 20); + newlen += 20; + + used += len; + size -= len; + buffer = (char *) buffer + len; + continue; + } + + newlen += sprintf(new + newlen, "%o %.*s", S_IFDIR, (int)(slash - path), path); + new[newlen++] = 0; + sha1 = (unsigned char *)(new + newlen); + newlen += 20; + + len = write_subdirectory(buffer, size, origpath, slash-origpath+1, sha1); + + used += len; + size -= len; + buffer = (char *) buffer + len; + } + + write_sha1_file(new, newlen, tree_type, result_sha1); + free(new); + return used; +} + +static void convert_tree(void *buffer, unsigned long size, unsigned char *result_sha1) +{ + void *orig_buffer = buffer; + unsigned long orig_size = size; + + while (size) { + size_t len = 1+strlen(buffer); + + convert_binary_sha1((char *) buffer + len); + + len += 20; + if (len > size) + die("corrupt tree object"); + size -= len; + buffer = (char *) buffer + len; + } + + write_subdirectory(orig_buffer, orig_size, "", 0, result_sha1); +} + +static unsigned long parse_oldstyle_date(const char *buf) +{ + char c, *p; + char buffer[100]; + struct tm tm; + const char *formats[] = { + "%c", + "%a %b %d %T", + "%Z", + "%Y", + " %Y", + NULL + }; + /* We only ever did two timezones in the bad old format .. */ + const char *timezones[] = { + "PDT", "PST", "CEST", NULL + }; + const char **fmt = formats; + + p = buffer; + while (isspace(c = *buf)) + buf++; + while ((c = *buf++) != '\n') + *p++ = c; + *p++ = 0; + buf = buffer; + memset(&tm, 0, sizeof(tm)); + do { + const char *next = strptime(buf, *fmt, &tm); + if (next) { + if (!*next) + return mktime(&tm); + buf = next; + } else { + const char **p = timezones; + while (isspace(*buf)) + buf++; + while (*p) { + if (!memcmp(buf, *p, strlen(*p))) { + buf += strlen(*p); + break; + } + p++; + } + } + fmt++; + } while (*buf && *fmt); + printf("left: %s\n", buf); + return mktime(&tm); +} + +static int convert_date_line(char *dst, void **buf, unsigned long *sp) +{ + unsigned long size = *sp; + char *line = *buf; + char *next = strchr(line, '\n'); + char *date = strchr(line, '>'); + int len; + + if (!next || !date) + die("missing or bad author/committer line %s", line); + next++; date += 2; + + *buf = next; + *sp = size - (next - line); + + len = date - line; + memcpy(dst, line, len); + dst += len; + + /* Is it already in new format? */ + if (isdigit(*date)) { + int datelen = next - date; + memcpy(dst, date, datelen); + return len + datelen; + } + + /* + * Hacky hacky: one of the sparse old-style commits does not have + * any date at all, but we can fake it by using the committer date. + */ + if (*date == '\n' && strchr(next, '>')) + date = strchr(next, '>')+2; + + return len + sprintf(dst, "%lu -0700\n", parse_oldstyle_date(date)); +} + +static void convert_date(void *buffer, unsigned long size, unsigned char *result_sha1) +{ + char *new = xmalloc(size + 100); + unsigned long newlen = 0; + + /* "tree \n" */ + memcpy(new + newlen, buffer, 46); + newlen += 46; + buffer = (char *) buffer + 46; + size -= 46; + + /* "parent \n" */ + while (!memcmp(buffer, "parent ", 7)) { + memcpy(new + newlen, buffer, 48); + newlen += 48; + buffer = (char *) buffer + 48; + size -= 48; + } + + /* "author xyz date" */ + newlen += convert_date_line(new + newlen, &buffer, &size); + /* "committer xyz date" */ + newlen += convert_date_line(new + newlen, &buffer, &size); + + /* Rest */ + memcpy(new + newlen, buffer, size); + newlen += size; + + write_sha1_file(new, newlen, commit_type, result_sha1); + free(new); +} + +static void convert_commit(void *buffer, unsigned long size, unsigned char *result_sha1) +{ + void *orig_buffer = buffer; + unsigned long orig_size = size; + + if (memcmp(buffer, "tree ", 5)) + die("Bad commit '%s'", (char*) buffer); + convert_ascii_sha1((char *) buffer + 5); + buffer = (char *) buffer + 46; /* "tree " + "hex sha1" + "\n" */ + while (!memcmp(buffer, "parent ", 7)) { + convert_ascii_sha1((char *) buffer + 7); + buffer = (char *) buffer + 48; + } + convert_date(orig_buffer, orig_size, result_sha1); +} + +static struct entry * convert_entry(unsigned char *sha1) +{ + struct entry *entry = lookup_entry(sha1); + enum object_type type; + void *buffer, *data; + unsigned long size; + + if (entry->converted) + return entry; + data = read_sha1_file(sha1, &type, &size); + if (!data) + die("unable to read object %s", sha1_to_hex(sha1)); + + buffer = xmalloc(size); + memcpy(buffer, data, size); + + if (type == OBJ_BLOB) { + write_sha1_file(buffer, size, blob_type, entry->new_sha1); + } else if (type == OBJ_TREE) + convert_tree(buffer, size, entry->new_sha1); + else if (type == OBJ_COMMIT) + convert_commit(buffer, size, entry->new_sha1); + else + die("unknown object type %d in %s", type, sha1_to_hex(sha1)); + entry->converted = 1; + free(buffer); + free(data); + return entry; +} + +int main(int argc, char **argv) +{ + unsigned char sha1[20]; + struct entry *entry; + + setup_git_directory(); + + if (argc != 2) + usage("git-convert-objects "); + if (get_sha1(argv[1], sha1)) + die("Not a valid object name %s", argv[1]); + + entry = convert_entry(sha1); + printf("new sha1: %s\n", sha1_to_hex(entry->new_sha1)); + return 0; +} diff --git a/contrib/convert-objects/git-convert-objects.txt b/contrib/convert-objects/git-convert-objects.txt new file mode 100644 index 0000000000..9718abf86d --- /dev/null +++ b/contrib/convert-objects/git-convert-objects.txt @@ -0,0 +1,28 @@ +git-convert-objects(1) +====================== + +NAME +---- +git-convert-objects - Converts old-style git repository + + +SYNOPSIS +-------- +'git-convert-objects' + +DESCRIPTION +----------- +Converts old-style git repository to the latest format + + +Author +------ +Written by Linus Torvalds + +Documentation +-------------- +Documentation by David Greaves, Junio C Hamano and the git-list . + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/contrib/examples/git-gc.sh b/contrib/examples/git-gc.sh index 2ae235b081..1597e9f33f 100755 --- a/contrib/examples/git-gc.sh +++ b/contrib/examples/git-gc.sh @@ -9,7 +9,7 @@ SUBDIRECTORY_OK=Yes . git-sh-setup no_prune=: -while case $# in 0) break ;; esac +while test $# != 0 do case "$1" in --prune) diff --git a/contrib/examples/git-reset.sh b/contrib/examples/git-reset.sh index 1dc606fbd3..bafeb52cd1 100755 --- a/contrib/examples/git-reset.sh +++ b/contrib/examples/git-reset.sh @@ -11,7 +11,7 @@ require_work_tree update= reset_type=--mixed unset rev -while case $# in 0) break ;; esac +while test $# != 0 do case "$1" in --mixed | --soft | --hard) diff --git a/contrib/examples/git-tag.sh b/contrib/examples/git-tag.sh index 5ee3f50a3c..ae7c531666 100755 --- a/contrib/examples/git-tag.sh +++ b/contrib/examples/git-tag.sh @@ -14,7 +14,7 @@ username= list= verify= LINES=0 -while case "$#" in 0) break ;; esac +while test $# != 0 do case "$1" in -a) diff --git a/contrib/examples/git-verify-tag.sh b/contrib/examples/git-verify-tag.sh index 37b0023b27..0902a5c21a 100755 --- a/contrib/examples/git-verify-tag.sh +++ b/contrib/examples/git-verify-tag.sh @@ -5,7 +5,7 @@ SUBDIRECTORY_OK='Yes' . git-sh-setup verbose= -while case $# in 0) break;; esac +while test $# != 0 do case "$1" in -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index adaaae6633..557649a14a 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -63,6 +63,14 @@ def system(cmd): if os.system(cmd) != 0: die("command failed: %s" % cmd) +def isP4Exec(kind): + """Determine if a Perforce 'kind' should have execute permission + + 'p4 help filetypes' gives a list of the types. If it starts with 'x', + or x follows one of a few letters. Otherwise, if there is an 'x' after + a plus sign, it is also executable""" + return (re.search(r"(^[cku]?x)|\+.*x", kind) != None) + def p4CmdList(cmd, stdin=None, stdin_mode='w+b'): cmd = "p4 -G %s" % cmd if verbose: @@ -932,7 +940,7 @@ class P4Sync(Command): data = file['data'] mode = "644" - if file["type"].startswith("x"): + if isP4Exec(file["type"]): mode = "755" elif file["type"] == "symlink": mode = "120000" diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index c589a39a0c..1f88099df4 100644 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -571,6 +571,15 @@ generate_delete_general_email() echo $LOGEND } +send_mail() +{ + if [ -n "$envelopesender" ]; then + /usr/sbin/sendmail -t -f "$envelopesender" + else + /usr/sbin/sendmail -t + fi +} + # ---------------------------- main() # --- Constants @@ -607,13 +616,8 @@ if [ -n "$1" -a -n "$2" -a -n "$3" ]; then # resend an email; they could redirect the output to sendmail themselves PAGER= generate_email $2 $3 $1 else - if [ -n "$envelopesender" ]; then - envelopesender="-f '$envelopesender'" - fi - while read oldrev newrev refname do - generate_email $oldrev $newrev $refname | - /usr/sbin/sendmail -t $envelopesender + generate_email $oldrev $newrev $refname | send_mail done fi diff --git a/contrib/hooks/setgitperms.perl b/contrib/hooks/setgitperms.perl new file mode 100644 index 0000000000..5e3b89def2 --- /dev/null +++ b/contrib/hooks/setgitperms.perl @@ -0,0 +1,213 @@ +#!/usr/bin/perl +# +# Copyright (c) 2006 Josh England +# +# This script can be used to save/restore full permissions and ownership data +# within a git working tree. +# +# To save permissions/ownership data, place this script in your .git/hooks +# directory and enable a `pre-commit` hook with the following lines: +# #!/bin/sh +# . git-sh-setup +# $GIT_DIR/hooks/setgitperms.perl -r +# +# To restore permissions/ownership data, place this script in your .git/hooks +# directory and enable a `post-merge` hook with the following lines: +# #!/bin/sh +# . git-sh-setup +# $GIT_DIR/hooks/setgitperms.perl -w +# +use strict; +use Getopt::Long; +use File::Find; +use File::Basename; + +my $usage = +"Usage: setgitperms.perl [OPTION]... <--read|--write> +This program uses a file `.gitmeta` to store/restore permissions and uid/gid +info for all files/dirs tracked by git in the repository. + +---------------------------------Read Mode------------------------------------- +-r, --read Reads perms/etc from working dir into a .gitmeta file +-s, --stdout Output to stdout instead of .gitmeta +-d, --diff Show unified diff of perms file (XOR with --stdout) + +---------------------------------Write Mode------------------------------------ +-w, --write Modify perms/etc in working dir to match the .gitmeta file +-v, --verbose Be verbose + +\n"; + +my ($stdout, $showdiff, $verbose, $read_mode, $write_mode); + +if ((@ARGV < 0) || !GetOptions( + "stdout", \$stdout, + "diff", \$showdiff, + "read", \$read_mode, + "write", \$write_mode, + "verbose", \$verbose, + )) { die $usage; } +die $usage unless ($read_mode xor $write_mode); + +my $topdir = `git-rev-parse --show-cdup` or die "\n"; chomp $topdir; +my $gitdir = $topdir . '.git'; +my $gitmeta = $topdir . '.gitmeta'; + +if ($write_mode) { + # Update the working dir permissions/ownership based on data from .gitmeta + open (IN, "<$gitmeta") or die "Could not open $gitmeta for reading: $!\n"; + while (defined ($_ = )) { + chomp; + if (/^(.*) mode=(\S+)\s+uid=(\d+)\s+gid=(\d+)/) { + # Compare recorded perms to actual perms in the working dir + my ($path, $mode, $uid, $gid) = ($1, $2, $3, $4); + my $fullpath = $topdir . $path; + my (undef,undef,$wmode,undef,$wuid,$wgid) = lstat($fullpath); + $wmode = sprintf "%04o", $wmode & 07777; + if ($mode ne $wmode) { + $verbose && print "Updating permissions on $path: old=$wmode, new=$mode\n"; + chmod oct($mode), $fullpath; + } + if ($uid != $wuid || $gid != $wgid) { + if ($verbose) { + # Print out user/group names instead of uid/gid + my $pwname = getpwuid($uid); + my $grpname = getgrgid($gid); + my $wpwname = getpwuid($wuid); + my $wgrpname = getgrgid($wgid); + $pwname = $uid if !defined $pwname; + $grpname = $gid if !defined $grpname; + $wpwname = $wuid if !defined $wpwname; + $wgrpname = $wgid if !defined $wgrpname; + + print "Updating uid/gid on $path: old=$wpwname/$wgrpname, new=$pwname/$grpname\n"; + } + chown $uid, $gid, $fullpath; + } + } + else { + warn "Invalid input format in $gitmeta:\n\t$_\n"; + } + } + close IN; +} +elsif ($read_mode) { + # Handle merge conflicts in the .gitperms file + if (-e "$gitdir/MERGE_MSG") { + if (`grep ====== $gitmeta`) { + # Conflict not resolved -- abort the commit + print "PERMISSIONS/OWNERSHIP CONFLICT\n"; + print " Resolve the conflict in the $gitmeta file and then run\n"; + print " `.git/hooks/setgitperms.perl --write` to reconcile.\n"; + exit 1; + } + elsif (`grep $gitmeta $gitdir/MERGE_MSG`) { + # A conflict in .gitmeta has been manually resolved. Verify that + # the working dir perms matches the current .gitmeta perms for + # each file/dir that conflicted. + # This is here because a `setgitperms.perl --write` was not + # performed due to a merge conflict, so permissions/ownership + # may not be consistent with the manually merged .gitmeta file. + my @conflict_diff = `git show \$(cat $gitdir/MERGE_HEAD)`; + my @conflict_files; + my $metadiff = 0; + + # Build a list of files that conflicted from the .gitmeta diff + foreach my $line (@conflict_diff) { + if ($line =~ m|^diff --git a/$gitmeta b/$gitmeta|) { + $metadiff = 1; + } + elsif ($line =~ /^diff --git/) { + $metadiff = 0; + } + elsif ($metadiff && $line =~ /^\+(.*) mode=/) { + push @conflict_files, $1; + } + } + + # Verify that each conflict file now has permissions consistent + # with the .gitmeta file + foreach my $file (@conflict_files) { + my $absfile = $topdir . $file; + my $gm_entry = `grep "^$file mode=" $gitmeta`; + if ($gm_entry =~ /mode=(\d+) uid=(\d+) gid=(\d+)/) { + my ($gm_mode, $gm_uid, $gm_gid) = ($1, $2, $3); + my (undef,undef,$mode,undef,$uid,$gid) = lstat("$absfile"); + $mode = sprintf("%04o", $mode & 07777); + if (($gm_mode ne $mode) || ($gm_uid != $uid) + || ($gm_gid != $gid)) { + print "PERMISSIONS/OWNERSHIP CONFLICT\n"; + print " Mismatch found for file: $file\n"; + print " Run `.git/hooks/setgitperms.perl --write` to reconcile.\n"; + exit 1; + } + } + else { + print "Warning! Permissions/ownership no longer being tracked for file: $file\n"; + } + } + } + } + + # No merge conflicts -- write out perms/ownership data to .gitmeta file + unless ($stdout) { + open (OUT, ">$gitmeta.tmp") or die "Could not open $gitmeta.tmp for writing: $!\n"; + } + + my @files = `git-ls-files`; + my %dirs; + + foreach my $path (@files) { + chomp $path; + # We have to manually add stats for parent directories + my $parent = dirname($path); + while (!exists $dirs{$parent}) { + $dirs{$parent} = 1; + next if $parent eq '.'; + printstats($parent); + $parent = dirname($parent); + } + # Now the git-tracked file + printstats($path); + } + + # diff the temporary metadata file to see if anything has changed + # If no metadata has changed, don't overwrite the real file + # This is just so `git commit -a` doesn't try to commit a bogus update + unless ($stdout) { + if (! -e $gitmeta) { + rename "$gitmeta.tmp", $gitmeta; + } + else { + my $diff = `diff -U 0 $gitmeta $gitmeta.tmp`; + if ($diff ne '') { + rename "$gitmeta.tmp", $gitmeta; + } + else { + unlink "$gitmeta.tmp"; + } + if ($showdiff) { + print $diff; + } + } + close OUT; + } + # Make sure the .gitmeta file is tracked + system("git add $gitmeta"); +} + + +sub printstats { + my $path = $_[0]; + $path =~ s/@/\@/g; + my (undef,undef,$mode,undef,$uid,$gid) = lstat($path); + $path =~ s/%/\%/g; + if ($stdout) { + print $path; + printf " mode=%04o uid=$uid gid=$gid\n", $mode & 07777; + } + else { + print OUT $path; + printf OUT " mode=%04o uid=$uid gid=$gid\n", $mode & 07777; + } +} diff --git a/convert-objects.c b/convert-objects.c deleted file mode 100644 index 90e7900e6d..0000000000 --- a/convert-objects.c +++ /dev/null @@ -1,329 +0,0 @@ -#include "cache.h" -#include "blob.h" -#include "commit.h" -#include "tree.h" - -struct entry { - unsigned char old_sha1[20]; - unsigned char new_sha1[20]; - int converted; -}; - -#define MAXOBJECTS (1000000) - -static struct entry *convert[MAXOBJECTS]; -static int nr_convert; - -static struct entry * convert_entry(unsigned char *sha1); - -static struct entry *insert_new(unsigned char *sha1, int pos) -{ - struct entry *new = xcalloc(1, sizeof(struct entry)); - hashcpy(new->old_sha1, sha1); - memmove(convert + pos + 1, convert + pos, (nr_convert - pos) * sizeof(struct entry *)); - convert[pos] = new; - nr_convert++; - if (nr_convert == MAXOBJECTS) - die("you're kidding me - hit maximum object limit"); - return new; -} - -static struct entry *lookup_entry(unsigned char *sha1) -{ - int low = 0, high = nr_convert; - - while (low < high) { - int next = (low + high) / 2; - struct entry *n = convert[next]; - int cmp = hashcmp(sha1, n->old_sha1); - if (!cmp) - return n; - if (cmp < 0) { - high = next; - continue; - } - low = next+1; - } - return insert_new(sha1, low); -} - -static void convert_binary_sha1(void *buffer) -{ - struct entry *entry = convert_entry(buffer); - hashcpy(buffer, entry->new_sha1); -} - -static void convert_ascii_sha1(void *buffer) -{ - unsigned char sha1[20]; - struct entry *entry; - - if (get_sha1_hex(buffer, sha1)) - die("expected sha1, got '%s'", (char*) buffer); - entry = convert_entry(sha1); - memcpy(buffer, sha1_to_hex(entry->new_sha1), 40); -} - -static unsigned int convert_mode(unsigned int mode) -{ - unsigned int newmode; - - newmode = mode & S_IFMT; - if (S_ISREG(mode)) - newmode |= (mode & 0100) ? 0755 : 0644; - return newmode; -} - -static int write_subdirectory(void *buffer, unsigned long size, const char *base, int baselen, unsigned char *result_sha1) -{ - char *new = xmalloc(size); - unsigned long newlen = 0; - unsigned long used; - - used = 0; - while (size) { - int len = 21 + strlen(buffer); - char *path = strchr(buffer, ' '); - unsigned char *sha1; - unsigned int mode; - char *slash, *origpath; - - if (!path || strtoul_ui(buffer, 8, &mode)) - die("bad tree conversion"); - mode = convert_mode(mode); - path++; - if (memcmp(path, base, baselen)) - break; - origpath = path; - path += baselen; - slash = strchr(path, '/'); - if (!slash) { - newlen += sprintf(new + newlen, "%o %s", mode, path); - new[newlen++] = '\0'; - hashcpy((unsigned char*)new + newlen, (unsigned char *) buffer + len - 20); - newlen += 20; - - used += len; - size -= len; - buffer = (char *) buffer + len; - continue; - } - - newlen += sprintf(new + newlen, "%o %.*s", S_IFDIR, (int)(slash - path), path); - new[newlen++] = 0; - sha1 = (unsigned char *)(new + newlen); - newlen += 20; - - len = write_subdirectory(buffer, size, origpath, slash-origpath+1, sha1); - - used += len; - size -= len; - buffer = (char *) buffer + len; - } - - write_sha1_file(new, newlen, tree_type, result_sha1); - free(new); - return used; -} - -static void convert_tree(void *buffer, unsigned long size, unsigned char *result_sha1) -{ - void *orig_buffer = buffer; - unsigned long orig_size = size; - - while (size) { - size_t len = 1+strlen(buffer); - - convert_binary_sha1((char *) buffer + len); - - len += 20; - if (len > size) - die("corrupt tree object"); - size -= len; - buffer = (char *) buffer + len; - } - - write_subdirectory(orig_buffer, orig_size, "", 0, result_sha1); -} - -static unsigned long parse_oldstyle_date(const char *buf) -{ - char c, *p; - char buffer[100]; - struct tm tm; - const char *formats[] = { - "%c", - "%a %b %d %T", - "%Z", - "%Y", - " %Y", - NULL - }; - /* We only ever did two timezones in the bad old format .. */ - const char *timezones[] = { - "PDT", "PST", "CEST", NULL - }; - const char **fmt = formats; - - p = buffer; - while (isspace(c = *buf)) - buf++; - while ((c = *buf++) != '\n') - *p++ = c; - *p++ = 0; - buf = buffer; - memset(&tm, 0, sizeof(tm)); - do { - const char *next = strptime(buf, *fmt, &tm); - if (next) { - if (!*next) - return mktime(&tm); - buf = next; - } else { - const char **p = timezones; - while (isspace(*buf)) - buf++; - while (*p) { - if (!memcmp(buf, *p, strlen(*p))) { - buf += strlen(*p); - break; - } - p++; - } - } - fmt++; - } while (*buf && *fmt); - printf("left: %s\n", buf); - return mktime(&tm); -} - -static int convert_date_line(char *dst, void **buf, unsigned long *sp) -{ - unsigned long size = *sp; - char *line = *buf; - char *next = strchr(line, '\n'); - char *date = strchr(line, '>'); - int len; - - if (!next || !date) - die("missing or bad author/committer line %s", line); - next++; date += 2; - - *buf = next; - *sp = size - (next - line); - - len = date - line; - memcpy(dst, line, len); - dst += len; - - /* Is it already in new format? */ - if (isdigit(*date)) { - int datelen = next - date; - memcpy(dst, date, datelen); - return len + datelen; - } - - /* - * Hacky hacky: one of the sparse old-style commits does not have - * any date at all, but we can fake it by using the committer date. - */ - if (*date == '\n' && strchr(next, '>')) - date = strchr(next, '>')+2; - - return len + sprintf(dst, "%lu -0700\n", parse_oldstyle_date(date)); -} - -static void convert_date(void *buffer, unsigned long size, unsigned char *result_sha1) -{ - char *new = xmalloc(size + 100); - unsigned long newlen = 0; - - /* "tree \n" */ - memcpy(new + newlen, buffer, 46); - newlen += 46; - buffer = (char *) buffer + 46; - size -= 46; - - /* "parent \n" */ - while (!memcmp(buffer, "parent ", 7)) { - memcpy(new + newlen, buffer, 48); - newlen += 48; - buffer = (char *) buffer + 48; - size -= 48; - } - - /* "author xyz date" */ - newlen += convert_date_line(new + newlen, &buffer, &size); - /* "committer xyz date" */ - newlen += convert_date_line(new + newlen, &buffer, &size); - - /* Rest */ - memcpy(new + newlen, buffer, size); - newlen += size; - - write_sha1_file(new, newlen, commit_type, result_sha1); - free(new); -} - -static void convert_commit(void *buffer, unsigned long size, unsigned char *result_sha1) -{ - void *orig_buffer = buffer; - unsigned long orig_size = size; - - if (memcmp(buffer, "tree ", 5)) - die("Bad commit '%s'", (char*) buffer); - convert_ascii_sha1((char *) buffer + 5); - buffer = (char *) buffer + 46; /* "tree " + "hex sha1" + "\n" */ - while (!memcmp(buffer, "parent ", 7)) { - convert_ascii_sha1((char *) buffer + 7); - buffer = (char *) buffer + 48; - } - convert_date(orig_buffer, orig_size, result_sha1); -} - -static struct entry * convert_entry(unsigned char *sha1) -{ - struct entry *entry = lookup_entry(sha1); - enum object_type type; - void *buffer, *data; - unsigned long size; - - if (entry->converted) - return entry; - data = read_sha1_file(sha1, &type, &size); - if (!data) - die("unable to read object %s", sha1_to_hex(sha1)); - - buffer = xmalloc(size); - memcpy(buffer, data, size); - - if (type == OBJ_BLOB) { - write_sha1_file(buffer, size, blob_type, entry->new_sha1); - } else if (type == OBJ_TREE) - convert_tree(buffer, size, entry->new_sha1); - else if (type == OBJ_COMMIT) - convert_commit(buffer, size, entry->new_sha1); - else - die("unknown object type %d in %s", type, sha1_to_hex(sha1)); - entry->converted = 1; - free(buffer); - free(data); - return entry; -} - -int main(int argc, char **argv) -{ - unsigned char sha1[20]; - struct entry *entry; - - setup_git_directory(); - - if (argc != 2) - usage("git-convert-objects "); - if (get_sha1(argv[1], sha1)) - die("Not a valid object name %s", argv[1]); - - entry = convert_entry(sha1); - printf("new sha1: %s\n", sha1_to_hex(entry->new_sha1)); - return 0; -} diff --git a/diff.c b/diff.c index 0ee9ea1c1b..35e3c61986 100644 --- a/diff.c +++ b/diff.c @@ -1675,7 +1675,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) return 0; } -void diff_free_filespec_data(struct diff_filespec *s) +void diff_free_filespec_data_large(struct diff_filespec *s) { if (s->should_free) free(s->data); @@ -1686,6 +1686,11 @@ void diff_free_filespec_data(struct diff_filespec *s) s->should_free = s->should_munmap = 0; s->data = NULL; } +} + +void diff_free_filespec_data(struct diff_filespec *s) +{ + diff_free_filespec_data_large(s); free(s->cnt_data); s->cnt_data = NULL; } diff --git a/diffcore-rename.c b/diffcore-rename.c index 41b35c3a9e..4fc200064a 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -184,7 +184,8 @@ static int estimate_similarity(struct diff_filespec *src, if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE) return 0; - if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) + if ((!src->cnt_data && diff_populate_filespec(src, 0)) + || (!dst->cnt_data && diff_populate_filespec(dst, 0))) return 0; /* error but caught downstream */ @@ -377,10 +378,10 @@ void diffcore_rename(struct diff_options *options) m->score = estimate_similarity(one, two, minimum_score); m->name_score = basename_same(one, two); - diff_free_filespec_data(one); + diff_free_filespec_data_large(one); } /* We do not need the text anymore */ - diff_free_filespec_data(two); + diff_free_filespec_data_large(two); dst_cnt++; } /* cost matrix sorted by most to least similar pair */ diff --git a/diffcore.h b/diffcore.h index eef17c4ca2..4bf175bda9 100644 --- a/diffcore.h +++ b/diffcore.h @@ -48,6 +48,7 @@ extern void fill_filespec(struct diff_filespec *, const unsigned char *, extern int diff_populate_filespec(struct diff_filespec *, int); extern void diff_free_filespec_data(struct diff_filespec *); +extern void diff_free_filespec_data_large(struct diff_filespec *); extern int diff_filespec_is_binary(struct diff_filespec *); struct diff_filepair { diff --git a/git-am.sh b/git-am.sh index 6809aa07f6..32c46d7ed4 100755 --- a/git-am.sh +++ b/git-am.sh @@ -62,10 +62,8 @@ fall_back_3way () { mkdir "$dotest/patch-merge-tmp-dir" # First see if the patch records the index info that we can use. - git apply -z --index-info "$dotest/patch" \ - >"$dotest/patch-merge-index-info" && - GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ - git update-index -z --index-info <"$dotest/patch-merge-index-info" && + git apply --build-fake-ancestor "$dotest/patch-merge-tmp-index" \ + "$dotest/patch" && GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ git write-tree >"$dotest/patch-merge-base+" || cannot_fallback "Repository lacks necessary blobs to fall back on 3-way merge." @@ -109,7 +107,7 @@ dotest=.dotest sign= utf8=t keep= skip= interactive= resolved= binary= resolvemsg= resume= git_apply_opt= -while case "$#" in 0) break;; esac +while test $# != 0 do case "$1" in -d=*|--d=*|--do=*|--dot=*|--dote=*|--dotes=*|--dotest=*) diff --git a/git-clean.sh b/git-clean.sh index a5cfd9f07a..4491738186 100755 --- a/git-clean.sh +++ b/git-clean.sh @@ -26,7 +26,7 @@ rmrf="rm -rf --" rm_refuse="echo Not removing" echo1="echo" -while case "$#" in 0) break ;; esac +while test $# != 0 do case "$1" in -d) diff --git a/git-commit.sh b/git-commit.sh index 3e46dbba74..cb14f06216 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -89,7 +89,7 @@ force_author= only_include_assumed= untracked_files= templatefile="`git config commit.template`" -while case "$#" in 0) break;; esac +while test $# != 0 do case "$1" in -F|--F|-f|--f|--fi|--fil|--file) diff --git a/git-fetch.sh b/git-fetch.sh index c3a200120d..e44af2c86d 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -27,7 +27,7 @@ shallow_depth= no_progress= test -t 1 || no_progress=--no-progress quiet= -while case "$#" in 0) break ;; esac +while test $# != 0 do case "$1" in -a|--a|--ap|--app|--appe|--appen|--append) diff --git a/git-filter-branch.sh b/git-filter-branch.sh index a4b6577bd4..a12f6c2d4c 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -105,8 +105,9 @@ filter_tag_name= filter_subdir= orig_namespace=refs/original/ force= -while case "$#" in 0) usage;; esac +while : do + test $# = 0 && usage case "$1" in --) shift diff --git a/git-instaweb.sh b/git-instaweb.sh index b79c6b6a42..f5629e7439 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -61,7 +61,7 @@ stop_httpd () { test -f "$fqgitdir/pid" && kill `cat "$fqgitdir/pid"` } -while case "$#" in 0) break ;; esac +while test $# != 0 do case "$1" in --stop|stop) diff --git a/git-ls-remote.sh b/git-ls-remote.sh index b7e5d04584..d56cf92ebf 100755 --- a/git-ls-remote.sh +++ b/git-ls-remote.sh @@ -13,7 +13,7 @@ die () { } exec= -while case "$#" in 0) break;; esac +while test $# != 0 do case "$1" in -h|--h|--he|--hea|--head|--heads) diff --git a/git-merge.sh b/git-merge.sh index 3a01db0d75..6c513dcbdf 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -97,6 +97,19 @@ finish () { fi ;; esac + + # Run a post-merge hook + if test -x "$GIT_DIR"/hooks/post-merge + then + case "$squash" in + t) + "$GIT_DIR"/hooks/post-merge 1 + ;; + '') + "$GIT_DIR"/hooks/post-merge 0 + ;; + esac + fi } merge_name () { @@ -122,7 +135,7 @@ merge_name () { case "$#" in 0) usage ;; esac have_message= -while case "$#" in 0) break ;; esac +while test $# != 0 do case "$1" in -n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\ diff --git a/git-mergetool.sh b/git-mergetool.sh index 47a80553ad..9f4f3134b6 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -12,6 +12,7 @@ USAGE='[--tool=tool] [file to merge] ...' SUBDIRECTORY_OK=Yes . git-sh-setup require_work_tree +prefix=$(git rev-parse --show-prefix) # Returns true if the mode reflects a symlink is_symlink () { @@ -162,9 +163,9 @@ merge_file () { local_mode=`git ls-files -u -- "$path" | awk '{if ($3==2) print $1;}'` remote_mode=`git ls-files -u -- "$path" | awk '{if ($3==3) print $1;}'` - base_present && git cat-file blob ":1:$path" > "$BASE" 2>/dev/null - local_present && git cat-file blob ":2:$path" > "$LOCAL" 2>/dev/null - remote_present && git cat-file blob ":3:$path" > "$REMOTE" 2>/dev/null + base_present && git cat-file blob ":1:$prefix$path" >"$BASE" 2>/dev/null + local_present && git cat-file blob ":2:$prefix$path" >"$LOCAL" 2>/dev/null + remote_present && git cat-file blob ":3:$prefix$path" >"$REMOTE" 2>/dev/null if test -z "$local_mode" -o -z "$remote_mode"; then echo "Deleted merge conflict for '$path':" @@ -191,10 +192,10 @@ merge_file () { case "$merge_tool" in kdiff3) if base_present ; then - (kdiff3 --auto --L1 "$path (Base)" -L2 "$path (Local)" --L3 "$path (Remote)" \ + (kdiff3 --auto --L1 "$path (Base)" --L2 "$path (Local)" --L3 "$path (Remote)" \ -o "$path" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1) else - (kdiff3 --auto -L1 "$path (Local)" --L2 "$path (Remote)" \ + (kdiff3 --auto --L1 "$path (Local)" --L2 "$path (Remote)" \ -o "$path" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1) fi status=$? @@ -251,9 +252,9 @@ merge_file () { ;; emerge) if base_present ; then - emacs -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$path" + emacs -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$path")" else - emacs -f emerge-files-command "$LOCAL" "$REMOTE" "$path" + emacs -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$path")" fi status=$? save_backup @@ -268,7 +269,7 @@ merge_file () { cleanup_temp_files } -while case $# in 0) break ;; esac +while test $# != 0 do case "$1" in -t|--tool*) diff --git a/git-pull.sh b/git-pull.sh index 5e96d1f228..c3f05f56de 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -16,7 +16,7 @@ test -z "$(git ls-files -u)" || die "You are in the middle of a conflicted merge." strategy_args= no_summary= no_commit= squash= -while case "$#,$1" in 0) break ;; *,-*) ;; *) break ;; esac +while : do case "$1" in -n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\ @@ -46,8 +46,8 @@ do -h|--h|--he|--hel|--help) usage ;; - -*) - # Pass thru anything that is meant for fetch. + *) + # Pass thru anything that may be meant for fetch. break ;; esac diff --git a/git-quiltimport.sh b/git-quiltimport.sh index 9de54d19fb..880c81d121 100755 --- a/git-quiltimport.sh +++ b/git-quiltimport.sh @@ -5,7 +5,7 @@ SUBDIRECTORY_ON=Yes dry_run="" quilt_author="" -while case "$#" in 0) break;; esac +while test $# != 0 do case "$1" in --au=*|--aut=*|--auth=*|--autho=*|--author=*) @@ -71,6 +71,10 @@ commit=$(git rev-parse HEAD) mkdir $tmp_dir || exit 2 for patch_name in $(grep -v '^#' < "$QUILT_PATCHES/series" ); do + if ! [ -f "$QUILT_PATCHES/$patch_name" ] ; then + echo "$patch_name doesn't exist. Skipping." + continue + fi echo $patch_name git mailinfo "$tmp_msg" "$tmp_patch" \ <"$QUILT_PATCHES/$patch_name" >"$tmp_info" || exit 3 diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index abc2b1c3e0..268a629c43 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -36,14 +36,14 @@ warn () { output () { case "$VERBOSE" in '') - "$@" > "$DOTEST"/output 2>&1 + output=$("$@" 2>&1 ) status=$? - test $status != 0 && - cat "$DOTEST"/output + test $status != 0 && printf "%s\n" "$output" return $status - ;; + ;; *) "$@" + ;; esac } @@ -63,6 +63,7 @@ comment_for_reflog () { ''|rebase*) GIT_REFLOG_ACTION="rebase -i ($1)" export GIT_REFLOG_ACTION + ;; esac } @@ -70,22 +71,23 @@ mark_action_done () { sed -e 1q < "$TODO" >> "$DONE" sed -e 1d < "$TODO" >> "$TODO".new mv -f "$TODO".new "$TODO" - count=$(($(wc -l < "$DONE"))) - total=$(($count+$(wc -l < "$TODO"))) + count=$(($(grep -ve '^$' -e '^#' < "$DONE" | wc -l))) + total=$(($count+$(grep -ve '^$' -e '^#' < "$TODO" | wc -l))) printf "Rebasing (%d/%d)\r" $count $total test -z "$VERBOSE" || echo } make_patch () { - parent_sha1=$(git rev-parse --verify "$1"^ 2> /dev/null) + parent_sha1=$(git rev-parse --verify "$1"^) || + die "Cannot get patch for $1^" git diff "$parent_sha1".."$1" > "$DOTEST"/patch + test -f "$DOTEST"/message || + git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message + test -f "$DOTEST"/author-script || + get_author_ident_from_commit "$1" > "$DOTEST"/author-script } die_with_patch () { - test -f "$DOTEST"/message || - git cat-file commit $sha1 | sed "1,/^$/d" > "$DOTEST"/message - test -f "$DOTEST"/author-script || - get_author_ident_from_commit $sha1 > "$DOTEST"/author-script make_patch "$1" die "$2" } @@ -95,13 +97,18 @@ die_abort () { die "$1" } +has_action () { + grep -vqe '^$' -e '^#' "$1" +} + pick_one () { no_ff= case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1" test -d "$REWRITTEN" && pick_one_preserving_merges "$@" && return - parent_sha1=$(git rev-parse --verify $sha1^ 2>/dev/null) + parent_sha1=$(git rev-parse --verify $sha1^) || + die "Could not get the parent of $sha1" current_sha1=$(git rev-parse --verify HEAD) if test $no_ff$current_sha1 = $parent_sha1; then output git reset --hard $sha1 @@ -129,7 +136,7 @@ pick_one_preserving_merges () { fast_forward=t preserve=t new_parents= - for p in $(git rev-list --parents -1 $sha1 | cut -d\ -f2-) + for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-) do if test -f "$REWRITTEN"/$p then @@ -141,41 +148,47 @@ pick_one_preserving_merges () { ;; # do nothing; that parent is already there *) new_parents="$new_parents $new_p" + ;; esac fi done case $fast_forward in t) output warn "Fast forward to $sha1" - test $preserve=f && echo $sha1 > "$REWRITTEN"/$sha1 + test $preserve = f || echo $sha1 > "$REWRITTEN"/$sha1 ;; f) test "a$1" = a-n && die "Refusing to squash a merge: $sha1" - first_parent=$(expr "$new_parents" : " \([^ ]*\)") + first_parent=$(expr "$new_parents" : ' \([^ ]*\)') # detach HEAD to current parent output git checkout $first_parent 2> /dev/null || die "Cannot move HEAD to $first_parent" echo $sha1 > "$DOTEST"/current-commit case "$new_parents" in - \ *\ *) + ' '*' '*) # redo merge author_script=$(get_author_ident_from_commit $sha1) eval "$author_script" - msg="$(git cat-file commit $sha1 | \ - sed -e '1,/^$/d' -e "s/[\"\\]/\\\\&/g")" + msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')" # NEEDSWORK: give rerere a chance - if ! output git merge $STRATEGY -m "$msg" $new_parents + if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ + GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ + GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ + output git merge $STRATEGY -m "$msg" \ + $new_parents then - echo "$msg" > "$GIT_DIR"/MERGE_MSG + printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG die Error redoing merge $sha1 fi ;; *) output git cherry-pick $STRATEGY "$@" || die_with_patch $sha1 "Could not pick $sha1" + ;; esac + ;; esac } @@ -212,11 +225,11 @@ peek_next_command () { } do_next () { - test -f "$DOTEST"/message && rm "$DOTEST"/message - test -f "$DOTEST"/author-script && rm "$DOTEST"/author-script + rm -f "$DOTEST"/message "$DOTEST"/author-script \ + "$DOTEST"/amend || exit read command sha1 rest < "$TODO" case "$command" in - \#|'') + '#'*|'') mark_action_done ;; pick) @@ -233,6 +246,7 @@ do_next () { pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" make_patch $sha1 + : > "$DOTEST"/amend warn warn "You can amend the commit now, with" warn @@ -243,7 +257,7 @@ do_next () { squash) comment_for_reflog squash - test -z "$(grep -ve '^$' -e '^#' < $DONE)" && + has_action "$DONE" || die "Cannot 'squash' without a previous commit" mark_action_done @@ -253,11 +267,12 @@ do_next () { EDIT_COMMIT= USE_OUTPUT=output cp "$MSG" "$SQUASH_MSG" - ;; + ;; *) EDIT_COMMIT=-e USE_OUTPUT= - test -f "$SQUASH_MSG" && rm "$SQUASH_MSG" + rm -f "$SQUASH_MSG" || exit + ;; esac failed=f @@ -269,7 +284,9 @@ do_next () { f) # This is like --amend, but with a different message eval "$author_script" - export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE + GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ + GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ + GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ $USE_OUTPUT git commit -F "$MSG" $EDIT_COMMIT ;; t) @@ -277,11 +294,13 @@ do_next () { warn warn "Could not apply $sha1... $rest" die_with_patch $sha1 "" + ;; esac ;; *) warn "Unknown command: $command $sha1 $rest" die_with_patch $sha1 "Please fix this in the file $TODO." + ;; esac test -s "$TODO" && return @@ -317,7 +336,7 @@ do_rest () { done } -while case $# in 0) break ;; esac +while test $# != 0 do case "$1" in --continue) @@ -330,7 +349,9 @@ do git update-index --refresh && git diff-files --quiet && ! git diff-index --cached --quiet HEAD && - . "$DOTEST"/author-script && + . "$DOTEST"/author-script && { + test ! -f "$DOTEST"/amend || git reset --soft HEAD^ + } && export GIT_AUTHOR_NAME GIT_AUTHOR_NAME GIT_AUTHOR_DATE && git commit -F "$DOTEST"/message -e @@ -406,7 +427,6 @@ do require_clean_work_tree - mkdir "$DOTEST" || die "Could not create temporary $DOTEST" if test ! -z "$2" then output git show-ref --verify --quiet "refs/heads/$2" || @@ -418,6 +438,8 @@ do HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?" UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base" + mkdir "$DOTEST" || die "Could not create temporary $DOTEST" + test -z "$ONTO" && ONTO=$UPSTREAM : > "$DOTEST"/interactive || die "Could not mark as interactive" @@ -468,17 +490,18 @@ EOF $UPSTREAM...$HEAD | \ sed -n "s/^>/pick /p" >> "$TODO" - test -z "$(grep -ve '^$' -e '^#' < $TODO)" && + has_action "$TODO" || die_abort "Nothing to do" cp "$TODO" "$TODO".backup git_editor "$TODO" || die "Could not execute editor" - test -z "$(grep -ve '^$' -e '^#' < $TODO)" && + has_action "$TODO" || die_abort "Nothing to do" output git checkout $ONTO && do_rest + ;; esac shift done diff --git a/git-rebase.sh b/git-rebase.sh index c9942f2400..1583402a06 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -122,15 +122,14 @@ finish_rb_merge () { is_interactive () { test -f "$dotest"/interactive || - while case $#,"$1" in 0,|*,-i|*,--interactive) break ;; esac - do + while :; do case $#,"$1" in 0,|*,-i|*,--interactive) break ;; esac shift done && test -n "$1" } is_interactive "$@" && exec git-rebase--interactive "$@" -while case "$#" in 0) break ;; esac +while test $# != 0 do case "$1" in --continue) diff --git a/git-repack.sh b/git-repack.sh index 156c5e8f4a..0aae1a3ed5 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -9,7 +9,7 @@ SUBDIRECTORY_OK='Yes' no_update_info= all_into_one= remove_redundant= local= quiet= no_reuse= extra= -while case "$#" in 0) break ;; esac +while test $# != 0 do case "$1" in -n) no_update_info=t ;; diff --git a/git-submodule.sh b/git-submodule.sh index 3320998c76..673aa27a45 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -251,7 +251,7 @@ modules_list() done } -while case "$#" in 0) break ;; esac +while test $# != 0 do case "$1" in add) diff --git a/git-svn.perl b/git-svn.perl index 288d32cb16..484b0576a8 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -3578,7 +3578,7 @@ sub config_pager { } sub run_pager { - return unless -t *STDOUT; + return unless -t *STDOUT && defined $pager; pipe my $rfd, my $wfd or return; defined(my $pid = fork) or ::fatal "Can't fork: $!\n"; if (!$pid) { diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b2bae1b250..3064298f28 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2809,7 +2809,7 @@ sub git_difftree_body { "diff_tree\">\n"; # header only for combined diff in 'commitdiff' view - my $has_header = @parents > 1 && $action eq 'commitdiff'; + my $has_header = @$difftree && @parents > 1 && $action eq 'commitdiff'; if ($has_header) { # table header print "\n" . @@ -3191,44 +3191,10 @@ sub git_patchset_body { } } until (!defined $to_name || $to_name eq $diffinfo->{'to_file'} || $patch_idx > $#$difftree); + # modifies %from, %to hashes parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents); - if ($diffinfo->{'nparents'}) { - # combined diff - $from{'file'} = []; - $from{'href'} = []; - fill_from_file_info($diffinfo, @hash_parents) - unless exists $diffinfo->{'from_file'}; - for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) { - $from{'file'}[$i] = $diffinfo->{'from_file'}[$i] || $diffinfo->{'to_file'}; - if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file - $from{'href'}[$i] = href(action=>"blob", - hash_base=>$hash_parents[$i], - hash=>$diffinfo->{'from_id'}[$i], - file_name=>$from{'file'}[$i]); - } else { - $from{'href'}[$i] = undef; - } - } - } else { - $from{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'}; - if ($diffinfo->{'status'} ne "A") { # not new (added) file - $from{'href'} = href(action=>"blob", hash_base=>$hash_parent, - hash=>$diffinfo->{'from_id'}, - file_name=>$from{'file'}); - } else { - delete $from{'href'}; - } - } - $to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'}; - if (!is_deleted($diffinfo)) { # file exists in result - $to{'href'} = href(action=>"blob", hash_base=>$hash, - hash=>$diffinfo->{'to_id'}, - file_name=>$to{'file'}); - } else { - delete $to{'href'}; - } # this is first patch for raw difftree line with $patch_idx index # we index @$difftree array from 0, but number patches from 1 print "
\n"; diff --git a/merge-recursive.c b/merge-recursive.c index 19d5f3b287..97dcf9bf02 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -96,6 +96,7 @@ static struct path_list current_directory_set = {NULL, 0, 0, 1}; static int call_depth = 0; static int verbosity = 2; +static int rename_limit = -1; static int buffer_output = 1; static struct output_buffer *output_list, *output_end; @@ -372,6 +373,7 @@ static struct path_list *get_renames(struct tree *tree, diff_setup(&opts); opts.recursive = 1; opts.detect_rename = DIFF_DETECT_RENAME; + opts.rename_limit = rename_limit; opts.output_format = DIFF_FORMAT_NO_OUTPUT; if (diff_setup_done(&opts) < 0) die("diff setup failed"); @@ -1693,6 +1695,10 @@ static int merge_config(const char *var, const char *value) verbosity = git_config_int(var, value); return 0; } + if (!strcasecmp(var, "diff.renamelimit")) { + rename_limit = git_config_int(var, value); + return 0; + } return git_default_config(var, value); } diff --git a/revision.c b/revision.c index 33d092c3c4..658471385c 100644 --- a/revision.c +++ b/revision.c @@ -1209,8 +1209,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i); if (opts > 0) { - if (strcmp(argv[i], "-z")) - revs->diff = 1; i += opts - 1; continue; } @@ -1254,6 +1252,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch add_pending_object_with_mode(revs, object, def, mode); } + /* Did the user ask for any diff output? Run the diff! */ + if (revs->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) + revs->diff = 1; + + /* Pickaxe needs diffs */ + if (revs->diffopt.pickaxe) + revs->diff = 1; + if (revs->topo_order) revs->limited = 1; diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 718c9c1fa3..1af73a47c6 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -80,7 +80,7 @@ cat "$1".tmp action=pick for line in $FAKE_LINES; do case $line in - squash) + squash|edit) action="$line";; *) echo sed -n "${line}s/^pick/$action/p" @@ -297,4 +297,16 @@ test_expect_success 'ignore patch if in upstream' ' test $HEAD = $(git rev-parse HEAD^) ' +test_expect_success '--continue tries to commit, even for "edit"' ' + parent=$(git rev-parse HEAD^) && + test_tick && + FAKE_LINES="edit 1" git rebase -i HEAD^ && + echo edited > file7 && + git add file7 && + FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue && + test edited = $(git show HEAD:file7) && + git show HEAD | grep chouette && + test $parent = $(git rev-parse HEAD^) +' + test_done diff --git a/t/t5402-post-merge-hook.sh b/t/t5402-post-merge-hook.sh new file mode 100755 index 0000000000..1c4b0b32ab --- /dev/null +++ b/t/t5402-post-merge-hook.sh @@ -0,0 +1,56 @@ +#!/bin/sh +# +# Copyright (c) 2006 Josh England +# + +test_description='Test the post-merge hook.' +. ./test-lib.sh + +test_expect_success setup ' + echo Data for commit0. >a && + git update-index --add a && + tree0=$(git write-tree) && + commit0=$(echo setup | git commit-tree $tree0) && + echo Changed data for commit1. >a && + git update-index a && + tree1=$(git write-tree) && + commit1=$(echo modify | git commit-tree $tree1 -p $commit0) && + git update-ref refs/heads/master $commit0 && + git-clone ./. clone1 && + GIT_DIR=clone1/.git git update-index --add a && + git-clone ./. clone2 && + GIT_DIR=clone2/.git git update-index --add a +' + +for clone in 1 2; do + cat >clone${clone}/.git/hooks/post-merge <<'EOF' +#!/bin/sh +echo $@ >> $GIT_DIR/post-merge.args +EOF + chmod u+x clone${clone}/.git/hooks/post-merge +done + +test_expect_failure 'post-merge does not run for up-to-date ' ' + GIT_DIR=clone1/.git git merge $commit0 && + test -e clone1/.git/post-merge.args +' + +test_expect_success 'post-merge runs as expected ' ' + GIT_DIR=clone1/.git git merge $commit1 && + test -e clone1/.git/post-merge.args +' + +test_expect_success 'post-merge from normal merge receives the right argument ' ' + grep 0 clone1/.git/post-merge.args +' + +test_expect_success 'post-merge from squash merge runs as expected ' ' + GIT_DIR=clone2/.git git merge --squash $commit1 && + test -e clone2/.git/post-merge.args +' + +test_expect_success 'post-merge from squash merge receives the right argument ' ' + grep 1 clone2/.git/post-merge.args +' + +test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index e9ea33c18d..83f9470202 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -30,7 +30,7 @@ test_expect_success 'Extract patches' ' ' test_expect_success 'Send patches' ' - git send-email -from="Example " --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors + git send-email --from="Example " --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors ' cat >expected <<\EOF