diff options
137 files changed, 4762 insertions, 1550 deletions
diff --git a/.gitignore b/.gitignore index bbd7f558e7..bac60ce31a 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ git-diff-files git-diff-index git-diff-tree git-describe +git-fast-export git-fast-import git-fetch git-fetch--tool diff --git a/Documentation/Makefile b/Documentation/Makefile index d88664177d..16ee0d3972 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -79,16 +79,16 @@ man7: $(DOC_MAN7) info: git.info install: man - $(INSTALL) -d -m755 $(DESTDIR)$(man1dir) - $(INSTALL) -d -m755 $(DESTDIR)$(man5dir) - $(INSTALL) -d -m755 $(DESTDIR)$(man7dir) - $(INSTALL) -m644 $(DOC_MAN1) $(DESTDIR)$(man1dir) - $(INSTALL) -m644 $(DOC_MAN5) $(DESTDIR)$(man5dir) - $(INSTALL) -m644 $(DOC_MAN7) $(DESTDIR)$(man7dir) + $(INSTALL) -d -m 755 $(DESTDIR)$(man1dir) + $(INSTALL) -d -m 755 $(DESTDIR)$(man5dir) + $(INSTALL) -d -m 755 $(DESTDIR)$(man7dir) + $(INSTALL) -m 644 $(DOC_MAN1) $(DESTDIR)$(man1dir) + $(INSTALL) -m 644 $(DOC_MAN5) $(DESTDIR)$(man5dir) + $(INSTALL) -m 644 $(DOC_MAN7) $(DESTDIR)$(man7dir) install-info: info - $(INSTALL) -d -m755 $(DESTDIR)$(infodir) - $(INSTALL) -m644 git.info $(DESTDIR)$(infodir) + $(INSTALL) -d -m 755 $(DESTDIR)$(infodir) + $(INSTALL) -m 644 git.info $(DESTDIR)$(infodir) if test -r $(DESTDIR)$(infodir)/dir; then \ $(INSTALL_INFO) --info-dir=$(DESTDIR)$(infodir) git.info ;\ else \ @@ -122,9 +122,9 @@ cmds_txt = cmds-ancillaryinterrogators.txt \ $(cmds_txt): cmd-list.made -cmd-list.made: cmd-list.perl $(MAN1_TXT) +cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT) $(RM) $@ - perl ./cmd-list.perl + perl ./cmd-list.perl ../command-list.txt date >$@ git.7 git.html: git.txt diff --git a/Documentation/RelNotes-1.5.3.7.txt b/Documentation/RelNotes-1.5.3.7.txt new file mode 100644 index 0000000000..2f690616c8 --- /dev/null +++ b/Documentation/RelNotes-1.5.3.7.txt @@ -0,0 +1,45 @@ +GIT v1.5.3.7 Release Notes +========================== + +Fixes since v1.5.3.6 +-------------------- + + * git-send-email added 8-bit contents to the payload without + marking it as 8-bit in a CTE header. + + * "git-bundle create a.bndl HEAD" dereferenced the symref and + did not record the ref as 'HEAD'; this prevented a bundle + from being used as a normal source of git-clone. + + * The code to reject nonsense command line of the form + "git-commit -a paths..." and "git-commit --interactive + paths..." were broken. + + * Adding a signature that is not ASCII-only to an original + commit that is ASCII-only would make the result non-ASCII. + "git-format-patch -s" did not mark such a message correctly + with MIME encoding header. + + * git-add sometimes did not mark the resulting index entry + stat-clean. This affected only cases when adding the + contents with the same length as the previously staged + contents, and the previous staging made the index entry + "racily clean". + + * git-commit did not honor GIT_INDEX_FILE the user had in the + environment. + + * When checking out a revision, git-checkout did not report where the + updated HEAD is if you happened to have a file called HEAD in the + work tree. + + * "git-rev-list --objects" mishandled a tree that points at a + submodule. + + * "git cvsimport" was not ready for packed refs that "git gc" can + produce and gave incorrect results. + + * Many scripted Porcelains were confused when you happened to have a + file called "HEAD" in your work tree. + +Also it contains updates to the user manual and documentation. diff --git a/Documentation/RelNotes-1.5.4.txt b/Documentation/RelNotes-1.5.4.txt index a4a2a7f429..c40fd1805f 100644 --- a/Documentation/RelNotes-1.5.4.txt +++ b/Documentation/RelNotes-1.5.4.txt @@ -1,98 +1,231 @@ GIT v1.5.4 Release Notes ======================== +Removal +------- + + * "git svnimport" was removed in favor of "git svn". It is still there + in the source tree (contrib/examples) but unsupported. + + +Deprecation notices +------------------- + + * Next feature release of git (this change is scheduled for v1.5.5 but + it could slip) will by default install dashed form of commands + (e.g. "git-commit") outside of users' normal $PATH, and will install + only selected commands ("git" itself, and "gitk") in $PATH. This + implies: + + - Using dashed form of git commands (e.g. "git-commit") from the + command line has been informally deprecated since early 2006, but + now it officially is, and will be removed in the future. Use + dashless form (e.g. "git commit") instead. + + - Using dashed from from your scripts, without first prepending the + return value from "git --exec-path" to the scripts' PATH, has been + informally deprecated since early 2006, but now it officially is. + + - Use of dashed form with "PATH=$(git --exec-path):$PATH; export + PATH" early in your script is not deprecated with this change. + + Users are strongly encouraged to adjust their habits and scripts now + to prepare for this. + + * The post-receive hook was introduced in March 2007 to supersede + post-update hook, primarily to overcome the command line length + limitation of the latter. Use of post-update hook will be deprecated + in future versions of git, perhaps in v1.5.5. + + * "git lost-found" was deprecated in favor of "git fsck"'s --lost-found + option, and will be removed in the future. + + * "git peek-remote" is deprecated, as "git ls-remote" was written in C + and works for all transports, and will be removed in the future. + + Updates since v1.5.3 -------------------- * Comes with much improved gitk. - * "progress display" from many commands are a lot nicer to the - eye. Transfer commands show throughput data. + * Comes with "git gui" 0.9.1 with i18n. + + * gitk is now merged as a subdirectory of git.git project, in + preparation for its i18n. + + * progress display from many commands are a lot nicer to the eye. + Transfer commands show throughput data. + + * many commands that pay attention to per-directory .gitignore now do + so lazily, which makes the usual case go much faster. + + * Output processing for '--pretty=format:<user format>' has been + optimized. + + * Rename detection of diff family, while detecting exact matches, has + been greatly optimized. - * git-reset is now built-in and its output can be squelched with -q. + * Rename detection of diff family tries to make more naturally looking + pairing. Earlier if more than one identical rename sources were + found in the preimage, they were picked pretty much at random. - * git-send-email can optionally talk over ssmtp and use SMTP-AUTH. + * Value "true" for color.diff and color.status configuration used to + mean "always" (even when the output is not going to a terminal). + This has been corrected to mean the same thing as "auto". + + * HTTP proxy can be specified per remote repository using + remote.*.httpproxy configuration, or global http.proxy configuration + variable. + + * Various Perforce importer updates. + + * Example update and post-receive hooks have been improved. - * git-rebase learned --whitespace option. + * Any command that wants to take a commit object name can now use + ":/string" syntax to name a commit. - * git-remote knows --mirror mode. + * "git reset" is now built-in and its output can be squelched with -q. - * git-merge can call the "post-merge" hook. + * "git send-email" can optionally talk over ssmtp and use SMTP-AUTH. - * git-pack-objects can optionally run deltification with multiple threads. + * "git rebase" learned --whitespace option. - * git-archive can optionally substitute keywords in files marked with + * In "git rebase", when you decide not to replay a particular change + after the command stopped with a conflict, you can say "git rebase + --skip" without first running "git reset --hard", as the command now + runs it for you. + + * "git rebase --interactive" mode can now work on detached HEAD. + + * "git rebase" now detaches head during its operation, so after a + successful "git rebase" operation, the reflog entry branch@{1} for + the current branch points at the commit before the rebase was + started. + + * "git rebase -i" also triggers rerere to help your repeated merges. + + * "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. - * git-for-each-ref learned %(xxxdate:<dateformat>) syntax to - show the various date fields in different formats. + * "git cherry-pick" made a misguided attempt to repeat the original + command line in the generated log message, when told to cherry-pick a + commit by naming a tag that points at it. It does not anymore. - * git-gc --auto is a low-impact way to automatically run a - variant of git-repack that does not lose unreferenced objects - (read: safer than the usual one) after the user accumulates - too many loose objects. + * "git for-each-ref" learned %(xxxdate:<dateformat>) syntax to show the + various date fields in different formats. + + * "git gc --auto" is a low-impact way to automatically run a variant of + "git repack" that does not lose unreferenced objects (read: safer + than the usual one) after the user accumulates too many loose + objects. + + * "git clean" has been rewritten in C. * You need to explicitly set clean.requireForce to "false" to allow - git-clean without -f to do any damage (lack of the configuration - variable used to mean "do not require", but we now use the safer - default). + "git clean" without -f to do any damage (lack of the configuration + variable used to mean "do not require -f option to lose untracked + files", but we now use the safer default). - * git-push has been rewritten in C. + * "git push" learned --dry-run option to show what would happen if a + push is run. - * git-push learned --dry-run option to show what would happen - if a push is run. + * "git push" does not update a tracking ref on the local side when the + remote refused to update the corresponding ref. - * git-remote learned "rm" subcommand. + * "git push" learned --mirror option. This is to push the local refs + one-to-one to the remote, and deletes refs from the remote that do + not exist anymore in the repository on the pushing side. - * git-rebase --interactive mode can now work on detached HEAD. + * "git push" can remove a corrupt ref at the remote site with the usual + ":ref" refspec. - * git-cvsserver can be run via git-shell. + * "git remote" knows --mirror mode. This is to set up configuration to + push into a remote repository to store local branch heads to the same + branch on the remote side, and remove branch heads locally removed + from local repository at the same time. Suitable for pushing into a + back-up repository. - * git-am and git-rebase are far less verbose. + * "git remote" learned "rm" subcommand. - * git-pull learned to pass --[no-]ff option to underlying git-merge. + * "git cvsserver" can be run via "git shell". - * Various Perforce importer updates. + * "git am" and "git rebase" are far less verbose. - * git-lost-found was deprecated in favor of git-fsck's --lost-found - option. + * "git pull" learned to pass --[no-]ff option to underlying "git + merge". - * "git log" learned --early-output option to help interactive - GUI implementations. + * "git pull --rebase" is a different way to integrate what you fetched + into your current branch. - * git-svnimport was removed in favor of git-svn. + * "git fast-export" produces datastream that can be fed to fast-import + to reproduce the history recorded in a git repository. - * git-bisect learned "skip" action to mark untestable commits. + * "git commit --allow-empty" allows you to create a single-parent + commit that records the same tree as its parent, overriding the usual + safety valve. - * git-format-patch learned "format.numbered" configuration variable - to automatically turn --numbered option on when more than one - commits are formatted. + * "git commit --amend" can amend a merge that does not change the tree + from its first parent. - * git-ls-files learned "--exclude-standard" to use the canned - set of exclude files. + * "git stash random-text" does not create a new stash anymore. It was + a UI mistake. Use "git stash save random-text", or "git stash" + (without extra args) for that. - * git-rebase now detaches head during its operation, so after a - successful "git rebase" operation, the reflog entry branch@{1} - for the current branch points at the commit before the rebase - was started. + * "git prune --expire <time>" can exempt young loose objects from + getting pruned. - * "git-tag -a -f existing" begins the editor session using the - existing annotation message. + * "git branch --contains <commit>" can list branches that are + descendants of a given commit. - * "git cvsexportcommit" learned -w option to specify and switch - to the CVS working directory. + * "git log" learned --early-output option to help interactive GUI + implementations. - * "git checkout" from a subdirectory learned to use "../path" - to allow checking out a path outside the current directory - without cd'ing up. + * "git bisect" learned "skip" action to mark untestable commits. - * Output processing for '--pretty=format:<user format>' has - been optimized. + * "git format-patch" learned "format.numbered" configuration variable + to automatically turn --numbered option on when more than one commits + are formatted. - * Rename detection diff family, while detecting exact matches, - has been greatly optimized. + * "git ls-files" learned "--exclude-standard" to use the canned set of + exclude files. - * Example update and post-receive hooks have been improved. + * "git tag -a -f existing" begins the editor session using the existing + annotation message. + + * "git tag -m one -m bar" (multiple -m options) behaves similarly to + "git commit"; the parameters to -m options are formatted as separate + paragraphs. + + * "git cvsexportcommit" learned -w option to specify and switch to the + CVS working directory. + + * "git checkout" from a subdirectory learned to use "../path" to allow + checking out a path outside the current directory without cd'ing up. + + * "git send-email --dry-run" shows full headers for easier diagnosis. + + * "git merge-ours" is now built-in. + + * "git svn" learned "info" and "show-externals" subcommands. + + * "git svn" run from a subdirectory failed to read settings from the + .git/config. + + * "git svn" learned --use-log-author option, which picks up more + descriptive name from From: and Signed-off-by: lines in the commit + message. + + * "git status" from a subdirectory now shows relative paths which makes + copy-and-pasting for git-checkout/git-add/git-rm easier. + + * "git checkout" from and to detached HEAD leaves a bit more + information in the reflog. * In addition there are quite a few internal clean-ups. Notably @@ -110,15 +243,14 @@ Fixes since v1.5.3 All of the fixes in v1.5.3 maintenance series are included in this release, unless otherwise noted. - * git-svn talking with the SVN over http will correctly quote branch - and project names. +These fixes are only in v1.5.4 and not backported to v1.5.3 maintenance +series. - * "git rev-list --objects A..B" choked when the lower boundary - of the range involved a subproject. This fix is also queued - for 'maint' (but not in there yet). + * "git svn" talking with the SVN over http will correctly quote branch + and project names. -- exec >/var/tmp/1 -O=v1.5.3.6-727-g5d3d1ca +O=v1.5.3.7-1003-gf38ca7c 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 57a790df63..c2d55cdb5e 100755 --- a/Documentation/cmd-list.perl +++ b/Documentation/cmd-list.perl @@ -28,8 +28,8 @@ sub format_one { } if (my ($verify_name, $text) = ($description =~ /^($name) - (.*)/)) { print $out "gitlink:$name\[1\]::\n\t"; - if ($attr) { - print $out "($attr) "; + if ($attr =~ / deprecated /) { + print $out "(deprecated) "; } print $out "$text.\n\n"; } @@ -39,12 +39,13 @@ sub format_one { } my %cmds = (); -while (<DATA>) { +for (sort <>) { next if /^#/; chomp; my ($name, $cat, $attr) = /^(\S+)\s+(.*?)(?:\s+(.*))?$/; - push @{$cmds{$cat}}, [$name, $attr]; + $attr = '' unless defined $attr; + push @{$cmds{$cat}}, [$name, " $attr "]; } for my $cat (qw(ancillaryinterrogators @@ -71,133 +72,3 @@ for my $cat (qw(ancillaryinterrogators rename "$out+", "$out"; } } - -# The following list is sorted with "sort -d" to make it easier -# to find entry in the resulting git.html manual page. -__DATA__ -git-add mainporcelain -git-am mainporcelain -git-annotate ancillaryinterrogators -git-apply plumbingmanipulators -git-archimport foreignscminterface -git-archive mainporcelain -git-bisect mainporcelain -git-blame ancillaryinterrogators -git-branch mainporcelain -git-bundle mainporcelain -git-cat-file plumbinginterrogators -git-check-attr purehelpers -git-checkout mainporcelain -git-checkout-index plumbingmanipulators -git-check-ref-format purehelpers -git-cherry ancillaryinterrogators -git-cherry-pick mainporcelain -git-citool mainporcelain -git-clean mainporcelain -git-clone mainporcelain -git-commit mainporcelain -git-commit-tree plumbingmanipulators -git-config ancillarymanipulators -git-count-objects ancillaryinterrogators -git-cvsexportcommit foreignscminterface -git-cvsimport foreignscminterface -git-cvsserver foreignscminterface -git-daemon synchingrepositories -git-describe mainporcelain -git-diff mainporcelain -git-diff-files plumbinginterrogators -git-diff-index plumbinginterrogators -git-diff-tree plumbinginterrogators -git-fast-import ancillarymanipulators -git-fetch mainporcelain -git-fetch-pack synchingrepositories -git-filter-branch ancillarymanipulators -git-fmt-merge-msg purehelpers -git-for-each-ref plumbinginterrogators -git-format-patch mainporcelain -git-fsck ancillaryinterrogators -git-gc mainporcelain -git-get-tar-commit-id ancillaryinterrogators -git-grep mainporcelain -git-gui mainporcelain -git-hash-object plumbingmanipulators -git-http-fetch synchelpers -git-http-push synchelpers -git-imap-send foreignscminterface -git-index-pack plumbingmanipulators -git-init mainporcelain -git-instaweb ancillaryinterrogators -gitk mainporcelain -git-log mainporcelain -git-lost-found ancillarymanipulators deprecated -git-ls-files plumbinginterrogators -git-ls-remote plumbinginterrogators -git-ls-tree plumbinginterrogators -git-mailinfo purehelpers -git-mailsplit purehelpers -git-merge mainporcelain -git-merge-base plumbinginterrogators -git-merge-file plumbingmanipulators -git-merge-index plumbingmanipulators -git-merge-one-file purehelpers -git-mergetool ancillarymanipulators -git-merge-tree ancillaryinterrogators -git-mktag plumbingmanipulators -git-mktree plumbingmanipulators -git-mv mainporcelain -git-name-rev plumbinginterrogators -git-pack-objects plumbingmanipulators -git-pack-redundant plumbinginterrogators -git-pack-refs ancillarymanipulators -git-parse-remote synchelpers -git-patch-id purehelpers -git-peek-remote purehelpers -git-prune ancillarymanipulators -git-prune-packed plumbingmanipulators -git-pull mainporcelain -git-push mainporcelain -git-quiltimport foreignscminterface -git-read-tree plumbingmanipulators -git-rebase mainporcelain -git-receive-pack synchelpers -git-reflog ancillarymanipulators -git-relink ancillarymanipulators -git-remote ancillarymanipulators -git-repack ancillarymanipulators -git-request-pull foreignscminterface -git-rerere ancillaryinterrogators -git-reset mainporcelain -git-revert mainporcelain -git-rev-list plumbinginterrogators -git-rev-parse ancillaryinterrogators -git-rm mainporcelain -git-runstatus ancillaryinterrogators -git-send-email foreignscminterface -git-send-pack synchingrepositories -git-shell synchelpers -git-shortlog mainporcelain -git-show mainporcelain -git-show-branch ancillaryinterrogators -git-show-index plumbinginterrogators -git-show-ref plumbinginterrogators -git-sh-setup purehelpers -git-stash mainporcelain -git-status mainporcelain -git-stripspace purehelpers -git-submodule mainporcelain -git-svn foreignscminterface -git-symbolic-ref plumbingmanipulators -git-tag mainporcelain -git-tar-tree plumbinginterrogators deprecated -git-unpack-file plumbinginterrogators -git-unpack-objects plumbingmanipulators -git-update-index plumbingmanipulators -git-update-ref plumbingmanipulators -git-update-server-info synchingrepositories -git-upload-archive synchelpers -git-upload-pack synchelpers -git-var plumbinginterrogators -git-verify-pack plumbinginterrogators -git-verify-tag ancillaryinterrogators -git-whatchanged ancillaryinterrogators -git-write-tree plumbingmanipulators diff --git a/Documentation/config.txt b/Documentation/config.txt index 39d1ef5298..72a33e98b2 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -346,6 +346,13 @@ branch.<name>.mergeoptions:: option values containing whitespace characters are currently not supported. +branch.<name>.rebase:: + When true, rebase the branch <name> on top of the fetched branch, + instead of merging the default branch from the default remote. + *NOTE*: this is a possibly dangerous operation; do *not* use + it unless you understand the implications (see gitlink:git-rebase[1] + for details). + clean.requireForce:: A boolean to make git-clean do nothing unless given -f or -n. Defaults to true. @@ -500,7 +507,9 @@ gc.rerereunresolved:: rerere.enabled:: Activate recording of resolved conflicts, so that identical conflict hunks can be resolved automatically, should they - be encountered again. See gitlink:git-rerere[1]. + be encountered again. gitlink:git-rerere[1] command is by + default enabled, but can be disabled by setting this option to + false. gitcvs.enabled:: Whether the CVS server interface is enabled for this repository. @@ -543,6 +552,11 @@ specified as 'gitcvs.<access_method>.<varname>' (where 'access_method' is one of "ext" and "pserver") to make them apply only for the given access method. +http.proxy:: + Override the HTTP proxy, normally configured using the 'http_proxy' + environment variable (see gitlink:curl[1]). This can be overridden + on a per-remote basis; see remote.<name>.proxy + http.sslVerify:: Whether to verify the SSL certificate when fetching or pushing over HTTPS. Can be overridden by the 'GIT_SSL_NO_VERIFY' environment @@ -691,6 +705,11 @@ remote.<name>.url:: The URL of a remote repository. See gitlink:git-fetch[1] or gitlink:git-push[1]. +remote.<name>.proxy:: + For remotes that require curl (http, https and ftp), the URL to + the proxy to use for that remote. Set to the empty string to + disable proxying for that remote. + remote.<name>.fetch:: The default set of "refspec" for gitlink:git-fetch[1]. See gitlink:git-fetch[1]. diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 5ce905de86..d3f21c7975 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -10,6 +10,7 @@ SYNOPSIS [verse] 'git-branch' [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]] + [--contains <commit>] 'git-branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>] 'git-branch' (-m | -M) [<oldbranch>] <newbranch> 'git-branch' (-d | -D) [-r] <branchname>... @@ -20,6 +21,9 @@ With no arguments given a list of existing branches will be shown, the current branch will be highlighted with an asterisk. Option `-r` causes the remote-tracking branches to be listed, and option `-a` shows both. +With `--contains <commit>`, shows only the branches that +contains the named commit (in other words, the branches whose +tip commits are descendant of the named commit). 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>. @@ -45,17 +49,22 @@ to happen. With a `-d` or `-D` option, `<branchname>` will be deleted. You may specify more than one branch for deletion. If the branch currently -has a reflog then the reflog will also be deleted. Use -r together with -d -to delete remote-tracking branches. +has a reflog then the reflog will also be deleted. + +Use -r together with -d to delete remote-tracking branches. Note, that it +only makes sense to delete remote-tracking branches if they no longer exist +in remote repository or if gitlink:git-fetch[1] was configured not to fetch +them again. See also 'prune' subcommand of gitlink:git-remote[1] for way to +clean up all obsolete remote-tracking branches. OPTIONS ------- -d:: - Delete a branch. The branch must be fully merged. + Delete a branch. The branch must be fully merged in HEAD. -D:: - Delete a branch irrespective of its index status. + Delete a branch irrespective of its merged status. -l:: Create the branch's reflog. This activates recording of @@ -153,9 +162,11 @@ $ git branch -d -r origin/todo origin/html origin/man <1> $ git branch -D test <2> ------------ + -<1> Delete remote-tracking branches "todo", "html", "man" -<2> Delete "test" branch even if the "master" branch does not have all -commits from test branch. +<1> Delete remote-tracking branches "todo", "html", "man". Next 'fetch' or +'pull' will create them again unless you configure them not to. See +gitlink:git-fetch[1]. +<2> Delete "test" branch even if the "master" branch (or whichever branch is +currently checked out) does not have all commits from test branch. Notes diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index d4bfd49ce1..4bb2791550 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -10,7 +10,7 @@ SYNOPSIS [verse] 'git-commit' [-a | --interactive] [-s] [-v] [-u] [(-c | -C) <commit> | -F <file> | -m <msg> | --amend] - [--no-verify] [-e] [--author <author>] + [--allow-empty] [--no-verify] [-e] [--author <author>] [--] [[-i | -o ]<file>...] DESCRIPTION @@ -89,6 +89,12 @@ OPTIONS This option bypasses the pre-commit hook. See also link:hooks.html[hooks]. +--allow-empty:: + Usually recording a commit that has the exact same tree as its + sole parent commit is a mistake, and the command prevents you + from making such a commit. This option bypasses the safety, and + is primarily for use by foreign scm interface scripts. + -e|--edit:: The message taken from file with `-F`, command line with `-m`, and from file with `-C` are usually used as the diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index a592b61e2f..7640450787 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -20,6 +20,7 @@ SYNOPSIS 'git-config' [<file-option>] --rename-section old_name new_name 'git-config' [<file-option>] --remove-section name 'git-config' [<file-option>] [-z|--null] -l | --list +'git-config' [<file-option>] --get-color name [default] DESCRIPTION ----------- @@ -134,6 +135,12 @@ See also <<FILES>>. output without getting confused e.g. by values that contain line breaks. +--get-color name default:: + + Find the color configured for `name` (e.g. `color.diff.new`) and + output it as the ANSI color escape sequence to the standard + output. The optional `default` parameter is used instead, if + there is no color configured for `name`. [[FILES]] FILES @@ -292,6 +299,15 @@ To add a new proxy, without altering any of the existing ones, use % git config core.gitproxy '"proxy-command" for example.com' ------------ +An example to use customized color from the configuration in your +script: + +------------ +#!/bin/sh +WS=$(git config --get-color color.diff.whitespace "blue reverse") +RESET=$(git config --get-color "" "reset") +echo "${WS}your whitespace color or blue reverse${RESET}" +------------ include::config.txt[] diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt new file mode 100644 index 0000000000..fd3d571464 --- /dev/null +++ b/Documentation/git-fast-export.txt @@ -0,0 +1,83 @@ +git-fast-export(1) +================== + +NAME +---- +git-fast-export - Git data exporter + + +SYNOPSIS +-------- +'git-fast-export [options]' | 'git-fast-import' + +DESCRIPTION +----------- +This program dumps the given revisions in a form suitable to be piped +into gitlink:git-fast-import[1]. + +You can use it as a human readable bundle replacement (see +gitlink:git-bundle[1]), or as a kind of an interactive +gitlink:git-filter-branch[1]. + + +OPTIONS +------- +--progress=<n>:: + Insert 'progress' statements every <n> objects, to be shown by + gitlink:git-fast-import[1] during import. + +--signed-tags=(verbatim|warn|strip|abort):: + Specify how to handle signed tags. Since any transformation + after the export can change the tag names (which can also happen + when excluding revisions) the signatures will not match. ++ +When asking to 'abort' (which is the default), this program will die +when encountering a signed tag. With 'strip', the tags will be made +unsigned, with 'verbatim', they will be silently exported +and with 'warn', they will be exported, but you will see a warning. + + +EXAMPLES +-------- + +------------------------------------------------------------------- +$ git fast-export --all | (cd /empty/repository && git fast-import) +------------------------------------------------------------------- + +This will export the whole repository and import it into the existing +empty repository. Except for reencoding commits that are not in +UTF-8, it would be a one-to-one mirror. + +----------------------------------------------------- +$ git fast-export master~5..master | + sed "s|refs/heads/master|refs/heads/other|" | + git fast-import +----------------------------------------------------- + +This makes a new branch called 'other' from 'master~5..master' +(i.e. if 'master' has linear history, it will take the last 5 commits). + +Note that this assumes that none of the blobs and commit messages +referenced by that revision range contains the string +'refs/heads/master'. + + +Limitations +----------- + +Since gitlink:git-fast-import[1] cannot tag trees, you will not be +able to export the linux-2.6.git repository completely, as it contains +a tag referencing a tree instead of a commit. + + +Author +------ +Written by Johannes E. Schindelin <johannes.schindelin@gmx.de>. + +Documentation +-------------- +Documentation by Johannes E. Schindelin <johannes.schindelin@gmx.de>. + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt new file mode 100644 index 0000000000..7ddbf467df --- /dev/null +++ b/Documentation/git-help.txt @@ -0,0 +1,48 @@ +git-help(1) +=========== + +NAME +---- +git-help - display help information about git + +SYNOPSIS +-------- +'git help' [-a|--all] [COMMAND] + +DESCRIPTION +----------- + +With no options and no COMMAND given, the synopsis of the 'git' +command and a list of the most commonly used git commands are printed +on the standard output. + +If the option '--all' or '-a' is given, then all available commands are +printed on the standard output. + +If a git command is named, a manual page for that command is brought +up. The 'man' program is used by default for this purpose. + +Note that 'git --help ...' is identical as 'git help ...' because the +former is internally converted into the latter. + +OPTIONS +------- +-a|--all:: + + Prints all the available commands on the standard output. This + option superseeds any other option. + +Author +------ +Written by Junio C Hamano <gitster@pobox.com> and the git-list +<git@vger.kernel.org>. + +Documentation +------------- +Initial documentation was part of the gitlink:git[7] man page. +Christian Couder <chriscool@tuxfamily.org> extracted and rewrote it a +little. Maintenance is done by the git-list <git@vger.kernel.org>. + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Documentation/git-peek-remote.txt b/Documentation/git-peek-remote.txt index abc171266a..38a5325af7 100644 --- a/Documentation/git-peek-remote.txt +++ b/Documentation/git-peek-remote.txt @@ -12,8 +12,7 @@ SYNOPSIS DESCRIPTION ----------- -Lists the references the remote repository has, and optionally -stores them in the local repository under the same name. +This command is deprecated; use `git-ls-remote` instead. OPTIONS ------- diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt index 0ace233d18..9835bdb878 100644 --- a/Documentation/git-prune.txt +++ b/Documentation/git-prune.txt @@ -8,7 +8,7 @@ git-prune - Prune all unreachable objects from the object database SYNOPSIS -------- -'git-prune' [-n] [--] [<head>...] +'git-prune' [-n] [--expire <expire>] [--] [<head>...] DESCRIPTION ----------- @@ -31,6 +31,9 @@ OPTIONS \--:: Do not interpret any more arguments as options. +\--expire <time>:: + Only expire loose objects older than <time>. + <head>...:: In addition to objects reachable from any of our references, keep objects diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index e1eb2c1d00..d4d26afea0 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -33,6 +33,16 @@ include::urls-remotes.txt[] include::merge-strategies.txt[] +\--rebase:: + Instead of a merge, perform a rebase after fetching. + *NOTE:* This is a potentially _dangerous_ mode of operation. + It rewrites history, which does not bode well when you + published that history already. Do *not* use this option + unless you have read gitlink:git-rebase[1] carefully. + +\--no-rebase:: + Override earlier \--rebase. + DEFAULT BEHAVIOUR ----------------- diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 4a68aaba34..b8003c63c7 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -63,6 +63,14 @@ the remote repository. Instead of naming each ref to push, specifies that all refs under `$GIT_DIR/refs/heads/` be pushed. +\--mirror:: + Instead of naming each ref to push, specifies that all + refs under `$GIT_DIR/refs/heads/` and `$GIT_DIR/refs/tags/` + be mirrored to the remote repository. Newly created local + refs will be pushed to the remote end, locally updated refs + will be force updated on the remote end, and deleted refs + will be removed from the remote end. + \--dry-run:: Do everything except actually send the updates. diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt index c4d4263238..8ce492c8f2 100644 --- a/Documentation/git-rerere.txt +++ b/Documentation/git-rerere.txt @@ -22,10 +22,6 @@ automerge results and corresponding hand-resolve results on the initial manual merge, and later by noticing the same automerge results and applying the previously recorded hand resolution. -[NOTE] -You need to set the config variable rerere.enabled to enable this -command. - COMMANDS -------- diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index 2fa01d4a3c..a2d9cb61be 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -85,7 +85,9 @@ Each pattern pair consists of the source side (before the colon) and the destination side (after the colon). The ref to be pushed is determined by finding a match that matches the source side, and where it is pushed is determined by using the -destination side. +destination side. The rules used to match a ref are the same +rules used by gitlink:git-rev-parse[1] to resolve a symbolic ref +name. - It is an error if <src> does not match exactly one of the local refs. diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 10d3e3fa95..784ec6d4c2 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -65,7 +65,9 @@ OPTIONS Typing "git tag" without arguments, also lists all tags. -m <msg>:: - Use the given tag message (instead of prompting) + Use the given tag message (instead of prompting). + If multiple `-m` options are given, there values are + concatenated as separate paragraphs. -F <file>:: Take the tag message from the given file. Use '-' to diff --git a/Documentation/git.txt b/Documentation/git.txt index 546020100a..1574ecd77e 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.7.txt[1.5.3.7], link:RelNotes-1.5.3.6.txt[1.5.3.6], link:RelNotes-1.5.3.5.txt[1.5.3.5], link:RelNotes-1.5.3.4.txt[1.5.3.4], @@ -100,9 +101,9 @@ OPTIONS --help:: Prints the synopsis and a list of the most commonly used - commands. If a git command is named this option will bring up - the man-page for that command. If the option '--all' or '-a' is - given then all available commands are printed. + commands. If the option '--all' or '-a' is given then all + available commands are printed. If a git command is named this + option will bring up the manual page for that command. --exec-path:: Path to wherever your core git programs are installed. @@ -535,7 +536,7 @@ Authors ------- * git's founding father is Linus Torvalds <torvalds@osdl.org>. * The current git nurse is Junio C Hamano <gitster@pobox.com>. -* The git potty was written by Andres Ericsson <ae@op5.se>. +* The git potty was written by Andreas Ericsson <ae@op5.se>. * General upbringing is handled by the git-list <git@vger.kernel.org>. Documentation diff --git a/Documentation/howto/maintain-git.txt b/Documentation/howto/maintain-git.txt new file mode 100644 index 0000000000..4357e26913 --- /dev/null +++ b/Documentation/howto/maintain-git.txt @@ -0,0 +1,277 @@ +From: Junio C Hamano <gitster@pobox.com> +Date: Wed, 21 Nov 2007 16:32:55 -0800 +Subject: Addendum to "MaintNotes" +Abstract: Imagine that git development is racing along as usual, when our friendly + neighborhood maintainer is struck down by a wayward bus. Out of the + hordes of suckers (loyal developers), you have been tricked (chosen) to + step up as the new maintainer. This howto will show you "how to" do it. + +The maintainer's git time is spent on three activities. + + - Communication (60%) + + Mailing list discussions on general design, fielding user + questions, diagnosing bug reports; reviewing, commenting on, + suggesting alternatives to, and rejecting patches. + + - Integration (30%) + + Applying new patches from the contributors while spotting and + correcting minor mistakes, shuffling the integration and + testing branches, pushing the results out, cutting the + releases, and making announcements. + + - Own development (10%) + + Scratching my own itch and sending proposed patch series out. + +The policy on Integration is informally mentioned in "A Note +from the maintainer" message, which is periodically posted to +this mailing list after each feature release is made. + +The policy. + + - Feature releases are numbered as vX.Y.Z and are meant to + contain bugfixes and enhancements in any area, including + functionality, performance and usability, without regression. + + - Maintenance releases are numbered as vX.Y.Z.W and are meant + to contain only bugfixes for the corresponding vX.Y.Z feature + release and earlier maintenance releases vX.Y.Z.V (V < W). + + - 'master' branch is used to prepare for the next feature + release. In other words, at some point, the tip of 'master' + branch is tagged with vX.Y.Z. + + - 'maint' branch is used to prepare for the next maintenance + release. After the feature release vX.Y.Z is made, the tip + of 'maint' branch is set to that release, and bugfixes will + accumulate on the branch, and at some point, the tip of the + branch is tagged with vX.Y.Z.1, vX.Y.Z.2, and so on. + + - 'next' branch is used to publish changes (both enhancements + and fixes) that (1) have worthwhile goal, (2) are in a fairly + good shape suitable for everyday use, (3) but have not yet + demonstrated to be regression free. New changes are tested + in 'next' before merged to 'master'. + + - 'pu' branch is used to publish other proposed changes that do + not yet pass the criteria set for 'next'. + + - The tips of 'master', 'maint' and 'next' branches will always + fast forward, to allow people to build their own + customization on top of them. + + - Usually 'master' contains all of 'maint', 'next' contains all + of 'master' and 'pu' contains all of 'next'. + + - The tip of 'master' is meant to be more stable than any + tagged releases, and the users are encouraged to follow it. + + - The 'next' branch is where new action takes place, and the + users are encouraged to test it so that regressions and bugs + are found before new topics are merged to 'master'. + + +A typical git day for the maintainer implements the above policy +by doing the following: + + - Scan mailing list and #git channel log. Respond with review + comments, suggestions etc. Kibitz. Collect potentially + usable patches from the mailing list. Patches about a single + topic go to one mailbox (I read my mail in Gnus, and type + \C-o to save/append messages in files in mbox format). + + - Review the patches in the saved mailboxes. Edit proposed log + message for typofixes and clarifications, and add Acks + collected from the list. Edit patch to incorporate "Oops, + that should have been like this" fixes from the discussion. + + - Classify the collected patches and handle 'master' and + 'maint' updates: + + - Obviously correct fixes that pertain to the tip of 'maint' + are directly applied to 'maint'. + + - Obviously correct fixes that pertain to the tip of 'master' + are directly applied to 'master'. + + This step is done with "git am". + + $ git checkout master ;# or "git checkout maint" + $ git am -3 -s mailbox + $ make test + + - Merge downwards (maint->master): + + $ git checkout master + $ git merge maint + $ make test + + - Review the last issue of "What's cooking" message, review the + topics scheduled for merging upwards (topic->master and + topic->maint), and merge. + + $ git checkout master ;# or "git checkout maint" + $ git merge ai/topic ;# or "git merge ai/maint-topic" + $ git log -p ORIG_HEAD.. ;# final review + $ git diff ORIG_HEAD.. ;# final review + $ make test ;# final review + $ git branch -d ai/topic ;# or "git branch -d ai/maint-topic" + + - Merge downwards (maint->master) if needed: + + $ git checkout master + $ git merge maint + $ make test + + - Merge downwards (master->next) if needed: + + $ git checkout next + $ git merge master + $ make test + + - Handle the remaining patches: + + - Anything unobvious that is applicable to 'master' (in other + words, does not depend on anything that is still in 'next' + and not in 'master') is applied to a new topic branch that + is forked from the tip of 'master'. This includes both + enhancements and unobvious fixes to 'master'. A topic + branch is named as ai/topic where "ai" is typically + author's initial and "topic" is a descriptive name of the + topic (in other words, "what's the series is about"). + + - An unobvious fix meant for 'maint' is applied to a new + topic branch that is forked from the tip of 'maint'. The + topic is named as ai/maint-topic. + + - Changes that pertain to an existing topic are applied to + the branch, but: + + - obviously correct ones are applied first; + + - questionable ones are discarded or applied to near the tip; + + - Replacement patches to an existing topic are accepted only + for commits not in 'next'. + + The above except the "replacement" are all done with: + + $ git am -3 -s mailbox + + while patch replacement is often done by: + + $ git format-patch ai/topic~$n..ai/topic ;# export existing + + then replace some parts with the new patch, and reapplying: + + $ git reset --hard ai/topic~$n + $ git am -3 -s 000*.txt + + The full test suite is always run for 'maint' and 'master' + after patch application; for topic branches the tests are run + as time permits. + + - Update "What's cooking" message to review the updates to + existing topics, newly added topics and graduated topics. + + This step is helped with Meta/UWC script (where Meta/ contains + a checkout of the 'todo' branch). + + - Merge topics to 'next'. For each branch whose tip is not + merged to 'next', one of three things can happen: + + - The commits are all next-worthy; merge the topic to next: + + $ git checkout next + $ git merge ai/topic ;# or "git merge ai/maint-topic" + $ make test + + - The new parts are of mixed quality, but earlier ones are + next-worthy; merge the early parts to next: + + $ git checkout next + $ git merge ai/topic~2 ;# the tip two are dubious + $ make test + + - Nothing is next-worthy; do not do anything. + + - Rebase topics that do not have any commit in next yet. This + step is optional but sometimes is worth doing when an old + series that is not in next can take advantage of low-level + framework change that is merged to 'master' already. + + $ git rebase master ai/topic + + This step is helped with Meta/git-topic.perl script to + identify which topic is rebaseable. There also is a + pre-rebase hook to make sure that topics that are already in + 'next' are not rebased beyond the merged commit. + + - Rebuild "pu" to merge the tips of topics not in 'next'. + + $ git checkout pu + $ git reset --hard next + $ git merge ai/topic ;# repeat for all remaining topics + $ make test + + This step is helped with Meta/PU script + + - Push four integration branches to a private repository at + k.org and run "make test" on all of them. + + - Push four integration branches to /pub/scm/git/git.git at + k.org. This triggers its post-update hook which: + + (1) runs "git pull" in $HOME/git-doc/ repository to pull + 'master' just pushed out; + + (2) runs "make doc" in $HOME/git-doc/, install the generated + documentation in staging areas, which are separate + repositories that have html and man branches checked + out. + + (3) runs "git commit" in the staging areas, and run "git + push" back to /pub/scm/git/git.git/ to update the html + and man branches. + + (4) installs generated documentation to /pub/software/scm/git/docs/ + to be viewed from http://www.kernel.org/ + + - Fetch html and man branches back from k.org, and push four + integration branches and the two documentation branches to + repo.or.cz + + +Some observations to be made. + + * Each topic is tested individually, and also together with + other topics cooking in 'next'. Until it matures, none part + of it is merged to 'master'. + + * A topic already in 'next' can get fixes while still in + 'next'. Such a topic will have many merges to 'next' (in + other words, "git log --first-parent next" will show many + "Merge ai/topic to next" for the same topic. + + * An unobvious fix for 'maint' is cooked in 'next' and then + merged to 'master' to make extra sure it is Ok and then + merged to 'maint'. + + * Even when 'next' becomes empty (in other words, all topics + prove stable and are merged to 'master' and "git diff master + next" shows empty), it has tons of merge commits that will + never be in 'master'. + + * In principle, "git log --first-parent master..next" should + show nothing but merges (in practice, there are fixup commits + and reverts that are not merges). + + * Commits near the tip of a topic branch that are not in 'next' + are fair game to be discarded, replaced or rewritten. + Commits already merged to 'next' will not be. + + * Being in the 'next' branch is not a guarantee for a topic to + be included in the next feature release. Being in the + 'master' branch typically is. diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 3661879f1a..93a47b439b 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -56,11 +56,12 @@ $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git The initial clone may be time-consuming for a large project, but you will only need to clone once. -The clone command creates a new directory named after the project -("git" or "linux-2.6" in the examples above). After you cd into this +The clone command creates a new directory named after the project ("git" +or "linux-2.6" in the examples above). After you cd into this directory, you will see that it contains a copy of the project files, -together with a special top-level directory named ".git", which -contains all the information about the history of the project. +called the <<def_working_tree,working tree>>, together with a special +top-level directory named ".git", which contains all the information +about the history of the project. [[how-to-check-out]] How to check out a different version of a project @@ -71,8 +72,13 @@ of files. It stores the history as a compressed collection of interrelated snapshots of the project's contents. In git each such version is called a <<def_commit,commit>>. -A single git repository may contain multiple branches. It keeps track -of them by keeping a list of <<def_head,heads>> which reference the +Those snapshots aren't necessarily all arranged in a single line from +oldest to newest; instead, work may simultaneously proceed along +parallel lines of development, called <def_branch,branches>>, which may +merge and diverge. + +A single git repository can track development on multiple branches. It +does this by keeping a list of <<def_head,heads>> which reference the latest commit on each branch; the gitlink:git-branch[1] command shows you the list of branch heads: @@ -1410,8 +1416,8 @@ with the changes to be reverted, then you will be asked to fix conflicts manually, just as in the case of <<resolving-a-merge, resolving a merge>>. -[[fixing-a-mistake-by-editing-history]] -Fixing a mistake by editing history +[[fixing-a-mistake-by-rewriting-history]] +Fixing a mistake by rewriting history ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If the problematic commit is the most recent commit, and you have not @@ -1434,7 +1440,7 @@ Again, you should never do this to a commit that may already have been merged into another branch; use gitlink:git-revert[1] instead in that case. -It is also possible to edit commits further back in the history, but +It is also possible to replace commits further back in the history, but this is an advanced topic to be left for <<cleaning-up-history,another chapter>>. @@ -1554,6 +1560,11 @@ This may be time-consuming. Unlike most other git operations (including git-gc when run without any options), it is not safe to prune while other git operations are in progress in the same repository. +If gitlink:git-fsck[1] complains about sha1 mismatches or missing +objects, you may have a much more serious problem; your best option is +probably restoring from backups. See +<<recovering-from-repository-corruption>> for a detailed discussion. + [[recovering-lost-changes]] Recovering lost changes ~~~~~~~~~~~~~~~~~~~~~~~ @@ -1923,15 +1934,9 @@ or just $ git push ssh://yourserver.com/~you/proj.git master ------------------------------------------------- -As with git-fetch, git-push will complain if this does not result in -a <<fast-forwards,fast forward>>. Normally this is a sign of -something wrong. However, if you are sure you know what you're -doing, you may force git-push to perform the update anyway by -preceding the branch name by a plus sign: - -------------------------------------------------- -$ git push ssh://yourserver.com/~you/proj.git +master -------------------------------------------------- +As with git-fetch, git-push will complain if this does not result in a +<<fast-forwards,fast forward>>; see the following section for details on +handling this case. Note that the target of a "push" is normally a <<def_bare_repository,bare>> repository. You can also push to a @@ -1959,6 +1964,52 @@ See the explanations of the remote.<name>.url, branch.<name>.remote, and remote.<name>.push options in gitlink:git-config[1] for details. +[[forcing-push]] +What to do when a push fails +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If a push would not result in a <<fast-forwards,fast forward>> of the +remote branch, then it will fail with an error like: + +------------------------------------------------- +error: remote 'refs/heads/master' is not an ancestor of + local 'refs/heads/master'. + Maybe you are not up-to-date and need to pull first? +error: failed to push to 'ssh://yourserver.com/~you/proj.git' +------------------------------------------------- + +This can happen, for example, if you: + + - use `git reset --hard` to remove already-published commits, or + - use `git commit --amend` to replace already-published commits + (as in <<fixing-a-mistake-by-rewriting-history>>), or + - use `git rebase` to rebase any already-published commits (as + in <<using-git-rebase>>). + +You may force git-push to perform the update anyway by preceding the +branch name with a plus sign: + +------------------------------------------------- +$ git push ssh://yourserver.com/~you/proj.git +master +------------------------------------------------- + +Normally whenever a branch head in a public repository is modified, it +is modified to point to a descendent of the commit that it pointed to +before. By forcing a push in this situation, you break that convention. +(See <<problems-with-rewriting-history>>.) + +Nevertheless, this is a common practice for people that need a simple +way to publish a work-in-progress patch series, and it is an acceptable +compromise as long as you warn other developers that this is how you +intend to manage the branch. + +It's also possible for a push to fail in this way when other people have +the right to push to the same repository. In that case, the correct +solution is to retry the push after first updating your work by either a +pull or a fetch followed by a rebase; see the +<<setting-up-a-shared-repository,next section>> and +link:cvs-migration.html[git for CVS users] for more. + [[setting-up-a-shared-repository]] Setting up a shared repository ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2426,11 +2477,11 @@ return mywork to the state it had before you started the rebase: $ git rebase --abort ------------------------------------------------- -[[modifying-one-commit]] -Modifying a single commit +[[rewriting-one-commit]] +Rewriting a single commit ------------------------- -We saw in <<fixing-a-mistake-by-editing-history>> that you can replace the +We saw in <<fixing-a-mistake-by-rewriting-history>> that you can replace the most recent commit using ------------------------------------------------- @@ -2440,8 +2491,10 @@ $ git commit --amend which will replace the old commit by a new commit incorporating your changes, giving you a chance to edit the old commit message first. -You can also use a combination of this and gitlink:git-rebase[1] to edit -commits further back in your history. First, tag the problematic commit with +You can also use a combination of this and gitlink:git-rebase[1] to +replace a commit further back in your history and recreate the +intervening changes on top of it. First, tag the problematic commit +with ------------------------------------------------- $ git tag bad mywork~5 @@ -3172,6 +3225,127 @@ confusing and scary messages, but it won't actually do anything bad. In contrast, running "git prune" while somebody is actively changing the repository is a *BAD* idea). +[[recovering-from-repository-corruption]] +Recovering from repository corruption +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By design, git treats data trusted to it with caution. However, even in +the absence of bugs in git itself, it is still possible that hardware or +operating system errors could corrupt data. + +The first defense against such problems is backups. You can back up a +git directory using clone, or just using cp, tar, or any other backup +mechanism. + +As a last resort, you can search for the corrupted objects and attempt +to replace them by hand. Back up your repository before attempting this +in case you corrupt things even more in the process. + +We'll assume that the problem is a single missing or corrupted blob, +which is sometimes a solveable problem. (Recovering missing trees and +especially commits is *much* harder). + +Before starting, verify that there is corruption, and figure out where +it is with gitlink:git-fsck[1]; this may be time-consuming. + +Assume the output looks like this: + +------------------------------------------------ +$ git-fsck --full +broken link from tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8 + to blob 4b9458b3786228369c63936db65827de3cc06200 +missing blob 4b9458b3786228369c63936db65827de3cc06200 +------------------------------------------------ + +(Typically there will be some "dangling object" messages too, but they +aren't interesting.) + +Now you know that blob 4b9458b3 is missing, and that the tree 2d9263c6 +points to it. If you could find just one copy of that missing blob +object, possibly in some other repository, you could move it into +.git/objects/4b/9458b3... and be done. Suppose you can't. You can +still examine the tree that pointed to it with gitlink:git-ls-tree[1], +which might output something like: + +------------------------------------------------ +$ git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8 +100644 blob 8d14531846b95bfa3564b58ccfb7913a034323b8 .gitignore +100644 blob ebf9bf84da0aab5ed944264a5db2a65fe3a3e883 .mailmap +100644 blob ca442d313d86dc67e0a2e5d584b465bd382cbf5c COPYING +... +100644 blob 4b9458b3786228369c63936db65827de3cc06200 myfile +... +------------------------------------------------ + +So now you know that the missing blob was the data for a file named +"myfile". And chances are you can also identify the directory--let's +say it's in "somedirectory". If you're lucky the missing copy might be +the same as the copy you have checked out in your working tree at +"somedirectory/myfile"; you can test whether that's right with +gitlink:git-hash-object[1]: + +------------------------------------------------ +$ git hash-object -w somedirectory/myfile +------------------------------------------------ + +which will create and store a blob object with the contents of +somedirectory/myfile, and output the sha1 of that object. if you're +extremely lucky it might be 4b9458b3786228369c63936db65827de3cc06200, in +which case you've guessed right, and the corruption is fixed! + +Otherwise, you need more information. How do you tell which version of +the file has been lost? + +The easiest way to do this is with: + +------------------------------------------------ +$ git log --raw --all --full-history -- somedirectory/myfile +------------------------------------------------ + +Because you're asking for raw output, you'll now get something like + +------------------------------------------------ +commit abc +Author: +Date: +... +:100644 100644 4b9458b... newsha... M somedirectory/myfile + + +commit xyz +Author: +Date: + +... +:100644 100644 oldsha... 4b9458b... M somedirectory/myfile +------------------------------------------------ + +This tells you that the immediately preceding version of the file was +"newsha", and that the immediately following version was "oldsha". +You also know the commit messages that went with the change from oldsha +to 4b9458b and with the change from 4b9458b to newsha. + +If you've been committing small enough changes, you may now have a good +shot at reconstructing the contents of the in-between state 4b9458b. + +If you can do that, you can now recreate the missing object with + +------------------------------------------------ +$ git hash-object -w <recreated-file> +------------------------------------------------ + +and your repository is good again! + +(Btw, you could have ignored the fsck, and started with doing a + +------------------------------------------------ +$ git log --raw --all +------------------------------------------------ + +and just looked for the sha of the missing object (4b9458b..) in that +whole thing. It's up to you - git does *have* a lot of information, it is +just missing one particular blob version. + [[the-index]] The index ----------- @@ -3533,7 +3707,7 @@ should use the `--remove` and `--add` flags respectively. NOTE! A `--remove` flag does 'not' mean that subsequent filenames will necessarily be removed: if the files still exist in your directory structure, the index will be updated with their new status, not -removed. The only thing `--remove` means is that update-cache will be +removed. The only thing `--remove` means is that update-index will be considering a removed file to be a valid thing, and if the file really does not exist any more, it will update the index accordingly. @@ -4382,4 +4556,7 @@ Write a chapter on using plumbing and writing scripts. Alternates, clone -reference, etc. -git unpack-objects -r for recovery +More on recovery from repository corruption. See: + http://marc.theaimsgroup.com/?l=git&m=117263864820799&w=2 + http://marc.theaimsgroup.com/?l=git&m=117147855503798&w=2 + http://marc.theaimsgroup.com/?l=git&m=117147855503798&w=2 @@ -111,7 +111,7 @@ all:: # times (my ext3 doesn't). # # Define USE_STDEV below if you want git to care about the underlying device -# change being considered an inode change from the update-cache perspective. +# change being considered an inode change from the update-index perspective. # # Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8 # @@ -213,8 +213,7 @@ BASIC_LDFLAGS = SCRIPT_SH = \ git-bisect.sh git-checkout.sh \ - git-clean.sh git-clone.sh \ - git-ls-remote.sh \ + git-clone.sh \ git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \ git-pull.sh git-rebase.sh git-rebase--interactive.sh \ git-repack.sh git-request-pull.sh \ @@ -243,7 +242,7 @@ PROGRAMS = \ git-fast-import$X \ git-daemon$X \ git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \ - git-peek-remote$X git-receive-pack$X \ + git-receive-pack$X \ git-send-pack$X git-shell$X \ git-show-index$X \ git-unpack-file$X \ @@ -260,7 +259,7 @@ EXTRA_PROGRAMS = BUILT_INS = \ git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \ git-get-tar-commit-id$X git-init$X git-repo-config$X \ - git-fsck-objects$X git-cherry-pick$X git-status$X\ + git-fsck-objects$X git-cherry-pick$X git-peek-remote$X git-status$X \ $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) # what 'all' will build and 'install' will install, in gitexecdir @@ -270,9 +269,6 @@ ALL_PROGRAMS += git-merge-subtree$X # what 'all' will build but not install in gitexecdir OTHER_PROGRAMS = git$X gitweb/gitweb.cgi -ifndef NO_TCLTK -OTHER_PROGRAMS += gitk-wish -endif # Set paths to tools early so that they can be used for version tests. ifndef SHELL_PATH @@ -330,6 +326,7 @@ BUILTIN_OBJS = \ builtin-check-attr.o \ builtin-checkout-index.o \ builtin-check-ref-format.o \ + builtin-clean.o \ builtin-commit.o \ builtin-commit-tree.o \ builtin-count-objects.o \ @@ -338,6 +335,7 @@ BUILTIN_OBJS = \ builtin-diff-files.o \ builtin-diff-index.o \ builtin-diff-tree.o \ + builtin-fast-export.o \ builtin-fetch.o \ builtin-fetch-pack.o \ builtin-fetch--tool.o \ @@ -350,6 +348,7 @@ BUILTIN_OBJS = \ builtin-log.o \ builtin-ls-files.o \ builtin-ls-tree.o \ + builtin-ls-remote.o \ builtin-mailinfo.o \ builtin-mailsplit.o \ builtin-merge-base.o \ @@ -363,6 +362,7 @@ BUILTIN_OBJS = \ builtin-push.o \ builtin-read-tree.o \ builtin-reflog.o \ + builtin-send-pack.o \ builtin-config.o \ builtin-rerere.o \ builtin-reset.o \ @@ -754,7 +754,7 @@ TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) LIBS = $(GITLIBS) $(EXTLIBS) BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \ - -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $(COMPAT_CFLAGS) + $(COMPAT_CFLAGS) LIB_OBJS += $(COMPAT_OBJS) ALL_CFLAGS += $(BASIC_CFLAGS) @@ -773,6 +773,7 @@ endif all:: ifndef NO_TCLTK $(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) all + $(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all endif $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) @@ -780,12 +781,6 @@ endif strip: $(PROGRAMS) git$X $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X -gitk-wish: gitk GIT-GUI-VARS - $(QUIET_GEN)$(RM) $@ $@+ && \ - sed -e '1,3s|^exec .* "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' <gitk >$@+ && \ - chmod +x $@+ && \ - mv -f $@+ $@ - git.o: git.c common-cmds.h GIT-CFLAGS $(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \ $(ALL_CFLAGS) -c $(filter %.c,$^) @@ -802,7 +797,7 @@ git-merge-subtree$X: git-merge-recursive$X $(BUILT_INS): git$X $(QUIET_BUILT_IN)$(RM) $@ && ln git$X $@ -common-cmds.h: ./generate-cmdlist.sh +common-cmds.h: ./generate-cmdlist.sh command-list.txt common-cmds.h: $(wildcard Documentation/git-*.txt) $(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@ @@ -900,6 +895,9 @@ exec_cmd.o: exec_cmd.c GIT-CFLAGS builtin-init-db.o: builtin-init-db.c GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $< +config.o: config.c GIT-CFLAGS + $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $< + http.o: http.c GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $< @@ -1016,14 +1014,14 @@ remove-dashes: ### Installation rules install: all - $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(bindir_SQ)' - $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)' + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)' + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL) git$X '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install $(MAKE) -C perl prefix='$(prefix_SQ)' install ifndef NO_TCLTK - $(INSTALL) gitk-wish '$(DESTDIR_SQ)$(bindir_SQ)'/gitk + $(MAKE) -C gitk-git install $(MAKE) -C git-gui install endif if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \ @@ -1116,7 +1114,7 @@ clean: $(MAKE) -C templates/ clean $(MAKE) -C t/ clean ifndef NO_TCLTK - $(RM) gitk-wish + $(MAKE) -C gitk-git clean $(MAKE) -C git-gui clean endif $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS @@ -1138,7 +1136,7 @@ check-docs:: esac ; \ test -f "Documentation/$$v.txt" || \ echo "no doc: $$v"; \ - sed -e '1,/^__DATA__/d' Documentation/cmd-list.perl | \ + sed -e '/^#/d' command-list.txt | \ grep -q "^$$v[ ]" || \ case "$$v" in \ git) ;; \ @@ -1146,9 +1144,9 @@ check-docs:: esac ; \ done; \ ( \ - sed -e '1,/^__DATA__/d' \ + sed -e '/^#/d' \ -e 's/[ ].*//' \ - -e 's/^/listed /' Documentation/cmd-list.perl; \ + -e 's/^/listed /' command-list.txt; \ ls -1 Documentation/git*txt | \ sed -e 's|Documentation/|documented |' \ -e 's/\.txt//'; \ @@ -1157,6 +1155,7 @@ check-docs:: case "$$how,$$cmd" in \ *,git-citool | \ *,git-gui | \ + *,git-help | \ documented,gitattributes | \ documented,gitignore | \ documented,gitmodules | \ diff --git a/builtin-branch.c b/builtin-branch.c index 2694c9cf49..c64768beb2 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -184,9 +184,30 @@ struct ref_item { struct ref_list { int index, alloc, maxwidth; struct ref_item *list; + struct commit_list *with_commit; int kinds; }; +static int has_commit(const unsigned char *sha1, struct commit_list *with_commit) +{ + struct commit *commit; + + if (!with_commit) + return 1; + commit = lookup_commit_reference_gently(sha1, 1); + if (!commit) + return 0; + while (with_commit) { + struct commit *other; + + other = with_commit->item; + with_commit = with_commit->next; + if (in_merge_bases(other, &commit, 1)) + return 1; + } + return 0; +} + static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { struct ref_list *ref_list = (struct ref_list*)(cb_data); @@ -206,6 +227,10 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, refname += 10; } + /* Filter with with_commit if specified */ + if (!has_commit(sha1, ref_list->with_commit)) + return 0; + /* Don't add types the caller doesn't want */ if ((kind & ref_list->kinds) == 0) return 0; @@ -296,19 +321,20 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, } } -static void print_ref_list(int kinds, int detached, int verbose, int abbrev) +static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit) { int i; struct ref_list ref_list; memset(&ref_list, 0, sizeof(ref_list)); ref_list.kinds = kinds; + ref_list.with_commit = with_commit; for_each_ref(append_ref, &ref_list); qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); detached = (detached && (kinds & REF_LOCAL_BRANCH)); - if (detached) { + if (detached && has_commit(head_sha1, with_commit)) { struct ref_item item; item.name = xstrdup("(no branch)"); item.kind = REF_LOCAL_BRANCH; @@ -505,12 +531,29 @@ static void rename_branch(const char *oldname, const char *newname, int force) die("Branch is renamed, but update of config-file failed"); } +static int opt_parse_with_commit(const struct option *opt, const char *arg, int unset) +{ + unsigned char sha1[20]; + struct commit *commit; + + if (!arg) + return -1; + if (get_sha1(arg, sha1)) + die("malformed object name %s", arg); + commit = lookup_commit_reference(sha1); + if (!commit) + die("no such commit %s", arg); + commit_list_insert(commit, opt->value); + return 0; +} + int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, rename = 0, force_create = 0; int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0; int reflog = 0, track; int kinds = REF_LOCAL_BRANCH; + struct commit_list *with_commit = NULL; struct option options[] = { OPT_GROUP("Generic options"), @@ -519,6 +562,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"), OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", REF_REMOTE_BRANCH), + OPT_CALLBACK(0, "contains", &with_commit, "commit", + "print only branches that contain the commit", + opt_parse_with_commit), + { + OPTION_CALLBACK, 0, "with", &with_commit, "commit", + "print only branches that contain the commit", + PARSE_OPT_HIDDEN, opt_parse_with_commit, + }, OPT__ABBREV(&abbrev), OPT_GROUP("Specific git-branch actions:"), @@ -554,7 +605,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (delete) return delete_branches(argc, argv, delete > 1, kinds); else if (argc == 0) - print_ref_list(kinds, detached, verbose, abbrev); + print_ref_list(kinds, detached, verbose, abbrev, with_commit); else if (rename && (argc == 1)) rename_branch(head, argv[0], rename > 1); else if (rename && (argc == 2)) diff --git a/builtin-clean.c b/builtin-clean.c new file mode 100644 index 0000000000..56ae4eb9bb --- /dev/null +++ b/builtin-clean.c @@ -0,0 +1,153 @@ +/* + * "git clean" builtin command + * + * Copyright (C) 2007 Shawn Bohrer + * + * Based on git-clean.sh by Pavel Roskin + */ + +#include "builtin.h" +#include "cache.h" +#include "dir.h" +#include "parse-options.h" + +static int force = -1; /* unset */ + +static const char *const builtin_clean_usage[] = { + "git-clean [-d] [-f] [-n] [-q] [-x | -X] [--] <paths>...", + NULL +}; + +static int git_clean_config(const char *var, const char *value) +{ + if (!strcmp(var, "clean.requireforce")) + force = !git_config_bool(var, value); + return git_default_config(var, value); +} + +int cmd_clean(int argc, const char **argv, const char *prefix) +{ + int j; + int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0; + int ignored_only = 0, baselen = 0, config_set = 0; + struct strbuf directory; + struct dir_struct dir; + const char *path, *base; + static const char **pathspec; + struct option options[] = { + OPT__QUIET(&quiet), + OPT__DRY_RUN(&show_only), + OPT_BOOLEAN('f', NULL, &force, "force"), + OPT_BOOLEAN('d', NULL, &remove_directories, + "remove whole directories"), + OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"), + OPT_BOOLEAN('X', NULL, &ignored_only, + "remove only ignored files"), + OPT_END() + }; + + git_config(git_clean_config); + if (force < 0) + force = 0; + else + config_set = 1; + + argc = parse_options(argc, argv, options, builtin_clean_usage, 0); + + memset(&dir, 0, sizeof(dir)); + if (ignored_only) + dir.show_ignored = 1; + + if (ignored && ignored_only) + die("-x and -X cannot be used together"); + + if (!show_only && !force) + die("clean.requireForce%s set and -n or -f not given; " + "refusing to clean", config_set ? "" : " not"); + + dir.show_other_directories = 1; + + if (!ignored) + setup_standard_excludes(&dir); + + pathspec = get_pathspec(prefix, argv); + read_cache(); + + /* + * Calculate common prefix for the pathspec, and + * use that to optimize the directory walk + */ + baselen = common_prefix(pathspec); + path = "."; + base = ""; + if (baselen) + path = base = xmemdupz(*pathspec, baselen); + read_directory(&dir, path, base, baselen, pathspec); + strbuf_init(&directory, 0); + + for (j = 0; j < dir.nr; ++j) { + struct dir_entry *ent = dir.entries[j]; + int len, pos, specs; + struct cache_entry *ce; + struct stat st; + char *seen; + + /* + * Remove the '/' at the end that directory + * walking adds for directory entries. + */ + len = ent->len; + if (len && ent->name[len-1] == '/') + len--; + pos = cache_name_pos(ent->name, len); + if (0 <= pos) + continue; /* exact match */ + pos = -pos - 1; + if (pos < active_nr) { + ce = active_cache[pos]; + if (ce_namelen(ce) == len && + !memcmp(ce->name, ent->name, len)) + continue; /* Yup, this one exists unmerged */ + } + + if (!lstat(ent->name, &st) && (S_ISDIR(st.st_mode))) { + int matched_path = 0; + strbuf_addstr(&directory, ent->name); + if (pathspec) { + for (specs =0; pathspec[specs]; ++specs) + /* nothing */; + seen = xcalloc(specs, 1); + /* Check if directory was explictly passed as + * pathspec. If so we want to remove it */ + if (match_pathspec(pathspec, ent->name, ent->len, + baselen, seen)) + matched_path = 1; + free(seen); + } + if (show_only && (remove_directories || matched_path)) { + printf("Would remove %s\n", directory.buf); + } else if (quiet && (remove_directories || matched_path)) { + remove_dir_recursively(&directory, 0); + } else if (remove_directories || matched_path) { + printf("Removing %s\n", directory.buf); + remove_dir_recursively(&directory, 0); + } else if (show_only) { + printf("Would not remove %s\n", directory.buf); + } else { + printf("Not removing %s\n", directory.buf); + } + strbuf_reset(&directory); + } else { + if (show_only) { + printf("Would remove %s\n", ent->name); + continue; + } else if (!quiet) { + printf("Removing %s\n", ent->name); + } + unlink(ent->name); + } + } + + strbuf_release(&directory); + return 0; +} diff --git a/builtin-config.c b/builtin-config.c index e5e243f27c..4c9ded3b1a 100644 --- a/builtin-config.c +++ b/builtin-config.c @@ -1,8 +1,9 @@ #include "builtin.h" #include "cache.h" +#include "color.h" static const char git_config_set_usage[] = -"git-config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list"; +"git-config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list | --get-color var [default]"; static char *key; static regex_t *key_regexp; @@ -81,7 +82,7 @@ static int get_value(const char* key_, const char* regex_) local = repo_config = xstrdup(git_path("config")); if (home) global = xstrdup(mkpath("%s/.gitconfig", home)); - system_wide = ETC_GITCONFIG; + system_wide = git_etc_gitconfig(); } key = xstrdup(key_); @@ -161,6 +162,53 @@ char *normalize_value(const char *key, const char *value) return normalized; } +static int get_color_found; +static const char *get_color_slot; +static char parsed_color[COLOR_MAXLEN]; + +static int git_get_color_config(const char *var, const char *value) +{ + if (!strcmp(var, get_color_slot)) { + color_parse(value, var, parsed_color); + get_color_found = 1; + } + return 0; +} + +static int get_color(int argc, const char **argv) +{ + /* + * grab the color setting for the given slot from the configuration, + * or parse the default value if missing, and return ANSI color + * escape sequence. + * + * e.g. + * git config --get-color color.diff.whitespace "blue reverse" + */ + const char *def_color = NULL; + + switch (argc) { + default: + usage(git_config_set_usage); + case 2: + def_color = argv[1]; + /* fallthru */ + case 1: + get_color_slot = argv[0]; + break; + } + + get_color_found = 0; + parsed_color[0] = '\0'; + git_config(git_get_color_config); + + if (!get_color_found && def_color) + color_parse(def_color, "command line", parsed_color); + + fputs(parsed_color, stdout); + return 0; +} + int cmd_config(int argc, const char **argv, const char *prefix) { int nongit = 0; @@ -191,7 +239,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) } } else if (!strcmp(argv[1], "--system")) - setenv(CONFIG_ENVIRONMENT, ETC_GITCONFIG, 1); + setenv(CONFIG_ENVIRONMENT, git_etc_gitconfig(), 1); else if (!strcmp(argv[1], "--file") || !strcmp(argv[1], "-f")) { if (argc < 3) usage(git_config_set_usage); @@ -234,8 +282,9 @@ int cmd_config(int argc, const char **argv, const char *prefix) return 1; } return 0; - } - else + } else if (!strcmp(argv[1], "--get-color")) { + return get_color(argc-2, argv+2); + } else break; argc--; argv++; diff --git a/builtin-fast-export.c b/builtin-fast-export.c new file mode 100755 index 0000000000..2136aadfd7 --- /dev/null +++ b/builtin-fast-export.c @@ -0,0 +1,406 @@ +/* + * "git fast-export" builtin command + * + * Copyright (C) 2007 Johannes E. Schindelin + */ +#include "builtin.h" +#include "cache.h" +#include "commit.h" +#include "object.h" +#include "tag.h" +#include "diff.h" +#include "diffcore.h" +#include "log-tree.h" +#include "revision.h" +#include "decorate.h" +#include "path-list.h" +#include "utf8.h" +#include "parse-options.h" + +static const char *fast_export_usage[] = { + "git-fast-export [rev-list-opts]", + NULL +}; + +static int progress; +static enum { VERBATIM, WARN, STRIP, ABORT } signed_tag_mode = ABORT; + +static int parse_opt_signed_tag_mode(const struct option *opt, + const char *arg, int unset) +{ + if (unset || !strcmp(arg, "abort")) + signed_tag_mode = ABORT; + else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore")) + signed_tag_mode = VERBATIM; + else if (!strcmp(arg, "warn")) + signed_tag_mode = WARN; + else if (!strcmp(arg, "strip")) + signed_tag_mode = STRIP; + else + return error("Unknown signed-tag mode: %s", arg); + return 0; +} + +static struct decoration idnums; +static uint32_t last_idnum; + +static int has_unshown_parent(struct commit *commit) +{ + struct commit_list *parent; + + for (parent = commit->parents; parent; parent = parent->next) + if (!(parent->item->object.flags & SHOWN) && + !(parent->item->object.flags & UNINTERESTING)) + return 1; + return 0; +} + +/* Since intptr_t is C99, we do not use it here */ +static void mark_object(struct object *object) +{ + last_idnum++; + add_decoration(&idnums, object, ((uint32_t *)NULL) + last_idnum); +} + +static int get_object_mark(struct object *object) +{ + void *decoration = lookup_decoration(&idnums, object); + if (!decoration) + return 0; + return (uint32_t *)decoration - (uint32_t *)NULL; +} + +static void show_progress(void) +{ + static int counter = 0; + if (!progress) + return; + if ((++counter % progress) == 0) + printf("progress %d objects\n", counter); +} + +static void handle_object(const unsigned char *sha1) +{ + unsigned long size; + enum object_type type; + char *buf; + struct object *object; + + if (is_null_sha1(sha1)) + return; + + object = parse_object(sha1); + if (!object) + die ("Could not read blob %s", sha1_to_hex(sha1)); + + if (object->flags & SHOWN) + return; + + buf = read_sha1_file(sha1, &type, &size); + if (!buf) + die ("Could not read blob %s", sha1_to_hex(sha1)); + + mark_object(object); + + printf("blob\nmark :%d\ndata %lu\n", last_idnum, size); + if (fwrite(buf, size, 1, stdout) != 1) + die ("Could not write blob %s", sha1_to_hex(sha1)); + printf("\n"); + + show_progress(); + + object->flags |= SHOWN; + free(buf); +} + +static void show_filemodify(struct diff_queue_struct *q, + struct diff_options *options, void *data) +{ + int i; + for (i = 0; i < q->nr; i++) { + struct diff_filespec *spec = q->queue[i]->two; + if (is_null_sha1(spec->sha1)) + printf("D %s\n", spec->path); + else { + struct object *object = lookup_object(spec->sha1); + printf("M 0%06o :%d %s\n", spec->mode, + get_object_mark(object), spec->path); + } + } +} + +static const char *find_encoding(const char *begin, const char *end) +{ + const char *needle = "\nencoding "; + char *bol, *eol; + + bol = memmem(begin, end ? end - begin : strlen(begin), + needle, strlen(needle)); + if (!bol) + return git_commit_encoding; + bol += strlen(needle); + eol = strchrnul(bol, '\n'); + *eol = '\0'; + return bol; +} + +static void handle_commit(struct commit *commit, struct rev_info *rev) +{ + int saved_output_format = rev->diffopt.output_format; + const char *author, *author_end, *committer, *committer_end; + const char *encoding, *message; + char *reencoded = NULL; + struct commit_list *p; + int i; + + rev->diffopt.output_format = DIFF_FORMAT_CALLBACK; + + parse_commit(commit); + author = strstr(commit->buffer, "\nauthor "); + if (!author) + die ("Could not find author in commit %s", + sha1_to_hex(commit->object.sha1)); + author++; + author_end = strchrnul(author, '\n'); + committer = strstr(author_end, "\ncommitter "); + if (!committer) + die ("Could not find committer in commit %s", + sha1_to_hex(commit->object.sha1)); + committer++; + committer_end = strchrnul(committer, '\n'); + message = strstr(committer_end, "\n\n"); + encoding = find_encoding(committer_end, message); + if (message) + message += 2; + + if (commit->parents) { + parse_commit(commit->parents->item); + diff_tree_sha1(commit->parents->item->tree->object.sha1, + commit->tree->object.sha1, "", &rev->diffopt); + } + else + diff_root_tree_sha1(commit->tree->object.sha1, + "", &rev->diffopt); + + for (i = 0; i < diff_queued_diff.nr; i++) + handle_object(diff_queued_diff.queue[i]->two->sha1); + + mark_object(&commit->object); + if (!is_encoding_utf8(encoding)) + reencoded = reencode_string(message, "UTF-8", encoding); + printf("commit %s\nmark :%d\n%.*s\n%.*s\ndata %u\n%s", + (const char *)commit->util, last_idnum, + (int)(author_end - author), author, + (int)(committer_end - committer), committer, + (unsigned)(reencoded + ? strlen(reencoded) : message + ? strlen(message) : 0), + reencoded ? reencoded : message ? message : ""); + if (reencoded) + free(reencoded); + + for (i = 0, p = commit->parents; p; p = p->next) { + int mark = get_object_mark(&p->item->object); + if (!mark) + continue; + if (i == 0) + printf("from :%d\n", mark); + else if (i == 1) + printf("merge :%d", mark); + else + printf(" :%d", mark); + i++; + } + if (i > 1) + printf("\n"); + + log_tree_diff_flush(rev); + rev->diffopt.output_format = saved_output_format; + + printf("\n"); + + show_progress(); +} + +static void handle_tail(struct object_array *commits, struct rev_info *revs) +{ + struct commit *commit; + while (commits->nr) { + commit = (struct commit *)commits->objects[commits->nr - 1].item; + if (has_unshown_parent(commit)) + return; + handle_commit(commit, revs); + commits->nr--; + } +} + +static void handle_tag(const char *name, struct tag *tag) +{ + unsigned long size; + enum object_type type; + char *buf; + const char *tagger, *tagger_end, *message; + size_t message_size = 0; + + buf = read_sha1_file(tag->object.sha1, &type, &size); + if (!buf) + die ("Could not read tag %s", sha1_to_hex(tag->object.sha1)); + message = memmem(buf, size, "\n\n", 2); + if (message) { + message += 2; + message_size = strlen(message); + } + tagger = memmem(buf, message ? message - buf : size, "\ntagger ", 8); + if (!tagger) + die ("No tagger for tag %s", sha1_to_hex(tag->object.sha1)); + tagger++; + tagger_end = strchrnul(tagger, '\n'); + + /* handle signed tags */ + if (message) { + const char *signature = strstr(message, + "\n-----BEGIN PGP SIGNATURE-----\n"); + if (signature) + switch(signed_tag_mode) { + case ABORT: + die ("Encountered signed tag %s; use " + "--signed-tag=<mode> to handle it.", + sha1_to_hex(tag->object.sha1)); + case WARN: + warning ("Exporting signed tag %s", + sha1_to_hex(tag->object.sha1)); + /* fallthru */ + case VERBATIM: + break; + case STRIP: + message_size = signature + 1 - message; + break; + } + } + + if (!prefixcmp(name, "refs/tags/")) + name += 10; + printf("tag %s\nfrom :%d\n%.*s\ndata %d\n%.*s\n", + name, get_object_mark(tag->tagged), + (int)(tagger_end - tagger), tagger, + (int)message_size, (int)message_size, message ? message : ""); +} + +static void get_tags_and_duplicates(struct object_array *pending, + struct path_list *extra_refs) +{ + struct tag *tag; + int i; + + for (i = 0; i < pending->nr; i++) { + struct object_array_entry *e = pending->objects + i; + unsigned char sha1[20]; + struct commit *commit = commit; + char *full_name; + + if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1) + continue; + + switch (e->item->type) { + case OBJ_COMMIT: + commit = (struct commit *)e->item; + break; + case OBJ_TAG: + tag = (struct tag *)e->item; + while (tag && tag->object.type == OBJ_TAG) { + path_list_insert(full_name, extra_refs)->util = tag; + tag = (struct tag *)tag->tagged; + } + if (!tag) + die ("Tag %s points nowhere?", e->name); + switch(tag->object.type) { + case OBJ_COMMIT: + commit = (struct commit *)tag; + break; + case OBJ_BLOB: + handle_object(tag->object.sha1); + continue; + } + break; + default: + die ("Unexpected object of type %s", + typename(e->item->type)); + } + if (commit->util) + /* more than one name for the same object */ + path_list_insert(full_name, extra_refs)->util = commit; + else + commit->util = full_name; + } +} + +static void handle_tags_and_duplicates(struct path_list *extra_refs) +{ + struct commit *commit; + int i; + + for (i = extra_refs->nr - 1; i >= 0; i--) { + const char *name = extra_refs->items[i].path; + struct object *object = extra_refs->items[i].util; + switch (object->type) { + case OBJ_TAG: + handle_tag(name, (struct tag *)object); + break; + case OBJ_COMMIT: + /* create refs pointing to already seen commits */ + commit = (struct commit *)object; + printf("reset %s\nfrom :%d\n\n", name, + get_object_mark(&commit->object)); + show_progress(); + break; + } + } +} + +int cmd_fast_export(int argc, const char **argv, const char *prefix) +{ + struct rev_info revs; + struct object_array commits = { 0, 0, NULL }; + struct path_list extra_refs = { NULL, 0, 0, 0 }; + struct commit *commit; + struct option options[] = { + OPT_INTEGER(0, "progress", &progress, + "show progress after <n> objects"), + OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, "mode", + "select handling of signed tags", + parse_opt_signed_tag_mode), + OPT_END() + }; + + /* we handle encodings */ + git_config(git_default_config); + + init_revisions(&revs, prefix); + argc = setup_revisions(argc, argv, &revs, NULL); + argc = parse_options(argc, argv, options, fast_export_usage, 0); + if (argc > 1) + usage_with_options (fast_export_usage, options); + + get_tags_and_duplicates(&revs.pending, &extra_refs); + + prepare_revision_walk(&revs); + revs.diffopt.format_callback = show_filemodify; + DIFF_OPT_SET(&revs.diffopt, RECURSIVE); + while ((commit = get_revision(&revs))) { + if (has_unshown_parent(commit)) { + struct commit_list *parent = commit->parents; + add_object_array(&commit->object, NULL, &commits); + for (; parent; parent = parent->next) + if (!parent->item->util) + parent->item->util = commit->util; + } + else { + handle_commit(commit, &revs); + handle_tail(&commits, &revs); + } + } + + handle_tags_and_duplicates(&extra_refs); + + return 0; +} diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index ed60847d9f..7460ab7fce 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -511,10 +511,14 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix) if (!strcmp("append-fetch-head", argv[1])) { int result; FILE *fp; + char *filename; if (argc != 8) return error("append-fetch-head takes 6 args"); - fp = fopen(git_path("FETCH_HEAD"), "a"); + filename = git_path("FETCH_HEAD"); + fp = fopen(filename, "a"); + if (!fp) + return error("cannot open %s: %s\n", filename, strerror(errno)); result = append_fetch_head(fp, argv[2], argv[3], argv[4], argv[5], argv[6], !!argv[7][0], @@ -525,10 +529,14 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix) if (!strcmp("native-store", argv[1])) { int result; FILE *fp; + char *filename; if (argc != 5) return error("fetch-native-store takes 3 args"); - fp = fopen(git_path("FETCH_HEAD"), "a"); + filename = git_path("FETCH_HEAD"); + fp = fopen(filename, "a"); + if (!fp) + return error("cannot open %s: %s\n", filename, strerror(errno)); result = fetch_native_store(fp, argv[2], argv[3], argv[4], verbose, force); fclose(fp); diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index bb1742f1a2..807fa93b53 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -462,34 +462,12 @@ static int sideband_demux(int fd, void *data) { int *xd = data; - close(xd[1]); return recv_sideband("fetch-pack", xd[0], fd, 2); } -static void setup_sideband(int fd[2], int xd[2], struct async *demux) -{ - if (!use_sideband) { - fd[0] = xd[0]; - fd[1] = xd[1]; - return; - } - /* xd[] is talking with upload-pack; subprocess reads from - * xd[0], spits out band#2 to stderr, and feeds us band#1 - * through demux->out. - */ - demux->proc = sideband_demux; - demux->data = xd; - if (start_async(demux)) - die("fetch-pack: unable to fork off sideband demultiplexer"); - close(xd[0]); - fd[0] = demux->out; - fd[1] = xd[1]; -} - static int get_pack(int xd[2], char **pack_lockfile) { struct async demux; - int fd[2]; const char *argv[20]; char keep_arg[256]; char hdr_arg[256]; @@ -497,7 +475,20 @@ static int get_pack(int xd[2], char **pack_lockfile) int do_keep = args.keep_pack; struct child_process cmd; - setup_sideband(fd, xd, &demux); + memset(&demux, 0, sizeof(demux)); + if (use_sideband) { + /* xd[] is talking with upload-pack; subprocess reads from + * xd[0], spits out band#2 to stderr, and feeds us band#1 + * through demux->out. + */ + demux.proc = sideband_demux; + demux.data = xd; + if (start_async(&demux)) + die("fetch-pack: unable to fork off sideband" + " demultiplexer"); + } + else + demux.out = xd[0]; memset(&cmd, 0, sizeof(cmd)); cmd.argv = argv; @@ -506,7 +497,7 @@ static int get_pack(int xd[2], char **pack_lockfile) if (!args.keep_pack && unpack_limit) { struct pack_header header; - if (read_pack_header(fd[0], &header)) + if (read_pack_header(demux.out, &header)) die("protocol error: bad pack header"); snprintf(hdr_arg, sizeof(hdr_arg), "--pack_header=%u,%u", ntohl(header.hdr_version), ntohl(header.hdr_entries)); @@ -542,11 +533,10 @@ static int get_pack(int xd[2], char **pack_lockfile) *av++ = hdr_arg; *av++ = NULL; - cmd.in = fd[0]; + cmd.in = demux.out; cmd.git_cmd = 1; if (start_command(&cmd)) die("fetch-pack: unable to fork off %s", argv[0]); - close(fd[1]); if (do_keep && pack_lockfile) *pack_lockfile = index_pack_lockfile(cmd.out); diff --git a/builtin-fetch.c b/builtin-fetch.c index be9e3ea2bc..f6d16fe966 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -14,7 +14,7 @@ static const char fetch_usage[] = "git-fetch [-a | --append] [--upload-pack <upl static int append, force, tags, no_tags, update_head_ok, verbose, quiet; static const char *depth; -static char *default_rla = NULL; +static struct strbuf default_rla = STRBUF_INIT; static struct transport *transport; static void unlock_pack(void) @@ -31,7 +31,7 @@ static void unlock_pack_on_signal(int signo) } static void add_merge_config(struct ref **head, - struct ref *remote_refs, + const struct ref *remote_refs, struct branch *branch, struct ref ***tail) { @@ -79,7 +79,7 @@ static struct ref *get_ref_map(struct transport *transport, struct ref *ref_map = NULL; struct ref **tail = &ref_map; - struct ref *remote_refs = transport_get_remote_refs(transport); + const struct ref *remote_refs = transport_get_remote_refs(transport); if (ref_count || tags) { for (i = 0; i < ref_count; i++) { @@ -142,7 +142,7 @@ static int s_update_ref(const char *action, static struct ref_lock *lock; if (!rla) - rla = default_rla; + rla = default_rla.buf; snprintf(msg, sizeof(msg), "%s: %s", rla, action); lock = lock_any_ref_for_update(ref->name, check_old ? ref->old_sha1 : NULL, 0); @@ -255,7 +255,7 @@ static int update_local_ref(struct ref *ref, } } -static void store_updated_refs(const char *url, struct ref *ref_map) +static int store_updated_refs(const char *url, struct ref *ref_map) { FILE *fp; struct commit *commit; @@ -263,8 +263,11 @@ static void store_updated_refs(const char *url, struct ref *ref_map) char note[1024]; const char *what, *kind; struct ref *rm; + char *filename = git_path("FETCH_HEAD"); - fp = fopen(git_path("FETCH_HEAD"), "a"); + fp = fopen(filename, "a"); + if (!fp) + return error("cannot open %s: %s\n", filename, strerror(errno)); for (rm = ref_map; rm; rm = rm->next) { struct ref *ref = NULL; @@ -335,6 +338,7 @@ static void store_updated_refs(const char *url, struct ref *ref_map) } } fclose(fp); + return 0; } /* @@ -404,7 +408,7 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map) if (ret) ret = transport_fetch_refs(transport, ref_map); if (!ret) - store_updated_refs(transport->url, ref_map); + ret |= store_updated_refs(transport->url, ref_map); transport_unlock_pack(transport); return ret; } @@ -424,12 +428,12 @@ static struct ref *find_non_local_tags(struct transport *transport, struct path_list new_refs = { NULL, 0, 0, 1 }; char *ref_name; int ref_name_len; - unsigned char *ref_sha1; - struct ref *tag_ref; + const unsigned char *ref_sha1; + const struct ref *tag_ref; struct ref *rm = NULL; struct ref *ref_map = NULL; struct ref **tail = &ref_map; - struct ref *ref; + const struct ref *ref; for_each_ref(add_existing, &existing_refs); for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) { @@ -487,8 +491,13 @@ static int do_fetch(struct transport *transport, die("Don't know how to fetch from %s", transport->url); /* if not appending, truncate FETCH_HEAD */ - if (!append) - fclose(fopen(git_path("FETCH_HEAD"), "w")); + if (!append) { + char *filename = git_path("FETCH_HEAD"); + FILE *fp = fopen(filename, "w"); + if (!fp) + return error("cannot open %s: %s\n", filename, strerror(errno)); + fclose(fp); + } ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags); @@ -534,16 +543,19 @@ static void set_option(const char *name, const char *value) int cmd_fetch(int argc, const char **argv, const char *prefix) { struct remote *remote; - int i, j, rla_offset; + int i; static const char **refs = NULL; int ref_nr = 0; - int cmd_len = 0; const char *upload_pack = NULL; int keep = 0; + /* Record the command line for the reflog */ + strbuf_addstr(&default_rla, "fetch"); + for (i = 1; i < argc; i++) + strbuf_addf(&default_rla, " %s", argv[i]); + for (i = 1; i < argc; i++) { const char *arg = argv[i]; - cmd_len += strlen(arg); if (arg[0] != '-') break; @@ -604,17 +616,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) usage(fetch_usage); } - for (j = i; j < argc; j++) - cmd_len += strlen(argv[j]); - - default_rla = xmalloc(cmd_len + 5 + argc + 1); - sprintf(default_rla, "fetch"); - rla_offset = strlen(default_rla); - for (j = 1; j < argc; j++) { - sprintf(default_rla + rla_offset, " %s", argv[j]); - rla_offset += strlen(argv[j]) + 1; - } - if (i == argc) remote = remote_get(NULL); else diff --git a/builtin-init-db.c b/builtin-init-db.c index 763fa55e76..e1393b8d1e 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -5,6 +5,7 @@ */ #include "cache.h" #include "builtin.h" +#include "exec_cmd.h" #ifndef DEFAULT_GIT_TEMPLATE_DIR #define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates" @@ -131,10 +132,19 @@ static void copy_templates(const char *git_dir, int len, const char *template_di int template_len; DIR *dir; - if (!template_dir) { + if (!template_dir) template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT); - if (!template_dir) - template_dir = DEFAULT_GIT_TEMPLATE_DIR; + if (!template_dir) { + /* + * if the hard-coded template is relative, it is + * interpreted relative to the exec_dir + */ + template_dir = DEFAULT_GIT_TEMPLATE_DIR; + if (!is_absolute_path(template_dir)) { + const char *exec_path = git_exec_path(); + template_dir = prefix_path(exec_path, strlen(exec_path), + template_dir); + } } strcpy(template_path, template_dir); template_len = strlen(template_path); diff --git a/builtin-ls-remote.c b/builtin-ls-remote.c new file mode 100644 index 0000000000..56f3f88023 --- /dev/null +++ b/builtin-ls-remote.c @@ -0,0 +1,74 @@ +#include "builtin.h" +#include "cache.h" +#include "transport.h" +#include "remote.h" + +static const char ls_remote_usage[] = +"git-ls-remote [--upload-pack=<git-upload-pack>] [<host>:]<directory>"; + +int cmd_ls_remote(int argc, const char **argv, const char *prefix) +{ + int i; + const char *dest = NULL; + int nongit = 0; + unsigned flags = 0; + const char *uploadpack = NULL; + + struct remote *remote; + struct transport *transport; + const struct ref *ref; + + setup_git_directory_gently(&nongit); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (*arg == '-') { + if (!prefixcmp(arg, "--upload-pack=")) { + uploadpack = arg + 14; + continue; + } + if (!prefixcmp(arg, "--exec=")) { + uploadpack = arg + 7; + continue; + } + if (!strcmp("--tags", arg)) { + flags |= REF_TAGS; + continue; + } + if (!strcmp("--heads", arg)) { + flags |= REF_HEADS; + continue; + } + if (!strcmp("--refs", arg)) { + flags |= REF_NORMAL; + continue; + } + usage(ls_remote_usage); + } + dest = arg; + break; + } + + if (!dest || i != argc - 1) + usage(ls_remote_usage); + + remote = nongit ? NULL : remote_get(dest); + if (remote && !remote->url_nr) + die("remote %s has no configured URL", dest); + transport = transport_get(remote, remote ? remote->url[0] : dest); + if (uploadpack != NULL) + transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); + + ref = transport_get_remote_refs(transport); + + if (!ref) + return 1; + + while (ref) { + if (check_ref_type(ref, flags)) + printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); + ref = ref->next; + } + return 0; +} diff --git a/builtin-prune.c b/builtin-prune.c index 44df59e4a7..b5e768421b 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -7,15 +7,24 @@ static const char prune_usage[] = "git-prune [-n]"; static int show_only; +static unsigned long expire; static int prune_object(char *path, const char *filename, const unsigned char *sha1) { + const char *fullpath = mkpath("%s/%s", path, filename); + if (expire) { + struct stat st; + if (lstat(fullpath, &st)) + return error("Could not stat '%s'", fullpath); + if (st.st_mtime > expire) + return 0; + } if (show_only) { enum object_type type = sha1_object_info(sha1, NULL); printf("%s %s\n", sha1_to_hex(sha1), (type > 0) ? typename(type) : "unknown"); } else - unlink(mkpath("%s/%s", path, filename)); + unlink(fullpath); return 0; } @@ -85,6 +94,16 @@ int cmd_prune(int argc, const char **argv, const char *prefix) show_only = 1; continue; } + if (!strcmp(arg, "--expire")) { + if (++i < argc) { + expire = approxidate(argv[i]); + continue; + } + } + else if (!prefixcmp(arg, "--expire=")) { + expire = approxidate(arg + 9); + continue; + } usage(prune_usage); } diff --git a/builtin-push.c b/builtin-push.c index 6d1da07c46..c8cb63e238 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -10,7 +10,7 @@ #include "parse-options.h" static const char * const push_usage[] = { - "git-push [--all] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]", + "git-push [--all | --mirror] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]", NULL, }; @@ -44,6 +44,15 @@ static void set_refspecs(const char **refs, int nr) strcat(tag, refs[i]); ref = tag; } + if (!strcmp("HEAD", ref)) { + unsigned char sha1_dummy[20]; + ref = resolve_ref(ref, sha1_dummy, 1, NULL); + if (!ref) + die("HEAD cannot be resolved."); + if (prefixcmp(ref, "refs/heads/")) + die("HEAD cannot be resolved to branch."); + ref = xstrdup(ref + 11); + } add_refspec(ref); } } @@ -91,6 +100,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) { int flags = 0; int all = 0; + int mirror = 0; int dry_run = 0; int force = 0; int tags = 0; @@ -100,6 +110,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT__VERBOSE(&verbose), OPT_STRING( 0 , "repo", &repo, "repository", "repository"), OPT_BOOLEAN( 0 , "all", &all, "push all refs"), + OPT_BOOLEAN( 0 , "mirror", &mirror, "mirror all refs"), OPT_BOOLEAN( 0 , "tags", &tags, "push tags"), OPT_BOOLEAN( 0 , "dry-run", &dry_run, "dry run"), OPT_BOOLEAN('f', "force", &force, "force updates"), @@ -121,13 +132,21 @@ int cmd_push(int argc, const char **argv, const char *prefix) add_refspec("refs/tags/*"); if (all) flags |= TRANSPORT_PUSH_ALL; + if (mirror) + flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); if (argc > 0) { repo = argv[0]; set_refspecs(argv + 1, argc - 1); } - if ((flags & TRANSPORT_PUSH_ALL) && refspec) + if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) && refspec) + usage_with_options(push_usage, options); + + if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) == + (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) { + error("--all and --mirror are incompatible"); usage_with_options(push_usage, options); + } return do_push(repo, flags); } diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index d1038a0e66..20d1789e01 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -327,7 +327,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0); strbuf_addf(&parsed, " --"); - sq_quote_argv(&parsed, argv, argc, 0); + sq_quote_argv(&parsed, argv, 0); puts(parsed.buf); return 0; } diff --git a/builtin-revert.c b/builtin-revert.c index 365b330f9e..4bf8eb2f58 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -30,7 +30,7 @@ static const char * const cherry_pick_usage[] = { NULL }; -static int edit, no_replay, no_commit, needed_deref, mainline; +static int edit, no_replay, no_commit, mainline; static enum { REVERT, CHERRY_PICK } action; static struct commit *commit; @@ -66,7 +66,6 @@ static void parse_args(int argc, const char **argv) if (commit->object.type == OBJ_TAG) { commit = (struct commit *) deref_tag((struct object *)commit, arg, strlen(arg)); - needed_deref = 1; } if (commit->object.type != OBJ_COMMIT) die ("'%s' does not point to a commit", arg); @@ -225,6 +224,27 @@ static int merge_recursive(const char *base_sha1, return run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | RUN_GIT_CMD); } +static char *help_msg(const unsigned char *sha1) +{ + static char helpbuf[1024]; + char *msg = getenv("GIT_CHERRY_PICK_HELP"); + + if (msg) + return msg; + + strcpy(helpbuf, " After resolving the conflicts,\n" + "mark the corrected paths with 'git add <paths>' " + "or 'git rm <paths>' and commit the result."); + + if (action == CHERRY_PICK) { + sprintf(helpbuf + strlen(helpbuf), + "\nWhen commiting, use the option " + "'-c %s' to retain authorship and message.", + find_unique_abbrev(sha1, DEFAULT_ABBREV)); + } + return helpbuf; +} + static int revert_or_cherry_pick(int argc, const char **argv) { unsigned char head[20]; @@ -333,17 +353,6 @@ static int revert_or_cherry_pick(int argc, const char **argv) add_to_msg(")\n"); } } - if (needed_deref) { - add_to_msg("(original 'git "); - add_to_msg(me); - add_to_msg("' arguments: "); - for (i = 0; i < argc; i++) { - if (i) - add_to_msg(" "); - add_to_msg(argv[i]); - } - add_to_msg(")\n"); - } if (merge_recursive(sha1_to_hex(base->object.sha1), sha1_to_hex(head), "HEAD", @@ -364,16 +373,8 @@ static int revert_or_cherry_pick(int argc, const char **argv) } if (close(msg_fd) || commit_lock_file(&msg_file) < 0) die ("Error wrapping up %s", defmsg); - fprintf(stderr, "Automatic %s failed. " - "After resolving the conflicts,\n" - "mark the corrected paths with 'git add <paths>' " - "and commit the result.\n", me); - if (action == CHERRY_PICK) { - fprintf(stderr, "When commiting, use the option " - "'-c %s' to retain authorship and message.\n", - find_unique_abbrev(commit->object.sha1, - DEFAULT_ABBREV)); - } + fprintf(stderr, "Automatic %s failed.%s\n", + me, help_msg(commit->object.sha1)); exit(1); } if (close(msg_fd) || commit_lock_file(&msg_file) < 0) diff --git a/builtin-send-pack.c b/builtin-send-pack.c new file mode 100644 index 0000000000..25ae1fe860 --- /dev/null +++ b/builtin-send-pack.c @@ -0,0 +1,652 @@ +#include "cache.h" +#include "commit.h" +#include "tag.h" +#include "refs.h" +#include "pkt-line.h" +#include "run-command.h" +#include "remote.h" +#include "send-pack.h" + +static const char send_pack_usage[] = +"git-send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n" +" --all and explicit <ref> specification are mutually exclusive."; + +static struct send_pack_args args = { + /* .receivepack = */ "git-receive-pack", +}; + +/* + * Make a pack stream and spit it out into file descriptor fd + */ +static int pack_objects(int fd, struct ref *refs) +{ + /* + * The child becomes pack-objects --revs; we feed + * the revision parameters to it via its stdin and + * let its stdout go back to the other end. + */ + const char *argv[] = { + "pack-objects", + "--all-progress", + "--revs", + "--stdout", + NULL, + NULL, + }; + struct child_process po; + + if (args.use_thin_pack) + argv[4] = "--thin"; + memset(&po, 0, sizeof(po)); + po.argv = argv; + po.in = -1; + po.out = fd; + po.git_cmd = 1; + if (start_command(&po)) + die("git-pack-objects failed (%s)", strerror(errno)); + + /* + * We feed the pack-objects we just spawned with revision + * parameters by writing to the pipe. + */ + while (refs) { + char buf[42]; + + if (!is_null_sha1(refs->old_sha1) && + has_sha1_file(refs->old_sha1)) { + memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40); + buf[0] = '^'; + buf[41] = '\n'; + if (!write_or_whine(po.in, buf, 42, + "send-pack: send refs")) + break; + } + if (!is_null_sha1(refs->new_sha1)) { + memcpy(buf, sha1_to_hex(refs->new_sha1), 40); + buf[40] = '\n'; + if (!write_or_whine(po.in, buf, 41, + "send-pack: send refs")) + break; + } + refs = refs->next; + } + + if (finish_command(&po)) + return error("pack-objects died with strange error"); + return 0; +} + +static void unmark_and_free(struct commit_list *list, unsigned int mark) +{ + while (list) { + struct commit_list *temp = list; + temp->item->object.flags &= ~mark; + list = temp->next; + free(temp); + } +} + +static int ref_newer(const unsigned char *new_sha1, + const unsigned char *old_sha1) +{ + struct object *o; + struct commit *old, *new; + struct commit_list *list, *used; + int found = 0; + + /* Both new and old must be commit-ish and new is descendant of + * old. Otherwise we require --force. + */ + o = deref_tag(parse_object(old_sha1), NULL, 0); + if (!o || o->type != OBJ_COMMIT) + return 0; + old = (struct commit *) o; + + o = deref_tag(parse_object(new_sha1), NULL, 0); + if (!o || o->type != OBJ_COMMIT) + return 0; + new = (struct commit *) o; + + if (parse_commit(new) < 0) + return 0; + + used = list = NULL; + commit_list_insert(new, &list); + while (list) { + new = pop_most_recent_commit(&list, 1); + commit_list_insert(new, &used); + if (new == old) { + found = 1; + break; + } + } + unmark_and_free(list, 1); + unmark_and_free(used, 1); + return found; +} + +static struct ref *local_refs, **local_tail; +static struct ref *remote_refs, **remote_tail; + +static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +{ + struct ref *ref; + int len = strlen(refname) + 1; + ref = xcalloc(1, sizeof(*ref) + len); + hashcpy(ref->new_sha1, sha1); + memcpy(ref->name, refname, len); + *local_tail = ref; + local_tail = &ref->next; + return 0; +} + +static void get_local_heads(void) +{ + local_tail = &local_refs; + for_each_ref(one_local_ref, NULL); +} + +static int receive_status(int in, struct ref *refs) +{ + struct ref *hint; + char line[1000]; + int ret = 0; + int len = packet_read_line(in, line, sizeof(line)); + if (len < 10 || memcmp(line, "unpack ", 7)) + return error("did not receive remote status"); + if (memcmp(line, "unpack ok\n", 10)) { + char *p = line + strlen(line) - 1; + if (*p == '\n') + *p = '\0'; + error("unpack failed: %s", line + 7); + ret = -1; + } + hint = NULL; + while (1) { + char *refname; + char *msg; + len = packet_read_line(in, line, sizeof(line)); + if (!len) + break; + if (len < 3 || + (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) { + fprintf(stderr, "protocol error: %s\n", line); + ret = -1; + break; + } + + line[strlen(line)-1] = '\0'; + refname = line + 3; + msg = strchr(refname, ' '); + if (msg) + *msg++ = '\0'; + + /* first try searching at our hint, falling back to all refs */ + if (hint) + hint = find_ref_by_name(hint, refname); + if (!hint) + hint = find_ref_by_name(refs, refname); + if (!hint) { + warning("remote reported status on unknown ref: %s", + refname); + continue; + } + if (hint->status != REF_STATUS_EXPECTING_REPORT) { + warning("remote reported status on unexpected ref: %s", + refname); + continue; + } + + if (line[0] == 'o' && line[1] == 'k') + hint->status = REF_STATUS_OK; + else { + hint->status = REF_STATUS_REMOTE_REJECT; + ret = -1; + } + if (msg) + hint->remote_status = xstrdup(msg); + /* start our next search from the next ref */ + hint = hint->next; + } + return ret; +} + +static void update_tracking_ref(struct remote *remote, struct ref *ref) +{ + struct refspec rs; + + if (ref->status != REF_STATUS_OK) + return; + + rs.src = ref->name; + rs.dst = NULL; + + if (!remote_find_tracking(remote, &rs)) { + if (args.verbose) + fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst); + if (ref->deletion) { + if (delete_ref(rs.dst, NULL)) + error("Failed to delete"); + } else + update_ref("update by push", rs.dst, + ref->new_sha1, NULL, 0, 0); + free(rs.dst); + } +} + +static const char *prettify_ref(const struct ref *ref) +{ + const char *name = ref->name; + return name + ( + !prefixcmp(name, "refs/heads/") ? 11 : + !prefixcmp(name, "refs/tags/") ? 10 : + !prefixcmp(name, "refs/remotes/") ? 13 : + 0); +} + +#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) + +static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg) +{ + fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary); + if (from) + fprintf(stderr, "%s -> %s", prettify_ref(from), prettify_ref(to)); + else + fputs(prettify_ref(to), stderr); + if (msg) { + fputs(" (", stderr); + fputs(msg, stderr); + fputc(')', stderr); + } + fputc('\n', stderr); +} + +static const char *status_abbrev(unsigned char sha1[20]) +{ + const char *abbrev; + abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV); + return abbrev ? abbrev : sha1_to_hex(sha1); +} + +static void print_ok_ref_status(struct ref *ref) +{ + if (ref->deletion) + print_ref_status('-', "[deleted]", ref, NULL, NULL); + else if (is_null_sha1(ref->old_sha1)) + print_ref_status('*', + (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" : + "[new branch]"), + ref, ref->peer_ref, NULL); + else { + char quickref[84]; + char type; + const char *msg; + + strcpy(quickref, status_abbrev(ref->old_sha1)); + if (ref->nonfastforward) { + strcat(quickref, "..."); + type = '+'; + msg = "forced update"; + } else { + strcat(quickref, ".."); + type = ' '; + msg = NULL; + } + strcat(quickref, status_abbrev(ref->new_sha1)); + + print_ref_status(type, quickref, ref, ref->peer_ref, msg); + } +} + +static int print_one_push_status(struct ref *ref, const char *dest, int count) +{ + if (!count) + fprintf(stderr, "To %s\n", dest); + + switch(ref->status) { + case REF_STATUS_NONE: + print_ref_status('X', "[no match]", ref, NULL, NULL); + break; + case REF_STATUS_REJECT_NODELETE: + print_ref_status('!', "[rejected]", ref, NULL, + "remote does not support deleting refs"); + break; + case REF_STATUS_UPTODATE: + print_ref_status('=', "[up to date]", ref, + ref->peer_ref, NULL); + break; + case REF_STATUS_REJECT_NONFASTFORWARD: + print_ref_status('!', "[rejected]", ref, ref->peer_ref, + "non-fast forward"); + break; + case REF_STATUS_REMOTE_REJECT: + print_ref_status('!', "[remote rejected]", ref, + ref->deletion ? NULL : ref->peer_ref, + ref->remote_status); + break; + case REF_STATUS_EXPECTING_REPORT: + print_ref_status('!', "[remote failure]", ref, + ref->deletion ? NULL : ref->peer_ref, + "remote failed to report status"); + break; + case REF_STATUS_OK: + print_ok_ref_status(ref); + break; + } + + return 1; +} + +static void print_push_status(const char *dest, struct ref *refs) +{ + struct ref *ref; + int n = 0; + + if (args.verbose) { + for (ref = refs; ref; ref = ref->next) + if (ref->status == REF_STATUS_UPTODATE) + n += print_one_push_status(ref, dest, n); + } + + for (ref = refs; ref; ref = ref->next) + if (ref->status == REF_STATUS_OK) + n += print_one_push_status(ref, dest, n); + + for (ref = refs; ref; ref = ref->next) { + if (ref->status != REF_STATUS_NONE && + ref->status != REF_STATUS_UPTODATE && + ref->status != REF_STATUS_OK) + n += print_one_push_status(ref, dest, n); + } +} + +static int refs_pushed(struct ref *ref) +{ + for (; ref; ref = ref->next) { + switch(ref->status) { + case REF_STATUS_NONE: + case REF_STATUS_UPTODATE: + break; + default: + return 1; + } + } + return 0; +} + +static int do_send_pack(int in, int out, struct remote *remote, const char *dest, int nr_refspec, const char **refspec) +{ + struct ref *ref; + int new_refs; + int ask_for_status_report = 0; + int allow_deleting_refs = 0; + int expect_status_report = 0; + int flags = MATCH_REFS_NONE; + int ret; + + if (args.send_all) + flags |= MATCH_REFS_ALL; + if (args.send_mirror) + flags |= MATCH_REFS_MIRROR; + + /* No funny business with the matcher */ + remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL); + get_local_heads(); + + /* Does the other end support the reporting? */ + if (server_supports("report-status")) + ask_for_status_report = 1; + if (server_supports("delete-refs")) + allow_deleting_refs = 1; + + /* match them up */ + if (!remote_tail) + remote_tail = &remote_refs; + if (match_refs(local_refs, remote_refs, &remote_tail, + nr_refspec, refspec, flags)) + return -1; + + if (!remote_refs) { + fprintf(stderr, "No refs in common and none specified; doing nothing.\n" + "Perhaps you should specify a branch such as 'master'.\n"); + return 0; + } + + /* + * Finally, tell the other end! + */ + new_refs = 0; + for (ref = remote_refs; ref; ref = ref->next) { + const unsigned char *new_sha1; + + if (!ref->peer_ref) { + if (!args.send_mirror) + continue; + new_sha1 = null_sha1; + } + else + new_sha1 = ref->peer_ref->new_sha1; + + + ref->deletion = is_null_sha1(new_sha1); + if (ref->deletion && !allow_deleting_refs) { + ref->status = REF_STATUS_REJECT_NODELETE; + continue; + } + if (!ref->deletion && + !hashcmp(ref->old_sha1, new_sha1)) { + ref->status = REF_STATUS_UPTODATE; + continue; + } + + /* This part determines what can overwrite what. + * The rules are: + * + * (0) you can always use --force or +A:B notation to + * selectively force individual ref pairs. + * + * (1) if the old thing does not exist, it is OK. + * + * (2) if you do not have the old thing, you are not allowed + * to overwrite it; you would not know what you are losing + * otherwise. + * + * (3) if both new and old are commit-ish, and new is a + * descendant of old, it is OK. + * + * (4) regardless of all of the above, removing :B is + * always allowed. + */ + + ref->nonfastforward = + !ref->deletion && + !is_null_sha1(ref->old_sha1) && + (!has_sha1_file(ref->old_sha1) + || !ref_newer(new_sha1, ref->old_sha1)); + + if (ref->nonfastforward && !ref->force && !args.force_update) { + ref->status = REF_STATUS_REJECT_NONFASTFORWARD; + continue; + } + + hashcpy(ref->new_sha1, new_sha1); + if (!ref->deletion) + new_refs++; + + if (!args.dry_run) { + char *old_hex = sha1_to_hex(ref->old_sha1); + char *new_hex = sha1_to_hex(ref->new_sha1); + + if (ask_for_status_report) { + packet_write(out, "%s %s %s%c%s", + old_hex, new_hex, ref->name, 0, + "report-status"); + ask_for_status_report = 0; + expect_status_report = 1; + } + else + packet_write(out, "%s %s %s", + old_hex, new_hex, ref->name); + } + ref->status = expect_status_report ? + REF_STATUS_EXPECTING_REPORT : + REF_STATUS_OK; + } + + packet_flush(out); + if (new_refs && !args.dry_run) { + if (pack_objects(out, remote_refs) < 0) { + close(out); + return -1; + } + } + close(out); + + if (expect_status_report) + ret = receive_status(in, remote_refs); + else + ret = 0; + + print_push_status(dest, remote_refs); + + if (!args.dry_run && remote) { + for (ref = remote_refs; ref; ref = ref->next) + update_tracking_ref(remote, ref); + } + + if (!refs_pushed(remote_refs)) + fprintf(stderr, "Everything up-to-date\n"); + if (ret < 0) + return ret; + for (ref = remote_refs; ref; ref = ref->next) { + switch (ref->status) { + case REF_STATUS_NONE: + case REF_STATUS_UPTODATE: + case REF_STATUS_OK: + break; + default: + return -1; + } + } + return 0; +} + +static void verify_remote_names(int nr_heads, const char **heads) +{ + int i; + + for (i = 0; i < nr_heads; i++) { + const char *remote = strchr(heads[i], ':'); + + remote = remote ? (remote + 1) : heads[i]; + switch (check_ref_format(remote)) { + case 0: /* ok */ + case -2: /* ok but a single level -- that is fine for + * a match pattern. + */ + case -3: /* ok but ends with a pattern-match character */ + continue; + } + die("remote part of refspec is not a valid name in %s", + heads[i]); + } +} + +int cmd_send_pack(int argc, const char **argv, const char *prefix) +{ + int i, nr_heads = 0; + const char **heads = NULL; + const char *remote_name = NULL; + struct remote *remote = NULL; + const char *dest = NULL; + + argv++; + for (i = 1; i < argc; i++, argv++) { + const char *arg = *argv; + + if (*arg == '-') { + if (!prefixcmp(arg, "--receive-pack=")) { + args.receivepack = arg + 15; + continue; + } + if (!prefixcmp(arg, "--exec=")) { + args.receivepack = arg + 7; + continue; + } + if (!prefixcmp(arg, "--remote=")) { + remote_name = arg + 9; + continue; + } + if (!strcmp(arg, "--all")) { + args.send_all = 1; + continue; + } + if (!strcmp(arg, "--dry-run")) { + args.dry_run = 1; + continue; + } + if (!strcmp(arg, "--mirror")) { + args.send_mirror = 1; + continue; + } + if (!strcmp(arg, "--force")) { + args.force_update = 1; + continue; + } + if (!strcmp(arg, "--verbose")) { + args.verbose = 1; + continue; + } + if (!strcmp(arg, "--thin")) { + args.use_thin_pack = 1; + continue; + } + usage(send_pack_usage); + } + if (!dest) { + dest = arg; + continue; + } + heads = (const char **) argv; + nr_heads = argc - i; + break; + } + if (!dest) + usage(send_pack_usage); + /* + * --all and --mirror are incompatible; neither makes sense + * with any refspecs. + */ + if ((heads && (args.send_all || args.send_mirror)) || + (args.send_all && args.send_mirror)) + usage(send_pack_usage); + + if (remote_name) { + remote = remote_get(remote_name); + if (!remote_has_url(remote, dest)) { + die("Destination %s is not a uri for %s", + dest, remote_name); + } + } + + return send_pack(&args, dest, remote, nr_heads, heads); +} + +int send_pack(struct send_pack_args *my_args, + const char *dest, struct remote *remote, + int nr_heads, const char **heads) +{ + int fd[2], ret; + struct child_process *conn; + + memcpy(&args, my_args, sizeof(args)); + + verify_remote_names(nr_heads, heads); + + conn = git_connect(fd, dest, args.receivepack, args.verbose ? CONNECT_VERBOSE : 0); + ret = do_send_pack(fd[0], fd[1], remote, dest, nr_heads, heads); + close(fd[0]); + close(fd[1]); + ret |= finish_connect(conn); + return !!ret; +} diff --git a/builtin-tag.c b/builtin-tag.c index 7626da3f47..729389bbd6 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -11,9 +11,15 @@ #include "refs.h" #include "tag.h" #include "run-command.h" - -static const char builtin_tag_usage[] = - "git-tag [-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg> | -F <file>] <tagname> [<head>]"; +#include "parse-options.h" + +static const char * const git_tag_usage[] = { + "git-tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]", + "git-tag -d <tagname>...", + "git-tag [-n [<num>]] -l [<pattern>]", + "git-tag -v <tagname>...", + NULL +}; static char signingkey[1000]; @@ -272,7 +278,7 @@ static void write_tag_body(int fd, const unsigned char *sha1) static void create_tag(const unsigned char *object, const char *tag, struct strbuf *buf, int message, int sign, - unsigned char *prev, unsigned char *result) + unsigned char *prev, unsigned char *result) { enum object_type type; char header_buf[1024]; @@ -331,105 +337,101 @@ static void create_tag(const unsigned char *object, const char *tag, die("unable to write tag file"); } +struct msg_arg { + int given; + struct strbuf buf; +}; + +static int parse_msg_arg(const struct option *opt, const char *arg, int unset) +{ + struct msg_arg *msg = opt->value; + + if (!arg) + return -1; + if (msg->buf.len) + strbuf_addstr(&(msg->buf), "\n\n"); + strbuf_addstr(&(msg->buf), arg); + msg->given = 1; + return 0; +} + int cmd_tag(int argc, const char **argv, const char *prefix) { struct strbuf buf; unsigned char object[20], prev[20]; - int annotate = 0, sign = 0, force = 0, lines = 0, message = 0; char ref[PATH_MAX]; const char *object_ref, *tag; - int i; struct ref_lock *lock; + int annotate = 0, sign = 0, force = 0, lines = 0, + delete = 0, verify = 0; + char *list = NULL, *msgfile = NULL, *keyid = NULL; + const char *no_pattern = "NO_PATTERN"; + struct msg_arg msg = { 0, STRBUF_INIT }; + struct option options[] = { + { OPTION_STRING, 'l', NULL, &list, "pattern", "list tag names", + PARSE_OPT_OPTARG, NULL, (intptr_t) no_pattern }, + { OPTION_INTEGER, 'n', NULL, &lines, NULL, + "print n lines of each tag message", + PARSE_OPT_OPTARG, NULL, 1 }, + OPT_BOOLEAN('d', NULL, &delete, "delete tags"), + OPT_BOOLEAN('v', NULL, &verify, "verify tags"), + + OPT_GROUP("Tag creation options"), + OPT_BOOLEAN('a', NULL, &annotate, + "annotated tag, needs a message"), + OPT_CALLBACK('m', NULL, &msg, "msg", + "message for the tag", parse_msg_arg), + OPT_STRING('F', NULL, &msgfile, "file", "message in a file"), + OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"), + OPT_STRING('u', NULL, &keyid, "key-id", + "use another key to sign the tag"), + OPT_BOOLEAN('f', NULL, &force, "replace the tag if exists"), + OPT_END() + }; + git_config(git_tag_config); - strbuf_init(&buf, 0); - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; + argc = parse_options(argc, argv, options, git_tag_usage, 0); - if (arg[0] != '-') - break; - if (!strcmp(arg, "-a")) { - annotate = 1; - continue; - } - if (!strcmp(arg, "-s")) { - annotate = 1; - sign = 1; - continue; - } - if (!strcmp(arg, "-f")) { - force = 1; - continue; - } - if (!strcmp(arg, "-n")) { - if (i + 1 == argc || *argv[i + 1] == '-') - /* no argument */ - lines = 1; - else - lines = isdigit(*argv[++i]) ? - atoi(argv[i]) : 1; - continue; - } - if (!strcmp(arg, "-m")) { - annotate = 1; - i++; - if (i == argc) - die("option -m needs an argument."); - if (message) - die("only one -F or -m option is allowed."); - strbuf_addstr(&buf, argv[i]); - message = 1; - continue; - } - if (!strcmp(arg, "-F")) { - annotate = 1; - i++; - if (i == argc) - die("option -F needs an argument."); - if (message) - die("only one -F or -m option is allowed."); - - if (!strcmp(argv[i], "-")) { + if (sign) + annotate = 1; + + if (list) + return list_tags(list == no_pattern ? NULL : list, lines); + if (delete) + return for_each_tag_name(argv, delete_tag); + if (verify) + return for_each_tag_name(argv, verify_tag); + + strbuf_init(&buf, 0); + if (msg.given || msgfile) { + if (msg.given && msgfile) + die("only one -F or -m option is allowed."); + annotate = 1; + if (msg.given) + strbuf_addbuf(&buf, &(msg.buf)); + else { + if (!strcmp(msgfile, "-")) { if (strbuf_read(&buf, 0, 1024) < 0) - die("cannot read %s", argv[i]); + die("cannot read %s", msgfile); } else { - if (strbuf_read_file(&buf, argv[i], 1024) < 0) + if (strbuf_read_file(&buf, msgfile, 1024) < 0) die("could not open or read '%s': %s", - argv[i], strerror(errno)); + msgfile, strerror(errno)); } - message = 1; - continue; - } - if (!strcmp(arg, "-u")) { - annotate = 1; - sign = 1; - i++; - if (i == argc) - die("option -u needs an argument."); - if (strlcpy(signingkey, argv[i], sizeof(signingkey)) - >= sizeof(signingkey)) - die("argument to option -u too long"); - continue; } - if (!strcmp(arg, "-l")) - return list_tags(argv[i + 1], lines); - if (!strcmp(arg, "-d")) - return for_each_tag_name(argv + i + 1, delete_tag); - if (!strcmp(arg, "-v")) - return for_each_tag_name(argv + i + 1, verify_tag); - usage(builtin_tag_usage); } - if (i == argc) { + if (argc == 0) { if (annotate) - usage(builtin_tag_usage); + usage_with_options(git_tag_usage, options); return list_tags(NULL, lines); } - tag = argv[i++]; + tag = argv[0]; - object_ref = i < argc ? argv[i] : "HEAD"; - if (i + 1 < argc) + object_ref = argc == 2 ? argv[1] : "HEAD"; + if (argc > 2) die("too many params"); if (get_sha1(object_ref, object)) @@ -446,7 +448,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) die("tag '%s' already exists", tag); if (annotate) - create_tag(object, tag, &buf, message, sign, prev, object); + create_tag(object, tag, &buf, msg.given || msgfile, + sign, prev, object); lock = lock_any_ref_for_update(ref, prev, 0); if (!lock) @@ -24,6 +24,7 @@ extern int cmd_check_attr(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); extern int cmd_cherry(int argc, const char **argv, const char *prefix); extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix); +extern int cmd_clean(int argc, const char **argv, const char *prefix); extern int cmd_commit(int argc, const char **argv, const char *prefix); extern int cmd_commit_tree(int argc, const char **argv, const char *prefix); extern int cmd_count_objects(int argc, const char **argv, const char *prefix); @@ -32,6 +33,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); +extern int cmd_fast_export(int argc, const char **argv, const char *prefix); extern int cmd_fetch(int argc, const char **argv, const char *prefix); extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix); extern int cmd_fetch__tool(int argc, const char **argv, const char *prefix); @@ -49,6 +51,7 @@ extern int cmd_log(int argc, const char **argv, const char *prefix); extern int cmd_log_reflog(int argc, const char **argv, const char *prefix); extern int cmd_ls_files(int argc, const char **argv, const char *prefix); extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); +extern int cmd_ls_remote(int argc, const char **argv, const char *prefix); extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); extern int cmd_merge_base(int argc, const char **argv, const char *prefix); @@ -70,6 +73,7 @@ extern int cmd_rev_list(int argc, const char **argv, const char *prefix); extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); extern int cmd_revert(int argc, const char **argv, const char *prefix); extern int cmd_rm(int argc, const char **argv, const char *prefix); +extern int cmd_send_pack(int argc, const char **argv, const char *prefix); extern int cmd_shortlog(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); @@ -192,6 +192,13 @@ enum object_type { OBJ_MAX, }; +static inline enum object_type object_type(unsigned int mode) +{ + return S_ISDIR(mode) ? OBJ_TREE : + S_ISGITLINK(mode) ? OBJ_COMMIT : + OBJ_BLOB; +} + #define GIT_DIR_ENVIRONMENT "GIT_DIR" #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE" #define DEFAULT_GIT_DIR_ENVIRONMENT ".git" @@ -290,6 +297,7 @@ extern int refresh_index(struct index_state *, unsigned int flags, const char ** struct lock_file { struct lock_file *next; + int fd; pid_t owner; char on_list; char filename[PATH_MAX]; @@ -415,6 +423,10 @@ extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int * extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); +extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules); +extern const char *ref_rev_parse_rules[]; +extern const char *ref_fetch_rules[]; + extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg); extern int validate_headref(const char *ref); @@ -500,8 +512,20 @@ struct ref { struct ref *next; unsigned char old_sha1[20]; unsigned char new_sha1[20]; - unsigned char force; - unsigned char merge; + unsigned int force:1, + merge:1, + nonfastforward:1, + deletion:1; + enum { + REF_STATUS_NONE = 0, + REF_STATUS_OK, + REF_STATUS_REJECT_NONFASTFORWARD, + REF_STATUS_REJECT_NODELETE, + REF_STATUS_UPTODATE, + REF_STATUS_REMOTE_REJECT, + REF_STATUS_EXPECTING_REPORT, + } status; + char *remote_status; struct ref *peer_ref; /* when renaming */ char name[FLEX_ARRAY]; /* more */ }; @@ -510,8 +534,10 @@ struct ref { #define REF_HEADS (1u << 1) #define REF_TAGS (1u << 2) +extern struct ref *find_ref_by_name(struct ref *list, const char *name); + #define CONNECT_VERBOSE (1u << 0) -extern struct child_process *git_connect(int fd[2], char *url, const char *prog, int flags); +extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags); extern int finish_connect(struct child_process *conn); extern int path_match(const char *path, int nr, char **match); extern int get_ack(int fd, unsigned char *result_sha1); @@ -557,6 +583,7 @@ extern int git_config_bool(const char *, const char *); extern int git_config_set(const char *, const char *); extern int git_config_set_multivar(const char *, const char *, const char *, int); extern int git_config_rename_section(const char *, const char *); +extern const char *git_etc_gitconfig(void); extern int check_repository_format_version(const char *var, const char *value); #define MAX_GITNAME (1000) @@ -598,7 +625,7 @@ extern void alloc_report(void); /* trace.c */ extern void trace_printf(const char *format, ...); -extern void trace_argv_printf(const char **argv, int count, const char *format, ...); +extern void trace_argv_printf(const char **argv, const char *format, ...); /* convert.c */ /* returns 1 if *dst was used */ @@ -118,21 +118,27 @@ bad: int git_config_colorbool(const char *var, const char *value) { - if (!value) - return 1; - if (!strcasecmp(value, "auto")) { - if (isatty(1) || (pager_in_use && pager_use_color)) { - char *term = getenv("TERM"); - if (term && strcmp(term, "dumb")) - return 1; - } - return 0; + if (value) { + if (!strcasecmp(value, "never")) + return 0; + if (!strcasecmp(value, "always")) + return 1; + if (!strcasecmp(value, "auto")) + goto auto_color; } - if (!strcasecmp(value, "never")) + + /* Missing or explicit false to turn off colorization */ + if (!git_config_bool(var, value)) return 0; - if (!strcasecmp(value, "always")) - return 1; - return git_config_bool(var, value); + + /* any normal truth value defaults to 'auto' */ + auto_color: + if (isatty(1) || (pager_in_use && pager_use_color)) { + char *term = getenv("TERM"); + if (term && strcmp(term, "dumb")) + return 1; + } + return 0; } static int color_vfprintf(FILE *fp, const char *color, const char *fmt, diff --git a/command-list.txt b/command-list.txt new file mode 100644 index 0000000000..28342da959 --- /dev/null +++ b/command-list.txt @@ -0,0 +1,130 @@ +# List of known git commands. +# command name category [deprecated] [common] +git-add mainporcelain common +git-am mainporcelain +git-annotate ancillaryinterrogators +git-apply plumbingmanipulators +git-archimport foreignscminterface +git-archive mainporcelain +git-bisect mainporcelain common +git-blame ancillaryinterrogators +git-branch mainporcelain common +git-bundle mainporcelain +git-cat-file plumbinginterrogators +git-check-attr purehelpers +git-checkout mainporcelain common +git-checkout-index plumbingmanipulators +git-check-ref-format purehelpers +git-cherry ancillaryinterrogators +git-cherry-pick mainporcelain +git-citool mainporcelain +git-clean mainporcelain +git-clone mainporcelain common +git-commit mainporcelain common +git-commit-tree plumbingmanipulators +git-config ancillarymanipulators +git-count-objects ancillaryinterrogators +git-cvsexportcommit foreignscminterface +git-cvsimport foreignscminterface +git-cvsserver foreignscminterface +git-daemon synchingrepositories +git-describe mainporcelain +git-diff mainporcelain common +git-diff-files plumbinginterrogators +git-diff-index plumbinginterrogators +git-diff-tree plumbinginterrogators +git-fast-export ancillarymanipulators +git-fast-import ancillarymanipulators +git-fetch mainporcelain common +git-fetch-pack synchingrepositories +git-filter-branch ancillarymanipulators +git-fmt-merge-msg purehelpers +git-for-each-ref plumbinginterrogators +git-format-patch mainporcelain +git-fsck ancillaryinterrogators +git-gc mainporcelain +git-get-tar-commit-id ancillaryinterrogators +git-grep mainporcelain common +git-gui mainporcelain +git-hash-object plumbingmanipulators +git-help ancillaryinterrogators +git-http-fetch synchelpers +git-http-push synchelpers +git-imap-send foreignscminterface +git-index-pack plumbingmanipulators +git-init mainporcelain common +git-instaweb ancillaryinterrogators +gitk mainporcelain +git-log mainporcelain common +git-lost-found ancillarymanipulators deprecated +git-ls-files plumbinginterrogators +git-ls-remote plumbinginterrogators +git-ls-tree plumbinginterrogators +git-mailinfo purehelpers +git-mailsplit purehelpers +git-merge mainporcelain common +git-merge-base plumbinginterrogators +git-merge-file plumbingmanipulators +git-merge-index plumbingmanipulators +git-merge-one-file purehelpers +git-mergetool ancillarymanipulators +git-merge-tree ancillaryinterrogators +git-mktag plumbingmanipulators +git-mktree plumbingmanipulators +git-mv mainporcelain common +git-name-rev plumbinginterrogators +git-pack-objects plumbingmanipulators +git-pack-redundant plumbinginterrogators +git-pack-refs ancillarymanipulators +git-parse-remote synchelpers +git-patch-id purehelpers +git-peek-remote purehelpers deprecated +git-prune ancillarymanipulators +git-prune-packed plumbingmanipulators +git-pull mainporcelain common +git-push mainporcelain common +git-quiltimport foreignscminterface +git-read-tree plumbingmanipulators +git-rebase mainporcelain common +git-receive-pack synchelpers +git-reflog ancillarymanipulators +git-relink ancillarymanipulators +git-remote ancillarymanipulators +git-repack ancillarymanipulators +git-request-pull foreignscminterface +git-rerere ancillaryinterrogators +git-reset mainporcelain common +git-revert mainporcelain +git-rev-list plumbinginterrogators +git-rev-parse ancillaryinterrogators +git-rm mainporcelain common +git-runstatus ancillaryinterrogators +git-send-email foreignscminterface +git-send-pack synchingrepositories +git-shell synchelpers +git-shortlog mainporcelain +git-show mainporcelain common +git-show-branch ancillaryinterrogators +git-show-index plumbinginterrogators +git-show-ref plumbinginterrogators +git-sh-setup purehelpers +git-stash mainporcelain +git-status mainporcelain common +git-stripspace purehelpers +git-submodule mainporcelain +git-svn foreignscminterface +git-symbolic-ref plumbingmanipulators +git-tag mainporcelain common +git-tar-tree plumbinginterrogators deprecated +git-unpack-file plumbinginterrogators +git-unpack-objects plumbingmanipulators +git-update-index plumbingmanipulators +git-update-ref plumbingmanipulators +git-update-server-info synchingrepositories +git-upload-archive synchelpers +git-upload-pack synchelpers +git-var plumbinginterrogators +git-verify-pack plumbinginterrogators +git-verify-tag ancillaryinterrogators +git-whatchanged ancillaryinterrogators +git-write-tree plumbingmanipulators @@ -6,6 +6,7 @@ * */ #include "cache.h" +#include "exec_cmd.h" #define MAXNAME (256) @@ -459,6 +460,21 @@ int git_config_from_file(config_fn_t fn, const char *filename) return ret; } +const char *git_etc_gitconfig(void) +{ + static const char *system_wide; + if (!system_wide) { + system_wide = ETC_GITCONFIG; + if (!is_absolute_path(system_wide)) { + /* interpret path relative to exec-dir */ + const char *exec_path = git_exec_path(); + system_wide = prefix_path(exec_path, strlen(exec_path), + system_wide); + } + } + return system_wide; +} + int git_config(config_fn_t fn) { int ret = 0; @@ -471,8 +487,8 @@ int git_config(config_fn_t fn) * config file otherwise. */ filename = getenv(CONFIG_ENVIRONMENT); if (!filename) { - if (!access(ETC_GITCONFIG, R_OK)) - ret += git_config_from_file(fn, ETC_GITCONFIG); + if (!access(git_etc_gitconfig(), R_OK)) + ret += git_config_from_file(fn, git_etc_gitconfig()); home = getenv("HOME"); filename = getenv(CONFIG_LOCAL_ENVIRONMENT); if (!filename) diff --git a/configure.ac b/configure.ac index 7bcf1a488c..5f8a15b9f9 100644 --- a/configure.ac +++ b/configure.ac @@ -415,7 +415,7 @@ GIT_PARSE_WITH(iconv)) # times (my ext3 doesn't). # # Define USE_STDEV below if you want git to care about the underlying device -# change being considered an inode change from the update-cache perspective. +# change being considered an inode change from the update-index perspective. ## Output files @@ -36,6 +36,11 @@ static int check_ref(const char *name, int len, unsigned int flags) return !(flags & ~REF_NORMAL); } +int check_ref_type(const struct ref *ref, int flags) +{ + return check_ref(ref->name, strlen(ref->name), flags); +} + /* * Read all the refs from the other end */ @@ -476,9 +481,10 @@ char *get_port(char *host) * * If it returns, the connect is successful; it just dies on errors. */ -struct child_process *git_connect(int fd[2], char *url, +struct child_process *git_connect(int fd[2], const char *url_orig, const char *prog, int flags) { + char *url = xstrdup(url_orig); char *host, *path = url; char *end; int c; @@ -568,6 +574,7 @@ struct child_process *git_connect(int fd[2], char *url, prog, path, 0, target_host, 0); free(target_host); + free(url); if (free_path) free(path); return NULL; @@ -619,6 +626,7 @@ struct child_process *git_connect(int fd[2], char *url, fd[0] = conn->out; /* read from child's stdout */ fd[1] = conn->in; /* write to child's stdin */ strbuf_release(&cmd); + free(url); if (free_path) free(path); return conn; diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 599b2fc571..58e0e53cd6 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -551,6 +551,20 @@ _git_describe () _git_diff () { + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + __gitcomp "--cached --stat --numstat --shortstat --summary + --patch-with-stat --name-only --name-status --color + --no-color --color-words --no-renames --check + --full-index --binary --abbrev --diff-filter + --find-copies-harder --pickaxe-all --pickaxe-regex + --text --ignore-space-at-eol --ignore-space-change + --ignore-all-space --exit-code --quiet --ext-diff + --no-ext-diff" + return + ;; + esac __git_complete_file } diff --git a/git-clean.sh b/contrib/examples/git-clean.sh index 01c95e9fe8..01c95e9fe8 100755 --- a/git-clean.sh +++ b/contrib/examples/git-clean.sh diff --git a/contrib/examples/git-commit.sh b/contrib/examples/git-commit.sh index 485339754c..2c4a4062a5 100755 --- a/contrib/examples/git-commit.sh +++ b/contrib/examples/git-commit.sh @@ -74,6 +74,7 @@ trap ' all= also= +allow_empty=f interactive= only= logfile= @@ -114,6 +115,10 @@ do -a|--a|--al|--all) all=t ;; + --allo|--allow|--allow-|--allow-e|--allow-em|--allow-emp|\ + --allow-empt|--allow-empty) + allow_empty=t + ;; --au=*|--aut=*|--auth=*|--autho=*|--author=*) force_author="${1#*=}" ;; @@ -515,13 +520,18 @@ else # we need to check if there is anything to commit run_status >/dev/null fi -if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" ] -then +case "$allow_empty,$?,$PARENTS" in +t,* | ?,0,* | ?,*,-p' '?*-p' '?*) + # an explicit --allow-empty, or a merge commit can record the + # same tree as its parent. Otherwise having commitable paths + # is required. + ;; +*) rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG" use_status_color=t run_status exit 1 -fi +esac case "$no_edit" in '') diff --git a/git-ls-remote.sh b/contrib/examples/git-ls-remote.sh index fec70bbf88..fec70bbf88 100755 --- a/git-ls-remote.sh +++ b/contrib/examples/git-ls-remote.sh diff --git a/contrib/examples/git-merge-ours.sh b/contrib/examples/git-merge-ours.sh index c81a790aa6..29dba4ba3a 100755 --- a/contrib/examples/git-merge-ours.sh +++ b/contrib/examples/git-merge-ours.sh @@ -9,6 +9,6 @@ # because the current index is what we will be committing as the # merge result. -git diff-index --quiet --cached HEAD || exit 2 +git diff-index --quiet --cached HEAD -- || exit 2 exit 0 diff --git a/contrib/remotes2config.sh b/contrib/remotes2config.sh index 5838b3ab05..1cda19f66a 100644..100755 --- a/contrib/remotes2config.sh +++ b/contrib/remotes2config.sh @@ -11,11 +11,11 @@ if [ -d "$GIT_DIR"/remotes ]; then { cd "$GIT_DIR"/remotes ls | while read f; do - name=$(printf "$f" | tr -c "A-Za-z0-9" ".") + name=$(printf "$f" | tr -c "A-Za-z0-9-" ".") sed -n \ - -e "s/^URL: \(.*\)$/remote.$name.url \1 ./p" \ - -e "s/^Pull: \(.*\)$/remote.$name.fetch \1 ^$ /p" \ - -e "s/^Push: \(.*\)$/remote.$name.push \1 ^$ /p" \ + -e "s/^URL:[ ]*\(.*\)$/remote.$name.url \1 ./p" \ + -e "s/^Pull:[ ]*\(.*\)$/remote.$name.fetch \1 ^$ /p" \ + -e "s/^Push:[ ]*\(.*\)$/remote.$name.push \1 ^$ /p" \ < "$f" done echo done diff --git a/diff-lib.c b/diff-lib.c index f8e936ae10..d85d8f34ba 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -231,7 +231,7 @@ static int handle_diff_files_args(struct rev_info *revs, static int is_outside_repo(const char *path, int nongit, const char *prefix) { int i; - if (nongit || !strcmp(path, "-") || path[0] == '/') + if (nongit || !strcmp(path, "-") || is_absolute_path(path)) return 1; if (prefixcmp(path, "../")) return 0; diff --git a/diffcore-break.c b/diffcore-break.c index c71a22621a..31cdcfe8bc 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -52,8 +52,10 @@ static int should_break(struct diff_filespec *src, * is the default. */ - if (!S_ISREG(src->mode) || !S_ISREG(dst->mode)) - return 0; /* leave symlink rename alone */ + if (S_ISREG(src->mode) != S_ISREG(dst->mode)) { + *merge_score_p = (int)MAX_SCORE; + return 1; /* even their types are different */ + } if (src->sha1_valid && dst->sha1_valid && !hashcmp(src->sha1, dst->sha1)) @@ -168,11 +170,13 @@ void diffcore_break(int break_score) struct diff_filepair *p = q->queue[i]; int score; - /* We deal only with in-place edit of non directory. + /* + * We deal only with in-place edit of blobs. * We do not break anything else. */ if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two) && - !S_ISDIR(p->one->mode) && !S_ISDIR(p->two->mode) && + object_type(p->one->mode) == OBJ_BLOB && + object_type(p->two->mode) == OBJ_BLOB && !strcmp(p->one->path, p->two->path)) { if (should_break(p->one, p->two, break_score, &score)) { diff --git a/diffcore-rename.c b/diffcore-rename.c index f9ebea5640..3d377251be 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -244,28 +244,35 @@ static int find_identical_files(struct file_similarity *src, * Walk over all the destinations ... */ do { - struct diff_filespec *one = dst->filespec; + struct diff_filespec *target = dst->filespec; struct file_similarity *p, *best; - int i = 100; + int i = 100, best_score = -1; /* * .. to find the best source match */ best = NULL; for (p = src; p; p = p->next) { - struct diff_filespec *two = p->filespec; + int score; + struct diff_filespec *source = p->filespec; /* False hash collission? */ - if (hashcmp(one->sha1, two->sha1)) + if (hashcmp(source->sha1, target->sha1)) continue; /* Non-regular files? If so, the modes must match! */ - if (!S_ISREG(one->mode) || !S_ISREG(two->mode)) { - if (one->mode != two->mode) + if (!S_ISREG(source->mode) || !S_ISREG(target->mode)) { + if (source->mode != target->mode) continue; } - best = p; - if (basename_same(one, two)) - break; + /* Give higher scores to sources that haven't been used already */ + score = !source->rename_used; + score += basename_same(source, target); + if (score > best_score) { + best = p; + best_score = score; + if (score == 2) + break; + } /* Too many identical alternatives? Pick one */ if (!--i) @@ -490,6 +497,19 @@ void diffcore_rename(struct diff_options *options) qsort(mx, num_create * num_src, sizeof(*mx), score_compare); for (i = 0; i < num_create * num_src; i++) { struct diff_rename_dst *dst = &rename_dst[mx[i].dst]; + struct diff_filespec *src; + if (dst->pair) + continue; /* already done, either exact or fuzzy. */ + if (mx[i].score < minimum_score) + break; /* there is no more usable pair. */ + src = rename_src[mx[i].src].one; + if (src->rename_used) + continue; + record_rename_pair(mx[i].dst, mx[i].src, mx[i].score); + rename_count++; + } + for (i = 0; i < num_create * num_src; i++) { + struct diff_rename_dst *dst = &rename_dst[mx[i].dst]; if (dst->pair) continue; /* already done, either exact or fuzzy. */ if (mx[i].score < minimum_score) @@ -144,17 +144,14 @@ void add_exclude(const char *string, const char *base, x->flags |= EXC_FLAG_NOWILDCARD; if (*string == '*' && no_wildcard(string+1)) x->flags |= EXC_FLAG_ENDSWITH; - if (which->nr == which->alloc) { - which->alloc = alloc_nr(which->alloc); - which->excludes = xrealloc(which->excludes, - which->alloc * sizeof(x)); - } + ALLOC_GROW(which->excludes, which->nr + 1, which->alloc); which->excludes[which->nr++] = x; } static int add_excludes_from_file_1(const char *fname, const char *base, int baselen, + char **buf_p, struct exclude_list *which) { struct stat st; @@ -175,6 +172,8 @@ static int add_excludes_from_file_1(const char *fname, goto err; close(fd); + if (buf_p) + *buf_p = buf; buf[size++] = '\n'; entry = buf; for (i = 0; i < size; i++) { @@ -196,31 +195,63 @@ static int add_excludes_from_file_1(const char *fname, void add_excludes_from_file(struct dir_struct *dir, const char *fname) { - if (add_excludes_from_file_1(fname, "", 0, + if (add_excludes_from_file_1(fname, "", 0, NULL, &dir->exclude_list[EXC_FILE]) < 0) die("cannot use %s as an exclude file", fname); } -int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen) +static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) { - char exclude_file[PATH_MAX]; - struct exclude_list *el = &dir->exclude_list[EXC_DIRS]; - int current_nr = el->nr; - - if (dir->exclude_per_dir) { - memcpy(exclude_file, base, baselen); - strcpy(exclude_file + baselen, dir->exclude_per_dir); - add_excludes_from_file_1(exclude_file, base, baselen, el); + struct exclude_list *el; + struct exclude_stack *stk = NULL; + int current; + + if ((!dir->exclude_per_dir) || + (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX)) + return; /* too long a path -- ignore */ + + /* Pop the ones that are not the prefix of the path being checked. */ + el = &dir->exclude_list[EXC_DIRS]; + while ((stk = dir->exclude_stack) != NULL) { + if (stk->baselen <= baselen && + !strncmp(dir->basebuf, base, stk->baselen)) + break; + dir->exclude_stack = stk->prev; + while (stk->exclude_ix < el->nr) + free(el->excludes[--el->nr]); + free(stk->filebuf); + free(stk); } - return current_nr; -} -void pop_exclude_per_directory(struct dir_struct *dir, int stk) -{ - struct exclude_list *el = &dir->exclude_list[EXC_DIRS]; + /* Read from the parent directories and push them down. */ + current = stk ? stk->baselen : -1; + while (current < baselen) { + struct exclude_stack *stk = xcalloc(1, sizeof(*stk)); + const char *cp; - while (stk < el->nr) - free(el->excludes[--el->nr]); + if (current < 0) { + cp = base; + current = 0; + } + else { + cp = strchr(base + current + 1, '/'); + if (!cp) + die("oops in prep_exclude"); + cp++; + } + stk->prev = dir->exclude_stack; + stk->baselen = cp - base; + stk->exclude_ix = el->nr; + memcpy(dir->basebuf + current, base + current, + stk->baselen - current); + strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir); + add_excludes_from_file_1(dir->basebuf, + dir->basebuf, stk->baselen, + &stk->filebuf, el); + dir->exclude_stack = stk; + current = stk->baselen; + } + dir->basebuf[baselen] = '\0'; } /* Scan the list and let the last match determines the fate. @@ -287,6 +318,7 @@ int excluded(struct dir_struct *dir, const char *pathname) const char *basename = strrchr(pathname, '/'); basename = (basename) ? basename+1 : pathname; + prep_exclude(dir, pathname, basename-pathname); for (st = EXC_CMDL; st <= EXC_FILE; st++) { switch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) { case 0: @@ -504,13 +536,10 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co int contents = 0; if (fdir) { - int exclude_stk; struct dirent *de; char fullname[PATH_MAX + 1]; memcpy(fullname, base, baselen); - exclude_stk = push_exclude_per_directory(dir, base, baselen); - while ((de = readdir(fdir)) != NULL) { int len, dtype; int exclude; @@ -584,8 +613,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co } exit_early: closedir(fdir); - - pop_exclude_per_directory(dir, exclude_stk); } return contents; @@ -654,37 +681,9 @@ static void free_simplify(struct path_simplify *simplify) int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen, const char **pathspec) { struct path_simplify *simplify = create_simplify(pathspec); - char *pp = NULL; - - /* - * Make sure to do the per-directory exclude for all the - * directories leading up to our base. - */ - if (baselen) { - if (dir->exclude_per_dir) { - char *p; - pp = xmalloc(baselen+1); - memcpy(pp, base, baselen+1); - p = pp; - while (1) { - char save = *p; - *p = 0; - push_exclude_per_directory(dir, pp, p-pp); - *p++ = save; - if (!save) - break; - p = strchr(p, '/'); - if (p) - p++; - else - p = pp + baselen; - } - } - } read_directory_recursive(dir, path, base, baselen, 0, simplify); free_simplify(simplify); - free(pp); qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name); qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name); return dir->nr; @@ -1,17 +1,6 @@ #ifndef DIR_H #define DIR_H -/* - * We maintain three exclude pattern lists: - * EXC_CMDL lists patterns explicitly given on the command line. - * EXC_DIRS lists patterns obtained from per-directory ignore files. - * EXC_FILE lists patterns from fallback ignore files. - */ -#define EXC_CMDL 0 -#define EXC_DIRS 1 -#define EXC_FILE 2 - - struct dir_entry { unsigned int len; char name[FLEX_ARRAY]; /* more */ @@ -34,6 +23,13 @@ struct exclude_list { } **excludes; }; +struct exclude_stack { + struct exclude_stack *prev; + char *filebuf; + int baselen; + int exclude_ix; +}; + struct dir_struct { int nr, alloc; int ignored_nr, ignored_alloc; @@ -48,6 +44,18 @@ struct dir_struct { /* Exclude info */ const char *exclude_per_dir; struct exclude_list exclude_list[3]; + /* + * We maintain three exclude pattern lists: + * EXC_CMDL lists patterns explicitly given on the command line. + * EXC_DIRS lists patterns obtained from per-directory ignore files. + * EXC_FILE lists patterns from fallback ignore files. + */ +#define EXC_CMDL 0 +#define EXC_DIRS 1 +#define EXC_FILE 2 + + struct exclude_stack *exclude_stack; + char basebuf[PATH_MAX]; }; extern int common_prefix(const char **pathspec); @@ -58,8 +66,6 @@ extern int common_prefix(const char **pathspec); extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen); extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen, const char **pathspec); -extern int push_exclude_per_directory(struct dir_struct *, const char *, int); -extern void pop_exclude_per_directory(struct dir_struct *, int); extern int excluded(struct dir_struct *, const char *); extern void add_excludes_from_file(struct dir_struct *, const char *fname); diff --git a/exec_cmd.c b/exec_cmd.c index 2d0a758512..e189caca62 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -80,7 +80,7 @@ int execv_git_cmd(const char **argv) tmp = argv[0]; argv[0] = cmd.buf; - trace_argv_printf(argv, -1, "trace: exec:"); + trace_argv_printf(argv, "trace: exec:"); /* execvp() can only ever return if it fails */ execvp(cmd.buf, (char **)argv); diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh index 17df47b950..a2913c2a2c 100755 --- a/generate-cmdlist.sh +++ b/generate-cmdlist.sh @@ -9,35 +9,8 @@ struct cmdname_help static struct cmdname_help common_cmds[] = {" -sort <<\EOF | -add -apply -archive -bisect -branch -checkout -cherry-pick -clone -commit -diff -fetch -grep -init -log -merge -mv -prune -pull -push -rebase -reset -revert -rm -show -show-branch -status -tag -EOF +sed -n -e 's/^git-\([^ ]*\)[ ].* common.*/\1/p' command-list.txt | +sort | while read cmd do sed -n ' @@ -218,7 +218,7 @@ fi case "$resolved" in '') - files=$(git diff-index --cached --name-only HEAD) || exit + files=$(git diff-index --cached --name-only HEAD --) || exit if [ "$files" ]; then echo "Dirty index: cannot apply patches (dirty: $files)" >&2 exit 1 @@ -307,9 +307,9 @@ do GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")" GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")" - if test -z "$GIT_AUTHOR_EMAIL" + if test -z "$GIT_AUTHOR_EMAIL" || test -z "$GIT_AUTHOR_DATE" then - echo "Patch does not have a valid e-mail address." + echo "Patch does not have valid authorship information." stop_here $this fi @@ -352,7 +352,7 @@ do case "$resolved$interactive" in tt) # This is used only for interactive view option. - git diff-index -p --cached HEAD >"$dotest/patch" + git diff-index -p --cached HEAD -- >"$dotest/patch" ;; esac esac @@ -411,7 +411,7 @@ do # trust what the user has in the index file and the # working tree. resolved= - git diff-index --quiet --cached HEAD && { + git diff-index --quiet --cached HEAD -- && { echo "No changes - did you forget to use 'git add'?" stop_here_user_resolve $this } @@ -433,7 +433,7 @@ do then # Applying the patch to an earlier tree and merging the # result may have produced the same tree as ours. - git diff-index --quiet --cached HEAD && { + git diff-index --quiet --cached HEAD -- && { echo No changes -- Patch already applied. go_next continue diff --git a/git-bisect.sh b/git-bisect.sh index 3aac8164c6..7a6521ec3c 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -37,7 +37,7 @@ sq() { } bisect_autostart() { - test -d "$GIT_DIR/refs/bisect" || { + test -f "$GIT_DIR/BISECT_NAMES" || { echo >&2 'You need to start by "git bisect start"' if test -t 0 then @@ -72,7 +72,7 @@ bisect_start() { ;; refs/heads/*) [ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree" - echo "$head" | sed 's#^refs/heads/##' >"$GIT_DIR/head-name" + echo "${head#refs/heads/}" >"$GIT_DIR/head-name" ;; *) die "Bad HEAD - strange symbolic ref" @@ -83,7 +83,6 @@ bisect_start() { # Get rid of any old bisect state # bisect_clean_state - mkdir "$GIT_DIR/refs/bisect" # # Check for one bad and then some good revisions. @@ -131,7 +130,7 @@ bisect_write() { good|skip) tag="$state"-"$rev" ;; *) die "Bad bisect_write argument: $state" ;; esac - echo "$rev" >"$GIT_DIR/refs/bisect/$tag" + git update-ref "refs/bisect/$tag" "$rev" echo "# $state: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" test -z "$nolog" && echo "git-bisect $state $rev" >>"$GIT_DIR/BISECT_LOG" } @@ -192,7 +191,7 @@ bisect_next_check() { ;; *) THEN='' - test -d "$GIT_DIR/refs/bisect" || { + test -f "$GIT_DIR/BISECT_NAMES" || { echo >&2 'You need to start by "git bisect start".' THEN='then ' } @@ -276,8 +275,7 @@ exit_if_skipped_commits () { if expr "$_tried" : ".*[|].*" > /dev/null ; then echo "There are only 'skip'ped commit left to test." echo "The first bad commit could be any of:" - echo "$_tried" | sed -e 's/[|]/\ -/g' + echo "$_tried" | tr '[|]' '[\012]' echo "We cannot bisect more!" exit 2 fi @@ -318,20 +316,23 @@ bisect_next() { exit_if_skipped_commits "$bisect_rev" echo "Bisecting: $bisect_nr revisions left to test after this" - echo "$bisect_rev" >"$GIT_DIR/refs/heads/new-bisect" + git branch -f new-bisect "$bisect_rev" git checkout -q new-bisect || exit - mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" && - GIT_DIR="$GIT_DIR" git symbolic-ref HEAD refs/heads/bisect + git branch -M new-bisect bisect git show-branch "$bisect_rev" } bisect_visualize() { bisect_next_check fail - not=`cd "$GIT_DIR/refs" && echo bisect/good-*` - eval gitk bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES") + not=$(git for-each-ref --format='%(refname)' "refs/bisect/good-*") + eval gitk refs/bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES") } bisect_reset() { + test -f "$GIT_DIR/BISECT_NAMES" || { + echo "We are not bisecting." + return + } case "$#" in 0) if [ -s "$GIT_DIR/head-name" ]; then branch=`cat "$GIT_DIR/head-name"` @@ -351,8 +352,12 @@ bisect_reset() { } bisect_clean_state() { - rm -fr "$GIT_DIR/refs/bisect" - rm -f "$GIT_DIR/refs/heads/bisect" + # There may be some refs packed during bisection. + git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* refs/heads/bisect | + while read ref hash + do + git update-ref -d $ref $hash + done rm -f "$GIT_DIR/BISECT_LOG" rm -f "$GIT_DIR/BISECT_NAMES" rm -f "$GIT_DIR/BISECT_RUN" diff --git a/git-checkout.sh b/git-checkout.sh index aa724ac1a3..f6d58ac044 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -175,7 +175,7 @@ detach_warn= describe_detached_head () { test -n "$quiet" || { printf >&2 "$1 " - GIT_PAGER= git log >&2 -1 --pretty=oneline --abbrev-commit "$2" + GIT_PAGER= git log >&2 -1 --pretty=oneline --abbrev-commit "$2" -- } } @@ -266,7 +266,7 @@ if [ "$?" -eq 0 ]; then if test -n "$branch" then old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'` - GIT_DIR="$GIT_DIR" git symbolic-ref -m "checkout: moving from $old_branch_name to $branch" HEAD "refs/heads/$branch" + GIT_DIR="$GIT_DIR" git symbolic-ref -m "checkout: moving from ${old_branch_name:-$old} to $branch" HEAD "refs/heads/$branch" if test -n "$quiet" then true # nothing @@ -278,7 +278,8 @@ if [ "$?" -eq 0 ]; then fi elif test -n "$detached" then - git update-ref --no-deref -m "checkout: moving to $arg" HEAD "$detached" || + old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'` + git update-ref --no-deref -m "checkout: moving from ${old_branch_name:-$old} to $arg" HEAD "$detached" || die "Cannot detach HEAD" if test -n "$detach_warn" then diff --git a/git-clone.sh b/git-clone.sh index 24ad179bbd..ecf9d89a10 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -229,7 +229,7 @@ cleanup() { trap cleanup 0 mkdir -p "$dir" && D=$(cd "$dir" && pwd) || usage test -n "$GIT_WORK_TREE" && mkdir -p "$GIT_WORK_TREE" && -W=$(cd "$GIT_WORK_TREE" && pwd) && export GIT_WORK_TREE="$W" +W=$(cd "$GIT_WORK_TREE" && pwd) && GIT_WORK_TREE="$W" && export GIT_WORK_TREE if test yes = "$bare" || test -n "$GIT_WORK_TREE"; then GIT_DIR="$D" else diff --git a/git-compat-util.h b/git-compat-util.h index 86c8962966..79eb10eacb 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -67,6 +67,8 @@ #include <fnmatch.h> #include <sys/poll.h> #include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/select.h> #include <assert.h> #include <regex.h> #include <netinet/in.h> diff --git a/git-cvsimport.perl b/git-cvsimport.perl index efa6a0c41a..92648f40c9 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -108,10 +108,6 @@ sub read_repo_config { } } } - if (@ARGV == 0) { - chomp(my $module = `git-repo-config --get cvsimport.module`); - push(@ARGV, $module); - } } my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:"; @@ -119,6 +115,10 @@ read_repo_config($opts); getopts($opts) or usage(); usage if $opt_h; +if (@ARGV == 0) { + chomp(my $module = `git-repo-config --get cvsimport.module`); + push(@ARGV, $module) if $? == 0; +} @ARGV <= 1 or usage("You can't specify more than one CVS module"); if ($opt_d) { @@ -527,18 +527,12 @@ sub is_sha1 { return $s =~ /^[a-f0-9]{40}$/; } -sub get_headref ($$) { - my $name = shift; - my $git_dir = shift; - - my $f = "$git_dir/$remote/$name"; - if (open(my $fh, $f)) { - chomp(my $r = <$fh>); - is_sha1($r) or die "Cannot get head id for $name ($r): $!"; - return $r; - } - die "unable to open $f: $!" unless $! == POSIX::ENOENT; - return undef; +sub get_headref ($) { + my $name = shift; + my $r = `git rev-parse --verify '$name' 2>/dev/null`; + return undef unless $? == 0; + chomp $r; + return $r; } -d $git_tree @@ -698,7 +692,8 @@ my (@old,@new,@skipped,%ignorebranch); $ignorebranch{'#CVSPS_NO_BRANCH'} = 1; sub commit { - if ($branch eq $opt_o && !$index{branch} && !get_headref($branch, $git_dir)) { + if ($branch eq $opt_o && !$index{branch} && + !get_headref("$remote/$branch")) { # looks like an initial commit # use the index primed by git-init $ENV{GIT_INDEX_FILE} = "$git_dir/index"; @@ -722,7 +717,7 @@ sub commit { update_index(@old, @new); @old = @new = (); my $tree = write_tree(); - my $parent = get_headref($last_branch, $git_dir); + my $parent = get_headref("$remote/$last_branch"); print "Parent ID " . ($parent ? $parent : "(empty)") . "\n" if $opt_v; my @commit_args; @@ -733,7 +728,7 @@ sub commit { foreach my $rx (@mergerx) { next unless $logmsg =~ $rx && $1; my $mparent = $1 eq 'HEAD' ? $opt_o : $1; - if (my $sha1 = get_headref($mparent, $git_dir)) { + if (my $sha1 = get_headref("$remote/$mparent")) { push @commit_args, '-p', $mparent; print "Merge parent branch: $mparent\n" if $opt_v; } @@ -870,29 +865,27 @@ while (<CVS>) { print STDERR "Branch $branch erroneously stems from itself -- changed ancestor to $opt_o\n"; $ancestor = $opt_o; } - if (-f "$git_dir/$remote/$branch") { + if (defined get_headref("$remote/$branch")) { print STDERR "Branch $branch already exists!\n"; $state=11; next; } - unless (open(H,"$git_dir/$remote/$ancestor")) { + my $id = get_headref("$remote/$ancestor"); + if (!$id) { print STDERR "Branch $ancestor does not exist!\n"; $ignorebranch{$branch} = 1; $state=11; next; } - chomp(my $id = <H>); - close(H); - unless (open(H,"> $git_dir/$remote/$branch")) { - print STDERR "Could not create branch $branch: $!\n"; + + system(qw(git update-ref -m cvsimport), + "$remote/$branch", $id); + if($? != 0) { + print STDERR "Could not create branch $branch\n"; $ignorebranch{$branch} = 1; $state=11; next; } - print H "$id\n" - or die "Could not write branch $branch: $!"; - close(H) - or die "Could not write branch $branch: $!"; } $last_branch = $branch if $branch ne $last_branch; $state = 9; @@ -1004,7 +997,7 @@ if ($orig_branch) { $orig_branch = "master"; print "DONE; creating $orig_branch branch\n" if $opt_v; system("git-update-ref", "refs/heads/master", "$remote/$opt_o") - unless -f "$git_dir/refs/heads/master"; + unless defined get_headref('refs/heads/master'); system("git-symbolic-ref", "$remote/HEAD", "$remote/$opt_o") if ($opt_r && $opt_o ne 'HEAD'); system('git-update-ref', 'HEAD', "$orig_branch"); diff --git a/git-filter-branch.sh b/git-filter-branch.sh index c9f515d0ee..29d35fd27c 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -8,6 +8,9 @@ # a new branch. You can specify a number of filters to modify the commits, # files and trees. +# The following functions will also be available in the commit filter: + +functions=$(cat << \EOF warn () { echo "$*" >&2 } @@ -46,6 +49,10 @@ die() echo "$*" >&2 exit 1 } +EOF +) + +eval "$functions" # When piped a commit, output a script to set the ident of either # "author" or "committer @@ -59,17 +66,17 @@ set_ident () { h s/^'$lid' \([^<]*\) <[^>]*> .*$/\1/ s/'\''/'\''\'\'\''/g - s/.*/export GIT_'$uid'_NAME='\''&'\''/p + s/.*/GIT_'$uid'_NAME='\''&'\''; export GIT_'$uid'_NAME/p g s/^'$lid' [^<]* <\([^>]*\)> .*$/\1/ s/'\''/'\''\'\'\''/g - s/.*/export GIT_'$uid'_EMAIL='\''&'\''/p + s/.*/GIT_'$uid'_EMAIL='\''&'\''; export GIT_'$uid'_EMAIL/p g s/^'$lid' [^<]* <[^>]*> \(.*\)$/\1/ s/'\''/'\''\'\'\''/g - s/.*/export GIT_'$uid'_DATE='\''&'\''/p + s/.*/GIT_'$uid'_DATE='\''&'\''; export GIT_'$uid'_DATE/p q } @@ -77,14 +84,9 @@ set_ident () { LANG=C LC_ALL=C sed -ne "$pick_id_script" # Ensure non-empty id name. - echo "[ -n \"\$GIT_${uid}_NAME\" ] || export GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\"" + echo "case \"\$GIT_${uid}_NAME\" in \"\") GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\" && export GIT_${uid}_NAME;; esac" } -# This script can be sourced by the commit filter to get the functions -test "a$SOURCE_FUNCTIONS" = a1 && return -this_script="$(cd "$(dirname "$0")"; pwd)"/$(basename "$0") -export this_script - USAGE="[--env-filter <command>] [--tree-filter <command>] \ [--index-filter <command>] [--parent-filter <command>] \ [--msg-filter <command>] [--commit-filter <command>] \ @@ -96,7 +98,7 @@ OPTIONS_SPEC= . git-sh-setup git diff-files --quiet && - git diff-index --cached --quiet HEAD || + git diff-index --cached --quiet HEAD -- || die "Cannot rewrite branch(es) with a dirty working directory." tempdir=.git-rewrite @@ -156,7 +158,7 @@ do filter_msg="$OPTARG" ;; --commit-filter) - filter_commit='SOURCE_FUNCTIONS=1 . "$this_script";'" $OPTARG" + filter_commit="$functions; $OPTARG" ;; --tag-name-filter) filter_tag_name="$OPTARG" @@ -204,7 +206,8 @@ done < "$tempdir"/backup-refs ORIG_GIT_DIR="$GIT_DIR" ORIG_GIT_WORK_TREE="$GIT_WORK_TREE" ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE" -export GIT_DIR GIT_WORK_TREE=. +GIT_WORK_TREE=. +export GIT_DIR GIT_WORK_TREE # These refs should be updated if their heads were rewritten @@ -229,7 +232,8 @@ done > "$tempdir"/heads test -s "$tempdir"/heads || die "Which ref do you want to rewrite?" -export GIT_INDEX_FILE="$(pwd)/../index" +GIT_INDEX_FILE="$(pwd)/../index" +export GIT_INDEX_FILE git read-tree || die "Could not seed the index" ret=0 @@ -265,7 +269,8 @@ while read commit parents; do git read-tree -i -m $commit:"$filter_subdir" esac || die "Could not initialize the index" - export GIT_COMMIT=$commit + GIT_COMMIT=$commit + export GIT_COMMIT git cat-file commit "$commit" >../commit || die "Cannot read commit $commit" @@ -399,7 +404,8 @@ if [ "$filter_tag_name" ]; then [ -f "../map/$sha1" ] || continue new_sha1="$(cat "../map/$sha1")" - export GIT_COMMIT="$sha1" + GIT_COMMIT="$sha1" + export GIT_COMMIT new_ref="$(echo "$ref" | eval "$filter_tag_name")" || die "tag name filter failed: $filter_tag_name" diff --git a/git-pull.sh b/git-pull.sh index 30fdc57310..698e82b116 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -17,6 +17,9 @@ test -z "$(git ls-files -u)" || die "You are in the middle of a conflicted merge." strategy_args= no_summary= no_commit= squash= no_ff= +curr_branch=$(git symbolic-ref -q HEAD) +curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||") +rebase=$(git config --bool branch.$curr_branch_short.rebase) while : do case "$1" in @@ -52,6 +55,12 @@ do esac strategy_args="${strategy_args}-s $strategy " ;; + -r|--r|--re|--reb|--reba|--rebas|--rebase) + rebase=true + ;; + --no-r|--no-re|--no-reb|--no-reba|--no-rebas|--no-rebase) + rebase=false + ;; -h|--h|--he|--hel|--help) usage ;; @@ -95,7 +104,6 @@ merge_head=$(sed -e '/ not-for-merge /d' \ case "$merge_head" in '') - curr_branch=$(git symbolic-ref -q HEAD) case $? in 0) ;; 1) echo >&2 "You are not currently on a branch; you must explicitly" @@ -142,5 +150,6 @@ then fi merge_name=$(git fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit +test true = "$rebase" && exec git-rebase $merge_head exec git-merge $no_summary $no_commit $squash $no_ff $strategy_args \ "$merge_name" HEAD $merge_head diff --git a/git-quiltimport.sh b/git-quiltimport.sh index 6b0c4d2f27..233e5eae1d 100755 --- a/git-quiltimport.sh +++ b/git-quiltimport.sh @@ -77,8 +77,9 @@ for patch_name in $(grep -v '^#' < "$QUILT_PATCHES/series" ); do } # Parse the author information - export GIT_AUTHOR_NAME=$(sed -ne 's/Author: //p' "$tmp_info") - export GIT_AUTHOR_EMAIL=$(sed -ne 's/Email: //p' "$tmp_info") + GIT_AUTHOR_NAME=$(sed -ne 's/Author: //p' "$tmp_info") + GIT_AUTHOR_EMAIL=$(sed -ne 's/Email: //p' "$tmp_info") + export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL while test -z "$GIT_AUTHOR_EMAIL" && test -z "$GIT_AUTHOR_NAME" ; do if [ -n "$quilt_author" ] ; then GIT_AUTHOR_NAME="$quilt_author_name"; @@ -104,8 +105,9 @@ for patch_name in $(grep -v '^#' < "$QUILT_PATCHES/series" ); do GIT_AUTHOR_EMAIL="$patch_author_email" fi done - export GIT_AUTHOR_DATE=$(sed -ne 's/Date: //p' "$tmp_info") - export SUBJECT=$(sed -ne 's/Subject: //p' "$tmp_info") + GIT_AUTHOR_DATE=$(sed -ne 's/Date: //p' "$tmp_info") + SUBJECT=$(sed -ne 's/Subject: //p' "$tmp_info") + export GIT_AUTHOR_DATE SUBJECT if [ -z "$SUBJECT" ] ; then SUBJECT=$(echo $patch_name | sed -e 's/.patch$//') fi diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index bf44b6af58..f83e00fe8f 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -30,6 +30,11 @@ test -d "$REWRITTEN" && PRESERVE_MERGES=t test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)" test -f "$DOTEST"/verbose && VERBOSE=t +GIT_CHERRY_PICK_HELP=" After resolving the conflicts, +mark the corrected paths with 'git add <paths>', and +run 'git rebase --continue'" +export GIT_CHERRY_PICK_HELP + warn () { echo "$*" >&2 } @@ -53,7 +58,7 @@ require_clean_work_tree () { git rev-parse --verify HEAD > /dev/null && git update-index --refresh && git diff-files --quiet && - git diff-index --cached --quiet HEAD || + git diff-index --cached --quiet HEAD -- || die "Working tree is dirty" } @@ -90,6 +95,7 @@ make_patch () { die_with_patch () { make_patch "$1" + git rerere die "$2" } @@ -175,13 +181,13 @@ pick_one_preserving_merges () { msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')" # No point in merging the first parent, that's HEAD new_parents=${new_parents# $first_parent} - # NEEDSWORK: give rerere a chance if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ output git merge $STRATEGY -m "$msg" \ $new_parents then + git rerere printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG die Error redoing merge $sha1 fi @@ -356,7 +362,7 @@ do git rev-parse --verify HEAD > /dev/null && git update-index --refresh && git diff-files --quiet && - ! git diff-index --cached --quiet HEAD && + ! git diff-index --cached --quiet HEAD -- && . "$DOTEST"/author-script && { test ! -f "$DOTEST"/amend || git reset --soft HEAD^ } && @@ -369,6 +375,7 @@ do --abort) comment_for_reflog abort + git rerere clear test -d "$DOTEST" || die "No interactive rebase running" HEADNAME=$(cat "$DOTEST"/head-name) @@ -385,6 +392,7 @@ do --skip) comment_for_reflog skip + git rerere clear test -d "$DOTEST" || die "No interactive rebase running" output git reset --hard && do_rest diff --git a/git-rebase.sh b/git-rebase.sh index df5fd65d56..bdcea0ed70 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -61,7 +61,7 @@ continue_merge () { fi cmt=`cat "$dotest/current"` - if ! git diff-index --quiet HEAD + if ! git diff-index --quiet HEAD -- then if ! git-commit -C "$cmt" then @@ -179,6 +179,7 @@ do exit ;; --skip) + git reset --hard HEAD || exit $? if test -d "$dotest" then git rerere clear @@ -284,7 +285,7 @@ fi # The tree must be really really clean. git update-index --refresh || exit -diff=$(git diff-index --cached --name-status -r HEAD) +diff=$(git diff-index --cached --name-status -r HEAD --) case "$diff" in ?*) echo "cannot rebase: your index is not up-to-date" echo "$diff" diff --git a/git-stash.sh b/git-stash.sh index 534eb168ab..f16fd9c3c0 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -1,7 +1,7 @@ #!/bin/sh # Copyright (c) 2007, Nanako Shiraishi -USAGE='[ | list | show | apply | clear]' +USAGE='[ | save | list | show | apply | clear | create ]' SUBDIRECTORY_OK=Yes OPTIONS_SPEC= @@ -15,7 +15,7 @@ trap 'rm -f "$TMP-*"' 0 ref_stash=refs/stash no_changes () { - git diff-index --quiet --cached HEAD && + git diff-index --quiet --cached HEAD -- && git diff-files --quiet } @@ -37,7 +37,7 @@ create_stash () { # state of the base commit if b_commit=$(git rev-parse --verify HEAD) then - head=$(git log --abbrev-commit --pretty=oneline -n 1 HEAD) + head=$(git log --no-color --abbrev-commit --pretty=oneline -n 1 HEAD --) else die "You do not have the initial commit yet" fi @@ -108,7 +108,7 @@ have_stash () { list_stash () { have_stash || return 0 - git log --pretty=oneline -g "$@" $ref_stash | + git log --no-color --pretty=oneline -g "$@" $ref_stash -- | sed -n -e 's/^[.0-9a-f]* refs\///p' } @@ -207,6 +207,10 @@ show) shift show_stash "$@" ;; +save) + shift + save_stash "$*" && git-reset --hard + ;; apply) shift apply_stash "$@" @@ -221,14 +225,12 @@ create) fi create_stash "$*" && echo "$w_commit" ;; -help | usage) - usage - ;; *) - if test $# -gt 0 && test "$1" = save + if test $# -eq 0 then - shift + save_stash && git-reset --hard + else + usage fi - save_stash "$*" && git-reset --hard ;; esac diff --git a/git-submodule.sh b/git-submodule.sh index 82ac28fa27..ad9fe628fd 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -158,7 +158,7 @@ module_add() die "'$path' already exists in the index" module_clone "$path" "$realrepo" || exit - (unset GIT_DIR && cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) || + (unset GIT_DIR; cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) || die "Unable to checkout submodule '$path'" git add "$path" || die "Failed to add submodule '$path'" @@ -228,14 +228,14 @@ modules_update() module_clone "$path" "$url" || exit subsha1= else - subsha1=$(unset GIT_DIR && cd "$path" && + subsha1=$(unset GIT_DIR; cd "$path" && git rev-parse --verify HEAD) || die "Unable to find current revision in submodule path '$path'" fi if test "$subsha1" != "$sha1" then - (unset GIT_DIR && cd "$path" && git-fetch && + (unset GIT_DIR; cd "$path" && git-fetch && git-checkout -q "$sha1") || die "Unable to checkout '$sha1' in submodule path '$path'" @@ -246,7 +246,7 @@ modules_update() set_name_rev () { revname=$( ( - unset GIT_DIR && + unset GIT_DIR cd "$1" && { git describe "$2" 2>/dev/null || git describe --tags "$2" 2>/dev/null || @@ -285,7 +285,7 @@ modules_list() else if test -z "$cached" then - sha1=$(unset GIT_DIR && cd "$path" && git rev-parse --verify HEAD) + sha1=$(unset GIT_DIR; cd "$path" && git rev-parse --verify HEAD) set_name_rev "$path" "$sha1" fi say "+$sha1 $path$revname" diff --git a/git-svn.perl b/git-svn.perl index 43e1591cef..9f884eb213 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -35,6 +35,7 @@ push @Git::SVN::Ra::ISA, 'SVN::Ra'; push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor'; push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor'; use Carp qw/croak/; +use Digest::MD5; use IO::File qw//; use File::Basename qw/dirname basename/; use File::Path qw/mkpath/; @@ -48,8 +49,7 @@ BEGIN { foreach (qw/command command_oneline command_noisy command_output_pipe command_input_pipe command_close_pipe/) { for my $package ( qw(SVN::Git::Editor SVN::Git::Fetcher - Git::SVN::Migration Git::SVN::Log Git::SVN - Git::SVN::Util), + Git::SVN::Migration Git::SVN::Log Git::SVN), __PACKAGE__) { *{"${package}::$_"} = \&{"Git::$_"}; } @@ -81,6 +81,7 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, 'quiet|q' => \$_q, 'repack-flags|repack-args|repack-opts=s' => \$Git::SVN::_repack_flags, + 'use-log-author' => \$Git::SVN::_use_log_author, %remote_opts ); my ($_trunk, $_tags, $_branches, $_stdlayout); @@ -142,6 +143,9 @@ my %cmd = ( 'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], + 'show-externals' => [ \&cmd_show_externals, "Show svn:externals listings", + { 'revision|r=i' => \$_revision + } ], 'multi-fetch' => [ \&cmd_multi_fetch, "Deprecated alias for $0 fetch --all", { 'revision|r=s' => \$_revision, %fc_opts } ], @@ -193,23 +197,6 @@ for (my $i = 0; $i < @ARGV; $i++) { } }; -my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd); - -read_repo_config(\%opts); -Getopt::Long::Configure('pass_through') if ($cmd && $cmd eq 'log'); -my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, - 'minimize-connections' => \$Git::SVN::Migration::_minimize, - 'id|i=s' => \$Git::SVN::default_ref_id, - 'svn-remote|remote|R=s' => sub { - $Git::SVN::no_reuse_existing = 1; - $Git::SVN::default_repo_id = $_[1] }); -exit 1 if (!$rv && $cmd && $cmd ne 'log'); - -usage(0) if $_help; -version() if $_version; -usage(1) unless defined $cmd; -load_authors() if $_authors; - # make sure we're always running unless ($cmd =~ /(?:clone|init|multi-init)$/) { unless (-d $ENV{GIT_DIR}) { @@ -231,6 +218,24 @@ unless ($cmd =~ /(?:clone|init|multi-init)$/) { $ENV{GIT_DIR} = $git_dir; } } + +my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd); + +read_repo_config(\%opts); +Getopt::Long::Configure('pass_through') if ($cmd && $cmd eq 'log'); +my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, + 'minimize-connections' => \$Git::SVN::Migration::_minimize, + 'id|i=s' => \$Git::SVN::default_ref_id, + 'svn-remote|remote|R=s' => sub { + $Git::SVN::no_reuse_existing = 1; + $Git::SVN::default_repo_id = $_[1] }); +exit 1 if (!$rv && $cmd && $cmd ne 'log'); + +usage(0) if $_help; +version() if $_version; +usage(1) unless defined $cmd; +load_authors() if $_authors; + unless ($cmd =~ /^(?:clone|init|multi-init|commit-diff)$/) { Git::SVN::Migration::migration_check(); } @@ -545,6 +550,8 @@ sub cmd_rebase { exit 1; } unless ($_local) { + # rebase will checkout for us, so no need to do it explicitly + $_no_checkout = 'true'; $_fetch_all ? $gs->fetch_all : $gs->fetch; } command_noisy(rebase_cmd(), $gs->refname); @@ -565,6 +572,21 @@ sub cmd_show_ignore { }); } +sub cmd_show_externals { + my ($url, $rev, $uuid, $gs) = working_head_info('HEAD'); + $gs ||= Git::SVN->new; + my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum); + $gs->prop_walk($gs->{path}, $r, sub { + my ($gs, $path, $props) = @_; + print STDOUT "\n# $path\n"; + my $s = $props->{'svn:externals'} or return; + $s =~ s/[\r\n]+/\n/g; + chomp $s; + $s =~ s#^#$path#gm; + print STDOUT "$s\n"; + }); +} + sub cmd_create_ignore { my ($url, $rev, $uuid, $gs) = working_head_info('HEAD'); $gs ||= Git::SVN->new; @@ -840,19 +862,19 @@ sub cmd_info { command_output_pipe(qw(cat-file blob), "HEAD:$path"); if ($file_type eq "link") { my $file_name = <$fh>; - $checksum = Git::SVN::Util::md5sum("link $file_name"); + $checksum = md5sum("link $file_name"); } else { - $checksum = Git::SVN::Util::md5sum($fh); + $checksum = md5sum($fh); } command_close_pipe($fh, $ctx); } elsif ($file_type eq "link") { my $file_name = command(qw(cat-file blob), "HEAD:$path"); $checksum = - Git::SVN::Util::md5sum("link " . $file_name); + md5sum("link " . $file_name); } else { open FILE, "<", $path or die $!; - $checksum = Git::SVN::Util::md5sum(\*FILE); + $checksum = md5sum(\*FILE); close FILE or die $!; } $result .= "Checksum: " . $checksum . "\n"; @@ -1193,11 +1215,6 @@ sub find_file_type_and_diff_status { return ("file", $diff_status); } -package Git::SVN::Util; -use strict; -use warnings; -use Digest::MD5; - sub md5sum { my $arg = shift; my $ref = ref $arg; @@ -1219,7 +1236,8 @@ use strict; use warnings; use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent $_repack $_repack_flags $_use_svm_props $_head - $_use_svnsync_props $no_reuse_existing $_minimize_url/; + $_use_svnsync_props $no_reuse_existing $_minimize_url + $_use_log_author/; use Carp qw/croak/; use File::Path qw/mkpath/; use File::Copy qw/copy/; @@ -2059,11 +2077,17 @@ sub do_git_commit { croak "$log_entry->{revision} = $c already exists! ", "Why are we refetching it?\n"; } - $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $log_entry->{name}; - $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = - $log_entry->{email}; + $ENV{GIT_AUTHOR_NAME} = $log_entry->{name}; + $ENV{GIT_AUTHOR_EMAIL} = $log_entry->{email}; $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date}; + $ENV{GIT_COMMITTER_NAME} = (defined $log_entry->{commit_name}) + ? $log_entry->{commit_name} + : $log_entry->{name}; + $ENV{GIT_COMMITTER_EMAIL} = (defined $log_entry->{commit_email}) + ? $log_entry->{commit_email} + : $log_entry->{email}; + my $tree = $log_entry->{tree}; if (!defined $tree) { $tree = $self->tmp_index_do(sub { @@ -2351,7 +2375,17 @@ sub make_log_entry { $log_entry{log} .= "\n"; my $author = $log_entry{author} = check_author($log_entry{author}); my ($name, $email) = defined $::users{$author} ? @{$::users{$author}} - : ($author, undef); + : ($author, undef); + + my ($commit_name, $commit_email) = ($name, $email); + if ($_use_log_author) { + if ($log_entry{log} =~ /From:\s+(.*?)\s+<(.*)>\s*\n/) { + ($name, $email) = ($1, $2); + } elsif ($log_entry{log} =~ + /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) { + ($name, $email) = ($1, $2); + } + } if (defined $headrev && $self->use_svm_props) { if ($self->rewrite_root) { die "Can't have both 'useSvmProps' and 'rewriteRoot' ", @@ -2374,23 +2408,28 @@ sub make_log_entry { remove_username($full_url); $log_entry{metadata} = "$full_url\@$r $uuid"; $log_entry{svm_revision} = $r; - $email ||= "$author\@$uuid" + $email ||= "$author\@$uuid"; + $commit_email ||= "$author\@$uuid"; } elsif ($self->use_svnsync_props) { my $full_url = $self->svnsync->{url}; $full_url .= "/$self->{path}" if length $self->{path}; remove_username($full_url); my $uuid = $self->svnsync->{uuid}; $log_entry{metadata} = "$full_url\@$rev $uuid"; - $email ||= "$author\@$uuid" + $email ||= "$author\@$uuid"; + $commit_email ||= "$author\@$uuid"; } else { my $url = $self->metadata_url; remove_username($url); $log_entry{metadata} = "$url\@$rev " . $self->ra->get_uuid; $email ||= "$author\@" . $self->ra->get_uuid; + $commit_email ||= "$author\@" . $self->ra->get_uuid; } $log_entry{name} = $name; $log_entry{email} = $email; + $log_entry{commit_name} = $commit_name; + $log_entry{commit_email} = $commit_email; \%log_entry; } @@ -2947,7 +2986,7 @@ sub apply_textdelta { if (defined $exp) { seek $base, 0, 0 or croak $!; - my $got = Git::SVN::Util::md5sum($base); + my $got = ::md5sum($base); die "Checksum mismatch: $fb->{path} $fb->{blob}\n", "expected: $exp\n", " got: $got\n" if ($got ne $exp); @@ -2966,7 +3005,7 @@ sub close_file { if (my $fh = $fb->{fh}) { if (defined $exp) { seek($fh, 0, 0) or croak $!; - my $got = Git::SVN::Util::md5sum($fh); + my $got = ::md5sum($fh); if ($got ne $exp) { die "Checksum mismatch: $path\n", "expected: $exp\n got: $got\n"; @@ -3321,7 +3360,7 @@ sub chg_file { $fh->flush == 0 or croak $!; seek $fh, 0, 0 or croak $!; - my $exp = Git::SVN::Util::md5sum($fh); + my $exp = ::md5sum($fh); seek $fh, 0, 0 or croak $!; my $pool = SVN::Pool->new; @@ -169,7 +169,7 @@ static int handle_alias(int *argcp, const char ***argv) strbuf_init(&buf, PATH_MAX); strbuf_addstr(&buf, alias_string); - sq_quote_argv(&buf, (*argv) + 1, *argcp - 1, PATH_MAX); + sq_quote_argv(&buf, (*argv) + 1, PATH_MAX); free(alias_string); alias_string = buf.buf; } @@ -198,7 +198,7 @@ static int handle_alias(int *argcp, const char ***argv) if (!strcmp(alias_command, new_argv[0])) die("recursive alias: %s", alias_command); - trace_argv_printf(new_argv, count, + trace_argv_printf(new_argv, "trace: alias expansion: %s =>", alias_command); @@ -252,11 +252,11 @@ static int run_command(struct cmd_struct *p, int argc, const char **argv) if (p->option & NEED_WORK_TREE) setup_work_tree(); - trace_argv_printf(argv, argc, "trace: built-in: git"); + trace_argv_printf(argv, "trace: built-in: git"); status = p->fn(argc, argv, prefix); if (status) - return status; + return status & 0xff; /* Somebody closed stdout? */ if (fstat(fileno(stdout), &st)) @@ -293,6 +293,7 @@ static void handle_internal_command(int argc, const char **argv) { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE }, { "cherry", cmd_cherry, RUN_SETUP }, { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE }, + { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE }, { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "config", cmd_config }, @@ -302,6 +303,7 @@ static void handle_internal_command(int argc, const char **argv) { "diff-files", cmd_diff_files }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, + { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, { "fetch--tool", cmd_fetch__tool, RUN_SETUP }, @@ -322,6 +324,7 @@ static void handle_internal_command(int argc, const char **argv) { "log", cmd_log, RUN_SETUP | USE_PAGER }, { "ls-files", cmd_ls_files, RUN_SETUP }, { "ls-tree", cmd_ls_tree, RUN_SETUP }, + { "ls-remote", cmd_ls_remote }, { "mailinfo", cmd_mailinfo }, { "mailsplit", cmd_mailsplit }, { "merge-base", cmd_merge_base, RUN_SETUP }, @@ -330,6 +333,7 @@ static void handle_internal_command(int argc, const char **argv) { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, + { "peek-remote", cmd_ls_remote }, { "pickaxe", cmd_blame, RUN_SETUP }, { "prune", cmd_prune, RUN_SETUP }, { "prune-packed", cmd_prune_packed, RUN_SETUP }, @@ -343,6 +347,7 @@ static void handle_internal_command(int argc, const char **argv) { "rev-parse", cmd_rev_parse }, { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE }, { "rm", cmd_rm, RUN_SETUP }, + { "send-pack", cmd_send_pack, RUN_SETUP }, { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, diff --git a/gitk-git/Makefile b/gitk-git/Makefile new file mode 100644 index 0000000000..9bc1e24082 --- /dev/null +++ b/gitk-git/Makefile @@ -0,0 +1,29 @@ +# The default target of this Makefile is... +all:: + +prefix ?= $(HOME) +bindir ?= $(prefix)/bin +TCLTK_PATH ?= wish +INSTALL ?= install +RM ?= rm -f + +DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) +bindir_SQ = $(subst ','\'',$(bindir)) +TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) + +ifndef V + QUIET = @ + QUIET_GEN = $(QUIET)echo ' ' GEN $@ && +endif + +all:: gitk-wish +install:: all + $(INSTALL) gitk-wish '$(DESTDIR_SQ)$(bindir_SQ)'/gitk +clean:: + $(RM) gitk-wish + +gitk-wish: gitk + $(QUIET_GEN)$(RM) $@ $@+ && \ + sed -e '1,3s|^exec .* "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' <gitk >$@+ && \ + chmod +x $@+ && \ + mv -f $@+ $@ diff --git a/gitk b/gitk-git/gitk index 1da0b0af1d..1da0b0af1d 100755..100644 --- a/gitk +++ b/gitk-git/gitk diff --git a/gitweb/README b/gitweb/README index 7186cede2f..b28f59f574 100644 --- a/gitweb/README +++ b/gitweb/README @@ -10,28 +10,96 @@ From the git version 1.4.0 gitweb is bundled with git. How to configure gitweb for your local system --------------------------------------------- +See also "Build time configuration" section in INSTALL +file for gitweb (in gitweb/INSTALL). + You can specify the following configuration variables when building GIT: + * GIT_BINDIR + Points out where to find git executable. You should set up it to + the place where git binary was installed (usually /usr/bin) if you + don't install git from sources together with gitweb. [Default: $(bindir)] * GITWEB_SITENAME - Shown in the title of all generated pages, defaults to the servers name. + Shown in the title of all generated pages, defaults to the server name + (SERVER_NAME CGI environment variable) if not set. [No default] * GITWEB_PROJECTROOT - The root directory for all projects shown by gitweb. + The root directory for all projects shown by gitweb. Must be set + correctly for gitweb to find repositories to display. See also + "Gitweb repositories" in INSTALL file for gitweb. [Default: /pub/git] + * GITWEB_PROJECT_MAXDEPTH + The filesystem traversing limit for getting projects list; the number + is taken as depth relative to the projectroot. It is used when + GITWEB_LIST is a directory (or is not set; then project root is used). + Is is meant to speed up project listing on large work trees by limiting + find depth. [Default: 2007] * GITWEB_LIST - points to a directory to scan for projects (defaults to project root) - or to a file for explicit listing of projects. + Points to a directory to scan for projects (defaults to project root + if not set / if empty) or to a file with explicit listing of projects + (together with projects' ownership). See "Generating projects list + using gitweb" in INSTALL file for gitweb to find out how to generate + such file from scan of a directory. [No default, which means use root + directory for projects] + * GITWEB_EXPORT_OK + Show repository only if this file exists (in repository). Only + effective if this variable evaluates to true. [No default / Not set] + * GITWEB_STRICT_EXPORT + Only allow viewing of repositories also shown on the overview page. + This for example makes GITWEB_EXPORT_OK to decide if repository is + available and not only if it is shown. If GITWEB_LIST points to + file with list of project, only those repositories listed would be + available for gitweb. [No default] * GITWEB_HOMETEXT - points to an .html file which is included on the gitweb project - overview page. + Points to an .html file which is included on the gitweb project + overview page ('projects_list' view), if it exists. Relative to + gitweb.cgi script. [Default: indextext.html] + * GITWEB_SITE_HEADER + Filename of html text to include at top of each page. Relative to + gitweb.cgi script. [No default] + * GITWEB_SITE_FOOTER + Filename of html text to include at bottom of each page. Relative to + gitweb.cgi script. [No default] + * GITWEB_HOME_LINK_STR + String of the home link on top of all pages, leading to $home_link + (usually main gitweb page, which means projects list). Used as first + part of gitweb view "breadcrumb trail": <home> / <project> / <view>. + [Default: projects] + * GITWEB_SITENAME + Name of your site or organization to appear in page titles. Set it + to something descriptive for clearer bookmarks etc. If not set + (if empty) gitweb uses "$SERVER_NAME Git", or "Untitled Git" if + SERVER_NAME CGI environment variable is not set (e.g. if running + gitweb as standalone script). [No default] + * GITWEB_BASE_URL + Git base URLs used for URL to where fetch project from, i.e. full + URL is "$git_base_url/$project". Shown on projects summary page. + Repository URL for project can be also configured per repository; this + takes precendence over URL composed from base URL and project name. + Note that you can setup multiple base URLs (for example one for + git:// protocol access, one for http:// access) from gitweb config + file. [No default] * GITWEB_CSS - Points to the location where you put gitweb.css on your web server. + Points to the location where you put gitweb.css on your web server + (or to be more generic URI of gitweb stylesheet). Relative to base + URI of gitweb. Note that you can setup multiple stylesheets from + gitweb config file. [Default: gitweb.css] * GITWEB_LOGO - Points to the location where you put git-logo.png on your web server. + Points to the location where you put git-logo.png on your web server + (or to be more generic URI of logo, 72x27 size, displayed in top right + corner of each gitweb page, and used as logo for Atom feed). Relative + to base URI of gitweb. [Default: git-logo.png] + * GITWEB_FAVICON + Points to the location where you put git-favicon.png on your web server + (or to be more generic URI of favicon, assumed to be image/png type; + web browsers that support favicons (website icons) may display them + in the browser's URL bar and next to site name in bookmarks). Relative + to base URI of gitweb. [Default: git-favicon.png] * GITWEB_CONFIG - This file will be loaded using 'require' and can be used to override any - of the options above as well as some other options - see the top of - 'gitweb.cgi' for their full list and description. If the environment - $GITWEB_CONFIG is set when gitweb.cgi is executed the file in the - environment variable will be loaded instead of the file - specified when gitweb.cgi was created. + This Perl file will be loaded using 'do' and can be used to override any + of the options above as well as some other options -- see the "Runtime + gitweb configuration" section below, and top of 'gitweb.cgi' for their + full list and description. If the environment variable GITWEB_CONFIG + is set when gitweb.cgi is executed, then the file specified in the + environment variable will be loaded instead of the file specified + when gitweb.cgi was created. [Default: gitweb_config.perl] Runtime gitweb configuration @@ -39,11 +107,122 @@ Runtime gitweb configuration You can adjust gitweb behaviour using the file specified in `GITWEB_CONFIG` (defaults to 'gitweb_config.perl' in the same directory as the CGI). -See the top of 'gitweb.cgi' for the list of variables and some description. The most notable thing that is not configurable at compile time are the -optional features, stored in the '%features' variable. You can find further -description on how to reconfigure the default features setting in your -`GITWEB_CONFIG` or per-project in `project.git/config` inside 'gitweb.cgi'. +optional features, stored in the '%features' variable. + +Ultimate description on how to reconfigure the default features setting +in your `GITWEB_CONFIG` or per-project in `project.git/config` can be found +as comments inside 'gitweb.cgi'. + +See also "Gitweb config file" (with example of gitweb config file), and +"Gitweb repositories" sections in INSTALL file for gitweb. + + +Gitweb config file is [fragment] of perl code. You can set variables +using "our $variable = value"; text from "#" character until the end +of a line is ignored. See perlsyn(1) man page for details. + +Below there is list of vaiables which you might want to set in gitweb config. +See the top of 'gitweb.cgi' for the full list of variables and their +descriptions. + +Gitweb config file variables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can set, among others, the following variables in gitweb config files: + * $GIT + Cure git executable to use. By default set to "$GIT_BINDIR/git", which + in turn is by default set to "$(bindir)/git". If you use git from binary + package, set this to "/usr/bin/git". This can just be "git" if your + webserver has a sensible PATH. If you have multiple git versions + installed it is / can be used to choose which one to use. + * $version + Gitweb version, set automatically when creating gitweb.cgi from + gitweb.perl. You might want to modify it if you are running modified + gitweb. + * $my_url, $my_uri + URL and absolute URL of gitweb script; you might need to set those + variables if you are using 'pathinfo' feature: see also below. + * $home_link + Target of the home link on top of all pages (the first part of view + "breadcrumbs"). By default set to absolute URI of a page; you might + need to set it up to [base] gitweb URI if you use 'pathinfo' feature + (alternative format of the URLs, with project name embedded directly + in the path part of URL). + * @stylesheets + List of URIs of stylesheets (relative to base URI of a page). You + might specify more than one stylesheet, for example use gitweb.css + as base, with site specific modifications in separate stylesheet + to make it easier to upgrade gitweb. You can add 'site' stylesheet + for example by using + push @stylesheets, "gitweb-site.css"; + in gitweb config file. + * $logo_url, $logo_label + URI and label (title) of GIT logo link (or your site logo, if you choose + to use different logo image). By default they point to git homepage; + in the past they pointed to git documentation at www.kernel.org. + * $projects_list_description_width + The width (in characters) of the projects list "Description" column. + Longer descriptions will be cut (trying to cut at word boundary); + full description is available as 'title' attribute (usually shown on + mouseover). By default set to 25, which might be too small if you + use long project descriptions. + * @git_base_url_list + List of git base URLs used for URL to where fetch project from, shown + in project summary page. Full URL is "$git_base_url/$project". + You can setup multiple base URLs (for example one for git:// protocol + access, and one for http:// "dumb" protocol access). Note that per + repository configuration in 'cloneurl' file, or as values of gitweb.url + project config. + * $default_blob_plain_mimetype + Default mimetype for blob_plain (raw) view, if mimetype checking + doesn't result in some other type; by default 'text/plain'. + * $default_text_plain_charset + Default charset for text files. If not set, web serwer configuration + would be used. + * $mimetypes_file + File to use for (filename extension based) guessing of MIME types before + trying /etc/mime.types. Path, if relative, is taken currently as taken + relative to current git repositoy. + * $fallback_encoding + Gitweb assumes this charset if line contains non-UTF-8 characters. + Fallback decoding is used without error checking, so it can be even + 'utf-8'. Value mist be valid encodig; see Encoding::Supported(3pm) man + page for a list. By default 'latin1', aka. 'iso-8859-1'. + * @diff_opts + Rename detection options for git-diff and git-diff-tree. By default + ('-M'); set it to ('-C') or ('-C', '-C') to also detect copies, or + set it to () if you don't want to have renames detection. + +Per-repository gitweb configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also configure individual repositories shown in gitweb by creating +file in the GIT_DIR of git repository, or by setting some repo configuration +variable (in GIT_DIR/config). + +You can use the following files in repository: + * README.html + A .html file (HTML fragment) which is included on the gitweb project + summary page inside <div> block element. You can use it for longer + description of a project, to provide links for example to projects + homepage, etc. + * description (or gitweb.description) + Short (shortened by default to 25 characters in the projects list page) + single line description of a project (of a repository). Plain text file; + HTML will be escaped. By default set to + Unnamed repository; edit this file to name it for gitweb. + from the template during creating repository. You can use + gitweb.description repo configuration variable, but the file takes + precendence. + * cloneurl (or multiple-valued gitweb.url) + File with repository URL (used for clone and fetch), one per line. + Displayed in the project summary page. You can use multiple-valued + gitweb.url repository configuration variable for that, but the file + takes precendence. + * various gitweb.* config variables (in config) + Read description of %feature hash for detailed list, and some + descriptions. Webserver configuration diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 491a3f41d2..24b31582af 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -695,10 +695,9 @@ sub validate_refname { # in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning sub to_utf8 { my $str = shift; - my $res; - eval { $res = decode_utf8($str, Encode::FB_CROAK); }; - if (defined $res) { - return $res; + if (utf8::valid($str)) { + utf8::decode($str); + return $str; } else { return decode($fallback_encoding, $str, Encode::FB_DEFAULT); } @@ -3786,6 +3785,8 @@ sub git_search_grep_body { "<td class=\"link\">" . $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") . " | " . + $cgi->a({-href => href(action=>"commitdiff", hash=>$co{'id'})}, "commitdiff") . + " | " . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); print "</td>\n" . "</tr>\n"; @@ -7,7 +7,6 @@ #include "builtin.h" #include "exec_cmd.h" #include "common-cmds.h" -#include <sys/ioctl.h> /* most GUI terminals set COLUMNS (although some don't export it) */ static int term_columns(void) @@ -238,7 +237,6 @@ void list_common_cmds_help(void) mput_char(' ', longest - strlen(common_cmds[i].name)); puts(common_cmds[i].help); } - puts("(use 'git help -a' to get a list of all installed git commands)"); } static void show_man_page(const char *git_cmd) diff --git a/http-push.c b/http-push.c index 9314621a11..78283b4de3 100644 --- a/http-push.c +++ b/http-push.c @@ -78,7 +78,7 @@ static struct curl_slist *no_pragma_header; static struct curl_slist *default_headers; static int push_verbosely; -static int push_all; +static int push_all = MATCH_REFS_NONE; static int force_all; static int dry_run; @@ -433,7 +433,7 @@ static void start_fetch_packed(struct transfer_request *request) packfile = fopen(request->tmpfile, "a"); if (!packfile) { fprintf(stderr, "Unable to open local file %s for pack", - filename); + request->tmpfile); remote->can_update_info_refs = 0; free(url); return; @@ -941,7 +941,7 @@ static int fetch_index(unsigned char *sha1) indexfile = fopen(tmpfile, "a"); if (!indexfile) return error("Unable to open local file %s for pack index", - filename); + tmpfile); slot = get_active_slot(); slot->results = &results; @@ -2300,7 +2300,7 @@ int main(int argc, char **argv) if (*arg == '-') { if (!strcmp(arg, "--all")) { - push_all = 1; + push_all = MATCH_REFS_ALL; continue; } if (!strcmp(arg, "--force")) { @@ -2393,7 +2393,7 @@ int main(int argc, char **argv) if (!remote_tail) remote_tail = &remote_refs; if (match_refs(local_refs, remote_refs, &remote_tail, - nr_refspec, refspec, push_all)) + nr_refspec, (const char **) refspec, push_all)) return -1; if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n"); diff --git a/http-walker.c b/http-walker.c index 444aebf526..a3fb596542 100644 --- a/http-walker.c +++ b/http-walker.c @@ -405,7 +405,7 @@ static int fetch_index(struct walker *walker, struct alt_base *repo, unsigned ch indexfile = fopen(tmpfile, "a"); if (!indexfile) return error("Unable to open local file %s for pack index", - filename); + tmpfile); slot = get_active_slot(); slot->results = &results; @@ -770,7 +770,7 @@ static int fetch_pack(struct walker *walker, struct alt_base *repo, unsigned cha packfile = fopen(tmpfile, "a"); if (!packfile) return error("Unable to open local file %s for pack", - filename); + tmpfile); slot = get_active_slot(); slot->results = &results; @@ -24,6 +24,7 @@ char *ssl_cainfo = NULL; long curl_low_speed_limit = -1; long curl_low_speed_time = -1; int curl_ftp_no_epsv = 0; +char *curl_http_proxy = NULL; struct curl_slist *pragma_header; @@ -160,6 +161,13 @@ static int http_options(const char *var, const char *value) curl_ftp_no_epsv = git_config_bool(var, value); return 0; } + if (!strcmp("http.proxy", var)) { + if (curl_http_proxy == NULL) { + curl_http_proxy = xmalloc(strlen(value)+1); + strcpy(curl_http_proxy, value); + } + return 0; + } /* Fall back on the default ones */ return git_default_config(var, value); @@ -205,6 +213,9 @@ static CURL* get_curl_handle(void) if (curl_ftp_no_epsv) curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0); + if (curl_http_proxy) + curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy); + return result; } @@ -113,25 +113,15 @@ static int add_raw(char *buf, size_t size, int offset, const char *str) static int crud(unsigned char c) { - static char crud_array[256]; - static int crud_array_initialized = 0; - - if (!crud_array_initialized) { - int k; - - for (k = 0; k <= 31; ++k) crud_array[k] = 1; - crud_array[' '] = 1; - crud_array['.'] = 1; - crud_array[','] = 1; - crud_array[':'] = 1; - crud_array[';'] = 1; - crud_array['<'] = 1; - crud_array['>'] = 1; - crud_array['"'] = 1; - crud_array['\''] = 1; - crud_array_initialized = 1; - } - return crud_array[c]; + return c <= 32 || + c == '.' || + c == ',' || + c == ':' || + c == ';' || + c == '<' || + c == '>' || + c == '"' || + c == '\''; } /* diff --git a/lockfile.c b/lockfile.c index 9a1f64d8d7..f45d3ed544 100644 --- a/lockfile.c +++ b/lockfile.c @@ -12,8 +12,10 @@ static void remove_lock_file(void) while (lock_file_list) { if (lock_file_list->owner == me && - lock_file_list->filename[0]) + lock_file_list->filename[0]) { + close(lock_file_list->fd); unlink(lock_file_list->filename); + } lock_file_list = lock_file_list->next; } } @@ -92,7 +94,7 @@ static char *resolve_symlink(char *p, size_t s) return p; } - if (link[0] == '/') { + if (is_absolute_path(link)) { /* absolute path simply replaces p */ if (link_len < s) strcpy(p, link); @@ -120,8 +122,6 @@ static char *resolve_symlink(char *p, size_t s) static int lock_file(struct lock_file *lk, const char *path) { - int fd; - if (strlen(path) >= sizeof(lk->filename)) return -1; strcpy(lk->filename, path); /* @@ -130,8 +130,8 @@ static int lock_file(struct lock_file *lk, const char *path) */ resolve_symlink(lk->filename, sizeof(lk->filename)-5); strcat(lk->filename, ".lock"); - fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666); - if (0 <= fd) { + lk->fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666); + if (0 <= lk->fd) { if (!lock_file_list) { signal(SIGINT, remove_lock_file_on_signal); atexit(remove_lock_file); @@ -148,7 +148,7 @@ static int lock_file(struct lock_file *lk, const char *path) } else lk->filename[0] = 0; - return fd; + return lk->fd; } int hold_lock_file_for_update(struct lock_file *lk, const char *path, int die_on_error) @@ -163,6 +163,7 @@ int commit_lock_file(struct lock_file *lk) { char result_file[PATH_MAX]; int i; + close(lk->fd); strcpy(result_file, lk->filename); i = strlen(result_file) - 5; /* .lock */ result_file[i] = 0; @@ -194,7 +195,9 @@ int commit_locked_index(struct lock_file *lk) void rollback_lock_file(struct lock_file *lk) { - if (lk->filename[0]) + if (lk->filename[0]) { + close(lk->fd); unlink(lk->filename); + } lk->filename[0] = 0; } @@ -1,7 +1,5 @@ #include "cache.h" -#include <sys/select.h> - /* * This is split up from the rest of git so that we might do * something different on Windows, for example. diff --git a/parse-options.c b/parse-options.c index d3e608ac45..e12b428c0a 100644 --- a/parse-options.c +++ b/parse-options.c @@ -216,6 +216,9 @@ is_abbreviated: return error("unknown option `%s'", arg); } +static NORETURN void usage_with_options_internal(const char * const *, + const struct option *, int); + int parse_options(int argc, const char **argv, const struct option *options, const char * const usagestr[], int flags) { @@ -249,6 +252,8 @@ int parse_options(int argc, const char **argv, const struct option *options, break; } + if (!strcmp(arg + 2, "help-all")) + usage_with_options_internal(usagestr, options, 1); if (!strcmp(arg + 2, "help")) usage_with_options(usagestr, options); if (parse_long_opt(&args, arg + 2, options)) @@ -263,8 +268,8 @@ int parse_options(int argc, const char **argv, const struct option *options, #define USAGE_OPTS_WIDTH 24 #define USAGE_GAP 2 -void usage_with_options(const char * const *usagestr, - const struct option *opts) +void usage_with_options_internal(const char * const *usagestr, + const struct option *opts, int full) { fprintf(stderr, "usage: %s\n", *usagestr++); while (*usagestr && **usagestr) @@ -285,6 +290,8 @@ void usage_with_options(const char * const *usagestr, fprintf(stderr, "%s\n", opts->help); continue; } + if (!full && (opts->flags & PARSE_OPT_HIDDEN)) + continue; pos = fprintf(stderr, " "); if (opts->short_name) @@ -335,6 +342,12 @@ void usage_with_options(const char * const *usagestr, exit(129); } +void usage_with_options(const char * const *usagestr, + const struct option *opts) +{ + usage_with_options_internal(usagestr, opts, 0); +} + /*----- some often used options -----*/ #include "cache.h" diff --git a/parse-options.h b/parse-options.h index a8760ac4b2..102ac31fb7 100644 --- a/parse-options.h +++ b/parse-options.h @@ -24,6 +24,7 @@ enum parse_opt_option_flags { PARSE_OPT_OPTARG = 1, PARSE_OPT_NOARG = 2, PARSE_OPT_NONEG = 4, + PARSE_OPT_HIDDEN = 8, }; struct option; @@ -57,6 +58,8 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset); * PARSE_OPT_OPTARG: says that the argument is optionnal (not for BOOLEANs) * PARSE_OPT_NOARG: says that this option takes no argument, for CALLBACKs * PARSE_OPT_NONEG: says that this option cannot be negated + * PARSE_OPT_HIDDEN this option is skipped in the default usage, showed in + * the long one. * * `callback`:: * pointer to the callback to use for OPTION_CALLBACK. diff --git a/peek-remote.c b/peek-remote.c deleted file mode 100644 index 8d20f7c9c6..0000000000 --- a/peek-remote.c +++ /dev/null @@ -1,73 +0,0 @@ -#include "cache.h" -#include "refs.h" -#include "pkt-line.h" - -static const char peek_remote_usage[] = -"git-peek-remote [--upload-pack=<git-upload-pack>] [<host>:]<directory>"; -static const char *uploadpack = "git-upload-pack"; - -static int peek_remote(int fd[2], unsigned flags) -{ - struct ref *ref; - - get_remote_heads(fd[0], &ref, 0, NULL, flags); - packet_flush(fd[1]); - - while (ref) { - printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); - ref = ref->next; - } - return 0; -} - -int main(int argc, char **argv) -{ - int i, ret; - char *dest = NULL; - int fd[2]; - struct child_process *conn; - int nongit = 0; - unsigned flags = 0; - - setup_git_directory_gently(&nongit); - - for (i = 1; i < argc; i++) { - char *arg = argv[i]; - - if (*arg == '-') { - if (!prefixcmp(arg, "--upload-pack=")) { - uploadpack = arg + 14; - continue; - } - if (!prefixcmp(arg, "--exec=")) { - uploadpack = arg + 7; - continue; - } - if (!strcmp("--tags", arg)) { - flags |= REF_TAGS; - continue; - } - if (!strcmp("--heads", arg)) { - flags |= REF_HEADS; - continue; - } - if (!strcmp("--refs", arg)) { - flags |= REF_NORMAL; - continue; - } - usage(peek_remote_usage); - } - dest = arg; - break; - } - - if (!dest || i != argc - 1) - usage(peek_remote_usage); - - conn = git_connect(fd, dest, uploadpack, 0); - ret = peek_remote(fd, flags); - close(fd[0]); - close(fd[1]); - ret |= finish_connect(conn); - return !!ret; -} diff --git a/perl/Git.pm b/perl/Git.pm index dca92c8adb..7468460f9a 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -549,6 +549,37 @@ sub config_bool { }; } +=item config_int ( VARIABLE ) + +Retrieve the integer configuration C<VARIABLE>. The return value +is simple decimal number. An optional value suffix of 'k', 'm', +or 'g' in the config file will cause the value to be multiplied +by 1024, 1048576 (1024^2), or 1073741824 (1024^3) prior to output. +It would return C<undef> if configuration variable is not defined, + +Must be called on a repository instance. + +This currently wraps command('config') so it is not so fast. + +=cut + +sub config_int { + my ($self, $var) = @_; + $self->repo_path() + or throw Error::Simple("not a repository"); + + try { + return $self->command_oneline('config', '--int', '--get', $var); + } catch Git::Error::Command with { + my $E = shift; + if ($E->value() == 1) { + # Key not found. + return undef; + } else { + throw $E; + } + }; +} =item ident ( TYPE | IDENTSTR ) @@ -56,20 +56,13 @@ void sq_quote_print(FILE *stream, const char *src) fputc('\'', stream); } -void sq_quote_argv(struct strbuf *dst, const char** argv, int count, - size_t maxlen) +void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen) { int i; - /* Count argv if needed. */ - if (count < 0) { - for (count = 0; argv[count]; count++) - ; /* just counting */ - } - /* Copy into destination buffer. */ - strbuf_grow(dst, 32 * count); - for (i = 0; i < count; ++i) { + strbuf_grow(dst, 255); + for (i = 0; argv[i]; ++i) { strbuf_addch(dst, ' '); sq_quote_buf(dst, argv[i]); if (maxlen && dst->len > maxlen) @@ -31,8 +31,7 @@ extern void sq_quote_print(FILE *stream, const char *src); extern void sq_quote_buf(struct strbuf *, const char *src); -extern void sq_quote_argv(struct strbuf *, const char **argv, int count, - size_t maxlen); +extern void sq_quote_argv(struct strbuf *, const char **argv, size_t maxlen); /* This unwraps what sq_quote() produces in place, but returns * NULL if the input does not look like what sq_quote would have diff --git a/receive-pack.c b/receive-pack.c index 38e35c06b9..fba4cf8235 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -200,12 +200,14 @@ static const char *update(struct command *cmd) } if (is_null_sha1(new_sha1)) { + if (!parse_object(old_sha1)) { + warning ("Allowing deletion of corrupt ref."); + old_sha1 = NULL; + } if (delete_ref(name, old_sha1)) { error("failed to delete %s", name); return "failed to delete"; } - fprintf(stderr, "%s: %s -> deleted\n", name, - sha1_to_hex(old_sha1)); return NULL; /* good */ } else { @@ -217,8 +219,6 @@ static const char *update(struct command *cmd) if (write_ref_sha1(lock, new_sha1, "push")) { return "failed to write"; /* error() already called */ } - fprintf(stderr, "%s: %s -> %s\n", name, - sha1_to_hex(old_sha1), sha1_to_hex(new_sha1)); return NULL; /* good */ } } @@ -643,6 +643,37 @@ int check_ref_format(const char *ref) } } +const char *ref_rev_parse_rules[] = { + "%.*s", + "refs/%.*s", + "refs/tags/%.*s", + "refs/heads/%.*s", + "refs/remotes/%.*s", + "refs/remotes/%.*s/HEAD", + NULL +}; + +const char *ref_fetch_rules[] = { + "%.*s", + "refs/%.*s", + "refs/heads/%.*s", + NULL +}; + +int refname_match(const char *abbrev_name, const char *full_name, const char **rules) +{ + const char **p; + const int abbrev_name_len = strlen(abbrev_name); + + for (p = rules; *p; p++) { + if (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name))) { + return 1; + } + } + + return 0; +} + static struct ref_lock *verify_lock(struct ref_lock *lock, const unsigned char *old_sha1, int mustexist) { @@ -1433,3 +1464,11 @@ int update_ref(const char *action, const char *refname, } return 0; } + +struct ref *find_ref_by_name(struct ref *list, const char *name) +{ + for ( ; list; list = list->next) + if (!strcmp(list->name, name)) + return list; + return NULL; +} @@ -278,6 +278,8 @@ static int handle_config(const char *key, const char *value) } else if (!strcmp(subkey, ".tagopt")) { if (!strcmp(value, "--no-tags")) remote->fetch_tags = -1; + } else if (!strcmp(subkey, ".proxy")) { + remote->http_proxy = xstrdup(value); } return 0; } @@ -417,25 +419,6 @@ int remote_has_url(struct remote *remote, const char *url) return 0; } -/* - * Returns true if, under the matching rules for fetching, name is the - * same as the given full name. - */ -static int ref_matches_abbrev(const char *name, const char *full) -{ - if (!prefixcmp(name, "refs/") || !strcmp(name, "HEAD")) - return !strcmp(name, full); - if (prefixcmp(full, "refs/")) - return 0; - if (!prefixcmp(name, "heads/") || - !prefixcmp(name, "tags/") || - !prefixcmp(name, "remotes/")) - return !strcmp(name, full + 5); - if (prefixcmp(full + 5, "heads/")) - return 0; - return !strcmp(full + 11, name); -} - int remote_find_tracking(struct remote *remote, struct refspec *refspec) { int find_src = refspec->src == NULL; @@ -485,7 +468,7 @@ struct ref *alloc_ref(unsigned namelen) return ret; } -static struct ref *copy_ref(struct ref *ref) +static struct ref *copy_ref(const struct ref *ref) { struct ref *ret = xmalloc(sizeof(struct ref) + strlen(ref->name) + 1); memcpy(ret, ref, sizeof(struct ref) + strlen(ref->name) + 1); @@ -493,6 +476,18 @@ static struct ref *copy_ref(struct ref *ref) return ret; } +struct ref *copy_ref_list(const struct ref *ref) +{ + struct ref *ret = NULL; + struct ref **tail = &ret; + while (ref) { + *tail = copy_ref(ref); + ref = ref->next; + tail = &((*tail)->next); + } + return ret; +} + void free_refs(struct ref *ref) { struct ref *next; @@ -519,10 +514,7 @@ static int count_refspec_match(const char *pattern, char *name = refs->name; int namelen = strlen(name); - if (namelen < patlen || - memcmp(name + namelen - patlen, pattern, patlen)) - continue; - if (namelen != patlen && name[namelen - patlen - 1] != '/') + if (!refname_match(pattern, name, ref_rev_parse_rules)) continue; /* A match is "weak" if it is with refs outside @@ -684,14 +676,6 @@ static int match_explicit_refs(struct ref *src, struct ref *dst, return -errs; } -static struct ref *find_ref_by_name(struct ref *list, const char *name) -{ - for ( ; list; list = list->next) - if (!strcmp(list->name, name)) - return list; - return NULL; -} - static const struct refspec *check_pattern_match(const struct refspec *rs, int rs_nr, const struct ref *src) @@ -710,10 +694,12 @@ static const struct refspec *check_pattern_match(const struct refspec *rs, * without thinking. */ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, - int nr_refspec, char **refspec, int all) + int nr_refspec, const char **refspec, int flags) { struct refspec *rs = parse_ref_spec(nr_refspec, (const char **) refspec); + int send_all = flags & MATCH_REFS_ALL; + int send_mirror = flags & MATCH_REFS_MIRROR; if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec)) return -1; @@ -730,7 +716,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, if (!pat) continue; } - else if (prefixcmp(src->name, "refs/heads/")) + else if (!send_mirror && prefixcmp(src->name, "refs/heads/")) /* * "matching refs"; traditionally we pushed everything * including refs outside refs/heads/ hierarchy, but @@ -751,10 +737,13 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, if (dst_peer && dst_peer->peer_ref) /* We're already sending something to this ref. */ goto free_name; - if (!dst_peer && !nr_refspec && !all) - /* Remote doesn't have it, and we have no + + if (!dst_peer && !nr_refspec && !(send_all || send_mirror)) + /* + * Remote doesn't have it, and we have no * explicit pattern, and we don't have - * --all. */ + * --all nor --mirror. + */ goto free_name; if (!dst_peer) { /* Create a new one and link it */ @@ -807,13 +796,13 @@ int branch_merge_matches(struct branch *branch, { if (!branch || i < 0 || i >= branch->merge_nr) return 0; - return ref_matches_abbrev(branch->merge[i]->src, refname); + return refname_match(branch->merge[i]->src, refname, ref_fetch_rules); } -static struct ref *get_expanded_map(struct ref *remote_refs, +static struct ref *get_expanded_map(const struct ref *remote_refs, const struct refspec *refspec) { - struct ref *ref; + const struct ref *ref; struct ref *ret = NULL; struct ref **tail = &ret; @@ -824,7 +813,7 @@ static struct ref *get_expanded_map(struct ref *remote_refs, if (strchr(ref->name, '^')) continue; /* a dereference item */ if (!prefixcmp(ref->name, refspec->src)) { - char *match; + const char *match; struct ref *cpy = copy_ref(ref); match = ref->name + remote_prefix_len; @@ -842,19 +831,19 @@ static struct ref *get_expanded_map(struct ref *remote_refs, return ret; } -static struct ref *find_ref_by_name_abbrev(struct ref *refs, const char *name) +static const struct ref *find_ref_by_name_abbrev(const struct ref *refs, const char *name) { - struct ref *ref; + const struct ref *ref; for (ref = refs; ref; ref = ref->next) { - if (ref_matches_abbrev(name, ref->name)) + if (refname_match(name, ref->name, ref_fetch_rules)) return ref; } return NULL; } -struct ref *get_remote_ref(struct ref *remote_refs, const char *name) +struct ref *get_remote_ref(const struct ref *remote_refs, const char *name) { - struct ref *ref = find_ref_by_name_abbrev(remote_refs, name); + const struct ref *ref = find_ref_by_name_abbrev(remote_refs, name); if (!ref) return NULL; @@ -887,7 +876,7 @@ static struct ref *get_local_ref(const char *name) return ret; } -int get_fetch_map(struct ref *remote_refs, +int get_fetch_map(const struct ref *remote_refs, const struct refspec *refspec, struct ref ***tail, int missing_ok) @@ -25,6 +25,11 @@ struct remote { const char *receivepack; const char *uploadpack; + + /* + * for curl remotes only + */ + char *http_proxy; }; struct remote *remote_get(const char *name); @@ -44,6 +49,10 @@ struct refspec { struct ref *alloc_ref(unsigned namelen); +struct ref *copy_ref_list(const struct ref *ref); + +int check_ref_type(const struct ref *ref, int flags); + /* * Frees the entire list and peers of elements. */ @@ -57,7 +66,7 @@ void ref_remove_duplicates(struct ref *ref_map); struct refspec *parse_ref_spec(int nr_refspec, const char **refspec); int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, - int nr_refspec, char **refspec, int all); + int nr_refspec, const char **refspec, int all); /* * Given a list of the remote refs and the specification of things to @@ -71,10 +80,10 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, * missing_ok is usually false, but when we are adding branch.$name.merge * it is Ok if the branch is not at the remote anymore. */ -int get_fetch_map(struct ref *remote_refs, const struct refspec *refspec, +int get_fetch_map(const struct ref *remote_refs, const struct refspec *refspec, struct ref ***tail, int missing_ok); -struct ref *get_remote_ref(struct ref *remote_refs, const char *name); +struct ref *get_remote_ref(const struct ref *remote_refs, const char *name); /* * For the given remote, reads the refspec's src and sets the other fields. @@ -98,4 +107,11 @@ struct branch *branch_get(const char *name); int branch_has_merge_config(struct branch *branch); int branch_merge_matches(struct branch *, int n, const char *); +/* Flags to match_refs. */ +enum match_refs_flags { + MATCH_REFS_NONE = 0, + MATCH_REFS_ALL = (1 << 0), + MATCH_REFS_MIRROR = (1 << 1), +}; + #endif diff --git a/send-pack.c b/send-pack.c deleted file mode 100644 index b74fd454f2..0000000000 --- a/send-pack.c +++ /dev/null @@ -1,461 +0,0 @@ -#include "cache.h" -#include "commit.h" -#include "tag.h" -#include "refs.h" -#include "pkt-line.h" -#include "run-command.h" -#include "remote.h" - -static const char send_pack_usage[] = -"git-send-pack [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n" -" --all and explicit <ref> specification are mutually exclusive."; -static const char *receivepack = "git-receive-pack"; -static int verbose; -static int send_all; -static int force_update; -static int use_thin_pack; -static int dry_run; - -/* - * Make a pack stream and spit it out into file descriptor fd - */ -static int pack_objects(int fd, struct ref *refs) -{ - /* - * The child becomes pack-objects --revs; we feed - * the revision parameters to it via its stdin and - * let its stdout go back to the other end. - */ - const char *args[] = { - "pack-objects", - "--all-progress", - "--revs", - "--stdout", - NULL, - NULL, - }; - struct child_process po; - - if (use_thin_pack) - args[4] = "--thin"; - memset(&po, 0, sizeof(po)); - po.argv = args; - po.in = -1; - po.out = fd; - po.git_cmd = 1; - if (start_command(&po)) - die("git-pack-objects failed (%s)", strerror(errno)); - - /* - * We feed the pack-objects we just spawned with revision - * parameters by writing to the pipe. - */ - while (refs) { - char buf[42]; - - if (!is_null_sha1(refs->old_sha1) && - has_sha1_file(refs->old_sha1)) { - memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40); - buf[0] = '^'; - buf[41] = '\n'; - if (!write_or_whine(po.in, buf, 42, - "send-pack: send refs")) - break; - } - if (!is_null_sha1(refs->new_sha1)) { - memcpy(buf, sha1_to_hex(refs->new_sha1), 40); - buf[40] = '\n'; - if (!write_or_whine(po.in, buf, 41, - "send-pack: send refs")) - break; - } - refs = refs->next; - } - - if (finish_command(&po)) - return error("pack-objects died with strange error"); - return 0; -} - -static void unmark_and_free(struct commit_list *list, unsigned int mark) -{ - while (list) { - struct commit_list *temp = list; - temp->item->object.flags &= ~mark; - list = temp->next; - free(temp); - } -} - -static int ref_newer(const unsigned char *new_sha1, - const unsigned char *old_sha1) -{ - struct object *o; - struct commit *old, *new; - struct commit_list *list, *used; - int found = 0; - - /* Both new and old must be commit-ish and new is descendant of - * old. Otherwise we require --force. - */ - o = deref_tag(parse_object(old_sha1), NULL, 0); - if (!o || o->type != OBJ_COMMIT) - return 0; - old = (struct commit *) o; - - o = deref_tag(parse_object(new_sha1), NULL, 0); - if (!o || o->type != OBJ_COMMIT) - return 0; - new = (struct commit *) o; - - if (parse_commit(new) < 0) - return 0; - - used = list = NULL; - commit_list_insert(new, &list); - while (list) { - new = pop_most_recent_commit(&list, 1); - commit_list_insert(new, &used); - if (new == old) { - found = 1; - break; - } - } - unmark_and_free(list, 1); - unmark_and_free(used, 1); - return found; -} - -static struct ref *local_refs, **local_tail; -static struct ref *remote_refs, **remote_tail; - -static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) -{ - struct ref *ref; - int len = strlen(refname) + 1; - ref = xcalloc(1, sizeof(*ref) + len); - hashcpy(ref->new_sha1, sha1); - memcpy(ref->name, refname, len); - *local_tail = ref; - local_tail = &ref->next; - return 0; -} - -static void get_local_heads(void) -{ - local_tail = &local_refs; - for_each_ref(one_local_ref, NULL); -} - -static int receive_status(int in) -{ - char line[1000]; - int ret = 0; - int len = packet_read_line(in, line, sizeof(line)); - if (len < 10 || memcmp(line, "unpack ", 7)) { - fprintf(stderr, "did not receive status back\n"); - return -1; - } - if (memcmp(line, "unpack ok\n", 10)) { - fputs(line, stderr); - ret = -1; - } - while (1) { - len = packet_read_line(in, line, sizeof(line)); - if (!len) - break; - if (len < 3 || - (memcmp(line, "ok", 2) && memcmp(line, "ng", 2))) { - fprintf(stderr, "protocol error: %s\n", line); - ret = -1; - break; - } - if (!memcmp(line, "ok", 2)) - continue; - fputs(line, stderr); - ret = -1; - } - return ret; -} - -static void update_tracking_ref(struct remote *remote, struct ref *ref) -{ - struct refspec rs; - int will_delete_ref; - - rs.src = ref->name; - rs.dst = NULL; - - if (!ref->peer_ref) - return; - - will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1); - - if (!will_delete_ref && - !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) - return; - - if (!remote_find_tracking(remote, &rs)) { - fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst); - if (is_null_sha1(ref->peer_ref->new_sha1)) { - if (delete_ref(rs.dst, NULL)) - error("Failed to delete"); - } else - update_ref("update by push", rs.dst, - ref->new_sha1, NULL, 0, 0); - free(rs.dst); - } -} - -static int send_pack(int in, int out, struct remote *remote, int nr_refspec, char **refspec) -{ - struct ref *ref; - int new_refs; - int ret = 0; - int ask_for_status_report = 0; - int allow_deleting_refs = 0; - int expect_status_report = 0; - - /* No funny business with the matcher */ - remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL); - get_local_heads(); - - /* Does the other end support the reporting? */ - if (server_supports("report-status")) - ask_for_status_report = 1; - if (server_supports("delete-refs")) - allow_deleting_refs = 1; - - /* match them up */ - if (!remote_tail) - remote_tail = &remote_refs; - if (match_refs(local_refs, remote_refs, &remote_tail, - nr_refspec, refspec, send_all)) - return -1; - - if (!remote_refs) { - fprintf(stderr, "No refs in common and none specified; doing nothing.\n" - "Perhaps you should specify a branch such as 'master'.\n"); - return 0; - } - - /* - * Finally, tell the other end! - */ - new_refs = 0; - for (ref = remote_refs; ref; ref = ref->next) { - char old_hex[60], *new_hex; - int will_delete_ref; - - if (!ref->peer_ref) - continue; - - - will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1); - if (will_delete_ref && !allow_deleting_refs) { - error("remote does not support deleting refs"); - ret = -2; - continue; - } - if (!will_delete_ref && - !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { - if (verbose) - fprintf(stderr, "'%s': up-to-date\n", ref->name); - continue; - } - - /* This part determines what can overwrite what. - * The rules are: - * - * (0) you can always use --force or +A:B notation to - * selectively force individual ref pairs. - * - * (1) if the old thing does not exist, it is OK. - * - * (2) if you do not have the old thing, you are not allowed - * to overwrite it; you would not know what you are losing - * otherwise. - * - * (3) if both new and old are commit-ish, and new is a - * descendant of old, it is OK. - * - * (4) regardless of all of the above, removing :B is - * always allowed. - */ - - if (!force_update && - !will_delete_ref && - !is_null_sha1(ref->old_sha1) && - !ref->force) { - if (!has_sha1_file(ref->old_sha1) || - !ref_newer(ref->peer_ref->new_sha1, - ref->old_sha1)) { - /* We do not have the remote ref, or - * we know that the remote ref is not - * an ancestor of what we are trying to - * push. Either way this can be losing - * commits at the remote end and likely - * we were not up to date to begin with. - */ - error("remote '%s' is not an ancestor of\n" - " local '%s'.\n" - " Maybe you are not up-to-date and " - "need to pull first?", - ref->name, - ref->peer_ref->name); - ret = -2; - continue; - } - } - hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); - if (!will_delete_ref) - new_refs++; - strcpy(old_hex, sha1_to_hex(ref->old_sha1)); - new_hex = sha1_to_hex(ref->new_sha1); - - if (!dry_run) { - if (ask_for_status_report) { - packet_write(out, "%s %s %s%c%s", - old_hex, new_hex, ref->name, 0, - "report-status"); - ask_for_status_report = 0; - expect_status_report = 1; - } - else - packet_write(out, "%s %s %s", - old_hex, new_hex, ref->name); - } - if (will_delete_ref) - fprintf(stderr, "deleting '%s'\n", ref->name); - else { - fprintf(stderr, "updating '%s'", ref->name); - if (strcmp(ref->name, ref->peer_ref->name)) - fprintf(stderr, " using '%s'", - ref->peer_ref->name); - fprintf(stderr, "\n from %s\n to %s\n", - old_hex, new_hex); - } - } - - packet_flush(out); - if (new_refs && !dry_run) - ret = pack_objects(out, remote_refs); - close(out); - - if (expect_status_report) { - if (receive_status(in)) - ret = -4; - } - - if (!dry_run && remote && ret == 0) { - for (ref = remote_refs; ref; ref = ref->next) - update_tracking_ref(remote, ref); - } - - if (!new_refs && ret == 0) - fprintf(stderr, "Everything up-to-date\n"); - return ret; -} - -static void verify_remote_names(int nr_heads, char **heads) -{ - int i; - - for (i = 0; i < nr_heads; i++) { - const char *remote = strchr(heads[i], ':'); - - remote = remote ? (remote + 1) : heads[i]; - switch (check_ref_format(remote)) { - case 0: /* ok */ - case -2: /* ok but a single level -- that is fine for - * a match pattern. - */ - case -3: /* ok but ends with a pattern-match character */ - continue; - } - die("remote part of refspec is not a valid name in %s", - heads[i]); - } -} - -int main(int argc, char **argv) -{ - int i, nr_heads = 0; - char *dest = NULL; - char **heads = NULL; - int fd[2], ret; - struct child_process *conn; - char *remote_name = NULL; - struct remote *remote = NULL; - - setup_git_directory(); - git_config(git_default_config); - - argv++; - for (i = 1; i < argc; i++, argv++) { - char *arg = *argv; - - if (*arg == '-') { - if (!prefixcmp(arg, "--receive-pack=")) { - receivepack = arg + 15; - continue; - } - if (!prefixcmp(arg, "--exec=")) { - receivepack = arg + 7; - continue; - } - if (!prefixcmp(arg, "--remote=")) { - remote_name = arg + 9; - continue; - } - if (!strcmp(arg, "--all")) { - send_all = 1; - continue; - } - if (!strcmp(arg, "--dry-run")) { - dry_run = 1; - continue; - } - if (!strcmp(arg, "--force")) { - force_update = 1; - continue; - } - if (!strcmp(arg, "--verbose")) { - verbose = 1; - continue; - } - if (!strcmp(arg, "--thin")) { - use_thin_pack = 1; - continue; - } - usage(send_pack_usage); - } - if (!dest) { - dest = arg; - continue; - } - heads = argv; - nr_heads = argc - i; - break; - } - if (!dest) - usage(send_pack_usage); - if (heads && send_all) - usage(send_pack_usage); - verify_remote_names(nr_heads, heads); - - if (remote_name) { - remote = remote_get(remote_name); - if (!remote_has_url(remote, dest)) { - die("Destination %s is not a uri for %s", - dest, remote_name); - } - } - - conn = git_connect(fd, dest, receivepack, verbose ? CONNECT_VERBOSE : 0); - ret = send_pack(fd[0], fd[1], remote, nr_heads, heads); - close(fd[0]); - close(fd[1]); - ret |= finish_connect(conn); - return !!ret; -} diff --git a/send-pack.h b/send-pack.h new file mode 100644 index 0000000000..8ff1dc3539 --- /dev/null +++ b/send-pack.h @@ -0,0 +1,18 @@ +#ifndef SEND_PACK_H +#define SEND_PACK_H + +struct send_pack_args { + const char *receivepack; + unsigned verbose:1, + send_all:1, + send_mirror:1, + force_update:1, + use_thin_pack:1, + dry_run:1; +}; + +int send_pack(struct send_pack_args *args, + const char *dest, struct remote *remote, + int nr_heads, const char **heads); + +#endif diff --git a/server-info.c b/server-info.c index 0d1312ca56..a051e49a9e 100644 --- a/server-info.c +++ b/server-info.c @@ -35,7 +35,7 @@ static int update_info_refs(int force) safe_create_leading_directories(path0); info_ref_fp = fopen(path1, "w"); if (!info_ref_fp) - return error("unable to update %s", path0); + return error("unable to update %s", path1); for_each_ref(add_info_ref, NULL); fclose(info_ref_fp); adjust_shared_perm(path1); @@ -59,7 +59,7 @@ const char *prefix_path(const char *prefix, int len, const char *path) const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) { static char path[PATH_MAX]; - if (!pfx || !*pfx || arg[0] == '/') + if (!pfx || !*pfx || is_absolute_path(arg)) return arg; memcpy(path, pfx, pfx_len); strcpy(path + pfx_len, arg); diff --git a/sha1_file.c b/sha1_file.c index f007874cbb..b0c24356ae 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -25,8 +25,10 @@ #ifdef NO_C99_FORMAT #define SZ_FMT "lu" +static unsigned long sz_fmt(size_t s) { return (unsigned long)s; } #else #define SZ_FMT "zu" +static size_t sz_fmt(size_t s) { return s; } #endif const unsigned char null_sha1[20]; @@ -86,7 +88,7 @@ int safe_create_leading_directories(char *path) char *pos = path; struct stat st; - if (*pos == '/') + if (is_absolute_path(path)) pos++; while (pos) { @@ -253,7 +255,7 @@ static int link_alt_odb_entry(const char * entry, int len, const char * relative int entlen = pfxlen + 43; int base_len = -1; - if (*entry != '/' && relative_base) { + if (!is_absolute_path(entry) && relative_base) { /* Relative alt-odb */ if (base_len < 0) base_len = strlen(relative_base) + 1; @@ -262,7 +264,7 @@ static int link_alt_odb_entry(const char * entry, int len, const char * relative } ent = xmalloc(sizeof(*ent) + entlen); - if (*entry != '/' && relative_base) { + if (!is_absolute_path(entry) && relative_base) { memcpy(ent->base, relative_base, base_len - 1); ent->base[base_len - 1] = '/'; memcpy(ent->base + base_len, entry, len); @@ -333,7 +335,7 @@ static void link_alt_odb_entries(const char *alt, const char *ep, int sep, while (cp < ep && *cp != sep) cp++; if (last != cp) { - if ((*last != '/') && depth) { + if (!is_absolute_path(last) && depth) { error("%s: ignoring relative alternate object store %s", relative_base, last); } else { @@ -423,9 +425,9 @@ void pack_report(void) "pack_report: getpagesize() = %10" SZ_FMT "\n" "pack_report: core.packedGitWindowSize = %10" SZ_FMT "\n" "pack_report: core.packedGitLimit = %10" SZ_FMT "\n", - (size_t) getpagesize(), - packed_git_window_size, - packed_git_limit); + sz_fmt(getpagesize()), + sz_fmt(packed_git_window_size), + sz_fmt(packed_git_limit)); fprintf(stderr, "pack_report: pack_used_ctr = %10u\n" "pack_report: pack_mmap_calls = %10u\n" @@ -435,7 +437,7 @@ void pack_report(void) pack_used_ctr, pack_mmap_calls, pack_open_windows, peak_pack_open_windows, - pack_mapped, peak_pack_mapped); + sz_fmt(pack_mapped), sz_fmt(peak_pack_mapped)); } static int check_packed_git_idx(const char *path, struct packed_git *p) diff --git a/sha1_name.c b/sha1_name.c index 2d727d54dc..13e11645e1 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -239,23 +239,13 @@ static int ambiguous_path(const char *path, int len) return slash; } -static const char *ref_fmt[] = { - "%.*s", - "refs/%.*s", - "refs/tags/%.*s", - "refs/heads/%.*s", - "refs/remotes/%.*s", - "refs/remotes/%.*s/HEAD", - NULL -}; - int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) { const char **p, *r; int refs_found = 0; *ref = NULL; - for (p = ref_fmt; *p; p++) { + for (p = ref_rev_parse_rules; *p; p++) { unsigned char sha1_from_ref[20]; unsigned char *this_result; @@ -277,7 +267,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) int logs_found = 0; *log = NULL; - for (p = ref_fmt; *p; p++) { + for (p = ref_rev_parse_rules; *p; p++) { struct stat st; unsigned char hash[20]; char path[PATH_MAX]; @@ -610,24 +600,35 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1) { struct commit_list *list = NULL, *backup = NULL, *l; int retval = -1; + char *temp_commit_buffer = NULL; if (prefix[0] == '!') { if (prefix[1] != '!') die ("Invalid search pattern: %s", prefix); prefix++; } - if (!save_commit_buffer) - return error("Could not expand oneline-name."); for_each_ref(handle_one_ref, &list); for (l = list; l; l = l->next) commit_list_insert(l->item, &backup); while (list) { char *p; struct commit *commit; + enum object_type type; + unsigned long size; commit = pop_most_recent_commit(&list, ONELINE_SEEN); parse_object(commit->object.sha1); - if (!commit->buffer || !(p = strstr(commit->buffer, "\n\n"))) + if (temp_commit_buffer) + free(temp_commit_buffer); + if (commit->buffer) + p = commit->buffer; + else { + p = read_sha1_file(commit->object.sha1, &type, &size); + if (!p) + continue; + temp_commit_buffer = p; + } + if (!(p = strstr(p, "\n\n"))) continue; if (!prefixcmp(p + 2, prefix)) { hashcpy(sha1, commit->object.sha1); @@ -635,6 +636,8 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1) break; } } + if (temp_commit_buffer) + free(temp_commit_buffer); free_commit_list(list); for (l = backup; l; l = l->next) clear_commit_marks(l->item, ONELINE_SEEN); diff --git a/t/t0001-init.sh b/t/t0001-init.sh index b14b3ec394..c015405f12 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -25,7 +25,7 @@ check_config () { test_expect_success 'plain' ' ( - unset GIT_DIR GIT_WORK_TREE && + unset GIT_DIR GIT_WORK_TREE mkdir plain && cd plain && git init @@ -35,7 +35,7 @@ test_expect_success 'plain' ' test_expect_success 'plain with GIT_WORK_TREE' ' if ( - unset GIT_DIR && + unset GIT_DIR mkdir plain-wt && cd plain-wt && GIT_WORK_TREE=$(pwd) git init @@ -48,7 +48,7 @@ test_expect_success 'plain with GIT_WORK_TREE' ' test_expect_success 'plain bare' ' ( - unset GIT_DIR GIT_WORK_TREE GIT_CONFIG && + unset GIT_DIR GIT_WORK_TREE GIT_CONFIG mkdir plain-bare-1 && cd plain-bare-1 && git --bare init @@ -58,7 +58,7 @@ test_expect_success 'plain bare' ' test_expect_success 'plain bare with GIT_WORK_TREE' ' if ( - unset GIT_DIR GIT_CONFIG && + unset GIT_DIR GIT_CONFIG mkdir plain-bare-2 && cd plain-bare-2 && GIT_WORK_TREE=$(pwd) git --bare init @@ -72,7 +72,7 @@ test_expect_success 'plain bare with GIT_WORK_TREE' ' test_expect_success 'GIT_DIR bare' ' ( - unset GIT_CONFIG && + unset GIT_CONFIG mkdir git-dir-bare.git && GIT_DIR=git-dir-bare.git git init ) && @@ -82,7 +82,7 @@ test_expect_success 'GIT_DIR bare' ' test_expect_success 'GIT_DIR non-bare' ' ( - unset GIT_CONFIG && + unset GIT_CONFIG mkdir non-bare && cd non-bare && GIT_DIR=.git git init @@ -93,7 +93,7 @@ test_expect_success 'GIT_DIR non-bare' ' test_expect_success 'GIT_DIR & GIT_WORK_TREE (1)' ' ( - unset GIT_CONFIG && + unset GIT_CONFIG mkdir git-dir-wt-1.git && GIT_WORK_TREE=$(pwd) GIT_DIR=git-dir-wt-1.git git init ) && @@ -103,7 +103,7 @@ test_expect_success 'GIT_DIR & GIT_WORK_TREE (1)' ' test_expect_success 'GIT_DIR & GIT_WORK_TREE (2)' ' if ( - unset GIT_CONFIG && + unset GIT_CONFIG mkdir git-dir-wt-2.git && GIT_WORK_TREE=$(pwd) GIT_DIR=git-dir-wt-2.git git --bare init ) diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index e5bbc384f7..f959aae846 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -175,4 +175,22 @@ test_expect_success 'recover and check' ' ' +test_expect_success 'prune --expire' ' + + before=$(git count-objects | sed "s/ .*//") && + BLOB=$(echo aleph | git hash-object -w --stdin) && + BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && + test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE && + git reset --hard && + git prune --expire=1.hour.ago && + test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE && + test-chmtime -86500 $BLOB_FILE && + git prune --expire 1.day && + test $before = $(git count-objects | sed "s/ .*//") && + ! test -f $BLOB_FILE + +' + test_done diff --git a/t/t3201-branch-contains.sh b/t/t3201-branch-contains.sh new file mode 100755 index 0000000000..9ef593f0e1 --- /dev/null +++ b/t/t3201-branch-contains.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +test_description='branch --contains <commit>' + +. ./test-lib.sh + +test_expect_success setup ' + + >file && + git add file && + test_tick && + git commit -m initial && + git branch side && + + echo 1 >file && + test_tick && + git commit -a -m "second on master" && + + git checkout side && + echo 1 >file && + test_tick && + git commit -a -m "second on side" && + + git merge master + +' + +test_expect_success 'branch --contains=master' ' + + git branch --contains=master >actual && + { + echo " master" && echo "* side" + } >expect && + diff -u expect actual + +' + +test_expect_success 'branch --contains master' ' + + git branch --contains master >actual && + { + echo " master" && echo "* side" + } >expect && + diff -u expect actual + +' + +test_expect_success 'branch --contains=side' ' + + git branch --contains=side >actual && + { + echo "* side" + } >expect && + diff -u expect actual + +' + +test_done diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh index 2ee5a00ea7..657f68104d 100755 --- a/t/t3403-rebase-skip.sh +++ b/t/t3403-rebase-skip.sh @@ -36,7 +36,6 @@ test_expect_failure 'rebase with git am -3 (default)' ' ' test_expect_success 'rebase --skip with am -3' ' - git reset --hard HEAD && git rebase --skip ' @@ -57,7 +56,6 @@ test_expect_success 'checkout skip-merge' 'git checkout -f skip-merge' test_expect_failure 'rebase with --merge' 'git rebase --merge master' test_expect_success 'rebase --skip with --merge' ' - git reset --hard HEAD && git rebase --skip ' diff --git a/t/t3902-quoted.sh b/t/t3902-quoted.sh index 245fb3babd..73da45f18c 100755 --- a/t/t3902-quoted.sh +++ b/t/t3902-quoted.sh @@ -20,6 +20,13 @@ LF=' ' DQ='"' +echo foo > "Name and an${HT}HT" +test -f "Name and an${HT}HT" || { + # since FAT/NTFS does not allow tabs in filenames, skip this test + say 'Your filesystem does not allow tabs in filenames, test skipped.' + test_done +} + for_each_name () { for name in \ Name "Name and a${LF}LF" "Name and an${HT}HT" "Name${DQ}" \ diff --git a/t/t4000-diff-format.sh b/t/t4000-diff-format.sh index 7d92ae3e99..c44b27aeb2 100755 --- a/t/t4000-diff-format.sh +++ b/t/t4000-diff-format.sh @@ -16,7 +16,7 @@ cat path0 >path1 chmod +x path1 test_expect_success \ - 'update-cache --add two files with and without +x.' \ + 'update-index --add two files with and without +x.' \ 'git update-index --add path0 path1' mv path0 path0- diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index 877c1ea5db..a32692417d 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -27,7 +27,7 @@ Line 15 ' test_expect_success \ - 'update-cache --add a file.' \ + 'update-index --add a file.' \ 'git update-index --add path0' test_expect_success \ diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh index 5836e3a899..26c2e4aa65 100755 --- a/t/t4008-diff-break-rewrite.sh +++ b/t/t4008-diff-break-rewrite.sh @@ -119,14 +119,14 @@ test_expect_success \ 'compare_diff_raw expected current' test_expect_success \ - 'run diff with -B' \ + 'run diff with -B -M' \ 'git diff-index -B -M "$tree" >current' -# This should not mistake file0 as the copy source of new file1 -# due to type differences. +# file0 changed from regular to symlink. file1 is very close to the preimage of file0. +# because we break file0, file1 can become a rename of it. cat >expected <<\EOF :100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T file0 -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100 file1 +:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 R file0 file1 EOF test_expect_success \ diff --git a/t/t4023-diff-rename-typechange.sh b/t/t4023-diff-rename-typechange.sh new file mode 100755 index 0000000000..255604effd --- /dev/null +++ b/t/t4023-diff-rename-typechange.sh @@ -0,0 +1,86 @@ +#!/bin/sh + +test_description='typechange rename detection' + +. ./test-lib.sh + +test_expect_success setup ' + + rm -f foo bar && + cat ../../COPYING >foo && + ln -s linklink bar && + git add foo bar && + git commit -a -m Initial && + git tag one && + + rm -f foo bar && + cat ../../COPYING >bar && + ln -s linklink foo && + git add foo bar && + git commit -a -m Second && + git tag two && + + rm -f foo bar && + cat ../../COPYING >foo && + git add foo && + git commit -a -m Third && + git tag three && + + mv foo bar && + ln -s linklink foo && + git add foo bar && + git commit -a -m Fourth && + git tag four && + + # This is purely for sanity check + + rm -f foo bar && + cat ../../COPYING >foo && + cat ../../Makefile >bar && + git add foo bar && + git commit -a -m Fifth && + git tag five && + + rm -f foo bar && + cat ../../Makefile >foo && + cat ../../COPYING >bar && + git add foo bar && + git commit -a -m Sixth && + git tag six + +' + +test_expect_success 'cross renames to be detected for regular files' ' + + git diff-tree five six -r --name-status -B -M | sort >actual && + { + echo "R100 foo bar" + echo "R100 bar foo" + } | sort >expect && + diff -u expect actual + +' + +test_expect_success 'cross renames to be detected for typechange' ' + + git diff-tree one two -r --name-status -B -M | sort >actual && + { + echo "R100 foo bar" + echo "R100 bar foo" + } | sort >expect && + diff -u expect actual + +' + +test_expect_success 'moves and renames' ' + + git diff-tree three four -r --name-status -B -M | sort >actual && + { + echo "R100 foo bar" + echo "T100 foo" + } | sort >expect && + diff -u expect actual + +' + +test_done diff --git a/t/t4100/t-apply-1.patch b/t/t4100/t-apply-1.patch index de587517f4..90ab54f0f5 100644 --- a/t/t4100/t-apply-1.patch +++ b/t/t4100/t-apply-1.patch @@ -90,7 +90,7 @@ diff --git a/Documentation/git.txt b/Documentation/git.txt diff --git a/Makefile b/Makefile --- a/Makefile +++ b/Makefile -@@ -30,7 +30,7 @@ PROG= git-update-cache git-diff-files +@@ -30,7 +30,7 @@ PROG= git-update-index git-diff-files git-checkout-cache git-diff-tree git-rev-tree git-ls-files \ git-check-files git-ls-tree git-merge-base git-merge-cache \ git-unpack-file git-export git-diff-cache git-convert-cache \ diff --git a/t/t4100/t-apply-2.patch b/t/t4100/t-apply-2.patch index cfdc80885b..f5c7d601fc 100644 --- a/t/t4100/t-apply-2.patch +++ b/t/t4100/t-apply-2.patch @@ -9,7 +9,7 @@ diff --git a/Makefile b/Makefile - git-deltafy-script + git-deltafy-script git-fetch-script - PROG= git-update-cache git-diff-files git-init-db git-write-tree \ + PROG= git-update-index git-diff-files git-init-db git-write-tree \ git-read-tree git-commit-tree git-cat-file git-fsck-cache \ diff --git a/git-pull-script b/git-fetch-script similarity index 87% diff --git a/t/t4100/t-apply-5.patch b/t/t4100/t-apply-5.patch index de11623d1b..5f6ddc1059 100644 --- a/t/t4100/t-apply-5.patch +++ b/t/t4100/t-apply-5.patch @@ -200,7 +200,7 @@ diff a/Documentation/git.txt b/Documentation/git.txt diff a/Makefile b/Makefile --- a/Makefile +++ b/Makefile -@@ -30,7 +30,7 @@ PROG= git-update-cache git-diff-files +@@ -30,7 +30,7 @@ PROG= git-update-index git-diff-files git-checkout-cache git-diff-tree git-rev-tree git-ls-files \ git-check-files git-ls-tree git-merge-base git-merge-cache \ git-unpack-file git-export git-diff-cache git-convert-cache \ diff --git a/t/t4100/t-apply-6.patch b/t/t4100/t-apply-6.patch index d9753637fc..a72729a712 100644 --- a/t/t4100/t-apply-6.patch +++ b/t/t4100/t-apply-6.patch @@ -8,7 +8,7 @@ diff a/Makefile b/Makefile - git-deltafy-script + git-deltafy-script git-fetch-script - PROG= git-update-cache git-diff-files git-init-db git-write-tree \ + PROG= git-update-index git-diff-files git-init-db git-write-tree \ git-read-tree git-commit-tree git-cat-file git-fsck-cache \ diff a/git-fetch-script b/git-fetch-script --- /dev/null diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index 65571e0549..b540f7295a 100755 --- a/t/t4119-apply-config.sh +++ b/t/t4119-apply-config.sh @@ -24,7 +24,7 @@ cat >gpatch.file <<\EOF && +++ file1+ 2007-02-21 01:07:44.000000000 -0800 @@ -1 +1 @@ -A -+B ++B EOF sed -e 's|file1|sub/&|' gpatch.file >gpatch-sub.file && diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index ba7579c251..f1106e6542 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -187,49 +187,51 @@ test_expect_success \ test-3-${packname_3}.idx' test_expect_success \ - 'corrupt a pack and see if verify catches' \ + 'verify-pack catches mismatched .idx and .pack files' \ 'cat test-1-${packname_1}.idx >test-3.idx && cat test-2-${packname_2}.pack >test-3.pack && if git verify-pack test-3.idx then false else :; - fi && + fi' - : PACK_SIGNATURE && - cat test-1-${packname_1}.pack >test-3.pack && +test_expect_success \ + 'verify-pack catches a corrupted pack signature' \ + 'cat test-1-${packname_1}.pack >test-3.pack && dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=2 && if git verify-pack test-3.idx then false else :; - fi && + fi' - : PACK_VERSION && - cat test-1-${packname_1}.pack >test-3.pack && +test_expect_success \ + 'verify-pack catches a corrupted pack version' \ + 'cat test-1-${packname_1}.pack >test-3.pack && dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=7 && if git verify-pack test-3.idx then false else :; - fi && + fi' - : TYPE/SIZE byte of the first packed object data && - cat test-1-${packname_1}.pack >test-3.pack && +test_expect_success \ + 'verify-pack catches a corrupted type/size of the 1st packed object data' \ + 'cat test-1-${packname_1}.pack >test-3.pack && dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=12 && if git verify-pack test-3.idx then false else :; - fi && + fi' - : sum of the index file itself && - l=`wc -c <test-3.idx` && +test_expect_success \ + 'verify-pack catches a corrupted sum of the index file itself' \ + 'l=`wc -c <test-3.idx` && l=`expr $l - 20` && cat test-1-${packname_1}.pack >test-3.pack && dd if=/dev/zero of=test-3.idx count=20 bs=1 conv=notrunc seek=$l && if git verify-pack test-3.pack then false else :; - fi && - - :' + fi' test_expect_success \ 'build pack index for an existing pack' \ diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index 4f58c4c3f9..2a2878b572 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -61,17 +61,33 @@ test_expect_success \ test_expect_success \ 'index v2: force some 64-bit offsets with pack-objects' \ - 'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list) && - git verify-pack -v "test-3-${pack3}.pack"' + 'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list)' + +have_64bits= +if msg=$(git verify-pack -v "test-3-${pack3}.pack" 2>&1) || + ! echo "$msg" | grep "pack too large .* off_t" +then + have_64bits=t +else + say "skipping tests concerning 64-bit offsets" +fi + +test "$have_64bits" && +test_expect_success \ + 'index v2: verify a pack with some 64-bit offsets' \ + 'git verify-pack -v "test-3-${pack3}.pack"' +test "$have_64bits" && test_expect_failure \ '64-bit offsets: should be different from previous index v2 results' \ 'cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"' +test "$have_64bits" && test_expect_success \ 'index v2: force some 64-bit offsets with index-pack' \ 'git-index-pack --index-version=2,0x40000 -o 3.idx "test-1-${pack1}.pack"' +test "$have_64bits" && test_expect_success \ '64-bit offsets: index-pack result should match pack-objects one' \ 'cmp "test-3-${pack3}.idx" "3.idx"' @@ -116,11 +132,11 @@ test_expect_failure \ test_expect_success \ '[index v2] 1) stream pack to repository' \ 'rm -f .git/objects/pack/* && - git-index-pack --index-version=2,0x40000 --stdin < "test-1-${pack1}.pack" && + git-index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" && git prune-packed && git count-objects | ( read nr rest && test "$nr" -eq 1 ) && cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" && - cmp "test-3-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx"' + cmp "test-2-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx"' test_expect_success \ '[index v2] 2) create a stealth corruption in a delta base reference' \ diff --git a/t/t5404-tracking-branches.sh b/t/t5404-tracking-branches.sh new file mode 100755 index 0000000000..1493a92c06 --- /dev/null +++ b/t/t5404-tracking-branches.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +test_description='tracking branch update checks for git push' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo 1 >file && + git add file && + git commit -m 1 && + git branch b1 && + git branch b2 && + git clone . aa && + git checkout b1 && + echo b1 >>file && + git commit -a -m b1 && + git checkout b2 && + echo b2 >>file && + git commit -a -m b2 +' + +test_expect_success 'prepare pushable branches' ' + cd aa && + b1=$(git rev-parse origin/b1) && + b2=$(git rev-parse origin/b2) && + git checkout -b b1 origin/b1 && + echo aa-b1 >>file && + git commit -a -m aa-b1 && + git checkout -b b2 origin/b2 && + echo aa-b2 >>file && + git commit -a -m aa-b2 && + git checkout master && + echo aa-master >>file && + git commit -a -m aa-master +' + +test_expect_success 'mixed-success push returns error' '! git push' + +test_expect_success 'check tracking branches updated correctly after push' ' + test "$(git rev-parse origin/master)" = "$(git rev-parse master)" +' + +test_expect_success 'check tracking branches not updated for failed refs' ' + test "$(git rev-parse origin/b1)" = "$b1" && + test "$(git rev-parse origin/b2)" = "$b2" +' + +test_expect_success 'deleted branches have their tracking branches removed' ' + git push origin :b1 && + test "$(git rev-parse origin/b1)" = "origin/b1" +' + +test_done diff --git a/t/t5405-send-pack-rewind.sh b/t/t5405-send-pack-rewind.sh new file mode 100755 index 0000000000..86abc62271 --- /dev/null +++ b/t/t5405-send-pack-rewind.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +test_description='forced push to replace commit we do not have' + +. ./test-lib.sh + +test_expect_success setup ' + + >file1 && git add file1 && test_tick && + git commit -m Initial && + + mkdir another && ( + cd another && + git init && + git fetch .. master:master + ) && + + >file2 && git add file2 && test_tick && + git commit -m Second + +' + +test_expect_success 'non forced push should die not segfault' ' + + ( + cd another && + git push .. master:master + test $? = 1 + ) + +' + +test_expect_success 'forced push should succeed' ' + + ( + cd another && + git push .. +master:master + ) + +' + +test_done diff --git a/t/t5406-remote-rejects.sh b/t/t5406-remote-rejects.sh new file mode 100755 index 0000000000..46b2cb4e46 --- /dev/null +++ b/t/t5406-remote-rejects.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +test_description='remote push rejects are reported by client' + +. ./test-lib.sh + +test_expect_success 'setup' ' + mkdir .git/hooks && + (echo "#!/bin/sh" ; echo "exit 1") >.git/hooks/update && + chmod +x .git/hooks/update && + echo 1 >file && + git add file && + git commit -m 1 && + git clone . child && + cd child && + echo 2 >file && + git commit -a -m 2 +' + +test_expect_success 'push reports error' '! git push 2>stderr' + +test_expect_success 'individual ref reports error' 'grep rejected stderr' + +test_done diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 35889c0a12..46a9c4d95c 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -95,6 +95,31 @@ test_expect_success 'fetch following tags' ' ' +test_expect_failure 'fetch must not resolve short tag name' ' + + cd "$D" && + + mkdir five && + cd five && + git init && + + git fetch .. anno:five + +' + +test_expect_failure 'fetch must not resolve short remote name' ' + + cd "$D" && + git-update-ref refs/remotes/six/HEAD HEAD + + mkdir six && + cd six && + git init && + + git fetch .. six:six + +' + test_expect_success 'create bundle 1' ' cd "$D" && echo >file updated again by origin && diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh new file mode 100755 index 0000000000..6ec5f7c48b --- /dev/null +++ b/t/t5512-ls-remote.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +test_description='git ls-remote' + +. ./test-lib.sh + +test_expect_success setup ' + + >file && + git add file && + test_tick && + git commit -m initial && + git tag mark && + git show-ref --tags -d | sed -e "s/ / /" >expected.tag && + ( + echo "$(git rev-parse HEAD) HEAD" + git show-ref -d | sed -e "s/ / /" + ) >expected.all && + + git remote add self $(pwd)/.git + +' + +test_expect_success 'ls-remote --tags .git' ' + + git ls-remote --tags .git >actual && + diff -u expected.tag actual + +' + +test_expect_success 'ls-remote .git' ' + + git ls-remote .git >actual && + diff -u expected.all actual + +' + +test_expect_success 'ls-remote --tags self' ' + + git ls-remote --tags self >actual && + diff -u expected.tag actual + +' + +test_expect_success 'ls-remote self' ' + + git ls-remote self >actual && + diff -u expected.all actual + +' + +test_done diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 86f9b5346a..9d2dc33cbd 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -145,11 +145,21 @@ test_expect_success 'push with no ambiguity (1)' ' test_expect_success 'push with no ambiguity (2)' ' mk_test remotes/origin/master && - git push testrepo master:master && + git push testrepo master:origin/master && check_push_result $the_commit remotes/origin/master ' +test_expect_success 'push with colon-less refspec, no ambiguity' ' + + mk_test heads/master heads/t/master && + git branch -f t/master master && + git push testrepo master && + check_push_result $the_commit heads/master && + check_push_result $the_first_commit heads/t/master + +' + test_expect_success 'push with weak ambiguity (1)' ' mk_test heads/master remotes/origin/master && @@ -244,12 +254,28 @@ test_expect_success 'push with colon-less refspec (4)' ' ' +test_expect_success 'push with HEAD' ' + + mk_test heads/master && + git checkout master && + git push testrepo HEAD && + check_push_result $the_commit heads/master + +' + +test_expect_success 'push with HEAD nonexisting at remote' ' + + mk_test heads/master && + git checkout -b local master && + git push testrepo HEAD && + check_push_result $the_commit heads/local +' + test_expect_success 'push with dry-run' ' mk_test heads/master && - cd testrepo && - old_commit=$(git show-ref -s --verify refs/heads/master) && - cd .. && + (cd testrepo && + old_commit=$(git show-ref -s --verify refs/heads/master)) && git push --dry-run testrepo && check_push_result $old_commit heads/master ' @@ -257,28 +283,40 @@ test_expect_success 'push with dry-run' ' test_expect_success 'push updates local refs' ' rm -rf parent child && - mkdir parent && cd parent && git init && - echo one >foo && git add foo && git commit -m one && - cd .. && - git clone parent child && cd child && + mkdir parent && + (cd parent && git init && + echo one >foo && git add foo && git commit -m one) && + git clone parent child && + (cd child && echo two >foo && git commit -a -m two && git push && - test $(git rev-parse master) = $(git rev-parse remotes/origin/master) + test $(git rev-parse master) = $(git rev-parse remotes/origin/master)) ' test_expect_success 'push does not update local refs on failure' ' rm -rf parent child && - mkdir parent && cd parent && git init && + mkdir parent && + (cd parent && git init && echo one >foo && git add foo && git commit -m one && echo exit 1 >.git/hooks/pre-receive && - chmod +x .git/hooks/pre-receive && - cd .. && - git clone parent child && cd child && - echo two >foo && git commit -a -m two || exit 1 - git push && exit 1 - test $(git rev-parse master) != $(git rev-parse remotes/origin/master) + chmod +x .git/hooks/pre-receive) && + git clone parent child && + (cd child && + echo two >foo && git commit -a -m two && + ! git push && + test $(git rev-parse master) != \ + $(git rev-parse remotes/origin/master)) + +' + +test_expect_success 'allow deleting an invalid remote ref' ' + + pwd && + rm -f testrepo/.git/objects/??/* && + git push testrepo :refs/heads/master && + (cd testrepo && ! git rev-parse --verify refs/heads/master) ' diff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh new file mode 100755 index 0000000000..ed3fec192a --- /dev/null +++ b/t/t5517-push-mirror.sh @@ -0,0 +1,228 @@ +#!/bin/sh + +test_description='pushing to a mirror repository' + +. ./test-lib.sh + +D=`pwd` + +invert () { + if "$@"; then + return 1 + else + return 0 + fi +} + +mk_repo_pair () { + rm -rf master mirror && + mkdir mirror && + ( + cd mirror && + git init + ) && + mkdir master && + ( + cd master && + git init && + git config remote.up.url ../mirror + ) +} + + +# BRANCH tests +test_expect_success 'push mirror creates new branches' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/heads/master) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror updates existing branches' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git push --mirror up && + echo two >foo && git add foo && git commit -m two && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/heads/master) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror force updates existing branches' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git push --mirror up && + echo two >foo && git add foo && git commit -m two && + git push --mirror up && + git reset --hard HEAD^ + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/heads/master) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror removes branches' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git branch remove master && + git push --mirror up && + git branch -D remove + git push --mirror up + ) && + ( + cd mirror && + invert git show-ref -s --verify refs/heads/remove + ) + +' + +test_expect_success 'push mirror adds, updates and removes branches together' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git branch remove master && + git push --mirror up && + git branch -D remove && + git branch add master && + echo two >foo && git add foo && git commit -m two && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/heads/master) && + master_add=$(cd master && git show-ref -s --verify refs/heads/add) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) && + mirror_add=$(cd mirror && git show-ref -s --verify refs/heads/add) && + test "$master_master" = "$mirror_master" && + test "$master_add" = "$mirror_add" && + ( + cd mirror && + invert git show-ref -s --verify refs/heads/remove + ) + +' + + +# TAG tests +test_expect_success 'push mirror creates new tags' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git tag -f tmaster master && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror updates existing tags' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git tag -f tmaster master && + git push --mirror up && + echo two >foo && git add foo && git commit -m two && + git tag -f tmaster master && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror force updates existing tags' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git tag -f tmaster master && + git push --mirror up && + echo two >foo && git add foo && git commit -m two && + git tag -f tmaster master && + git push --mirror up && + git reset --hard HEAD^ + git tag -f tmaster master && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror removes tags' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git tag -f tremove master && + git push --mirror up && + git tag -d tremove + git push --mirror up + ) && + ( + cd mirror && + invert git show-ref -s --verify refs/tags/tremove + ) + +' + +test_expect_success 'push mirror adds, updates and removes tags together' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git tag -f tmaster master && + git tag -f tremove master && + git push --mirror up && + git tag -d tremove && + git tag tadd master && + echo two >foo && git add foo && git commit -m two && + git tag -f tmaster master && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) && + master_add=$(cd master && git show-ref -s --verify refs/tags/tadd) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) && + mirror_add=$(cd mirror && git show-ref -s --verify refs/tags/tadd) && + test "$master_master" = "$mirror_master" && + test "$master_add" = "$mirror_add" && + ( + cd mirror && + invert git show-ref -s --verify refs/tags/tremove + ) + +' + +test_done diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 93eaf2c154..52b3a0c6dd 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -53,4 +53,26 @@ test_expect_success 'the default remote . should not break explicit pull' ' test `cat file` = modified ' +test_expect_success '--rebase' ' + git branch to-rebase && + echo modified again > file && + git commit -m file file && + git checkout to-rebase && + echo new > file2 && + git add file2 && + git commit -m "new file" && + git tag before-rebase && + git pull --rebase . copy && + test $(git rev-parse HEAD^) = $(git rev-parse copy) && + test new = $(git show HEAD:file2) +' + +test_expect_success 'branch.to-rebase.rebase' ' + git reset --hard before-rebase && + git config branch.to-rebase.rebase 1 && + git pull . copy && + test $(git rev-parse HEAD^) = $(git rev-parse copy) && + test new = $(git show HEAD:file2) +' + test_done diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 53956c08e2..2ba4b00e52 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -71,6 +71,43 @@ test_expect_success 'bisect start with one bad and good' ' git bisect next ' +test_expect_success 'bisect reset: back in the master branch' ' + git bisect reset && + echo "* master" > branch.expect && + git branch > branch.output && + cmp branch.expect branch.output +' + +test_expect_success 'bisect reset: back in another branch' ' + git checkout -b other && + git bisect start && + git bisect good $HASH1 && + git bisect bad $HASH3 && + git bisect reset && + echo " master" > branch.expect && + echo "* other" >> branch.expect && + git branch > branch.output && + cmp branch.expect branch.output +' + +test_expect_success 'bisect reset when not bisecting' ' + git bisect reset && + git branch > branch.output && + cmp branch.expect branch.output +' + +test_expect_success 'bisect reset removes packed refs' ' + git bisect reset && + git bisect start && + git bisect good $HASH1 && + git bisect bad $HASH3 && + git pack-refs --all --prune && + git bisect next && + git bisect reset && + test -z "$(git for-each-ref "refs/bisect/*")" && + test -z "$(git for-each-ref "refs/heads/bisect")" +' + # $HASH1 is good, $HASH4 is bad, we skip $HASH3 # but $HASH2 is bad, # so we should find $HASH2 as the first bad commit @@ -167,7 +204,7 @@ test_expect_success 'bisect skip: add line and then a new test' ' git bisect skip && git bisect good > my_bisect_log.txt && grep "$HASH5 is first bad commit" my_bisect_log.txt && - git bisect log > log_to_replay.txt + git bisect log > log_to_replay.txt && git bisect reset ' diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index 2089351f7d..5f60b22d87 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -114,7 +114,7 @@ test_expect_success 'use index-filter to move into a subdirectory' ' test_expect_success 'stops when msg filter fails' ' old=$(git rev-parse HEAD) && - ! git-filter-branch -f --msg-filter false && + ! git-filter-branch -f --msg-filter false HEAD && test $old = $(git rev-parse HEAD) && rm -rf .git-rewrite ' diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 5f7e388d7a..c7130c4dcc 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -339,20 +339,14 @@ test_expect_success \ ' test_expect_success \ - 'trying to create tags giving many -m or -F options should fail' ' + 'trying to create tags giving both -m or -F options should fail' ' echo "message file 1" >msgfile1 && echo "message file 2" >msgfile2 && ! tag_exists msgtag && - ! git-tag -m "message 1" -m "message 2" msgtag && - ! tag_exists msgtag && - ! git-tag -F msgfile1 -F msgfile2 msgtag && - ! tag_exists msgtag && ! git-tag -m "message 1" -F msgfile1 msgtag && ! tag_exists msgtag && ! git-tag -F msgfile1 -m "message 1" msgtag && ! tag_exists msgtag && - ! git-tag -F msgfile1 -m "message 1" -F msgfile2 msgtag && - ! tag_exists msgtag && ! git-tag -m "message 1" -F msgfile1 -m "message 2" msgtag && ! tag_exists msgtag ' @@ -673,6 +667,22 @@ test_expect_success 'creating a signed tag with -F - should succeed' ' git diff expect actual ' +cat >fakeeditor <<'EOF' +#!/bin/sh +test -n "$1" && exec >"$1" +echo A signed tag message +echo from a fake editor. +EOF +chmod +x fakeeditor +get_tag_header implied-annotate $commit commit $time >expect +./fakeeditor >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success '-s implies annotated tag' ' + GIT_EDITOR=./fakeeditor git-tag -s implied-annotate && + get_tag_msg implied-annotate >actual && + git diff expect actual +' + test_expect_success \ 'trying to create a signed tag with non-existing -F file should fail' ' ! test -f nonexistingfile && diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index ce83af3a02..19c4b2c556 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -79,7 +79,8 @@ test_expect_success \ cat >editor <<\EOF #!/bin/sh -sed -i -e "s/a file/an amend commit/g" $1 +sed -e "s/a file/an amend commit/g" < $1 > $1- +mv $1- $1 EOF chmod 755 editor @@ -98,7 +99,8 @@ test_expect_success \ cat >editor <<\EOF #!/bin/sh -sed -i -e "s/amend/older/g" $1 +sed -e "s/amend/older/g" < $1 > $1- +mv $1- $1 EOF chmod 755 editor @@ -267,4 +269,45 @@ test_expect_success 'git commit <file> with dirty index' ' git diff --cached | grep chz ' +test_expect_success 'same tree (single parent)' ' + + git reset --hard + + if git commit -m empty + then + echo oops -- should have complained + false + else + : happy + fi + +' + +test_expect_success 'same tree (single parent) --allow-empty' ' + + git commit --allow-empty -m "forced empty" && + git cat-file commit HEAD | grep forced + +' + +test_expect_success 'same tree (merge and amend merge)' ' + + git checkout -b side HEAD^ && + echo zero >zero && + git add zero && + git commit -m "add zero" && + git checkout master && + + git merge -s ours side -m "empty ok" && + git diff HEAD^ HEAD >actual && + : >expected && + diff -u expected actual && + + git commit --amend -m "empty really ok" && + git diff HEAD^ HEAD >actual && + : >expected && + diff -u expected actual + +' + test_done diff --git a/t/t9301-fast-export.sh b/t/t9301-fast-export.sh new file mode 100755 index 0000000000..f09bfb1117 --- /dev/null +++ b/t/t9301-fast-export.sh @@ -0,0 +1,123 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes E. Schindelin +# + +test_description='git-fast-export' +. ./test-lib.sh + +test_expect_success 'setup' ' + + echo Wohlauf > file && + git add file && + test_tick && + git commit -m initial && + echo die Luft > file && + echo geht frisch > file2 && + git add file file2 && + test_tick && + git commit -m second && + echo und > file2 && + test_tick && + git commit -m third file2 && + test_tick && + git tag rein && + git checkout -b wer HEAD^ && + echo lange > file2 + test_tick && + git commit -m sitzt file2 && + test_tick && + git tag -a -m valentin muss && + git merge -s ours master + +' + +test_expect_success 'fast-export | fast-import' ' + + MASTER=$(git rev-parse --verify master) && + REIN=$(git rev-parse --verify rein) && + WER=$(git rev-parse --verify wer) && + MUSS=$(git rev-parse --verify muss) && + mkdir new && + git --git-dir=new/.git init && + git fast-export --all | + (cd new && + git fast-import && + test $MASTER = $(git rev-parse --verify refs/heads/master) && + test $REIN = $(git rev-parse --verify refs/tags/rein) && + test $WER = $(git rev-parse --verify refs/heads/wer) && + test $MUSS = $(git rev-parse --verify refs/tags/muss)) + +' + +test_expect_success 'fast-export master~2..master' ' + + git fast-export master~2..master | + sed "s/master/partial/" | + (cd new && + git fast-import && + test $MASTER != $(git rev-parse --verify refs/heads/partial) && + git diff master..partial && + git diff master^..partial^ && + ! git rev-parse partial~2) + +' + +test_expect_success 'iso-8859-1' ' + + git config i18n.commitencoding ISO-8859-1 && + # use author and committer name in ISO-8859-1 to match it. + . ../t3901-8859-1.txt && + test_tick && + echo rosten >file && + git commit -s -m den file && + git fast-export wer^..wer | + sed "s/wer/i18n/" | + (cd new && + git fast-import && + git cat-file commit i18n | grep "Áéí óú") + +' + +cat > signed-tag-import << EOF +tag sign-your-name +from $(git rev-parse HEAD) +tagger C O Mitter <committer@example.com> 1112911993 -0700 +data 210 +A message for a sign +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.5 (GNU/Linux) + +fakedsignaturefakedsignaturefakedsignaturefakedsignaturfakedsign +aturefakedsignaturefake= +=/59v +-----END PGP SIGNATURE----- +EOF + +test_expect_success 'set up faked signed tag' ' + + cat signed-tag-import | git fast-import + +' + +test_expect_success 'signed-tags=abort' ' + + ! git fast-export --signed-tags=abort sign-your-name + +' + +test_expect_success 'signed-tags=verbatim' ' + + git fast-export --signed-tags=verbatim sign-your-name > output && + grep PGP output + +' + +test_expect_success 'signed-tags=strip' ' + + git fast-export --signed-tags=strip sign-your-name > output && + ! grep PGP output + +' + +test_done diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh new file mode 100755 index 0000000000..7706430d81 --- /dev/null +++ b/t/t9600-cvsimport.sh @@ -0,0 +1,150 @@ +#!/bin/sh + +test_description='git-cvsimport basic tests' +. ./test-lib.sh + +if ! type cvs >/dev/null 2>&1 +then + say 'skipping cvsimport tests, cvs not found' + test_done + exit +fi + +cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'` +case "$cvsps_version" in +2.1) + ;; +'') + say 'skipping cvsimport tests, cvsps not found' + test_done + exit + ;; +*) + say 'skipping cvsimport tests, cvsps too old' + test_done + exit + ;; +esac + +CVSROOT=$(pwd)/cvsroot +export CVSROOT +# for clean cvsps cache +HOME=$(pwd) +export HOME + +test_expect_success 'setup cvsroot' 'cvs init' + +test_expect_success 'setup a cvs module' ' + + mkdir $CVSROOT/module && + cvs co -d module-cvs module && + cd module-cvs && + cat <<EOF >o_fortuna && +O Fortuna +velut luna +statu variabilis, + +semper crescis +aut decrescis; +vita detestabilis + +nunc obdurat +et tunc curat +ludo mentis aciem, + +egestatem, +potestatem +dissolvit ut glaciem. +EOF + cvs add o_fortuna && + cat <<EOF >message && +add "O Fortuna" lyrics + +These public domain lyrics make an excellent sample text. +EOF + cvs commit -F message && + cd .. +' + +test_expect_success 'import a trivial module' ' + + git cvsimport -a -z 0 -C module-git module && + git diff module-cvs/o_fortuna module-git/o_fortuna + +' + +test_expect_success 'pack refs' 'cd module-git && git gc && cd ..' + +test_expect_success 'update cvs module' ' + + cd module-cvs && + cat <<EOF >o_fortuna && +O Fortune, +like the moon +you are changeable, + +ever waxing +and waning; +hateful life + +first oppresses +and then soothes +as fancy takes it; + +poverty +and power +it melts them like ice. +EOF + cat <<EOF >message && +translate to English + +My Latin is terrible. +EOF + cvs commit -F message && + cd .. +' + +test_expect_success 'update git module' ' + + cd module-git && + git cvsimport -a -z 0 module && + git merge origin && + cd .. && + git diff module-cvs/o_fortuna module-git/o_fortuna + +' + +test_expect_success 'update cvs module' ' + + cd module-cvs && + echo 1 >tick && + cvs add tick && + cvs commit -m 1 + cd .. + +' + +test_expect_success 'cvsimport.module config works' ' + + cd module-git && + git config cvsimport.module module && + git cvsimport -a -z0 && + git merge origin && + cd .. && + git diff module-cvs/tick module-git/tick + +' + +test_expect_success 'import from a CVS working tree' ' + + cvs co -d import-from-wt module && + cd import-from-wt && + git cvsimport -a -z0 && + echo 1 >expect && + git log -1 --pretty=format:%s%n >actual && + git diff actual expect && + cd .. + +' + +test_done diff --git a/templates/Makefile b/templates/Makefile index 6f4dbd362f..ebd3a62fd8 100644 --- a/templates/Makefile +++ b/templates/Makefile @@ -46,6 +46,6 @@ clean: $(RM) -r blt boilerplates.made install: all - $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(template_dir_SQ)' + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(template_dir_SQ)' (cd blt && $(TAR) cf - .) | \ (cd '$(DESTDIR_SQ)$(template_dir_SQ)' && $(TAR) xf -) diff --git a/templates/hooks--pre-commit b/templates/hooks--pre-commit index a19279b3e4..7092bae263 100644 --- a/templates/hooks--pre-commit +++ b/templates/hooks--pre-commit @@ -13,7 +13,7 @@ if git-rev-parse --verify HEAD 2>/dev/null then - git-diff-index -p -M --cached HEAD + git-diff-index -p -M --cached HEAD -- else # NEEDSWORK: we should produce a diff with an empty tree here # if we want to do the same verification for the initial import. @@ -37,7 +37,7 @@ static int get_trace_fd(int *need_close) return STDERR_FILENO; if (strlen(trace) == 1 && isdigit(*trace)) return atoi(trace); - if (*trace == '/') { + if (is_absolute_path(trace)) { int fd = open(trace, O_WRONLY | O_APPEND | O_CREAT, 0666); if (fd == -1) { fprintf(stderr, @@ -93,7 +93,7 @@ void trace_printf(const char *fmt, ...) close(fd); } -void trace_argv_printf(const char **argv, int count, const char *fmt, ...) +void trace_argv_printf(const char **argv, const char *fmt, ...) { struct strbuf buf; va_list ap; @@ -117,7 +117,7 @@ void trace_argv_printf(const char **argv, int count, const char *fmt, ...) } strbuf_setlen(&buf, len); - sq_quote_argv(&buf, argv, count, 0); + sq_quote_argv(&buf, argv, 0); strbuf_addch(&buf, '\n'); write_or_whine_pipe(fd, buf.buf, buf.len, err_msg); strbuf_release(&buf); diff --git a/transport.c b/transport.c index 43b9e7c410..3eb93b4875 100644 --- a/transport.c +++ b/transport.c @@ -6,6 +6,7 @@ #endif #include "pkt-line.h" #include "fetch-pack.h" +#include "send-pack.h" #include "walker.h" #include "bundle.h" #include "dir.h" @@ -141,7 +142,7 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list) } } -static struct ref *get_refs_via_rsync(const struct transport *transport) +static struct ref *get_refs_via_rsync(struct transport *transport) { struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT; struct ref dummy, *tail = &dummy; @@ -283,6 +284,9 @@ static int rsync_transport_push(struct transport *transport, struct child_process rsync; const char *args[10]; + if (flags & TRANSPORT_PUSH_MIRROR) + return error("rsync transport does not support mirror mode"); + /* first push the objects */ strbuf_addstr(&buf, transport->url); @@ -388,6 +392,9 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons int argc; int err; + if (flags & TRANSPORT_PUSH_MIRROR) + return error("http transport does not support mirror mode"); + argv = xmalloc((refspec_nr + 12) * sizeof(char *)); argv[0] = "http-push"; argc = 1; @@ -432,7 +439,7 @@ static int missing__target(int code, int result) #define missing_target(a) missing__target((a)->http_code, (a)->curl_result) -static struct ref *get_refs_via_curl(const struct transport *transport) +static struct ref *get_refs_via_curl(struct transport *transport) { struct buffer buffer; char *data, *start, *mid; @@ -463,6 +470,10 @@ static struct ref *get_refs_via_curl(const struct transport *transport) curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_URL, refs_url); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); + if (transport->remote->http_proxy) + curl_easy_setopt(slot->curl, CURLOPT_PROXY, + transport->remote->http_proxy); + if (start_active_slot(slot)) { run_active_slot(slot); if (results.curl_result != CURLE_OK) { @@ -529,7 +540,7 @@ struct bundle_transport_data { struct bundle_header header; }; -static struct ref *get_refs_from_bundle(const struct transport *transport) +static struct ref *get_refs_from_bundle(struct transport *transport) { struct bundle_transport_data *data = transport->data; struct ref *result = NULL; @@ -601,7 +612,7 @@ static int set_git_option(struct transport *connection, return 1; } -static struct ref *get_refs_via_connect(const struct transport *transport) +static struct ref *get_refs_via_connect(struct transport *transport) { struct git_transport_data *data = transport->data; struct ref *refs; @@ -654,50 +665,17 @@ static int fetch_refs_via_pack(struct transport *transport, static int git_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) { struct git_transport_data *data = transport->data; - const char **argv; - char *rem; - int argc; - int err; + struct send_pack_args args; - argv = xmalloc((refspec_nr + 12) * sizeof(char *)); - argv[0] = "send-pack"; - argc = 1; - if (flags & TRANSPORT_PUSH_ALL) - argv[argc++] = "--all"; - if (flags & TRANSPORT_PUSH_FORCE) - argv[argc++] = "--force"; - if (flags & TRANSPORT_PUSH_DRY_RUN) - argv[argc++] = "--dry-run"; - if (flags & TRANSPORT_PUSH_VERBOSE) - argv[argc++] = "--verbose"; - if (data->receivepack) { - char *rp = xmalloc(strlen(data->receivepack) + 16); - sprintf(rp, "--receive-pack=%s", data->receivepack); - argv[argc++] = rp; - } - if (data->thin) - argv[argc++] = "--thin"; - rem = xmalloc(strlen(transport->remote->name) + 10); - sprintf(rem, "--remote=%s", transport->remote->name); - argv[argc++] = rem; - argv[argc++] = transport->url; - while (refspec_nr--) - argv[argc++] = *refspec++; - argv[argc] = NULL; - err = run_command_v_opt(argv, RUN_GIT_CMD); - switch (err) { - case -ERR_RUN_COMMAND_FORK: - error("unable to fork for %s", argv[0]); - case -ERR_RUN_COMMAND_EXEC: - error("unable to exec %s", argv[0]); - break; - case -ERR_RUN_COMMAND_WAITPID: - case -ERR_RUN_COMMAND_WAITPID_WRONG_PID: - case -ERR_RUN_COMMAND_WAITPID_SIGNAL: - case -ERR_RUN_COMMAND_WAITPID_NOEXIT: - error("%s died with strange error", argv[0]); - } - return !!err; + args.receivepack = data->receivepack; + args.send_all = !!(flags & TRANSPORT_PUSH_ALL); + args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR); + args.force_update = !!(flags & TRANSPORT_PUSH_FORCE); + args.use_thin_pack = data->thin; + args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE); + args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN); + + return send_pack(&args, transport->url, transport->remote, refspec_nr, refspec); } static int disconnect_git(struct transport *transport) @@ -789,7 +767,7 @@ int transport_push(struct transport *transport, return transport->push(transport, refspec_nr, refspec, flags); } -struct ref *transport_get_remote_refs(struct transport *transport) +const struct ref *transport_get_remote_refs(struct transport *transport) { if (!transport->remote_refs) transport->remote_refs = transport->get_refs_list(transport); diff --git a/transport.h b/transport.h index 2f80ab4b03..6fb4526cda 100644 --- a/transport.h +++ b/transport.h @@ -8,7 +8,7 @@ struct transport { struct remote *remote; const char *url; void *data; - struct ref *remote_refs; + const struct ref *remote_refs; /** * Returns 0 if successful, positive if the option is not @@ -18,7 +18,7 @@ struct transport { int (*set_option)(struct transport *connection, const char *name, const char *value); - struct ref *(*get_refs_list)(const struct transport *transport); + struct ref *(*get_refs_list)(struct transport *transport); int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs); int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags); @@ -30,7 +30,8 @@ struct transport { #define TRANSPORT_PUSH_ALL 1 #define TRANSPORT_PUSH_FORCE 2 #define TRANSPORT_PUSH_DRY_RUN 4 -#define TRANSPORT_PUSH_VERBOSE 8 +#define TRANSPORT_PUSH_MIRROR 8 +#define TRANSPORT_PUSH_VERBOSE 16 /* Returns a transport suitable for the url */ struct transport *transport_get(struct remote *, const char *); @@ -62,7 +63,7 @@ int transport_set_option(struct transport *transport, const char *name, int transport_push(struct transport *connection, int refspec_nr, const char **refspec, int flags); -struct ref *transport_get_remote_refs(struct transport *transport); +const struct ref *transport_get_remote_refs(struct transport *transport); int transport_fetch_refs(struct transport *transport, struct ref *refs); void transport_unlock_pack(struct transport *transport); diff --git a/tree-walk.h b/tree-walk.h index 903a7b0f48..db0fbdc701 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -7,13 +7,6 @@ struct name_entry { unsigned int mode; }; -static inline enum object_type object_type(unsigned int mode) -{ - return S_ISDIR(mode) ? OBJ_TREE : - S_ISGITLINK(mode) ? OBJ_COMMIT : - OBJ_BLOB; -} - struct tree_desc { const void *buffer; struct name_entry entry; diff --git a/unpack-trees.c b/unpack-trees.c index aea16adde8..e9eb795d64 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -71,12 +71,8 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, int remove; int baselen = strlen(base); int src_size = len + 1; - int i_stk = i_stk; int retval = 0; - if (o->dir) - i_stk = push_exclude_per_directory(o->dir, base, strlen(base)); - do { int i; const char *first; @@ -255,8 +251,6 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, } while (1); leave_directory: - if (o->dir) - pop_exclude_per_directory(o->dir, i_stk); return retval; } diff --git a/wt-status.c b/wt-status.c index 52ab41ceb6..d35386dae1 100644 --- a/wt-status.c +++ b/wt-status.c @@ -251,6 +251,7 @@ static void wt_status_print_updated(struct wt_status *s) rev.diffopt.format_callback_data = s; rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = 100; + rev.diffopt.break_opt = 0; wt_read_cache(s); run_diff_index(&rev, 1); } |