diff options
375 files changed, 19851 insertions, 2926 deletions
diff --git a/.gitignore b/.gitignore index 10808e3a73..ac02a580da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,180 +1,195 @@ -GIT-BUILD-OPTIONS -GIT-CFLAGS -GIT-GUI-VARS -GIT-VERSION-FILE -git -git-add -git-add--interactive -git-am -git-annotate -git-apply -git-archimport -git-archive -git-bisect -git-bisect--helper -git-blame -git-branch -git-bundle -git-cat-file -git-check-attr -git-check-ref-format -git-checkout -git-checkout-index -git-cherry -git-cherry-pick -git-clean -git-clone -git-commit -git-commit-tree -git-config -git-count-objects -git-cvsexportcommit -git-cvsimport -git-cvsserver -git-daemon -git-diff -git-diff-files -git-diff-index -git-diff-tree -git-difftool -git-difftool--helper -git-describe -git-fast-export -git-fast-import -git-fetch -git-fetch--tool -git-fetch-pack -git-filter-branch -git-fmt-merge-msg -git-for-each-ref -git-format-patch -git-fsck -git-fsck-objects -git-gc -git-get-tar-commit-id -git-grep -git-hash-object -git-help -git-http-fetch -git-http-push -git-imap-send -git-index-pack -git-init -git-init-db -git-instaweb -git-log -git-lost-found -git-ls-files -git-ls-remote -git-ls-tree -git-mailinfo -git-mailsplit -git-merge -git-merge-base -git-merge-index -git-merge-file -git-merge-tree -git-merge-octopus -git-merge-one-file -git-merge-ours -git-merge-recursive -git-merge-resolve -git-merge-subtree -git-mergetool -git-mergetool--lib -git-mktag -git-mktree -git-name-rev -git-mv -git-pack-redundant -git-pack-objects -git-pack-refs -git-parse-remote -git-patch-id -git-peek-remote -git-prune -git-prune-packed -git-pull -git-push -git-quiltimport -git-read-tree -git-rebase -git-rebase--interactive -git-receive-pack -git-reflog -git-relink -git-remote -git-repack -git-replace -git-repo-config -git-request-pull -git-rerere -git-reset -git-rev-list -git-rev-parse -git-revert -git-rm -git-send-email -git-send-pack -git-sh-setup -git-shell -git-shortlog -git-show -git-show-branch -git-show-index -git-show-ref -git-stage -git-stash -git-status -git-stripspace -git-submodule -git-svn -git-symbolic-ref -git-tag -git-tar-tree -git-unpack-file -git-unpack-objects -git-update-index -git-update-ref -git-update-server-info -git-upload-archive -git-upload-pack -git-var -git-verify-pack -git-verify-tag -git-web--browse -git-whatchanged -git-write-tree -git-core-*/?* -gitk-wish -gitweb/gitweb.cgi -test-chmtime -test-ctype -test-date -test-delta -test-dump-cache-tree -test-genrandom -test-match-trees -test-parse-options -test-path-utils -test-sha1 -test-sigchain -common-cmds.h +/GIT-BUILD-OPTIONS +/GIT-CFLAGS +/GIT-GUI-VARS +/GIT-VERSION-FILE +/git +/git-add +/git-add--interactive +/git-am +/git-annotate +/git-apply +/git-archimport +/git-archive +/git-bisect +/git-bisect--helper +/git-blame +/git-branch +/git-bundle +/git-cat-file +/git-check-attr +/git-check-ref-format +/git-checkout +/git-checkout-index +/git-cherry +/git-cherry-pick +/git-clean +/git-clone +/git-commit +/git-commit-tree +/git-config +/git-count-objects +/git-cvsexportcommit +/git-cvsimport +/git-cvsserver +/git-daemon +/git-diff +/git-diff-files +/git-diff-index +/git-diff-tree +/git-difftool +/git-difftool--helper +/git-describe +/git-fast-export +/git-fast-import +/git-fetch +/git-fetch--tool +/git-fetch-pack +/git-filter-branch +/git-fmt-merge-msg +/git-for-each-ref +/git-format-patch +/git-fsck +/git-fsck-objects +/git-gc +/git-get-tar-commit-id +/git-grep +/git-hash-object +/git-help +/git-http-backend +/git-http-fetch +/git-http-push +/git-imap-send +/git-index-pack +/git-init +/git-init-db +/git-instaweb +/git-log +/git-lost-found +/git-ls-files +/git-ls-remote +/git-ls-tree +/git-mailinfo +/git-mailsplit +/git-merge +/git-merge-base +/git-merge-index +/git-merge-file +/git-merge-tree +/git-merge-octopus +/git-merge-one-file +/git-merge-ours +/git-merge-recursive +/git-merge-resolve +/git-merge-subtree +/git-mergetool +/git-mergetool--lib +/git-mktag +/git-mktree +/git-name-rev +/git-mv +/git-notes +/git-pack-redundant +/git-pack-objects +/git-pack-refs +/git-parse-remote +/git-patch-id +/git-peek-remote +/git-prune +/git-prune-packed +/git-pull +/git-push +/git-quiltimport +/git-read-tree +/git-rebase +/git-rebase--interactive +/git-receive-pack +/git-reflog +/git-relink +/git-remote +/git-remote-curl +/git-repack +/git-replace +/git-repo-config +/git-request-pull +/git-rerere +/git-reset +/git-rev-list +/git-rev-parse +/git-revert +/git-rm +/git-send-email +/git-send-pack +/git-sh-setup +/git-shell +/git-shortlog +/git-show +/git-show-branch +/git-show-index +/git-show-ref +/git-stage +/git-stash +/git-status +/git-stripspace +/git-submodule +/git-svn +/git-symbolic-ref +/git-tag +/git-tar-tree +/git-unpack-file +/git-unpack-objects +/git-update-index +/git-update-ref +/git-update-server-info +/git-upload-archive +/git-upload-pack +/git-var +/git-verify-pack +/git-verify-tag +/git-web--browse +/git-whatchanged +/git-write-tree +/git-core-*/?* +/gitk-git/gitk-wish +/gitweb/gitweb.cgi +/test-chmtime +/test-ctype +/test-date +/test-delta +/test-dump-cache-tree +/test-genrandom +/test-match-trees +/test-parse-options +/test-path-utils +/test-sha1 +/test-sigchain +/common-cmds.h *.tar.gz *.dsc *.deb -git.spec +/git.spec *.exe *.[aos] *.py[co] -config.mak -autom4te.cache -config.cache -config.log -config.status -config.mak.autogen -config.mak.append -configure -tags -TAGS -cscope* +*+ +/config.mak +/autom4te.cache +/config.cache +/config.log +/config.status +/config.mak.autogen +/config.mak.append +/configure +/tags +/TAGS +/cscope* +*.obj +*.lib +*.sln +*.suo +*.ncb +*.vcproj +*.user +*.idb +*.pdb +/Debug/ +/Release/ @@ -41,6 +41,7 @@ Michele Ballabio <barra_cuda@katamail.com> Nanako Shiraishi <nanako3@bluebottle.com> Nanako Shiraishi <nanako3@lavabit.com> Nguyễn Thái Ngọc Duy <pclouds@gmail.com> +<nico@fluxnic.net> <nico@cam.org> Philippe Bruhat <book@cpan.org> Ramsay Allan Jones <ramsay@ramsay1.demon.co.uk> René Scharfe <rene.scharfe@lsrfire.ath.cx> diff --git a/Documentation/Makefile b/Documentation/Makefile index 06b0c57b95..cd5b4396db 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -103,6 +103,14 @@ ifdef DOCBOOK_SUPPRESS_SP XMLTO_EXTRA += -m manpage-suppress-sp.xsl endif +# If your target system uses GNU groff, it may try to render +# apostrophes as a "pretty" apostrophe using unicode. This breaks +# cut&paste, so you should set GNU_ROFF to force them to be ASCII +# apostrophes. Unfortunately does not work with non-GNU roff. +ifdef GNU_ROFF +XMLTO_EXTRA += -m manpage-quote-apos.xsl +endif + SHELL_PATH ?= $(SHELL) # Shell quote; SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) diff --git a/Documentation/RelNotes-1.6.4.3.txt b/Documentation/RelNotes-1.6.4.3.txt new file mode 100644 index 0000000000..4f29babdeb --- /dev/null +++ b/Documentation/RelNotes-1.6.4.3.txt @@ -0,0 +1,29 @@ +GIT v1.6.4.3 Release Notes +========================== + +Fixes since v1.6.4.2 +-------------------- + +* "git clone" from an empty repository gave unnecessary error message, + even though it did everything else correctly. + +* "git cvsserver" invoked git commands via "git-foo" style, which has long + been deprecated. + +* "git fetch" and "git clone" had an extra sanity check to verify the + presense of the corresponding *.pack file before downloading *.idx + file by issuing a HEAD request. Github server however sometimes + gave 500 (Internal server error) response to HEAD even if a GET + request for *.pack file to the same URL would have succeeded, and broke + clone over HTTP from some of their repositories. As a workaround, this + verification has been removed (as it is not absolutely necessary). + +* "git grep" did not like relative pathname to refer outside the current + directory when run from a subdirectory. + +* an error message from "git push" was formatted in a very ugly way. + +* "git svn" did not quote the subversion user name correctly when + running its author-prog helper program. + +Other minor documentation updates are included. diff --git a/Documentation/RelNotes-1.6.4.4.txt b/Documentation/RelNotes-1.6.4.4.txt new file mode 100644 index 0000000000..0ead45fc72 --- /dev/null +++ b/Documentation/RelNotes-1.6.4.4.txt @@ -0,0 +1,26 @@ +GIT v1.6.4.4 Release Notes +========================== + +Fixes since v1.6.4.4 +-------------------- + +* The workaround for Github server that sometimes gave 500 (Internal server + error) response to HEAD requests in 1.6.4.3 introduced a regression that + caused re-fetching projects over http to segfault in certain cases due + to uninitialized pointer being freed. + +* "git pull" on an unborn branch used to consider anything in the work + tree and the index discardable. + +* "git diff -b/w" did not work well on the incomplete line at the end of + the file, due to an incorrect hashing of lines in the low-level xdiff + routines. + +* "git checkout-index --prefix=$somewhere" used to work when $somewhere is + a symbolic link to a directory elsewhere, but v1.6.4.2 broke it. + +* "git unpack-objects --strict", invoked when receive.fsckobjects + configuration is set in the receiving repository of "git push", did not + properly check the objects, especially the submodule links, it received. + +Other minor documentation updates are included. diff --git a/Documentation/RelNotes-1.6.5.1.txt b/Documentation/RelNotes-1.6.5.1.txt new file mode 100644 index 0000000000..309ba181b2 --- /dev/null +++ b/Documentation/RelNotes-1.6.5.1.txt @@ -0,0 +1,20 @@ +GIT v1.6.5.1 Release Notes +========================== + +Fixes since v1.6.5 +------------------ + + * An corrupt pack could make codepath to read objects into an + infinite loop. + + * Download throughput display was always shown in KiB/s but on fast links + it is more appropriate to show it in MiB/s. + + * "git grep -f filename" used uninitialized variable and segfaulted. + + * "git clone -b branch" gave a wrong commit object name to post-checkout + hook. + + * "git pull" over http did not work on msys. + +Other minor documentation updates are included. diff --git a/Documentation/RelNotes-1.6.5.2.txt b/Documentation/RelNotes-1.6.5.2.txt new file mode 100644 index 0000000000..aa7ccce3a2 --- /dev/null +++ b/Documentation/RelNotes-1.6.5.2.txt @@ -0,0 +1,19 @@ +GIT v1.6.5.2 Release Notes +========================== + +Fixes since v1.6.5.1 +-------------------- + + * Installation of templates triggered a bug in busybox when using tar + implementation from it. + + * "git add -i" incorrectly ignored paths that are already in the index + if they matched .gitignore patterns. + + * "git describe --always" should have produced some output even there + were no tags in the repository, but it didn't. + + * "git ls-files" when showing tracked files incorrectly paid attention + to the exclude patterns. + +Other minor documentation updates are included. diff --git a/Documentation/RelNotes-1.6.5.3.txt b/Documentation/RelNotes-1.6.5.3.txt new file mode 100644 index 0000000000..b2fad1b22e --- /dev/null +++ b/Documentation/RelNotes-1.6.5.3.txt @@ -0,0 +1,63 @@ +Git v1.6.5.3 Release Notes +========================== + +Fixes since v1.6.5.2 +-------------------- + + * info/grafts file didn't ignore trailing CR at the end of lines. + + * Packages generated on newer FC were unreadable by older versions of + RPM as the new default is to use stronger hash. + + * output from "git blame" was unreadable when the file ended in an + incomplete line. + + * "git add -i/-p" didn't handle deletion of empty files correctly. + + * "git clone" takes up to two parameters, but did not complain when + given more arguments than necessary and silently ignored them. + + * "git cvsimport" did not read files given as command line arguments + correctly when it is run from a subdirectory. + + * "git diff --color-words -U0" didn't work correctly. + + * The handling of blank lines at the end of file by "git diff/apply + --whitespace" was inconsistent with the other kinds of errors. + They are now colored, warned against, and fixed the same way as others. + + * There was no way to allow blank lines at the end of file without + allowing extra blanks at the end of lines. You can use blank-at-eof + and blank-at-eol whitespace error class to specify them separately. + The old trailing-space error class is now a short-hand to set both. + + * "-p" option to "git format-patch" was supposed to suppress diffstat + generation, but it was broken since 1.6.1. + + * "git imap-send" did not compile cleanly with newer OpenSSL. + + * "git help -a" outside of a git repository was broken. + + * "git ls-files -i" was supposed to be inverse of "git ls-files" without -i + with respect to exclude patterns, but it was broken since 1.6.5.2. + + * "git ls-remote" outside of a git repository over http was broken. + + * "git rebase -i" gave bogus error message when the command word was + misspelled. + + * "git receive-pack" that is run in response to "git push" did not run + garbage collection nor update-server-info, but in larger hosting sites, + these almost always need to be run. To help site administrators, the + command now runs "gc --auto" and "u-s-i" by setting receive.autogc + and receive.updateserverinfo configuration variables, respectively. + + * Release notes spelled the package name with incorrect capitalization. + + * "gitweb" did not escape non-ascii characters correctly in the URL. + + * "gitweb" showed "patch" link even for merge commits. + + * "gitweb" showed incorrect links for blob line numbers in pathinfo mode. + +Other minor documentation updates are included. diff --git a/Documentation/RelNotes-1.6.5.txt b/Documentation/RelNotes-1.6.5.txt index 84a84519d4..ee141c19ad 100644 --- a/Documentation/RelNotes-1.6.5.txt +++ b/Documentation/RelNotes-1.6.5.txt @@ -1,8 +1,9 @@ GIT v1.6.5 Release Notes ======================== -In git 1.7.0, which is planned to be the release after 1.6.5, "git push" -into a branch that is currently checked out will be refused by default. +In git 1.7.0, which was planned to be the release after 1.6.5, "git +push" into a branch that is currently checked out will be refused by +default. You can choose what should happen upon such a push by setting the configuration variable receive.denyCurrentBranch in the receiving @@ -31,21 +32,53 @@ Updates since v1.6.4 (subsystems) - * various updates to git-svn and gitweb. + * various updates to gitk, git-svn and gitweb. (portability) * more improvements on mingw port. + * mingw will also give FRSX as the default value for the LESS + environment variable when the user does not have one. + + * initial support to compile git on Windows with MSVC. + (performance) * On major platforms, the system can be compiled to use with Linus's block-sha1 implementation of the SHA-1 hash algorithm, which outperforms the default fallback implementation we borrowed from - Mozzilla. + Mozilla. + + * Unnecessary inefficiency in deepening of a shallow repository has + been removed. + + * "git clone" does not grab objects that it does not need (i.e. + referenced only from refs outside refs/heads and refs/tags + hierarchy) anymore. + + * The "git" main binary used to link with libcurl, which then dragged + in a large number of external libraries. When using basic plumbing + commands in scripts, this unnecessarily slowed things down. We now + implement http/https/ftp transfer as a separate executable as we + used to. + + * "git clone" run locally hardlinks or copies the files in .git/ to + newly created repository. It used to give new mtime to copied files, + but this delayed garbage collection to trigger unnecessarily in the + cloned repository. We now preserve mtime for these files to avoid + this issue. (usability, bells and whistles) + * Human writable date format to various options, e.g. --since=yesterday, + master@{2000.09.17}, are taught to infer some omitted input properly. + + * A few programs gave verbose "advice" messages to help uninitiated + people when issuing error messages. An infrastructure to allow + users to squelch them has been introduced, and a few such messages + can be silenced now. + * refs/replace/ hierarchy is designed to be usable as a replacement of the "grafts" mechanism, with the added advantage that it can be transferred across repositories. @@ -54,15 +87,39 @@ Updates since v1.6.4 * "git am" handles input e-mail files that has CRLF line endings sensibly. + * "git am" learned "--scissors" option to allow you to discard early part + of an incoming e-mail. + + * "git archive -o output.zip" works without being told what format to + use with an explicit "--format=zip".option. + + * "git checkout", "git reset" and "git stash" learned to pick and + choose to use selected changes you made, similar to "git add -p". + + * "git clone" learned a "-b" option to pick a HEAD to check out + different from the remote's default branch. + + * "git clone" learned --recursive option. + + * "git clone" from a local repository on a different filesystem used to + copy individual object files without preserving the old timestamp, giving + them extra lifetime in the new repository until they gc'ed. + * "git commit --dry-run $args" is a new recommended way to ask "what would happen if I try to commit with these arguments." - * "git cvsimport" now supports password-protected pserver access. + * "git commit --dry-run" and "git status" shows conflicted paths in a + separate section to make them easier to spot during a merge. + + * "git cvsimport" now supports password-protected pserver access even + when the password is not taken from ~/.cvspass file. * "git fast-export" learned --no-data option that can be useful when reordering commits and trees without touching the contents of blobs. + * "git fast-import" has a pair of new front-end in contrib/ area. + * "git init" learned to mkdir/chdir into a directory when given an extra argument (i.e. "git init this"). @@ -71,13 +128,19 @@ Updates since v1.6.4 * "git log --decorate" can optionally be told with --decorate=full to give the reference name in full. + * "git merge" issued an unnecessarily scary message when it detected + that the merge may have to touch the path that the user has local + uncommitted changes to. The message has been reworded to make it + clear that the command aborted, without doing any harm. + * "git push" can be told to be --quiet. + * "git push" pays attention to url.$base.pushInsteadOf and uses a URL + that is derived from the URL used for fetching. + * informational output from "git reset" that lists the locally modified paths is made consistent with that of "git checkout $another_branch". - * "git status" gives more descriptive output for unmerged paths. - * "git submodule" learned to give submodule name to scripts run with "foreach" subcommand. @@ -87,23 +150,20 @@ Updates since v1.6.4 tree vs the commit bound at submodule path, instead of comparing the index. + * "git upload-pack", which is the server side support for "git clone" and + "git fetch", can call a new post-upload-pack hook for statistics purposes. + (developers) * With GIT_TEST_OPTS="--root=/p/a/t/h", tests can be run outside the source directory; using tmpfs may give faster turnaround. + * With NO_PERL_MAKEMAKER set, DESTDIR= is now honoured, so you can + build for one location, and install into another location to tar it + up. Fixes since v1.6.4 ------------------ -# All of the fixes in v1.6.4.X maintenance series are included in this -# release, unless otherwise noted. - -# Here are fixes that this release has, but have not been backported to -# v1.6.4.X series. - --- -exec >/var/tmp/1 -O=v1.6.4.1-266-g235db15 -echo O=$(git describe master) -git shortlog --no-merges $O..master --not maint +All of the fixes in v1.6.4.X maintenance series are included in this +release, unless otherwise noted. diff --git a/Documentation/RelNotes-1.6.6.txt b/Documentation/RelNotes-1.6.6.txt new file mode 100644 index 0000000000..2f9c25404e --- /dev/null +++ b/Documentation/RelNotes-1.6.6.txt @@ -0,0 +1,108 @@ +Git v1.6.6 Release Notes +======================== + +In this release, "git fsck" defaults to "git fsck --full" and checks +packfiles, and because of this it will take much longer to complete +than before. If you prefer a quicker check only on loose objects (the +old default), you can say "git fsck --no-full". This has been +supported by 1.5.4 and newer versions of git, so it is safe to write +it in your script even if you use slightly older git on some of your +machines. + +In git 1.7.0, which is planned to be the release after 1.6.6, "git +push" into a branch that is currently checked out will be refused by +default. + +You can choose what should happen upon such a push by setting the +configuration variable receive.denyCurrentBranch in the receiving +repository. + +Also, "git push $there :$killed" to delete the branch $killed in a remote +repository $there, when $killed branch is the current branch pointed at by +its HEAD, will be refused by default. + +You can choose what should happen upon such a push by setting the +configuration variable receive.denyDeleteCurrent in the receiving +repository. + +To ease the transition plan, the receiving repository of such a +push running this release will issue a big warning when the +configuration variable is missing. Please refer to: + + http://git.or.cz/gitwiki/GitFaq#non-bare + http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007 + +for more details on the reason why this change is needed and the +transition plan. + +Updates since v1.6.5 +-------------------- + +(subsystems) + + * various git-gui updates including new translations, wm states, etc. + +(portability) + +(performance) + +(usability, bells and whistles) + + * The object replace mechanism can be bypassed with --no-replace-objects + global option given to the "git" program. + + * "git bisect reset" can reset to an arbitrary commit. + + * "git checkout frotz" when there is no local branch "frotz" but there + is only one remote tracking branch "frotz" is taken as a request to + start the named branch at the corresponding remote tracking branch. + + * "git describe" can be told to add "-dirty" suffix with "--dirty" option. + + * "git diff" learned --submodule option to show a list of one-line logs + instead of differences between the commit object names. + + * "git fsck" by default checks the packfiles (i.e. "--full" is the + default); you can turn it off with "git fsck --no-full". + + * import-tars contributed fast-import frontend learned more types of + compressed tarballs. + + * "git instaweb" knows how to talk with mod_cgid to apache2. + + * "git log --decorate" shows the location of HEAD as well. + + * "--pretty=format" option to "log" family of commands learned: + + . to wrap text with the "%w()" specifier. + . to show reflog information with "%g[sdD]" specifier. + + * "git merge" (and "git pull") learned --ff-only option to make it fail + if the merge does not result in a fast-forward. + + * "git mergetool" learned to use p4merge. + + * "git rebase -i" learned "reword" that acts like "edit" but immediately + starts an editor to tweak the log message without returning control to + the shell, which is done by "edit" to give an opportunity to tweak the + contents. + + * "git svn" learned to read SVN 1.5+ and SVK merge tickets. + + * Author names shown in gitweb output are links to search commits by the + author. + + +(developers) + +Fixes since v1.6.5 +------------------ + +All of the fixes in v1.6.5.X maintenance series are included in this +release, unless otherwise noted. + +--- +exec >/var/tmp/1 +echo O=$(git describe master) +O=v1.6.5.3-152-g122d0f6 +git shortlog --no-merges $O..master --not maint diff --git a/Documentation/config.txt b/Documentation/config.txt index 5256c7fb81..78ee906319 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -113,6 +113,21 @@ For command-specific variables, you will find a more detailed description in the appropriate manual page. You will find a description of non-core porcelain configuration variables in the respective porcelain documentation. +advice.*:: + When set to 'true', display the given optional help message. + When set to 'false', do not display. The configuration variables + are: ++ +-- + pushNonFastForward:: + Advice shown when linkgit:git-push[1] refuses + non-fast-forward refs. Default: true. + statusHints:: + Directions on how to stage/unstage/add shown in the + output of linkgit:git-status[1] and the template shown + when writing commit messages. Default: true. +-- + core.fileMode:: If false, the executable bit differences between the index and the working copy are ignored; useful on broken filesystems like FAT. @@ -372,9 +387,7 @@ core.editor:: Commands such as `commit` and `tag` that lets you edit messages by launching an editor uses the value of this variable when it is set, and the environment variable - `GIT_EDITOR` is not set. The order of preference is - `GIT_EDITOR` environment, `core.editor`, `VISUAL` and - `EDITOR` environment variables and then finally `vi`. + `GIT_EDITOR` is not set. See linkgit:git-var[1]. core.pager:: The command that git will use to paginate output. Can @@ -401,13 +414,17 @@ core.whitespace:: consider them as errors. You can prefix `-` to disable any of them (e.g. `-trailing-space`): + -* `trailing-space` treats trailing whitespaces at the end of the line +* `blank-at-eol` treats trailing whitespaces at the end of the line as an error (enabled by default). * `space-before-tab` treats a space character that appears immediately before a tab character in the initial indent part of the line as an error (enabled by default). * `indent-with-non-tab` treats a line that is indented with 8 or more space characters as an error (not enabled by default). +* `blank-at-eof` treats blank lines added at the end of file as an error + (enabled by default). +* `trailing-space` is a short-hand to cover both `blank-at-eol` and + `blank-at-eof`. * `cr-at-eol` treats a carriage-return at the end of line as part of the line terminator, i.e. with it, `trailing-space` does not trigger if the character before such a carriage-return @@ -439,6 +456,19 @@ On some file system/operating system combinations, this is unreliable. Set this config setting to 'rename' there; However, This will remove the check that makes sure that existing object files will not get overwritten. +core.notesRef:: + When showing commit messages, also show notes which are stored in + the given ref. This ref is expected to contain files named + after the full SHA-1 of the commit they annotate. ++ +If such a file exists in the given ref, the referenced blob is read, and +appended to the commit message, separated by a "Notes:" line. If the +given ref itself does not exist, it is not an error, but means that no +notes should be printed. ++ +This setting defaults to "refs/notes/commits", and can be overridden by +the `GIT_NOTES_REF` environment variable. + add.ignore-errors:: Tells 'git-add' to continue adding files when some files cannot be added due to indexing errors. Equivalent to the '--ignore-errors' @@ -524,7 +554,7 @@ branch.<name>.merge:: branch.<name>.mergeoptions:: Sets default options for merging into branch <name>. The syntax and - supported options are equal to that of linkgit:git-merge[1], but + supported options are the same as those of linkgit:git-merge[1], but option values containing whitespace characters are currently not supported. @@ -1074,6 +1104,14 @@ http.maxRequests:: How many HTTP requests to launch in parallel. Can be overridden by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5. +http.postBuffer:: + Maximum size in bytes of the buffer used by smart HTTP + transports when POSTing data to the remote system. + For requests larger than this buffer size, HTTP/1.1 and + Transfer-Encoding: chunked is used to avoid creating a + massive pack file locally. Default is 1 MiB, which is + sufficient for most requests. + http.lowSpeedLimit, http.lowSpeedTime:: If the HTTP transfer speed is less than 'http.lowSpeedLimit' for longer than 'http.lowSpeedTime' seconds, the transfer is aborted. @@ -1305,6 +1343,11 @@ rebase.stat:: Whether to show a diffstat of what changed upstream since the last rebase. False by default. +receive.autogc:: + By default, git-receive-pack will run "git-gc --auto" after + receiving data from git-push and updating refs. You can stop + it by setting this variable to false. + receive.fsckObjects:: If it is set to true, git-receive-pack will check all received objects. It will abort in the case of a malformed object or a @@ -1336,10 +1379,14 @@ receive.denyCurrentBranch:: receive.denyNonFastForwards:: If set to true, git-receive-pack will deny a ref update which is - not a fast forward. Use this to prevent such an update via a push, + not a fast-forward. Use this to prevent such an update via a push, even if that push is forced. This configuration variable is set when initializing a shared repository. +receive.updateserverinfo:: + If set to true, git-receive-pack will run git-update-server-info + after receiving data from git-push and updating refs. + remote.<name>.url:: The URL of a remote repository. See linkgit:git-fetch[1] or linkgit:git-push[1]. @@ -1500,6 +1547,19 @@ url.<base>.insteadOf:: never-before-seen repository on the site. When more than one insteadOf strings match a given URL, the longest match is used. +url.<base>.pushInsteadOf:: + Any URL that starts with this value will not be pushed to; + instead, it will be rewritten to start with <base>, and the + resulting URL will be pushed to. In cases where some site serves + a large number of repositories, and serves them with multiple + access methods, some of which do not allow push, this feature + allows people to specify a pull-only URL and have git + automatically use an appropriate URL to push, even for a + never-before-seen repository on the site. When more than one + pushInsteadOf strings match a given URL, the longest match is + used. If a remote has an explicit pushurl, git will ignore this + setting for that remote. + user.email:: Your email address to be recorded in any newly created commits. Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 9276faeb11..2b37193a37 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -14,7 +14,8 @@ endif::git-format-patch[] ifdef::git-format-patch[] -p:: - Generate patches without diffstat. +--no-stat:: + Generate plain patches without any diffstats. endif::git-format-patch[] ifndef::git-format-patch[] @@ -27,33 +28,40 @@ endif::git-format-patch[] -U<n>:: --unified=<n>:: Generate diffs with <n> lines of context instead of - the usual three. Implies "-p". + the usual three. +ifndef::git-format-patch[] + Implies `-p`. +endif::git-format-patch[] +ifndef::git-format-patch[] --raw:: Generate the raw format. {git-diff-core? This is the default.} +endif::git-format-patch[] +ifndef::git-format-patch[] --patch-with-raw:: - Synonym for "-p --raw". + Synonym for `-p --raw`. +endif::git-format-patch[] --patience:: Generate a diff using the "patience diff" algorithm. --stat[=width[,name-width]]:: Generate a diffstat. You can override the default - output width for 80-column terminal by "--stat=width". + output width for 80-column terminal by `--stat=width`. The width of the filename part can be controlled by giving another width to it separated by a comma. --numstat:: - Similar to \--stat, but shows number of added and + Similar to `\--stat`, but shows number of added and deleted lines in decimal notation and pathname without abbreviation, to make it more machine friendly. For binary files, outputs two `-` instead of saying `0 0`. --shortstat:: - Output only the last line of the --stat format containing total + Output only the last line of the `--stat` format containing total number of modified files, as well as number of added and deleted lines. @@ -61,24 +69,26 @@ endif::git-format-patch[] Output the distribution of relative amount of changes (number of lines added or removed) for each sub-directory. Directories with changes below a cut-off percent (3% by default) are not shown. The cut-off percent - can be set with "--dirstat=limit". Changes in a child directory is not - counted for the parent directory, unless "--cumulative" is used. + can be set with `--dirstat=limit`. Changes in a child directory is not + counted for the parent directory, unless `--cumulative` is used. --dirstat-by-file[=limit]:: - Same as --dirstat, but counts changed files instead of lines. + Same as `--dirstat`, but counts changed files instead of lines. --summary:: Output a condensed summary of extended header information such as creations, renames and mode changes. +ifndef::git-format-patch[] --patch-with-stat:: - Synonym for "-p --stat". - {git-format-patch? This is the default.} + Synonym for `-p --stat`. +endif::git-format-patch[] +ifndef::git-format-patch[] -z:: - NUL-line termination on output. This affects the --raw + NUL-line termination on output. This affects the `--raw` output field terminator. Also output from commands such - as "git-log" will be delimited with NUL between commits. + as `git-log` will be delimited with NUL between commits. --name-only:: Show only names of changed files. @@ -87,6 +97,13 @@ endif::git-format-patch[] Show only names and status of changed files. See the description of the `--diff-filter` option on what the status letters mean. +--submodule[=<format>]:: + Chose the output format for submodule differences. <format> can be one of + 'short' and 'log'. 'short' just shows pairs of commit names, this format + is used when this option is not given. 'log' is the default value for this + option and lists the commits in that commit range like the 'summary' + option of linkgit:git-submodule[1] does. + --color:: Show colored diff. @@ -110,16 +127,19 @@ The regex can also be set via a diff driver or configuration option, see linkgit:gitattributes[1] or linkgit:git-config[1]. Giving it explicitly overrides any diff driver or configuration setting. Diff drivers override configuration settings. +endif::git-format-patch[] --no-renames:: Turn off rename detection, even when the configuration file gives the default to do so. +ifndef::git-format-patch[] --check:: Warn if changes introduce trailing whitespace or an indent that uses a space before a tab. Exits with non-zero status if problems are found. Not compatible with --exit-code. +endif::git-format-patch[] --full-index:: Instead of the first handful of characters, show the full @@ -127,16 +147,16 @@ override configuration settings. line when generating patch format output. --binary:: - In addition to --full-index, output "binary diff" that - can be applied with "git apply". + In addition to `--full-index`, output a binary diff that + can be applied with `git-apply`. --abbrev[=<n>]:: Instead of showing the full 40-byte hexadecimal object name in diff-raw format output and diff-tree header lines, show only a partial prefix. This is - independent of --full-index option above, which controls + independent of the `--full-index` option above, which controls the diff-patch output format. Non default number of - digits can be specified with --abbrev=<n>. + digits can be specified with `--abbrev=<n>`. -B:: Break complete rewrite changes into pairs of delete and create. @@ -147,6 +167,7 @@ override configuration settings. -C:: Detect copies as well as renames. See also `--find-copies-harder`. +ifndef::git-format-patch[] --diff-filter=[ACDMRTUXB*]:: Select only files that are Added (`A`), Copied (`C`), Deleted (`D`), Modified (`M`), Renamed (`R`), have their @@ -158,6 +179,7 @@ override configuration settings. paths are selected if there is any file that matches other criteria in the comparison; if there is no file that matches other criteria, nothing is selected. +endif::git-format-patch[] --find-copies-harder:: For performance reasons, by default, `-C` option finds copies only @@ -169,12 +191,13 @@ override configuration settings. `-C` option has the same effect. -l<num>:: - -M and -C options require O(n^2) processing time where n + The `-M` and `-C` options require O(n^2) processing time where n is the number of potential rename/copy targets. This option prevents rename/copy detection from running if the number of rename/copy targets exceeds the specified number. +ifndef::git-format-patch[] -S<string>:: Look for differences that introduce or remove an instance of <string>. Note that this is different than the string simply @@ -182,18 +205,20 @@ override configuration settings. linkgit:gitdiffcore[7] for more details. --pickaxe-all:: - When -S finds a change, show all the changes in that + When `-S` finds a change, show all the changes in that changeset, not just the files that contain the change in <string>. --pickaxe-regex:: Make the <string> not a plain string but an extended POSIX regex to match. +endif::git-format-patch[] -O<orderfile>:: Output the patch in the order specified in the <orderfile>, which has one shell glob pattern per line. +ifndef::git-format-patch[] -R:: Swap two inputs; that is, show differences from index or on-disk file to tree contents. @@ -205,6 +230,7 @@ override configuration settings. not in a subdirectory (e.g. in a bare repository), you can name which subdirectory to make the output relative to by giving a <path> as an argument. +endif::git-format-patch[] -a:: --text:: @@ -229,13 +255,15 @@ override configuration settings. Show the context between diff hunks, up to the specified number of lines, thereby fusing hunks that are close to each other. +ifndef::git-format-patch[] --exit-code:: Make the program exit with codes similar to diff(1). That is, it exits with 1 if there were differences and 0 means no differences. --quiet:: - Disable all output of the program. Implies --exit-code. + Disable all output of the program. Implies `--exit-code`. +endif::git-format-patch[] --ext-diff:: Allow an external diff helper to be executed. If you set an diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index ea3b1bc19f..28868747da 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -1,23 +1,13 @@ --q:: ---quiet:: - Pass --quiet to git-fetch-pack and silence any other internally - used git commands. - --v:: ---verbose:: - Be verbose. - -a:: --append:: Append ref names and object names of fetched refs to the existing contents of `.git/FETCH_HEAD`. Without this option old data in `.git/FETCH_HEAD` will be overwritten. ---upload-pack <upload-pack>:: - When given, and the repository to fetch from is handled - by 'git-fetch-pack', '--exec=<upload-pack>' is passed to - the command to specify non-default path for the command - run on the other end. +--depth=<depth>:: + Deepen the history of a 'shallow' repository created by + `git clone` with `--depth=<depth>` option (see linkgit:git-clone[1]) + by the specified number of commits. -f:: --force:: @@ -27,6 +17,10 @@ fetches is a descendant of `<lbranch>`. This option overrides that check. +-k:: +--keep:: + Keep downloaded pack. + ifdef::git-pull[] --no-tags:: endif::git-pull[] @@ -47,10 +41,6 @@ endif::git-pull[] flag lets all tags and their associated objects be downloaded. --k:: ---keep:: - Keep downloaded pack. - -u:: --update-head-ok:: By default 'git-fetch' refuses to update the head which @@ -60,7 +50,19 @@ endif::git-pull[] implementing your own Porcelain you are not supposed to use it. ---depth=<depth>:: - Deepen the history of a 'shallow' repository created by - `git clone` with `--depth=<depth>` option (see linkgit:git-clone[1]) - by the specified number of commits. +--upload-pack <upload-pack>:: + When given, and the repository to fetch from is handled + by 'git-fetch-pack', '--exec=<upload-pack>' is passed to + the command to specify non-default path for the command + run on the other end. + +ifndef::git-pull[] +-q:: +--quiet:: + Pass --quiet to git-fetch-pack and silence any other internally + used git commands. + +-v:: +--verbose:: + Be verbose. +endif::git-pull[] diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index e67b7e875e..e93e606f45 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -72,9 +72,14 @@ OPTIONS -p:: --patch:: - Similar to Interactive mode but the initial command loop is - bypassed and the 'patch' subcommand is invoked using each of - the specified filepatterns before exiting. + Interactively choose hunks of patch between the index and the + work tree and add them to the index. This gives the user a chance + to review the difference before adding modified contents to the + index. ++ +This effectively runs `add --interactive`, but bypasses the +initial command menu and directly jumps to the `patch` subcommand. +See ``Interactive mode'' for details. -e, \--edit:: Open the diff vs. the index in an editor and let the user diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index fcacc94650..67ad5da9cc 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -13,7 +13,7 @@ SYNOPSIS [--3way] [--interactive] [--committer-date-is-author-date] [--ignore-date] [--ignore-space-change | --ignore-whitespace] [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>] - [--reject] [-q | --quiet] + [--reject] [-q | --quiet] [--scissors | --no-scissors] [<mbox> | <Maildir>...] 'git am' (--skip | --resolved | --abort) @@ -39,6 +39,14 @@ OPTIONS --keep:: Pass `-k` flag to 'git-mailinfo' (see linkgit:git-mailinfo[1]). +-c:: +--scissors:: + Remove everything in body before a scissors line (see + linkgit:git-mailinfo[1]). + +---no-scissors:: + Ignore scissors lines (see linkgit:git-mailinfo[1]). + -q:: --quiet:: Be quiet. Only print error messages. @@ -128,10 +136,8 @@ the commit, after stripping common prefix "[PATCH <anything>]". The "Subject: " line is supposed to concisely describe what the commit is about in one line of text. -"From: " and "Subject: " lines starting the body (the rest of the -message after the blank line terminating the RFC2822 headers) -override the respective commit author name and title values taken -from the headers. +"From: " and "Subject: " lines starting the body override the respective +commit author name and title values taken from the headers. The commit message is formed by the title taken from the "Subject: ", a blank line and the body of the message up to diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index 92444ddf10..3d1c1e75b7 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git archive' [--format=<fmt>] [--list] [--prefix=<prefix>/] [<extra>] - [--output=<file>] [--worktree-attributes] + [-o | --output=<file>] [--worktree-attributes] [--remote=<repo> [--exec=<git-upload-archive>]] <tree-ish> [path...] @@ -34,8 +34,11 @@ OPTIONS ------- --format=<fmt>:: - Format of the resulting archive: 'tar' or 'zip'. The default - is 'tar'. + Format of the resulting archive: 'tar' or 'zip'. If this option + is not given, and the output file is specified, the format is + inferred from the filename if possible (e.g. writing to "foo.zip" + makes the output to be in the zip format). Otherwise the output + format is `tar`. -l:: --list:: @@ -48,6 +51,7 @@ OPTIONS --prefix=<prefix>/:: Prepend <prefix>/ to each filename in the archive. +-o <file>:: --output=<file>:: Write the archive to <file> instead of stdout. @@ -129,6 +133,12 @@ git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs Put everything in the current head's Documentation/ directory into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'. +git archive -o latest.zip HEAD:: + + Create a Zip archive that contains the contents of the latest + commit on the current branch. Note that the output format is + inferred by the extension of the output file. + SEE ALSO -------- diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index 63e7a42cb3..d2ffae0c10 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -20,7 +20,7 @@ on the subcommand: git bisect bad [<rev>] git bisect good [<rev>...] git bisect skip [(<rev>|<range>)...] - git bisect reset [<branch>] + git bisect reset [<commit>] git bisect visualize git bisect replay <logfile> git bisect log @@ -81,16 +81,27 @@ will have been left with the first bad kernel revision in "refs/bisect/bad". Bisect reset ~~~~~~~~~~~~ -To return to the original head after a bisect session, issue the -following command: +After a bisect session, to clean up the bisection state and return to +the original HEAD, issue the following command: ------------------------------------------------ $ git bisect reset ------------------------------------------------ -This resets the tree to the original branch instead of being on the -bisection commit ("git bisect start" will also do that, as it resets -the bisection state). +By default, this will return your tree to the commit that was checked +out before `git bisect start`. (A new `git bisect start` will also do +that, as it cleans up the old bisection state.) + +With an optional argument, you can return to a different commit +instead: + +------------------------------------------------ +$ git bisect reset <commit> +------------------------------------------------ + +For example, `git bisect reset HEAD` will leave you on the current +bisection commit and avoid switching commits at all, while `git bisect +reset bisect/bad` will check out the first bad revision. Bisect visualize ~~~~~~~~~~~~~~~~ diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index aad71dc59a..0e836809c2 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -30,10 +30,8 @@ commit) will be listed. With `--no-merged` only branches not merged into the named commit will be listed. If the <commit> argument is missing it defaults to 'HEAD' (i.e. the tip of the current branch). -In the command's second form, a new branch named <branchname> will be created. -It will start out with a head equal to the one given as <start-point>. -If no <start-point> is given, the branch will be created with a head -equal to that of the currently checked out branch. +The command's second form creates a new branch head named <branchname> +which points to the current 'HEAD', or <start-point> if given. Note that this will create the new branch, but it will not switch the working tree to it; use "git checkout <newbranch>" to switch to the @@ -134,11 +132,13 @@ start-point is either a local or remote branch. --contains <commit>:: Only list branches which contain the specified commit. ---merged:: - Only list branches which are fully contained by HEAD. +--merged [<commit>]:: + Only list branches whose tips are reachable from the + specified commit (HEAD if not specified). ---no-merged:: - Do not list branches which are fully contained by HEAD. +--no-merged [<commit>]:: + Only list branches whose tips are not reachable from the + specified commit (HEAD if not specified). <branchname>:: The name of the branch to create or delete. @@ -147,9 +147,9 @@ start-point is either a local or remote branch. may restrict the characters allowed in a branch name. <start-point>:: - The new branch will be created with a HEAD equal to this. It may - be given as a branch name, a commit-id, or a tag. If this option - is omitted, the current branch is assumed. + The new branch head will point to this commit. It may be + given as a branch name, a commit-id, or a tag. If this + option is omitted, the current HEAD will be used instead. <oldbranch>:: The name of an existing branch to rename. @@ -214,7 +214,9 @@ SEE ALSO -------- linkgit:git-check-ref-format[1], linkgit:git-fetch[1], -linkgit:git-remote[1]. +linkgit:git-remote[1], +link:user-manual.html#what-is-a-branch[``Understanding history: What is +a branch?''] in the Git User's Manual. Author ------ diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index 0b7982ea76..0aeef24780 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -9,7 +9,8 @@ SYNOPSIS -------- [verse] 'git check-ref-format' <refname> -'git check-ref-format' [--branch] <branchname-shorthand> +'git check-ref-format' --print <refname> +'git check-ref-format' --branch <branchname-shorthand> DESCRIPTION ----------- @@ -63,16 +64,31 @@ reference name expressions (see linkgit:git-rev-parse[1]): . at-open-brace `@{` is used as a notation to access a reflog entry. -With the `--branch` option, it expands a branch name shorthand and -prints the name of the branch the shorthand refers to. +With the `--print` option, if 'refname' is acceptable, it prints the +canonicalized name of a hypothetical reference with that name. That is, +it prints 'refname' with any extra `/` characters removed. -EXAMPLE -------- +With the `--branch` option, it expands the ``previous branch syntax'' +`@{-n}`. For example, `@{-1}` is a way to refer the last branch you +were on. This option should be used by porcelains to accept this +syntax anywhere a branch name is expected, so they can act as if you +typed the branch name. -git check-ref-format --branch @{-1}:: - -Print the name of the previous branch. +EXAMPLES +-------- +* Print the name of the previous branch: ++ +------------ +$ git check-ref-format --branch @{-1} +------------ + +* Determine the reference name to use for a new branch: ++ +------------ +$ ref=$(git check-ref-format --print "refs/heads/$newbranch") || +die "we do not like '$newbranch' as a branch name." +------------ GIT --- diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index b1314b5614..37c1810e3f 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -11,6 +11,7 @@ SYNOPSIS 'git checkout' [-q] [-f] [-m] [<branch>] 'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>] 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>... +'git checkout' --patch [<tree-ish>] [--] [<paths>...] DESCRIPTION ----------- @@ -25,7 +26,7 @@ use the --track or --no-track options, which will be passed to `git branch`. As a convenience, --track without `-b` implies branch creation; see the description of --track below. -When <paths> are given, this command does *not* switch +When <paths> or --patch are given, this command does *not* switch branches. It updates the named paths in the working tree from the index file, or from a named <tree-ish> (most often a commit). In this case, the `-b` and `--track` options are meaningless and giving @@ -115,6 +116,16 @@ the conflicted merge in the specified paths. "merge" (default) and "diff3" (in addition to what is shown by "merge" style, shows the original contents). +-p:: +--patch:: + Interactively select hunks in the difference between the + <tree-ish> (or the index, if unspecified) and the working + tree. The chosen hunks are then applied in reverse to the + working tree (and if a <tree-ish> was specified, the index). ++ +This means that you can use `git checkout -p` to selectively discard +edits from your current working tree. + <branch>:: Branch to checkout; if it refers to a branch (i.e., a name that, when prepended with "refs/heads/", is a valid ref), then that diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 2e0785e1de..7e7d9fcf50 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -19,8 +19,9 @@ DESCRIPTION Clones a repository into a newly created directory, creates remote-tracking branches for each branch in the cloned repository -(visible using `git branch -r`), and creates and checks out an initial -branch equal to the cloned repository's currently active branch. +(visible using `git branch -r`), and creates and checks out an +initial branch that is forked from the cloned repository's +currently active branch. After the clone, a plain `git fetch` without arguments will update all the remote-tracking branches, and a `git pull` without @@ -38,7 +39,7 @@ OPTIONS --local:: -l:: When the repository to clone from is on a local machine, - this flag bypasses normal "git aware" transport + this flag bypasses the normal "git aware" transport mechanism and clones the repository by making a copy of HEAD and everything under objects and refs directories. The files under `.git/objects/` directory are hardlinked @@ -59,7 +60,7 @@ OPTIONS -s:: When the repository to clone is on the local machine, instead of using hard links, automatically setup - .git/objects/info/alternates to share the objects + `.git/objects/info/alternates` to share the objects with the source repository. The resulting repository starts out without any object of its own. + @@ -68,7 +69,7 @@ it unless you understand what it does. If you clone your repository using this option and then delete branches (or use any other git command that makes any existing commit unreferenced) in the source repository, some objects may become unreferenced (or dangling). -These objects may be removed by normal git operations (such as 'git-commit') +These objects may be removed by normal git operations (such as `git commit`) which automatically call `git gc --auto`. (See linkgit:git-gc[1].) If these objects are removed and were referenced by the cloned repository, then the cloned repository will become corrupt. @@ -85,13 +86,13 @@ objects from the source repository into a pack in the cloned repository. --reference <repository>:: If the reference repository is on the local machine, - automatically setup .git/objects/info/alternates to + automatically setup `.git/objects/info/alternates` to obtain objects from the reference repository. Using an already existing repository as an alternate will require fewer objects to be copied from the repository being cloned, reducing network and local storage costs. + -*NOTE*: see NOTE to --shared option. +*NOTE*: see the NOTE for the `--shared` option. --quiet:: -q:: @@ -100,7 +101,7 @@ objects from the source repository into a pack in the cloned repository. --verbose:: -v:: - Display the progressbar, even in case the standard output is not + Display the progress bar, even in case the standard output is not a terminal. --no-checkout:: @@ -120,12 +121,19 @@ objects from the source repository into a pack in the cloned repository. configuration variables are created. --mirror:: - Set up a mirror of the remote repository. This implies --bare. + Set up a mirror of the remote repository. This implies `--bare`. --origin <name>:: -o <name>:: - Instead of using the remote name 'origin' to keep track - of the upstream repository, use <name>. + Instead of using the remote name `origin` to keep track + of the upstream repository, use `<name>`. + +--branch <name>:: +-b <name>:: + Instead of pointing the newly created HEAD to the branch pointed + to by the cloned repository's HEAD, point to `<name>` branch + instead. In a non-bare repository, this is the branch that will + be checked out. --upload-pack <upload-pack>:: -u <upload-pack>:: @@ -150,7 +158,7 @@ objects from the source repository into a pack in the cloned repository. --recursive:: After the clone is created, initialize all submodules within, using their default settings. This is equivalent to running - 'git submodule update --init --recursive' immediately after + `git submodule update --init --recursive` immediately after the clone is finished. This option is ignored if the cloned repository does not have a worktree/checkout (i.e. if any of `--no-checkout`/`-n`, `--bare`, or `--mirror` is given) @@ -163,8 +171,8 @@ objects from the source repository into a pack in the cloned repository. <directory>:: The name of a new directory to clone into. The "humanish" part of the source repository is used if no directory is - explicitly given ("repo" for "/path/to/repo.git" and "foo" - for "host.xz:foo/.git"). Cloning into an existing directory + explicitly given (`repo` for `/path/to/repo.git` and `foo` + for `host.xz:foo/.git`). Cloning into an existing directory is only allowed if the directory is empty. :git-clone: 1 diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 64f94cfe12..3ea80c820f 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run] - [(-c | -C) <commit>] [-F <file> | -m <msg>] [--dry-run] + [(-c | -C) <commit>] [-F <file> | -m <msg>] [--allow-empty] [--no-verify] [-e] [--author=<author>] [--cleanup=<mode>] [--] [[-i | -o ]<file>...] @@ -69,12 +69,6 @@ OPTIONS Like '-C', but with '-c' the editor is invoked, so that the user can further edit the commit message. ---dry-run:: - Do not actually make a commit, but show the list of paths - with updates in the index, paths with changes in the work tree, - and paths that are untracked, similar to the one that is given - in the commit log editor. - -F <file>:: --file=<file>:: Take the commit message from the given file. Use '-' to @@ -329,7 +323,7 @@ ENVIRONMENT AND CONFIGURATION VARIABLES The editor used to edit the commit log message will be chosen from the GIT_EDITOR environment variable, the core.editor configuration variable, the VISUAL environment variable, or the EDITOR environment variable (in that -order). +order). See linkgit:git-var[1] for details. HOOKS ----- diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index b231dbb947..78b9808aa3 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -8,7 +8,9 @@ git-describe - Show the most recent tag that is reachable from a commit SYNOPSIS -------- +[verse] 'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] <committish>... +'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] --dirty[=<mark>] DESCRIPTION ----------- @@ -27,6 +29,11 @@ OPTIONS <committish>...:: Committish object names to describe. +--dirty[=<mark>]:: + Describe the working tree. + It means describe HEAD and appends <mark> (`-dirty` by + default) if the working tree is dirty. + --all:: Instead of using only the annotated tags, use any ref found in `.git/refs/`. This option enables matching @@ -44,7 +51,9 @@ OPTIONS --abbrev=<n>:: Instead of using the default 7 hexadecimal digits as the - abbreviated object name, use <n> digits. + abbreviated object name, use <n> digits, or as many digits + as needed to form a unique object name. An <n> of 0 + will suppress long format, only showing the closest tag. --candidates=<n>:: Instead of considering only the 10 most recent tags as @@ -68,8 +77,8 @@ OPTIONS This is useful when you want to see parts of the commit object name in "describe" output, even when the commit in question happens to be a tagged version. Instead of just emitting the tag name, it will - describe such a commit as v1.2-0-deadbeef (0th commit since tag v1.2 - that points at object deadbeef....). + describe such a commit as v1.2-0-gdeadbee (0th commit since tag v1.2 + that points at object deadbee....). --match <pattern>:: Only consider tags matching the given pattern (can be used to avoid @@ -108,7 +117,7 @@ the output shows the reference path as well: [torvalds@g5 git]$ git describe --all --abbrev=4 v1.0.5^2 tags/v1.0.0-21-g975b - [torvalds@g5 git]$ git describe --all HEAD^ + [torvalds@g5 git]$ git describe --all --abbrev=4 HEAD^ heads/lt/describe-7-g975b With --abbrev set to 0, the command can be used to find the @@ -117,6 +126,13 @@ closest tagname without any suffix: [torvalds@g5 git]$ git describe --abbrev=0 v1.0.5^2 tags/v1.0.0 +Note that the suffix you get if you type these commands today may be +longer than what Linus saw above when he ran these commands, as your +git repository may have new commits whose object names begin with +975b that did not exist back then, and "-g975b" suffix alone may not +be sufficient to disambiguate these commits. + + SEARCH STRATEGY --------------- diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index 96a6c51a4b..8e9aed67d7 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -31,7 +31,7 @@ OPTIONS Use the diff tool specified by <tool>. Valid merge tools are: kdiff3, kompare, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, - ecmerge, diffuse, opendiff and araxis. + ecmerge, diffuse, opendiff, p4merge and araxis. + If a diff tool is not specified, 'git-difftool' will use the configuration variable `diff.tool`. If the diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index c2f483a8d2..288032c7b8 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -316,7 +316,7 @@ change to the project. data ('from' SP <committish> LF)? ('merge' SP <committish> LF)? - (filemodify | filedelete | filecopy | filerename | filedeleteall)* + (filemodify | filedelete | filecopy | filerename | filedeleteall | notemodify)* LF? .... @@ -339,14 +339,13 @@ commit message use a 0 length data. Commit messages are free-form and are not interpreted by Git. Currently they must be encoded in UTF-8, as fast-import does not permit other encodings to be specified. -Zero or more `filemodify`, `filedelete`, `filecopy`, `filerename` -and `filedeleteall` commands +Zero or more `filemodify`, `filedelete`, `filecopy`, `filerename`, +`filedeleteall` and `notemodify` commands may be included to update the contents of the branch prior to creating the commit. These commands may be supplied in any order. However it is recommended that a `filedeleteall` command precede -all `filemodify`, `filecopy` and `filerename` commands in the same -commit, as `filedeleteall` -wipes the branch clean (see below). +all `filemodify`, `filecopy`, `filerename` and `notemodify` commands in +the same commit, as `filedeleteall` wipes the branch clean (see below). The `LF` after the command is optional (it used to be required). @@ -595,6 +594,40 @@ more memory per active branch (less than 1 MiB for even most large projects); so frontends that can easily obtain only the affected paths for a commit are encouraged to do so. +`notemodify` +^^^^^^^^^^^^ +Included in a `commit` command to add a new note (annotating a given +commit) or change the content of an existing note. This command has +two different means of specifying the content of the note. + +External data format:: + The data content for the note was already supplied by a prior + `blob` command. The frontend just needs to connect it to the + commit that is to be annotated. ++ +.... + 'N' SP <dataref> SP <committish> LF +.... ++ +Here `<dataref>` can be either a mark reference (`:<idnum>`) +set by a prior `blob` command, or a full 40-byte SHA-1 of an +existing Git blob object. + +Inline data format:: + The data content for the note has not been supplied yet. + The frontend wants to supply it as part of this modify + command. ++ +.... + 'N' SP 'inline' SP <committish> LF + data +.... ++ +See below for a detailed description of the `data` command. + +In both formats `<committish>` is any of the commit specification +expressions also accepted by `from` (see above). + `mark` ~~~~~~ Arranges for fast-import to save a reference to the current object, allowing diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index d3164c5c88..f2483d624e 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -37,6 +37,35 @@ include::pull-fetch-param.txt[] include::urls-remotes.txt[] + +EXAMPLES +-------- + +* Update the remote-tracking branches: ++ +------------------------------------------------ +$ git fetch origin +------------------------------------------------ ++ +The above command copies all branches from the remote refs/heads/ +namespace and stores them to the local refs/remotes/origin/ namespace, +unless the branch.<name>.fetch option is used to specify a non-default +refspec. + +* Using refspecs explicitly: ++ +------------------------------------------------ +$ git fetch origin +pu:pu maint:tmp +------------------------------------------------ ++ +This updates (or creates, as necessary) branches `pu` and `tmp` in +the local repository by fetching from the branches (respectively) +`pu` and `maint` from the remote repository. ++ +The `pu` branch will be updated even if it is does not fast-forward, +because it is prefixed with a plus sign; `tmp` will not be. + + SEE ALSO -------- linkgit:git-pull[1] diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index 32ea8564a5..394a77a35f 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -12,6 +12,7 @@ SYNOPSIS [--index-filter <command>] [--parent-filter <command>] [--msg-filter <command>] [--commit-filter <command>] [--tag-name-filter <command>] [--subdirectory-filter <directory>] + [--prune-empty] [--original <namespace>] [-d <directory>] [-f | --force] [--] [<rev-list options>...] @@ -158,7 +159,18 @@ to other tags will be rewritten to point to the underlying commit. --subdirectory-filter <directory>:: Only look at the history which touches the given subdirectory. The result will contain that directory (and only that) as its - project root. + project root. Implies --remap-to-ancestor. + +--remap-to-ancestor:: + Rewrite refs to the nearest rewritten ancestor instead of + ignoring them. ++ +Normally, positive refs on the command line are only changed if the +commit they point to was rewritten. However, you can limit the extent +of this rewriting by using linkgit:rev-list[1] arguments, e.g., path +limiters. Refs pointing to such excluded commits would then normally +be ignored. With this option, they are instead rewritten to point at +the nearest ancestor that was not excluded. --prune-empty:: Some kind of filters will generate empty commits, that left the tree diff --git a/Documentation/git-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt index 1c24796d66..a586950b48 100644 --- a/Documentation/git-fmt-merge-msg.txt +++ b/Documentation/git-fmt-merge-msg.txt @@ -18,8 +18,8 @@ Takes the list of merged objects on stdin and produces a suitable commit message to be used for the merge commit, usually to be passed as the '<merge-message>' argument of 'git-merge'. -This script is intended mostly for internal use by scripts -automatically invoking 'git-merge'. +This command is intended mostly for internal use by scripts +automatically invoking 'git merge'. OPTIONS ------- diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 687e667598..f1fd0df08a 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -43,28 +43,28 @@ There are two ways to specify which commits to operate on. The first rule takes precedence in the case of a single <commit>. To apply the second rule, i.e., format everything since the beginning of -history up until <commit>, use the '\--root' option: "git format-patch -\--root <commit>". If you want to format only <commit> itself, you -can do this with "git format-patch -1 <commit>". +history up until <commit>, use the '\--root' option: `git format-patch +\--root <commit>`. If you want to format only <commit> itself, you +can do this with `git format-patch -1 <commit>`. By default, each output file is numbered sequentially from 1, and uses the first line of the commit message (massaged for pathname safety) as -the filename. With the --numbered-files option, the output file names +the filename. With the `--numbered-files` option, the output file names will only be numbers, without the first line of the commit appended. The names of the output files are printed to standard -output, unless the --stdout option is specified. +output, unless the `--stdout` option is specified. -If -o is specified, output files are created in <dir>. Otherwise +If `-o` is specified, output files are created in <dir>. Otherwise they are created in the current working directory. By default, the subject of a single patch is "[PATCH] First Line" and the subject when multiple patches are output is "[PATCH n/m] First -Line". To force 1/1 to be added for a single patch, use -n. To omit -patch numbers from the subject, use -N +Line". To force 1/1 to be added for a single patch, use `-n`. To omit +patch numbers from the subject, use `-N`. -If given --thread, 'git-format-patch' will generate In-Reply-To and -References headers to make the second and subsequent patch mails appear -as replies to the first mail; this also generates a Message-Id header to +If given `--thread`, `git-format-patch` will generate `In-Reply-To` and +`References` headers to make the second and subsequent patch mails appear +as replies to the first mail; this also generates a `Message-Id` header to reference. OPTIONS @@ -112,7 +112,7 @@ include::diff-options.txt[] --attach[=<boundary>]:: Create multipart/mixed attachment, the first part of which is the commit message and the patch itself in the - second part, with "Content-Disposition: attachment". + second part, with `Content-Disposition: attachment`. --no-attach:: Disable the creation of an attachment, overriding the @@ -121,13 +121,13 @@ include::diff-options.txt[] --inline[=<boundary>]:: Create multipart/mixed attachment, the first part of which is the commit message and the patch itself in the - second part, with "Content-Disposition: inline". + second part, with `Content-Disposition: inline`. --thread[=<style>]:: --no-thread:: - Controls addition of In-Reply-To and References headers to + Controls addition of `In-Reply-To` and `References` headers to make the second and subsequent mails appear as replies to the - first. Also controls generation of the Message-Id header to + first. Also controls generation of the `Message-Id` header to reference. + The optional <style> argument can be either `shallow` or `deep`. @@ -136,16 +136,16 @@ series, where the head is chosen from the cover letter, the `\--in-reply-to`, and the first patch mail, in this order. 'deep' threading makes every mail a reply to the previous one. + -The default is --no-thread, unless the 'format.thread' configuration -is set. If --thread is specified without a style, it defaults to the +The default is `--no-thread`, unless the 'format.thread' configuration +is set. If `--thread` is specified without a style, it defaults to the style specified by 'format.thread' if any, or else `shallow`. + Beware that the default for 'git send-email' is to thread emails -itself. If you want 'git format-patch' to take care of hreading, you -will want to ensure that threading is disabled for 'git send-email'. +itself. If you want `git format-patch` to take care of threading, you +will want to ensure that threading is disabled for `git send-email`. --in-reply-to=Message-Id:: - Make the first mail (or all the mails with --no-thread) appear as a + Make the first mail (or all the mails with `--no-thread`) appear as a reply to the given Message-Id, which avoids breaking threads to provide a new patch series. @@ -160,16 +160,16 @@ will want to ensure that threading is disabled for 'git send-email'. Instead of the standard '[PATCH]' prefix in the subject line, instead use '[<Subject-Prefix>]'. This allows for useful naming of a patch series, and can be - combined with the --numbered option. + combined with the `--numbered` option. --cc=<email>:: - Add a "Cc:" header to the email headers. This is in addition + Add a `Cc:` header to the email headers. This is in addition to any configured headers, and may be used multiple times. --add-header=<header>:: Add an arbitrary header to the email headers. This is in addition to any configured headers, and may be used multiple times. - For example, --add-header="Organization: git-foo" + For example, `--add-header="Organization: git-foo"` --cover-letter:: In addition to the patches, generate a cover letter file diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index 287c4fc5e0..6fe9484da3 100644 --- a/Documentation/git-fsck.txt +++ b/Documentation/git-fsck.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs] - [--full] [--strict] [--verbose] [--lost-found] [<object>*] + [--[no-]full] [--strict] [--verbose] [--lost-found] [<object>*] DESCRIPTION ----------- @@ -52,7 +52,8 @@ index file, all SHA1 references in .git/refs/*, and all reflogs (unless or $GIT_DIR/objects/info/alternates, and in packed git archives found in $GIT_DIR/objects/pack and corresponding pack subdirectories in alternate - object pools. + object pools. This is now default; you can turn it off + with --no-full. --strict:: Enable more strict checking, namely to catch a file mode diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index dcac8c8e29..4cd9cdf905 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -106,7 +106,7 @@ much time is spent optimizing the delta compression of the objects in the repository when the --aggressive option is specified. The larger the value, the more time is spent optimizing the delta compression. See the documentation for the --window' option in linkgit:git-repack[1] for -more details. This defaults to 10. +more details. This defaults to 250. The optional configuration variable 'gc.pruneExpire' controls how old the unreferenced loose objects have to be before they are pruned. The @@ -120,7 +120,7 @@ Notes particular, it will keep not only objects referenced by your current set of branches and tags, but also objects referenced by the index, remote tracking branches, refs saved by 'git-filter-branch' in -refs/original/, or reflogs (which may references commits in branches +refs/original/, or reflogs (which may reference commits in branches that were later amended or rewound). If you are expecting some objects to be collected and they aren't, check diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt new file mode 100644 index 0000000000..67aec067c8 --- /dev/null +++ b/Documentation/git-http-backend.txt @@ -0,0 +1,178 @@ +git-http-backend(1) +=================== + +NAME +---- +git-http-backend - Server side implementation of Git over HTTP + +SYNOPSIS +-------- +[verse] +'git-http-backend' + +DESCRIPTION +----------- +A simple CGI program to serve the contents of a Git repository to Git +clients accessing the repository over http:// and https:// protocols. +The program supports clients fetching using both the smart HTTP protcol +and the backwards-compatible dumb HTTP protocol, as well as clients +pushing using the smart HTTP protocol. + +By default, only the `upload-pack` service is enabled, which serves +'git-fetch-pack' and 'git-ls-remote' clients, which are invoked from +'git-fetch', 'git-pull', and 'git-clone'. If the client is authenticated, +the `receive-pack` service is enabled, which serves 'git-send-pack' +clients, which is invoked from 'git-push'. + +SERVICES +-------- +These services can be enabled/disabled using the per-repository +configuration file: + +http.getanyfile:: + This serves older Git clients which are unable to use the + upload pack service. When enabled, clients are able to read + any file within the repository, including objects that are + no longer reachable from a branch but are still present. + It is enabled by default, but a repository can disable it + by setting this configuration item to `false`. + +http.uploadpack:: + This serves 'git-fetch-pack' and 'git-ls-remote' clients. + It is enabled by default, but a repository can disable it + by setting this configuration item to `false`. + +http.receivepack:: + This serves 'git-send-pack' clients, allowing push. It is + disabled by default for anonymous users, and enabled by + default for users authenticated by the web server. It can be + disabled by setting this item to `false`, or enabled for all + users, including anonymous users, by setting it to `true`. + +URL TRANSLATION +--------------- +To determine the location of the repository on disk, 'git-http-backend' +concatenates the environment variables PATH_INFO, which is set +automatically by the web server, and GIT_PROJECT_ROOT, which must be set +manually in the web server configuration. If GIT_PROJECT_ROOT is not +set, 'git-http-backend' reads PATH_TRANSLATED, which is also set +automatically by the web server. + +EXAMPLES +-------- +All of the following examples map 'http://$hostname/git/foo/bar.git' +to '/var/www/git/foo/bar.git'. + +Apache 2.x:: + Ensure mod_cgi, mod_alias, and mod_env are enabled, set + GIT_PROJECT_ROOT (or DocumentRoot) appropriately, and + create a ScriptAlias to the CGI: ++ +---------------------------------------------------------------- +SetEnv GIT_PROJECT_ROOT /var/www/git +ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ +---------------------------------------------------------------- ++ +To enable anonymous read access but authenticated write access, +require authorization with a LocationMatch directive: ++ +---------------------------------------------------------------- +<LocationMatch "^/git/.*/git-receive-pack$"> + AuthType Basic + AuthName "Git Access" + Require group committers + ... +</LocationMatch> +---------------------------------------------------------------- ++ +To require authentication for both reads and writes, use a Location +directive around the repository, or one of its parent directories: ++ +---------------------------------------------------------------- +<Location /git/private> + AuthType Basic + AuthName "Private Git Access" + Require group committers + ... +</Location> +---------------------------------------------------------------- ++ +To serve gitweb at the same url, use a ScriptAliasMatch to only +those URLs that 'git-http-backend' can handle, and forward the +rest to gitweb: ++ +---------------------------------------------------------------- +ScriptAliasMatch \ + "(?x)^/git/(.*/(HEAD | \ + info/refs | \ + objects/(info/[^/]+ | \ + [0-9a-f]{2}/[0-9a-f]{38} | \ + pack/pack-[0-9a-f]{40}\.(pack|idx)) | \ + git-(upload|receive)-pack))$" \ + /usr/libexec/git-core/git-http-backend/$1 + +ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/ +---------------------------------------------------------------- + +Accelerated static Apache 2.x:: + Similar to the above, but Apache can be used to return static + files that are stored on disk. On many systems this may + be more efficient as Apache can ask the kernel to copy the + file contents from the file system directly to the network: ++ +---------------------------------------------------------------- +SetEnv GIT_PROJECT_ROOT /var/www/git + +AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /var/www/git/$1 +AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/git/$1 +ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ +---------------------------------------------------------------- ++ +This can be combined with the gitweb configuration: ++ +---------------------------------------------------------------- +SetEnv GIT_PROJECT_ROOT /var/www/git + +AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /var/www/git/$1 +AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/git/$1 +ScriptAliasMatch \ + "(?x)^/git/(.*/(HEAD | \ + info/refs | \ + objects/info/[^/]+ | \ + git-(upload|receive)-pack))$" \ + /usr/libexec/git-core/git-http-backend/$1 +ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/ +---------------------------------------------------------------- + + +ENVIRONMENT +----------- +'git-http-backend' relies upon the CGI environment variables set +by the invoking web server, including: + +* PATH_INFO (if GIT_PROJECT_ROOT is set, otherwise PATH_TRANSLATED) +* REMOTE_USER +* REMOTE_ADDR +* CONTENT_TYPE +* QUERY_STRING +* REQUEST_METHOD + +The backend process sets GIT_COMMITTER_NAME to '$REMOTE_USER' and +GIT_COMMITTER_EMAIL to '$\{REMOTE_USER}@http.$\{REMOTE_ADDR\}', +ensuring that any reflogs created by 'git-receive-pack' contain some +identifying information of the remote user who performed the push. + +All CGI environment variables are available to each of the hooks +invoked by the 'git-receive-pack'. + +Author +------ +Written by Shawn O. Pearce <spearce@spearce.org>. + +Documentation +-------------- +Documentation by Shawn O. Pearce <spearce@spearce.org>. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt index aef383e0b1..ddf7a18dc4 100644 --- a/Documentation/git-http-push.txt +++ b/Documentation/git-http-push.txt @@ -82,11 +82,11 @@ destination side. Without '--force', the <src> ref is stored at the remote only if <dst> does not exist, or <dst> is a proper subset (i.e. an -ancestor) of <src>. This check, known as "fast forward check", +ancestor) of <src>. This check, known as "fast-forward check", is performed in order to avoid accidentally overwriting the remote ref and lose other peoples' commits from there. -With '--force', the fast forward check is disabled for all refs. +With '--force', the fast-forward check is disabled for all refs. Optionally, a <ref> parameter can be prefixed with a plus '+' sign to disable the fast-forward check only on that ref. diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 021066e95d..625723e41f 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -48,8 +48,10 @@ OPTIONS -i:: --ignored:: - Show ignored files in the output. - Note that this also reverses any exclude list present. + Show only ignored files in the output. When showing files in the + index, print only those matched by an exclude pattern. When + showing "other" files, show only those matched by an exclude + pattern. -s:: --stage:: diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt index 8d95aaa304..996c3fcc6c 100644 --- a/Documentation/git-mailinfo.txt +++ b/Documentation/git-mailinfo.txt @@ -8,7 +8,7 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message SYNOPSIS -------- -'git mailinfo' [-k] [-u | --encoding=<encoding> | -n] <msg> <patch> +'git mailinfo' [-k] [-u | --encoding=<encoding> | -n] [--scissors] <msg> <patch> DESCRIPTION @@ -49,6 +49,25 @@ conversion, even with this flag. -n:: Disable all charset re-coding of the metadata. +--scissors:: + Remove everything in body before a scissors line. A line that + mainly consists of scissors (either ">8" or "8<") and perforation + (dash "-") marks is called a scissors line, and is used to request + the reader to cut the message at that line. If such a line + appears in the body of the message before the patch, everything + before it (including the scissors line itself) is ignored when + this option is used. ++ +This is useful if you want to begin your message in a discussion thread +with comments and suggestions on the message you are responding to, and to +conclude it with a patch submission, separating the discussion and the +beginning of the proposed commit log message with a scissors line. ++ +This can enabled by default with the configuration option mailinfo.scissors. + +--no-scissors:: + Ignore scissors lines. Useful for overriding mailinfo.scissors settings. + <msg>:: The commit log message extracted from e-mail, usually except the title line which comes from e-mail Subject. diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index af68d694a0..e886c2ef54 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -28,9 +28,10 @@ OPTIONS include::merge-options.txt[] -m <msg>:: - The commit message to be used for the merge commit (in case - it is created). The 'git-fmt-merge-msg' script can be used - to give a good default for automated 'git-merge' invocations. + Set the commit message to be used for the merge commit (in + case one is created). The 'git fmt-merge-msg' command can be + used to give a good default for automated 'git merge' + invocations. <remote>...:: Other branch heads to merge into our branch. You need at @@ -49,8 +50,8 @@ include::merge-config.txt[] branch.<name>.mergeoptions:: Sets default options for merging into branch <name>. The syntax and - supported options are equal to that of 'git-merge', but option values - containing whitespace characters are currently not supported. + supported options are the same as those of 'git merge', but option + values containing whitespace characters are currently not supported. HOW MERGE WORKS --------------- @@ -211,6 +212,39 @@ You can work through the conflict with a number of tools: common ancestor, 'git show :2:filename' shows the HEAD version and 'git show :3:filename' shows the remote version. + +EXAMPLES +-------- + +* Merge branches `fixes` and `enhancements` on top of + the current branch, making an octopus merge: ++ +------------------------------------------------ +$ git merge fixes enhancements +------------------------------------------------ + +* Merge branch `obsolete` into the current branch, using `ours` + merge strategy: ++ +------------------------------------------------ +$ git merge -s ours obsolete +------------------------------------------------ + +* Merge branch `maint` into the current branch, but do not make + a new commit automatically: ++ +------------------------------------------------ +$ git merge --no-commit maint +------------------------------------------------ ++ +This can be used when you want to include further changes to the +merge, or want to write your own merge commit message. ++ +You should refrain from abusing this option to sneak substantial +changes into a merge commit. Small fixups like bumping +release/version name would be acceptable. + + SEE ALSO -------- linkgit:git-fmt-merge-msg[1], linkgit:git-pull[1], diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 68ed6c0956..4a6f7f3a2d 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -27,7 +27,7 @@ OPTIONS Use the merge resolution program specified by <tool>. Valid merge tools are: kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, - diffuse, tortoisemerge, opendiff and araxis. + diffuse, tortoisemerge, opendiff, p4merge and araxis. + If a merge resolution program is not specified, 'git-mergetool' will use the configuration variable `merge.tool`. If the diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt new file mode 100644 index 0000000000..94cceb1319 --- /dev/null +++ b/Documentation/git-notes.txt @@ -0,0 +1,60 @@ +git-notes(1) +============ + +NAME +---- +git-notes - Add/inspect commit notes + +SYNOPSIS +-------- +[verse] +'git-notes' (edit [-F <file> | -m <msg>] | show) [commit] + +DESCRIPTION +----------- +This command allows you to add notes to commit messages, without +changing the commit. To discern these notes from the message stored +in the commit object, the notes are indented like the message, after +an unindented line saying "Notes:". + +To disable commit notes, you have to set the config variable +core.notesRef to the empty string. Alternatively, you can set it +to a different ref, something like "refs/notes/bugzilla". This setting +can be overridden by the environment variable "GIT_NOTES_REF". + + +SUBCOMMANDS +----------- + +edit:: + Edit the notes for a given commit (defaults to HEAD). + +show:: + Show the notes for a given commit (defaults to HEAD). + + +OPTIONS +------- +-m <msg>:: + Use the given note message (instead of prompting). + If multiple `-m` (or `-F`) options are given, their + values are concatenated as separate paragraphs. + +-F <file>:: + Take the note message from the given file. Use '-' to + read the note message from the standard input. + If multiple `-F` (or `-m`) options are given, their + values are concatenated as separate paragraphs. + + +Author +------ +Written by Johannes Schindelin <johannes.schindelin@gmx.de> + +Documentation +------------- +Documentation by Johannes Schindelin + +GIT +--- +Part of the linkgit:git[7] suite diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 7578623edb..b93201158f 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -26,6 +26,10 @@ Also note that options meant for 'git-pull' itself and underlying OPTIONS ------- + +Options related to merging +~~~~~~~~~~~~~~~~~~~~~~~~~~ + include::merge-options.txt[] :git-pull: 1 @@ -47,6 +51,9 @@ unless you have read linkgit:git-rebase[1] carefully. --no-rebase:: Override earlier --rebase. +Options related to fetching +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + include::fetch-options.txt[] include::pull-fetch-param.txt[] @@ -131,54 +138,13 @@ $ git pull origin next ------------------------------------------------ + This leaves a copy of `next` temporarily in FETCH_HEAD, but -does not update any remote-tracking branches. - -* Bundle local branch `fixes` and `enhancements` on top of - the current branch, making an Octopus merge: -+ ------------------------------------------------- -$ git pull . fixes enhancements ------------------------------------------------- -+ -This `git pull .` syntax is equivalent to `git merge`. - -* Merge local branch `obsolete` into the current branch, using `ours` - merge strategy: -+ ------------------------------------------------- -$ git pull -s ours . obsolete ------------------------------------------------- - -* Merge local branch `maint` into the current branch, but do not make - a commit automatically: +does not update any remote-tracking branches. Using remote-tracking +branches, the same can be done by invoking fetch and merge: + ------------------------------------------------ -$ git pull --no-commit . maint +$ git fetch origin +$ git merge origin/next ------------------------------------------------ -+ -This can be used when you want to include further changes to the -merge, or want to write your own merge commit message. -+ -You should refrain from abusing this option to sneak substantial -changes into a merge commit. Small fixups like bumping -release/version name would be acceptable. - -* Command line pull of multiple branches from one repository: -+ ------------------------------------------------- -$ git checkout master -$ git fetch origin +pu:pu maint:tmp -$ git pull . tmp ------------------------------------------------- -+ -This updates (or creates, as necessary) branches `pu` and `tmp` in -the local repository by fetching from the branches (respectively) -`pu` and `maint` from the remote repository. -+ -The `pu` branch will be updated even if it is does not fast-forward; -the others will not be. -+ -The final command then merges the newly fetched `tmp` into master. If you tried a pull which resulted in a complex conflicts and diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 58d2bd5d4a..52c0538df5 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -9,7 +9,7 @@ git-push - Update remote refs along with associated objects SYNOPSIS -------- [verse] -'git push' [--all | --mirror | --tags] [--dry-run] [--receive-pack=<git-receive-pack>] +'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [-v | --verbose] [<repository> <refspec>...] @@ -50,9 +50,9 @@ updated. + The object referenced by <src> is used to update the <dst> reference on the remote side, but by default this is only allowed if the -update can fast forward <dst>. By having the optional leading `{plus}`, +update can fast-forward <dst>. By having the optional leading `{plus}`, you can tell git to update the <dst> ref even when the update is not a -fast forward. This does *not* attempt to merge <src> into <dst>. See +fast-forward. This does *not* attempt to merge <src> into <dst>. See EXAMPLES below for details. + `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`. @@ -60,7 +60,7 @@ EXAMPLES below for details. Pushing an empty <src> allows you to delete the <dst> ref from the remote repository. + -The special refspec `:` (or `{plus}:` to allow non-fast forward updates) +The special refspec `:` (or `{plus}:` to allow non-fast-forward updates) directs git to push "matching" branches: for every branch that exists on the local side, the remote side is updated if a branch of the same name already exists on the remote side. This is the default operation mode @@ -82,6 +82,7 @@ nor in any Push line of the corresponding remotes file---see below). if the configuration option `remote.<remote>.mirror` is set. +-n:: --dry-run:: Do everything except actually send the updates. @@ -137,6 +138,11 @@ useful if you write an alias or script around 'git-push'. --verbose:: Run verbosely. +-q:: +--quiet:: + Suppress all output, including the listing of updated refs, + unless an error occurs. + include::urls-remotes.txt[] OUTPUT @@ -170,10 +176,10 @@ summary:: For a successfully pushed ref, the summary shows the old and new values of the ref in a form suitable for using as an argument to `git log` (this is `<old>..<new>` in most cases, and - `<old>...<new>` for forced non-fast forward updates). For a + `<old>...<new>` for forced non-fast-forward updates). For a failed update, more details are given for the failure. The string `rejected` indicates that git did not try to send the - ref at all (typically because it is not a fast forward). The + ref at all (typically because it is not a fast-forward). The string `remote rejected` indicates that the remote end refused the update; this rejection is typically caused by a hook on the remote side. The string `remote failure` indicates that the @@ -341,9 +347,9 @@ git push origin :experimental:: git push origin {plus}dev:master:: Update the origin repository's master branch with the dev branch, - allowing non-fast forward updates. *This can leave unreferenced + allowing non-fast-forward updates. *This can leave unreferenced commits dangling in the origin repository.* Consider the - following situation, where a fast forward is not possible: + following situation, where a fast-forward is not possible: + ---- o---o---o---A---B origin/master diff --git a/Documentation/git-quiltimport.txt b/Documentation/git-quiltimport.txt index d4037de512..579e8d2f3b 100644 --- a/Documentation/git-quiltimport.txt +++ b/Documentation/git-quiltimport.txt @@ -9,7 +9,7 @@ git-quiltimport - Applies a quilt patchset onto the current branch SYNOPSIS -------- [verse] -'git quiltimport' [--dry-run] [--author <author>] [--patches <dir>] +'git quiltimport' [--dry-run | -n] [--author <author>] [--patches <dir>] DESCRIPTION diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 4a932b08c6..a10ce4ba40 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -144,7 +144,7 @@ Two Tree Merge Typically, this is invoked as `git read-tree -m $H $M`, where $H is the head commit of the current repository, and $M is the head of a foreign tree, which is simply ahead of $H (i.e. we are in a -fast forward situation). +fast-forward situation). When two trees are specified, the user is telling 'git-read-tree' the following: diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 0aefc34d0d..33e0ef1f6d 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -368,14 +368,17 @@ By replacing the command "pick" with the command "edit", you can tell the files and/or the commit message, amend the commit, and continue rebasing. +If you just want to edit the commit message for a commit, replace the +command "pick" with the command "reword". + If you want to fold two or more commits into one, replace the command "pick" with "squash" for the second and subsequent commit. If the commits had different authors, it will attribute the squashed commit to the author of the first commit. -In both cases, or when a "pick" does not succeed (because of merge -errors), the loop will stop to let you fix things, and you can continue -the loop with `git rebase --continue`. +'git-rebase' will stop when "pick" has been replaced with "edit" or +when a command fails due to merge errors. When you are done editing +and/or resolving conflicts you can continue with `git rebase --continue`. For example, if you want to reorder the last 5 commits, such that what was HEAD~4 becomes the new HEAD. To achieve that, you would call diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index 514f03c979..cb5f405280 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -20,7 +20,7 @@ The UI for the protocol is on the 'git-send-pack' side, and the program pair is meant to be used to push updates to remote repository. For pull operations, see linkgit:git-fetch-pack[1]. -The command allows for creation and fast forwarding of sha1 refs +The command allows for creation and fast-forwarding of sha1 refs (heads/tags) on the remote end (strictly speaking, it is the local end 'git-receive-pack' runs, but to the user who is sitting at the send-pack end, it is updating the remote. Confused?) diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt new file mode 100644 index 0000000000..8beb42dbb9 --- /dev/null +++ b/Documentation/git-remote-helpers.txt @@ -0,0 +1,146 @@ +git-remote-helpers(1) +===================== + +NAME +---- +git-remote-helpers - Helper programs for interoperation with remote git + +SYNOPSIS +-------- +'git remote-<transport>' <remote> + +DESCRIPTION +----------- + +These programs are normally not used directly by end users, but are +invoked by various git programs that interact with remote repositories +when the repository they would operate on will be accessed using +transport code not linked into the main git binary. Various particular +helper programs will behave as documented here. + +COMMANDS +-------- + +Commands are given by the caller on the helper's standard input, one per line. + +'capabilities':: + Lists the capabilities of the helper, one per line, ending + with a blank line. + +'list':: + Lists the refs, one per line, in the format "<value> <name> + [<attr> ...]". The value may be a hex sha1 hash, "@<dest>" for + a symref, or "?" to indicate that the helper could not get the + value of the ref. A space-separated list of attributes follows + the name; unrecognized attributes are ignored. After the + complete list, outputs a blank line. ++ +If 'push' is supported this may be called as 'list for-push' +to obtain the current refs prior to sending one or more 'push' +commands to the helper. + +'option' <name> <value>:: + Set the transport helper option <name> to <value>. Outputs a + single line containing one of 'ok' (option successfully set), + 'unsupported' (option not recognized) or 'error <msg>' + (option <name> is supported but <value> is not correct + for it). Options should be set before other commands, + and may how those commands behave. ++ +Supported if the helper has the "option" capability. + +'fetch' <sha1> <name>:: + Fetches the given object, writing the necessary objects + to the database. Fetch commands are sent in a batch, one + per line, and the batch is terminated with a blank line. + Outputs a single blank line when all fetch commands in the + same batch are complete. Only objects which were reported + in the ref list with a sha1 may be fetched this way. ++ +Optionally may output a 'lock <file>' line indicating a file under +GIT_DIR/objects/pack which is keeping a pack until refs can be +suitably updated. ++ +Supported if the helper has the "fetch" capability. + +'push' +<src>:<dst>:: + Pushes the given <src> commit or branch locally to the + remote branch described by <dst>. A batch sequence of + one or more push commands is terminated with a blank line. ++ +Zero or more protocol options may be entered after the last 'push' +command, before the batch's terminating blank line. ++ +When the push is complete, outputs one or more 'ok <dst>' or +'error <dst> <why>?' lines to indicate success or failure of +each pushed ref. The status report output is terminated by +a blank line. The option field <why> may be quoted in a C +style string if it contains an LF. ++ +Supported if the helper has the "push" capability. + +If a fatal error occurs, the program writes the error message to +stderr and exits. The caller should expect that a suitable error +message has been printed if the child closes the connection without +completing a valid response for the current command. + +Additional commands may be supported, as may be determined from +capabilities reported by the helper. + +CAPABILITIES +------------ + +'fetch':: + This helper supports the 'fetch' command. + +'option':: + This helper supports the option command. + +'push':: + This helper supports the 'push' command. + +REF LIST ATTRIBUTES +------------------- + +'for-push':: + The caller wants to use the ref list to prepare push + commands. A helper might chose to acquire the ref list by + opening a different type of connection to the destination. + +OPTIONS +------- +'option verbosity' <N>:: + Change the level of messages displayed by the helper. + When N is 0 the end-user has asked the process to be + quiet, and the helper should produce only error output. + N of 1 is the default level of verbosity, higher values + of N correspond to the number of -v flags passed on the + command line. + +'option progress' \{'true'|'false'\}:: + Enable (or disable) progress messages displayed by the + transport helper during a command. + +'option depth' <depth>:: + Deepen the history of a shallow repository. + +'option followtags' \{'true'|'false'\}:: + If enabled the helper should automatically fetch annotated + tag objects if the object the tag points at was transferred + during the fetch command. If the tag is not fetched by + the helper a second fetch command will usually be sent to + ask for the tag specifically. Some helpers may be able to + use this option to avoid a second network connection. + +'option dry-run' \{'true'|'false'\}: + If true, pretend the operation completed successfully, + but don't actually change any repository data. For most + helpers this only applies to the 'push', if supported. + +Documentation +------------- +Documentation by Daniel Barkalow. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt index 915cb77b29..8adc1ef55c 100644 --- a/Documentation/git-replace.txt +++ b/Documentation/git-replace.txt @@ -23,6 +23,26 @@ replacement object. Unless `-f` is given, the replace reference must not yet exist in `.git/refs/replace/` directory. +Replace references will be used by default by all git commands except +those doing reachability traversal (prune, pack transfer and fsck). + +It is possible to disable use of replacement refs for any command +using the --no-replace-objects option just after "git". + +For example if commit "foo" has been replaced by commit "bar": + +------------------------------------------------ +$ git --no-replace-object cat-file commit foo +------------------------------------------------ + +show information about commit "foo", while: + +------------------------------------------------ +$ git cat-file commit foo +------------------------------------------------ + +show information about commit "bar". + OPTIONS ------- -f:: @@ -54,6 +74,7 @@ SEE ALSO -------- linkgit:git-tag[1] linkgit:git-branch[1] +linkgit:git[1] Author ------ diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index abb25d1c00..2d27e405a3 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -10,6 +10,7 @@ SYNOPSIS [verse] 'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>] 'git reset' [-q] [<commit>] [--] <paths>... +'git reset' --patch [<commit>] [--] [<paths>...] DESCRIPTION ----------- @@ -23,8 +24,9 @@ the undo in the history. If you want to undo a commit other than the latest on a branch, linkgit:git-revert[1] is your friend. -The second form with 'paths' is used to revert selected paths in -the index from a given commit, without moving HEAD. +The second and third forms with 'paths' and/or --patch are used to +revert selected paths in the index from a given commit, without moving +HEAD. OPTIONS @@ -50,6 +52,15 @@ OPTIONS and updates the files that are different between the named commit and the current commit in the working tree. +-p:: +--patch:: + Interactively select hunks in the difference between the index + and <commit> (defaults to HEAD). The chosen hunks are applied + in reverse to the index. ++ +This means that `git reset -p` is the opposite of `git add -p` (see +linkgit:git-add[1]). + -q:: Be quiet, only report errors. @@ -139,7 +150,7 @@ Automatic merge failed; fix conflicts and then commit the result. $ git reset --hard <2> $ git pull . topic/branch <3> Updating from 41223... to 13134... -Fast forward +Fast-forward $ git reset --hard ORIG_HEAD <4> ------------ + @@ -150,7 +161,7 @@ right now, so you decide to do that later. which is a synonym for "git reset --hard HEAD" clears the mess from the index file and the working tree. <3> Merge a topic branch into the current branch, which resulted -in a fast forward. +in a fast-forward. <4> But you decided that the topic branch is not ready for public consumption yet. "pull" or "merge" always leaves the original tip of the current branch in ORIG_HEAD, so resetting hard to it diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 767cf4d4bd..c85d7f4385 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -60,8 +60,8 @@ The --bcc option must be repeated for each user you want on the bcc list. The --cc option must be repeated for each user you want on the cc list. --compose:: - Use $GIT_EDITOR, core.editor, $VISUAL, or $EDITOR to edit an - introductory message for the patch series. + Invoke a text editor (see GIT_EDITOR in linkgit:git-var[1]) + to edit an introductory message for the patch series. + When '--compose' is used, git send-email will use the From, Subject, and In-Reply-To headers specified in the message. If the body of the message diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index 399821832c..5a04c6eaf7 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -105,11 +105,11 @@ name. See linkgit:git-rev-parse[1]. Without '--force', the <src> ref is stored at the remote only if <dst> does not exist, or <dst> is a proper subset (i.e. an -ancestor) of <src>. This check, known as "fast forward check", +ancestor) of <src>. This check, known as "fast-forward check", is performed in order to avoid accidentally overwriting the remote ref and lose other peoples' commits from there. -With '--force', the fast forward check is disabled for all refs. +With '--force', the fast-forward check is disabled for all refs. Optionally, a <ref> parameter can be prefixed with a plus '+' sign to disable the fast-forward check only on that ref. diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt index f4429bdc68..70f400b266 100644 --- a/Documentation/git-show-ref.txt +++ b/Documentation/git-show-ref.txt @@ -8,7 +8,7 @@ git-show-ref - List references in a local repository SYNOPSIS -------- [verse] -'git show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] +'git show-ref' [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] <pattern>... 'git show-ref' --exclude-existing[=<pattern>] < ref-list @@ -30,7 +30,6 @@ the `.git` directory. OPTIONS ------- --h:: --head:: Show the HEAD reference. diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 2f5ca7b1a3..3f14b727b8 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -13,7 +13,7 @@ SYNOPSIS 'git stash' drop [-q|--quiet] [<stash>] 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>] 'git stash' branch <branchname> [<stash>] -'git stash' [save [--keep-index] [-q|--quiet] [<message>]] +'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]] 'git stash' clear 'git stash' create @@ -42,15 +42,27 @@ is also possible). OPTIONS ------- -save [--keep-index] [-q|--quiet] [<message>]:: +save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]:: Save your local modifications to a new 'stash', and run `git reset - --hard` to revert them. This is the default action when no - subcommand is given. The <message> part is optional and gives - the description along with the stashed state. + --hard` to revert them. The <message> part is optional and gives + the description along with the stashed state. For quickly making + a snapshot, you can omit _both_ "save" and <message>, but giving + only <message> does not trigger this action to prevent a misspelled + subcommand from making an unwanted stash. + If the `--keep-index` option is used, all changes already added to the index are left intact. ++ +With `--patch`, you can interactively select hunks from in the diff +between HEAD and the working tree to be stashed. The stash entry is +constructed such that its index state is the same as the index state +of your repository, and its worktree contains only the changes you +selected interactively. The selected changes are then rolled back +from your worktree. ++ +The `--patch` option implies `--keep-index`. You can use +`--no-keep-index` to override this. list [<options>]:: diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 5ccdd18c89..4ef70c42eb 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git submodule' [--quiet] add [-b branch] - [--reference <repository>] [--] <repository> <path> + [--reference <repository>] [--] <repository> [<path>] 'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...] 'git submodule' [--quiet] init [--] [<path>...] 'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase] @@ -69,7 +69,11 @@ add:: to the changeset to be committed next to the current project: the current project is termed the "superproject". + -This requires two arguments: <repository> and <path>. +This requires at least one argument: <repository>. The optional +argument <path> is the relative location for the cloned submodule +to exist in the superproject. If <path> is not given, the +"humanish" part of the source repository is used ("repo" for +"/path/to/repo.git" and "foo" for "host.xz:foo/.git"). + <repository> is the URL of the new submodule's origin repository. This may be either an absolute URL, or (if it begins with ./ diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 1812890a7e..4cdca0d874 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -320,6 +320,13 @@ Any other arguments are passed directly to 'git log' directories. The output is suitable for appending to the $GIT_DIR/info/exclude file. +'mkdirs':: + Attempts to recreate empty directories that core git cannot track + based on information in $GIT_DIR/svn/<refname>/unhandled.log files. + Empty directories are automatically recreated when using + "git svn clone" and "git svn rebase", so "mkdirs" is intended + for use after commands like "git checkout" or "git reset". + 'commit-diff':: Commits the diff of two tree-ish arguments from the command-line. This command does not rely on being inside an `git svn @@ -735,6 +742,16 @@ merges you've made. Furthermore, if you merge or pull from a git branch that is a mirror of an SVN branch, 'dcommit' may commit to the wrong branch. +If you do merge, note the following rule: 'git svn dcommit' will +attempt to commit on top of the SVN commit named in +------------------------------------------------------------------------ +git log --grep=^git-svn-id: --first-parent -1 +------------------------------------------------------------------------ +You 'must' therefore ensure that the most recent commit of the branch +you want to dcommit to is the 'first' parent of the merge. Chaos will +ensue otherwise, especially if the first parent is an older commit on +the same SVN branch. + 'git clone' does not clone branches under the refs/remotes/ hierarchy or any 'git svn' metadata, or config. So repositories created and managed with using 'git svn' should use 'rsync' for cloning, if cloning is to be done diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 25e0bbea86..6052484ab9 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -99,6 +99,10 @@ in the index e.g. when merging in a commit; thus, in case the assumed-untracked file is changed upstream, you will need to handle the situation manually. +--really-refresh:: + Like '--refresh', but checks stat information unconditionally, + without regard to the "assume unchanged" setting. + -g:: --again:: Runs 'git-update-index' itself on the paths whose index @@ -308,7 +312,7 @@ Configuration ------------- The command honors `core.filemode` configuration variable. If -your repository is on an filesystem whose executable bits are +your repository is on a filesystem whose executable bits are unreliable, this should be set to 'false' (see linkgit:git-config[1]). This causes the command to ignore differences in file modes recorded in the index and the file mode on the filesystem if they differ only on diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt index b8e49dce4a..63f3b5c742 100644 --- a/Documentation/git-upload-pack.txt +++ b/Documentation/git-upload-pack.txt @@ -20,6 +20,8 @@ The UI for the protocol is on the 'git-fetch-pack' side, and the program pair is meant to be used to pull updates from a remote repository. For push operations, see 'git-send-pack'. +After finishing the operation successfully, `post-upload-pack` +hook is called (see linkgit:githooks[5]). OPTIONS ------- diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt index e2f4c0901b..ef6aa81872 100644 --- a/Documentation/git-var.txt +++ b/Documentation/git-var.txt @@ -36,6 +36,20 @@ GIT_AUTHOR_IDENT:: GIT_COMMITTER_IDENT:: The person who put a piece of code into git. +GIT_EDITOR:: + Text editor for use by git commands. The value is meant to be + interpreted by the shell when it is used. Examples: `~/bin/vi`, + `$SOME_ENVIRONMENT_VARIABLE`, `"C:\Program Files\Vim\gvim.exe" + --nofork`. The order of preference is the `$GIT_EDITOR` + environment variable, then `core.editor` configuration, then + `$VISUAL`, then `$EDITOR`, and then finally 'vi'. + +GIT_PAGER:: + Text viewer for use by git commands (e.g., 'less'). The value + is meant to be interpreted by the shell. The order of preference + is the `$GIT_PAGER` environment variable, then `core.pager` + configuration, then `$PAGER`, and then finally 'less'. + Diagnostics ----------- You don't exist. Go away!:: diff --git a/Documentation/git.txt b/Documentation/git.txt index ad44cac71d..8e577cc4fe 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path] - [-p|--paginate|--no-pager] + [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS] @@ -43,9 +43,19 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.6.4.2/git.html[documentation for release 1.6.4.2] +* link:v1.6.5.3/git.html[documentation for release 1.6.5.3] * release notes for + link:RelNotes-1.6.5.3.txt[1.6.5.3], + link:RelNotes-1.6.5.2.txt[1.6.5.2], + link:RelNotes-1.6.5.1.txt[1.6.5.1], + link:RelNotes-1.6.5.txt[1.6.5]. + +* link:v1.6.4.4/git.html[documentation for release 1.6.4.4] + +* release notes for + link:RelNotes-1.6.4.4.txt[1.6.4.4], + link:RelNotes-1.6.4.3.txt[1.6.4.3], link:RelNotes-1.6.4.2.txt[1.6.4.2], link:RelNotes-1.6.4.1.txt[1.6.4.1], link:RelNotes-1.6.4.txt[1.6.4]. @@ -230,6 +240,10 @@ help ...`. environment is not set, it is set to the current working directory. +--no-replace-objects:: + Do not use replacement refs to replace git objects. See + linkgit:git-replace[1] for more information. + FURTHER DOCUMENTATION --------------------- diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 1195e83b6e..1f472cea59 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -560,6 +560,16 @@ in the file. E.g. the string `$Format:%H$` will be replaced by the commit hash. +Packing objects +~~~~~~~~~~~~~~~ + +`delta` +^^^^^^^ + +Delta compression will not be attempted for blobs for paths with the +attribute `delta` set to false. + + Viewing files in GUI tools ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/gitcli.txt b/Documentation/gitcli.txt index be39ed7c15..6928724a05 100644 --- a/Documentation/gitcli.txt +++ b/Documentation/gitcli.txt @@ -81,7 +81,7 @@ couple of magic command line options: + --------------------------------------------- $ git describe -h -usage: git-describe [options] <committish>* +usage: git describe [options] <committish>* --contains find the tag that comes after the commit --debug debug search strategy on stderr diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index b3640c4e64..b7380b069a 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -185,7 +185,7 @@ object is. git will tell you that you have a "blob" object (i.e., just a regular file), and you can see the contents with ---------------- -$ git cat-file "blob" 557db03 +$ git cat-file blob 557db03 ---------------- which will print out "Hello World". The object `557db03` is nothing @@ -993,7 +993,7 @@ would be different) ---------------- Updating from ae3a2da... to a80b4aa.... -Fast forward (no commit created; -m option ignored) +Fast-forward (no commit created; -m option ignored) example | 1 + hello | 1 + 2 files changed, 2 insertions(+), 0 deletions(-) @@ -1003,7 +1003,7 @@ Because your branch did not contain anything more than what had already been merged into the `master` branch, the merge operation did not actually do a merge. Instead, it just updated the top of the tree of your branch to that of the `master` branch. This is -often called 'fast forward' merge. +often called 'fast-forward' merge. You can run `gitk \--all` again to see how the commit ancestry looks like, or run 'show-branch', which tells you this. @@ -1188,7 +1188,7 @@ $ git show-branch -- + [mybranch] Some work. * [master] Some fun. -*+ [mybranch^] New day. +*+ [mybranch^] Initial commit ------------ Now we are ready to experiment with the merge by hand. @@ -1204,11 +1204,11 @@ $ mb=$(git merge-base HEAD mybranch) The command writes the commit object name of the common ancestor to the standard output, so we captured its output to a variable, because we will be using it in the next step. By the way, the common -ancestor commit is the "New day." commit in this case. You can +ancestor commit is the "Initial commit" commit in this case. You can tell it by: ------------ -$ git name-rev $mb +$ git name-rev --name-only --tags $mb my-first-tag ------------ @@ -1237,8 +1237,8 @@ inspect the index file with this command: ------------ $ git ls-files --stage 100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example -100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello -100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello +100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello +100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello 100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello ------------ @@ -1253,8 +1253,8 @@ To look at only non-zero stages, use `\--unmerged` flag: ------------ $ git ls-files --unmerged -100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello -100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello +100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello +100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello 100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello ------------ @@ -1283,8 +1283,8 @@ the working tree.. This can be seen if you run `ls-files ------------ $ git ls-files --stage 100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example -100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello -100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello +100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello +100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello 100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello ------------ diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 1c736738cc..4cc3d1387f 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -26,8 +26,11 @@ executable by default. This document describes the currently defined hooks. +HOOKS +----- + applypatch-msg --------------- +~~~~~~~~~~~~~~ This hook is invoked by 'git-am' script. It takes a single parameter, the name of the file that holds the proposed commit @@ -43,7 +46,7 @@ The default 'applypatch-msg' hook, when enabled, runs the 'commit-msg' hook, if the latter is enabled. pre-applypatch --------------- +~~~~~~~~~~~~~~ This hook is invoked by 'git-am'. It takes no parameter, and is invoked after the patch is applied, but before a commit is made. @@ -58,7 +61,7 @@ The default 'pre-applypatch' hook, when enabled, runs the 'pre-commit' hook, if the latter is enabled. post-applypatch ---------------- +~~~~~~~~~~~~~~~ This hook is invoked by 'git-am'. It takes no parameter, and is invoked after the patch is applied and a commit is made. @@ -67,7 +70,7 @@ This hook is meant primarily for notification, and cannot affect the outcome of 'git-am'. pre-commit ----------- +~~~~~~~~~~ This hook is invoked by 'git-commit', and can be bypassed with `\--no-verify` option. It takes no parameter, and is @@ -84,7 +87,7 @@ variable `GIT_EDITOR=:` if the command will not bring up an editor to modify the commit message. prepare-commit-msg ------------------- +~~~~~~~~~~~~~~~~~~ This hook is invoked by 'git-commit' right after preparing the default log message, and before the editor is started. @@ -109,7 +112,7 @@ The sample `prepare-commit-msg` hook that comes with git comments out the `Conflicts:` part of a merge's commit message. commit-msg ----------- +~~~~~~~~~~ This hook is invoked by 'git-commit', and can be bypassed with `\--no-verify` option. It takes a single parameter, the @@ -126,7 +129,7 @@ The default 'commit-msg' hook, when enabled, detects duplicate "Signed-off-by" lines, and aborts the commit if one is found. post-commit ------------ +~~~~~~~~~~~ This hook is invoked by 'git-commit'. It takes no parameter, and is invoked after a commit is made. @@ -135,14 +138,14 @@ This hook is meant primarily for notification, and cannot affect the outcome of 'git-commit'. pre-rebase ----------- +~~~~~~~~~~ This hook is called by 'git-rebase' and can be used to prevent a branch from getting rebased. post-checkout ------------ +~~~~~~~~~~~~~ This hook is invoked when a 'git-checkout' is run after having updated the worktree. The hook is given three parameters: the ref of the previous HEAD, @@ -160,7 +163,7 @@ differences from the previous HEAD if different, or set working dir metadata properties. post-merge ------------ +~~~~~~~~~~ This hook is invoked by 'git-merge', which happens when a 'git-pull' is done on a local repository. The hook takes a single parameter, a status @@ -175,7 +178,7 @@ for an example of how to do this. [[pre-receive]] pre-receive ------------ +~~~~~~~~~~~ This hook is invoked by 'git-receive-pack' on the remote repository, which happens when a 'git-push' is done on a local repository. @@ -204,7 +207,7 @@ for the user. [[update]] update ------- +~~~~~~ This hook is invoked by 'git-receive-pack' on the remote repository, which happens when a 'git-push' is done on a local repository. @@ -226,7 +229,7 @@ from updating that ref. This hook can be used to prevent 'forced' update on certain refs by making sure that the object name is a commit object that is a descendant of the commit object named by the old object name. -That is, to enforce a "fast forward only" policy. +That is, to enforce a "fast-forward only" policy. It could also be used to log the old..new status. However, it does not know the entire set of branches, so it would end up @@ -242,12 +245,12 @@ Both standard output and standard error output are forwarded to for the user. The default 'update' hook, when enabled--and with -`hooks.allowunannotated` config option turned on--prevents +`hooks.allowunannotated` config option unset or set to false--prevents unannotated tags to be pushed. [[post-receive]] post-receive ------------- +~~~~~~~~~~~~ This hook is invoked by 'git-receive-pack' on the remote repository, which happens when a 'git-push' is done on a local repository. @@ -277,7 +280,7 @@ emails. [[post-update]] post-update ------------ +~~~~~~~~~~~ This hook is invoked by 'git-receive-pack' on the remote repository, which happens when a 'git-push' is done on a local repository. @@ -307,8 +310,37 @@ Both standard output and standard error output are forwarded to 'git-send-pack' on the other end, so you can simply `echo` messages for the user. +post-upload-pack +---------------- + +After upload-pack successfully finishes its operation, this hook is called +for logging purposes. + +The hook is passed various pieces of information, one per line, from its +standard input. Currently the following items can be fed to the hook, but +more types of information may be added in the future: + +want SHA-1:: + 40-byte hexadecimal object name the client asked to include in the + resulting pack. Can occur one or more times in the input. + +have SHA-1:: + 40-byte hexadecimal object name the client asked to exclude from + the resulting pack, claiming to have them already. Can occur zero + or more times in the input. + +time float:: + Number of seconds spent for creating the packfile. + +size decimal:: + Size of the resulting packfile in bytes. + +kind string: + Either "clone" (when the client did not give us any "have", and asked + for all our refs with "want"), or "fetch" (otherwise). + pre-auto-gc ------------ +~~~~~~~~~~~ This hook is invoked by 'git-gc --auto'. It takes no parameter, and exiting with non-zero status from this script causes the 'git-gc --auto' diff --git a/Documentation/gitworkflows.txt b/Documentation/gitworkflows.txt index 2b021e3c15..91c0eea890 100644 --- a/Documentation/gitworkflows.txt +++ b/Documentation/gitworkflows.txt @@ -209,6 +209,121 @@ chance to see if their in-progress work will be compatible. `git.git` has such an official throw-away integration branch called 'pu'. +Branch management for a release +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Assuming you are using the merge approach discussed above, when you +are releasing your project you will need to do some additional branch +management work. + +A feature release is created from the 'master' branch, since 'master' +tracks the commits that should go into the next feature release. + +The 'master' branch is supposed to be a superset of 'maint'. If this +condition does not hold, then 'maint' contains some commits that +are not included on 'master'. The fixes represented by those commits +will therefore not be included in your feature release. + +To verify that 'master' is indeed a superset of 'maint', use git log: + +.Verify 'master' is a superset of 'maint' +[caption="Recipe: "] +===================================== +git log master..maint +===================================== + +This command should not list any commits. Otherwise, check out +'master' and merge 'maint' into it. + +Now you can proceed with the creation of the feature release. Apply a +tag to the tip of 'master' indicating the release version: + +.Release tagging +[caption="Recipe: "] +===================================== +`git tag -s -m "GIT X.Y.Z" vX.Y.Z master` +===================================== + +You need to push the new tag to a public git server (see +"DISTRIBUTED WORKFLOWS" below). This makes the tag available to +others tracking your project. The push could also trigger a +post-update hook to perform release-related items such as building +release tarballs and preformatted documentation pages. + +Similarly, for a maintenance release, 'maint' is tracking the commits +to be released. Therefore, in the steps above simply tag and push +'maint' rather than 'master'. + + +Maintenance branch management after a feature release +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +After a feature release, you need to manage your maintenance branches. + +First, if you wish to continue to release maintenance fixes for the +feature release made before the recent one, then you must create +another branch to track commits for that previous release. + +To do this, the current maintenance branch is copied to another branch +named with the previous release version number (e.g. maint-X.Y.(Z-1) +where X.Y.Z is the current release). + +.Copy maint +[caption="Recipe: "] +===================================== +`git branch maint-X.Y.(Z-1) maint` +===================================== + +The 'maint' branch should now be fast-forwarded to the newly released +code so that maintenance fixes can be tracked for the current release: + +.Update maint to new release +[caption="Recipe: "] +===================================== +* `git checkout maint` +* `git merge --ff-only master` +===================================== + +If the merge fails because it is not a fast-forward, then it is +possible some fixes on 'maint' were missed in the feature release. +This will not happen if the content of the branches was verified as +described in the previous section. + + +Branch management for next and pu after a feature release +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +After a feature release, the integration branch 'next' may optionally be +rewound and rebuilt from the tip of 'master' using the surviving +topics on 'next': + +.Rewind and rebuild next +[caption="Recipe: "] +===================================== +* `git checkout next` +* `git reset --hard master` +* `git merge ai/topic_in_next1` +* `git merge ai/topic_in_next2` +* ... +===================================== + +The advantage of doing this is that the history of 'next' will be +clean. For example, some topics merged into 'next' may have initially +looked promising, but were later found to be undesirable or premature. +In such a case, the topic is reverted out of 'next' but the fact +remains in the history that it was once merged and reverted. By +recreating 'next', you give another incarnation of such topics a clean +slate to retry, and a feature release is a good point in history to do +so. + +If you do this, then you should make a public announcement indicating +that 'next' was rewound and rebuilt. + +The same rewind and rebuild process may be followed for 'pu'. A public +announcement is not necessary since 'pu' is a throw-away branch, as +described above. + + DISTRIBUTED WORKFLOWS --------------------- diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 572374f7a6..1f029f8aa0 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -124,7 +124,7 @@ to point at the new commit. An evil merge is a <<def_merge,merge>> that introduces changes that do not appear in any <<def_parent,parent>>. -[[def_fast_forward]]fast forward:: +[[def_fast_forward]]fast-forward:: A fast-forward is a special type of <<def_merge,merge>> where you have a <<def_revision,revision>> and you are "merging" another <<def_branch,branch>>'s changes that happen to be a descendant of what @@ -220,7 +220,7 @@ to point at the new commit. conflict, manual intervention may be required to complete the merge. + -As a noun: unless it is a <<def_fast_forward,fast forward>>, a +As a noun: unless it is a <<def_fast_forward,fast-forward>>, a successful merge results in the creation of a new <<def_commit,commit>> representing the result of the merge, and having as <<def_parent,parents>> the tips of the merged <<def_branch,branches>>. @@ -456,6 +456,6 @@ This commit is referred to as a "merge commit", or sometimes just a of 'A' is 'origin/B' sometimes we say "'A' is tracking 'origin/B'". [[def_working_tree]]working tree:: - The tree of actual checked out files. The working tree is - normally equal to the <<def_HEAD,HEAD>> plus any local changes - that you have made but not yet committed. + The tree of actual checked out files. The working tree normally + contains the contents of the <<def_HEAD,HEAD>> commit's tree, + plus any local changes that you have made but not yet committed. diff --git a/Documentation/howto/maintain-git.txt b/Documentation/howto/maintain-git.txt index 4357e26913..d527b30770 100644 --- a/Documentation/howto/maintain-git.txt +++ b/Documentation/howto/maintain-git.txt @@ -59,7 +59,7 @@ The policy. 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 + fast-forward, to allow people to build their own customization on top of them. - Usually 'master' contains all of 'maint', 'next' contains all diff --git a/Documentation/howto/revert-branch-rebase.txt b/Documentation/howto/revert-branch-rebase.txt index e70d8a31e7..8c32da6deb 100644 --- a/Documentation/howto/revert-branch-rebase.txt +++ b/Documentation/howto/revert-branch-rebase.txt @@ -85,7 +85,7 @@ Fortunately I did not have to; what I have in the current branch ------------------------------------------------ $ git checkout master -$ git merge revert-c99 ;# this should be a fast forward +$ git merge revert-c99 ;# this should be a fast-forward Updating from 10d781b9caa4f71495c7b34963bef137216f86a8 to e3a693c... cache.h | 8 ++++---- commit.c | 2 +- @@ -95,7 +95,7 @@ Updating from 10d781b9caa4f71495c7b34963bef137216f86a8 to e3a693c... 5 files changed, 8 insertions(+), 8 deletions(-) ------------------------------------------------ -There is no need to redo the test at this point. We fast forwarded +There is no need to redo the test at this point. We fast-forwarded and we know 'master' matches 'revert-c99' exactly. In fact: ------------------------------------------------ diff --git a/Documentation/howto/update-hook-example.txt b/Documentation/howto/update-hook-example.txt index 697d918885..b7f8d416d6 100644 --- a/Documentation/howto/update-hook-example.txt +++ b/Documentation/howto/update-hook-example.txt @@ -76,7 +76,7 @@ case "$1" in if expr "$2" : '0*$' >/dev/null; then info "The branch '$1' is new..." else - # updating -- make sure it is a fast forward + # updating -- make sure it is a fast-forward mb=$(git-merge-base "$2" "$3") case "$mb,$2" in "$2,$mb") info "Update is fast-forward" ;; diff --git a/Documentation/manpage-quote-apos.xsl b/Documentation/manpage-quote-apos.xsl new file mode 100644 index 0000000000..aeb8839f33 --- /dev/null +++ b/Documentation/manpage-quote-apos.xsl @@ -0,0 +1,16 @@ +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<!-- work around newer groff/man setups using a prettier apostrophe + that unfortunately does not quote anything when cut&pasting + examples to the shell --> +<xsl:template name="escape.apostrophe"> + <xsl:param name="content"/> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target">'</xsl:with-param> + <xsl:with-param name="replacement">\(aq</xsl:with-param> + </xsl:call-template> +</xsl:template> + +</xsl:stylesheet> diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt index c0f96e7070..a403155052 100644 --- a/Documentation/merge-config.txt +++ b/Documentation/merge-config.txt @@ -23,7 +23,7 @@ merge.tool:: Controls which merge resolution program is used by linkgit:git-mergetool[1]. Valid built-in values are: "kdiff3", "tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", - "diffuse", "ecmerge", "tortoisemerge", "araxis", and + "diffuse", "ecmerge", "tortoisemerge", "p4merge", "araxis" and "opendiff". Any other value is treated is custom merge tool and there must be a corresponding mergetool.<tool>.cmd option. diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index adadf8e4bf..fec3394305 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -1,43 +1,42 @@ --q:: ---quiet:: - Operate quietly. - --v:: ---verbose:: - Be verbose. - ---stat:: - Show a diffstat at the end of the merge. The diffstat is also - controlled by the configuration option merge.stat. - --n:: ---no-stat:: - Do not show a diffstat at the end of the merge. +--commit:: +--no-commit:: + Perform the merge and commit the result. This option can + be used to override --no-commit. ++ +With --no-commit perform the merge but pretend the merge +failed and do not autocommit, to give the user a chance to +inspect and further tweak the merge result before committing. ---summary:: ---no-summary:: - Synonyms to --stat and --no-stat; these are deprecated and will be - removed in the future. +--ff:: +--no-ff:: + Do not generate a merge commit if the merge resolved as + a fast-forward, only update the branch pointer. This is + the default behavior of git-merge. ++ +With --no-ff Generate a merge commit even if the merge +resolved as a fast-forward. --log:: +--no-log:: In addition to branch names, populate the log message with one-line descriptions from the actual commits that are being merged. ++ +With --no-log do not list one-line descriptions from the +actual commits being merged. ---no-log:: - Do not list one-line descriptions from the actual commits being - merged. - ---no-commit:: - Perform the merge but pretend the merge failed and do - not autocommit, to give the user a chance to inspect and - further tweak the merge result before committing. ---commit:: - Perform the merge and commit the result. This option can - be used to override --no-commit. +--stat:: +-n:: +--no-stat:: + Show a diffstat at the end of the merge. The diffstat is also + controlled by the configuration option merge.stat. ++ +With -n or --no-stat do not show a diffstat at the end of the +merge. --squash:: +--no-squash:: Produce the working tree and index state as if a real merge happened (except for the merge information), but do not actually make a commit or @@ -46,19 +45,14 @@ commit. This allows you to create a single commit on top of the current branch whose effect is the same as merging another branch (or more in case of an octopus). ++ +With --no-squash perform the merge and commit the result. This +option can be used to override --squash. ---no-squash:: - Perform the merge and commit the result. This option can - be used to override --squash. - ---no-ff:: - Generate a merge commit even if the merge resolved as a - fast-forward. - ---ff:: - Do not generate a merge commit if the merge resolved as - a fast-forward, only update the branch pointer. This is - the default behavior of git-merge. +--ff-only:: + Refuse to merge and exit with a non-zero status unless the + current `HEAD` is already up-to-date or the merge can be + resolved as a fast-forward. -s <strategy>:: --strategy=<strategy>:: @@ -67,3 +61,16 @@ If there is no `-s` option, a built-in list of strategies is used instead ('git-merge-recursive' when merging a single head, 'git-merge-octopus' otherwise). + +--summary:: +--no-summary:: + Synonyms to --stat and --no-stat; these are deprecated and will be + removed in the future. + +-q:: +--quiet:: + Operate quietly. + +-v:: +--verbose:: + Be verbose. diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 2a845b1e57..09462021ea 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -123,6 +123,10 @@ The placeholders are: - '%s': subject - '%f': sanitized subject line, suitable for a filename - '%b': body +- '%N': commit notes +- '%gD': reflog selector, e.g., `refs/stash@\{1\}` +- '%gd': shortened reflog selector, e.g., `stash@\{1\}` +- '%gs': reflog subject - '%Cred': switch color to red - '%Cgreen': switch color to green - '%Cblue': switch color to blue @@ -132,6 +136,12 @@ The placeholders are: - '%n': newline - '%x00': print a byte from a hex code +NOTE: Some placeholders may depend on other options given to the +revision traversal engine. For example, the `%g*` reflog options will +insert an empty string unless we are traversing reflog entries (e.g., by +`git log -g`). The `%d` placeholder will use the "short" decoration +format if `--decorate` was not already provided on the command line. + * 'tformat:' + The 'tformat:' format works exactly like 'format:', except that it diff --git a/Documentation/pt_BR/gittutorial.txt b/Documentation/pt_BR/gittutorial.txt index 81e7ad7df4..beba065252 100644 --- a/Documentation/pt_BR/gittutorial.txt +++ b/Documentation/pt_BR/gittutorial.txt @@ -1,15 +1,15 @@ gittutorial(7) ============== -NAME +NOME ---- gittutorial - Um tutorial de introdução ao git (para versão 1.5.1 ou mais nova) -SYNOPSIS +SINOPSE -------- git * -DESCRIPTION +DESCRIÇÃO ----------- Este tutorial explica como importar um novo projeto para o git, @@ -64,11 +64,11 @@ Git irá responder Initialized empty Git repository in .git/ ------------------------------------------------ -Você agora iniciou seu diretório de trabalho--você deve ter notado um -novo diretório criado, com o nome de ".git". +Agora que você iniciou seu diretório de trabalho, você deve ter notado que um +novo diretório foi criado com o nome de ".git". A seguir, diga ao git para gravar um instantâneo do conteúdo de todos os -arquivos sob o diretório corrente (note o '.'), com 'git-add': +arquivos sob o diretório atual (note o '.'), com 'git-add': ------------------------------------------------ $ git add . @@ -126,8 +126,8 @@ mudanças com: $ git commit ------------------------------------------------ -Isto irá novamente te pedir por uma mensagem descrevendo a mudança, e, -então, gravar a nova versão do projeto. +Ao executar esse comando, ele irá te pedir uma mensagem descrevendo a mudança, +e, então, irá gravar a nova versão do projeto. Alternativamente, ao invés de executar 'git-add' antes, você pode usar @@ -143,7 +143,7 @@ idéia começar a mensagem com uma simples e curta (menos de 50 caracteres) linha sumarizando a mudança, seguida de uma linha em branco e, então, uma descrição mais detalhada. Ferramentas que transformam commits em email, por exemplo, usam a primeira linha no campo de -cabeçalho Subject: e o resto no corpo. +cabeçalho "Subject:" e o resto no corpo. Git rastreia conteúdo, não arquivos ---------------------------- @@ -155,7 +155,7 @@ usado tanto para arquivos novos e arquivos recentemente modificados, e em ambos os casos, ele tira o instantâneo dos arquivos dados e armazena o conteúdo no índice, pronto para inclusão do próximo commit. -Visualizando história do projeto +Visualizando a história do projeto ----------------------- Em qualquer ponto você pode visualizar a história das suas mudanças @@ -165,7 +165,7 @@ usando $ git log ------------------------------------------------ -Se você também quer ver a diferença completa a cada passo, use +Se você também quiser ver a diferença completa a cada passo, use ------------------------------------------------ $ git log -p diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt index f9811f2473..44d936341f 100644 --- a/Documentation/pull-fetch-param.txt +++ b/Documentation/pull-fetch-param.txt @@ -11,9 +11,9 @@ + The remote ref that matches <src> is fetched, and if <dst> is not empty string, the local -ref that matches it is fast forwarded using <src>. +ref that matches it is fast-forwarded using <src>. If the optional plus `+` is used, the local ref -is updated even if it does not result in a fast forward +is updated even if it does not result in a fast-forward update. + [NOTE] diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt index 9cd48b4859..7950eeeda4 100644 --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/technical/pack-protocol.txt @@ -1,41 +1,494 @@ -Pack transfer protocols -======================= - -There are two Pack push-pull protocols. - -upload-pack (S) | fetch/clone-pack (C) protocol: - - # Tell the puller what commits we have and what their names are - S: SHA1 name - S: ... - S: SHA1 name - S: # flush -- it's your turn - # Tell the pusher what commits we want, and what we have - C: want name - C: .. - C: want name - C: have SHA1 - C: have SHA1 - C: ... - C: # flush -- occasionally ask "had enough?" - S: NAK - C: have SHA1 - C: ... - C: have SHA1 - S: ACK - C: done - S: XXXXXXX -- packfile contents. - -send-pack | receive-pack protocol. - - # Tell the pusher what commits we have and what their names are - C: SHA1 name - C: ... - C: SHA1 name - C: # flush -- it's your turn - # Tell the puller what the pusher has - S: old-SHA1 new-SHA1 name - S: old-SHA1 new-SHA1 name - S: ... - S: # flush -- done with the list - S: XXXXXXX --- packfile contents. +Packfile transfer protocols +=========================== + +Git supports transferring data in packfiles over the ssh://, git:// and +file:// transports. There exist two sets of protocols, one for pushing +data from a client to a server and another for fetching data from a +server to a client. All three transports (ssh, git, file) use the same +protocol to transfer data. + +The processes invoked in the canonical Git implementation are 'upload-pack' +on the server side and 'fetch-pack' on the client side for fetching data; +then 'receive-pack' on the server and 'send-pack' on the client for pushing +data. The protocol functions to have a server tell a client what is +currently on the server, then for the two to negotiate the smallest amount +of data to send in order to fully update one or the other. + +Transports +---------- +There are three transports over which the packfile protocol is +initiated. The Git transport is a simple, unauthenticated server that +takes the command (almost always 'upload-pack', though Git +servers can be configured to be globally writable, in which 'receive- +pack' initiation is also allowed) with which the client wishes to +communicate and executes it and connects it to the requesting +process. + +In the SSH transport, the client just runs the 'upload-pack' +or 'receive-pack' process on the server over the SSH protocol and then +communicates with that invoked process over the SSH connection. + +The file:// transport runs the 'upload-pack' or 'receive-pack' +process locally and communicates with it over a pipe. + +Git Transport +------------- + +The Git transport starts off by sending the command and repository +on the wire using the pkt-line format, followed by a NUL byte and a +hostname paramater, terminated by a NUL byte. + + 0032git-upload-pack /project.git\0host=myserver.com\0 + +-- + git-proto-request = request-command SP pathname NUL [ host-parameter NUL ] + request-command = "git-upload-pack" / "git-receive-pack" / + "git-upload-archive" ; case sensitive + pathname = *( %x01-ff ) ; exclude NUL + host-parameter = "host=" hostname [ ":" port ] +-- + +Only host-parameter is allowed in the git-proto-request. Clients +MUST NOT attempt to send additional parameters. It is used for the +git-daemon name based virtual hosting. See --interpolated-path +option to git daemon, with the %H/%CH format characters. + +Basically what the Git client is doing to connect to an 'upload-pack' +process on the server side over the Git protocol is this: + + $ echo -e -n \ + "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" | + nc -v example.com 9418 + + +SSH Transport +------------- + +Initiating the upload-pack or receive-pack processes over SSH is +executing the binary on the server via SSH remote execution. +It is basically equivalent to running this: + + $ ssh git.example.com "git-upload-pack '/project.git'" + +For a server to support Git pushing and pulling for a given user over +SSH, that user needs to be able to execute one or both of those +commands via the SSH shell that they are provided on login. On some +systems, that shell access is limited to only being able to run those +two commands, or even just one of them. + +In an ssh:// format URI, it's absolute in the URI, so the '/' after +the host name (or port number) is sent as an argument, which is then +read by the remote git-upload-pack exactly as is, so it's effectively +an absolute path in the remote filesystem. + + git clone ssh://user@example.com/project.git + | + v + ssh user@example.com "git-upload-pack '/project.git'" + +In a "user@host:path" format URI, its relative to the user's home +directory, because the Git client will run: + + git clone user@example.com:project.git + | + v + ssh user@example.com "git-upload-pack 'project.git'" + +The exception is if a '~' is used, in which case +we execute it without the leading '/'. + + ssh://user@example.com/~alice/project.git, + | + v + ssh user@example.com "git-upload-pack '~alice/project.git'" + +A few things to remember here: + +- The "command name" is spelled with dash (e.g. git-upload-pack), but + this can be overridden by the client; + +- The repository path is always quoted with single quotes. + +Fetching Data From a Server +=========================== + +When one Git repository wants to get data that a second repository +has, the first can 'fetch' from the second. This operation determines +what data the server has that the client does not then streams that +data down to the client in packfile format. + + +Reference Discovery +------------------- + +When the client initially connects the server will immediately respond +with a listing of each reference it has (all branches and tags) along +with the object name that each reference currently points to. + + $ echo -e -n "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" | + nc -v example.com 9418 + 00887217a7c7e582c46cec22a130adf4b9d7d950fba0 HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag + 00441d3fcd5ced445d1abc402225c0b8a1299641f497 refs/heads/integration + 003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 refs/heads/master + 003cb88d2441cac0977faf98efc80305012112238d9d refs/tags/v0.9 + 003c525128480b96c89e6418b1e40909bf6c5b2d580f refs/tags/v1.0 + 003fe92df48743b7bc7d26bcaabfddde0a1e20cae47c refs/tags/v1.0^{} + 0000 + +Server SHOULD terminate each non-flush line using LF ("\n") terminator; +client MUST NOT complain if there is no terminator. + +The returned response is a pkt-line stream describing each ref and +its current value. The stream MUST be sorted by name according to +the C locale ordering. + +If HEAD is a valid ref, HEAD MUST appear as the first advertised +ref. If HEAD is not a valid ref, HEAD MUST NOT appear in the +advertisement list at all, but other refs may still appear. + +The stream MUST include capability declarations behind a NUL on the +first ref. The peeled value of a ref (that is "ref^{}") MUST be +immediately after the ref itself, if presented. A conforming server +MUST peel the ref if its an annotated tag. + +---- + advertised-refs = (no-refs / list-of-refs) + flush-pkt + + no-refs = PKT-LINE(zero-id SP "capabilities^{}" + NUL capability-list LF) + + list-of-refs = first-ref *other-ref + first-ref = PKT-LINE(obj-id SP refname + NUL capability-list LF) + + other-ref = PKT-LINE(other-tip / other-peeled) + other-tip = obj-id SP refname LF + other-peeled = obj-id SP refname "^{}" LF + + capability-list = capability *(SP capability) + capability = 1*(LC_ALPHA / DIGIT / "-" / "_") + LC_ALPHA = %x61-7A +---- + +Server and client MUST use lowercase for obj-id, both MUST treat obj-id +as case-insensitive. + +See protocol-capabilities.txt for a list of allowed server capabilities +and descriptions. + +Packfile Negotiation +-------------------- +After reference and capabilities discovery, the client can decide +to terminate the connection by sending a flush-pkt, telling the +server it can now gracefully terminate (as happens with the ls-remote +command) or it can enter the negotiation phase, where the client and +server determine what the minimal packfile necessary for transport is. + +Once the client has the initial list of references that the server +has, as well as the list of capabilities, it will begin telling the +server what objects it wants and what objects it has, so the server +can make a packfile that only contains the objects that the client needs. +The client will also send a list of the capabilities it wants to be in +effect, out of what the server said it could do with the first 'want' line. + +---- + upload-request = want-list + have-list + compute-end + + want-list = first-want + *additional-want + flush-pkt + + first-want = PKT-LINE("want" SP obj-id SP capability-list LF) + additional-want = PKT-LINE("want" SP obj-id LF) + + have-list = *have-line + have-line = PKT-LINE("have" SP obj-id LF) + compute-end = flush-pkt / PKT-LINE("done") +---- + +Clients MUST send all the obj-ids it wants from the reference +discovery phase as 'want' lines. Clients MUST send at least one +'want' command in the request body. Clients MUST NOT mention an +obj-id in a 'want' command which did not appear in the response +obtained through ref discovery. + +If client is requesting a shallow clone, it will now send a 'deepen' +line with the depth it is requesting. + +Once all the "want"s (and optional 'deepen') are transferred, +clients MUST send a flush-pkt. If the client has all the references +on the server, client flushes and disconnects. + +TODO: shallow/unshallow response and document the deepen command in the ABNF. + +Now the client will send a list of the obj-ids it has using 'have' +lines. In multi_ack mode, the canonical implementation will send up +to 32 of these at a time, then will send a flush-pkt. The canonical +implementation will skip ahead and send the next 32 immediately, +so that there is always a block of 32 "in-flight on the wire" at a +time. + +If the server reads 'have' lines, it then will respond by ACKing any +of the obj-ids the client said it had that the server also has. The +server will ACK obj-ids differently depending on which ack mode is +chosen by the client. + +In multi_ack mode: + + * the server will respond with 'ACK obj-id continue' for any common + commits. + + * once the server has found an acceptable common base commit and is + ready to make a packfile, it will blindly ACK all 'have' obj-ids + back to the client. + + * the server will then send a 'NACK' and then wait for another response + from the client - either a 'done' or another list of 'have' lines. + +In multi_ack_detailed mode: + + * the server will differentiate the ACKs where it is signaling + that it is ready to send data with 'ACK obj-id ready' lines, and + signals the identified common commits with 'ACK obj-id common' lines. + +Without either multi_ack or multi_ack_detailed: + + * upload-pack sends "ACK obj-id" on the first common object it finds. + After that it says nothing until the client gives it a "done". + + * upload-pack sends "NAK" on a flush-pkt if no common object + has been found yet. If one has been found, and thus an ACK + was already sent, its silent on the flush-pkt. + +After the client has gotten enough ACK responses that it can determine +that the server has enough information to send an efficient packfile +(in the canonical implementation, this is determined when it has received +enough ACKs that it can color everything left in the --date-order queue +as common with the server, or the --date-order queue is empty), or the +client determines that it wants to give up (in the canonical implementation, +this is determined when the client sends 256 'have' lines without getting +any of them ACKed by the server - meaning there is nothing in common and +the server should just send all it's objects), then the client will send +a 'done' command. The 'done' command signals to the server that the client +is ready to receive it's packfile data. + +However, the 256 limit *only* turns on in the canonical client +implementation if we have received at least one "ACK %s continue" +during a prior round. This helps to ensure that at least one common +ancestor is found before we give up entirely. + +Once the 'done' line is read from the client, the server will either +send a final 'ACK obj-id' or it will send a 'NAK'. The server only sends +ACK after 'done' if there is at least one common base and multi_ack or +multi_ack_detailed is enabled. The server always sends NAK after 'done' +if there is no common base found. + +Then the server will start sending it's packfile data. + +---- + server-response = *ack_multi ack / nak + ack_multi = PKT-LINE("ACK" SP obj-id ack_status LF) + ack_status = "continue" / "common" / "ready" + ack = PKT-LINE("ACK SP obj-id LF) + nak = PKT-LINE("NAK" LF) +---- + +A simple clone may look like this (with no 'have' lines): + +---- + C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d\0multi_ack \ + side-band-64k ofs-delta\n + C: 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe\n + C: 0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a\n + C: 0032want 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n + C: 0032want 74730d410fcb6603ace96f1dc55ea6196122532d\n + C: 0000 + C: 0009done\n + + S: 0008NAK\n + S: [PACKFILE] +---- + +An incremental update (fetch) response might look like this: + +---- + C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d\0multi_ack \ + side-band-64k ofs-delta\n + C: 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe\n + C: 0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a\n + C: 0000 + C: 0032have 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n + C: [30 more have lines] + C: 0032have 74730d410fcb6603ace96f1dc55ea6196122532d\n + C: 0000 + + S: 003aACK 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01 continue\n + S: 003aACK 74730d410fcb6603ace96f1dc55ea6196122532d continue\n + S: 0008NAK\n + + C: 0009done\n + + S: 003aACK 74730d410fcb6603ace96f1dc55ea6196122532d\n + S: [PACKFILE] +---- + + +Packfile Data +------------- + +Now that the client and server have finished negotiation about what +the minimal amount of data that needs to be sent to the client is, the server +will construct and send the required data in packfile format. + +See pack-format.txt for what the packfile itself actually looks like. + +If 'side-band' or 'side-band-64k' capabilities have been specified by +the client, the server will send the packfile data multiplexed. + +Each packet starting with the packet-line length of the amount of data +that follows, followed by a single byte specifying the sideband the +following data is coming in on. + +In 'side-band' mode, it will send up to 999 data bytes plus 1 control +code, for a total of up to 1000 bytes in a pkt-line. In 'side-band-64k' +mode it will send up to 65519 data bytes plus 1 control code, for a +total of up to 65520 bytes in a pkt-line. + +The sideband byte will be a '1', '2' or a '3'. Sideband '1' will contain +packfile data, sideband '2' will be used for progress information that the +client will generally print to stderr and sideband '3' is used for error +information. + +If no 'side-band' capability was specified, the server will stream the +entire packfile without multiplexing. + + +Pushing Data To a Server +======================== + +Pushing data to a server will invoke the 'receive-pack' process on the +server, which will allow the client to tell it which references it should +update and then send all the data the server will need for those new +references to be complete. Once all the data is received and validated, +the server will then update its references to what the client specified. + +Authentication +-------------- + +The protocol itself contains no authentication mechanisms. That is to be +handled by the transport, such as SSH, before the 'receive-pack' process is +invoked. If 'receive-pack' is configured over the Git transport, those +repositories will be writable by anyone who can access that port (9418) as +that transport is unauthenticated. + +Reference Discovery +------------------- + +The reference discovery phase is done nearly the same way as it is in the +fetching protocol. Each reference obj-id and name on the server is sent +in packet-line format to the client, followed by a flush-pkt. The only +real difference is that the capability listing is different - the only +possible values are 'report-status', 'delete-refs' and 'ofs-delta'. + +Reference Update Request and Packfile Transfer +---------------------------------------------- + +Once the client knows what references the server is at, it can send a +list of reference update requests. For each reference on the server +that it wants to update, it sends a line listing the obj-id currently on +the server, the obj-id the client would like to update it to and the name +of the reference. + +This list is followed by a flush-pkt and then the packfile that should +contain all the objects that the server will need to complete the new +references. + +---- + update-request = command-list [pack-file] + + command-list = PKT-LINE(command NUL capability-list LF) + *PKT-LINE(command LF) + flush-pkt + + command = create / delete / update + create = zero-id SP new-id SP name + delete = old-id SP zero-id SP name + update = old-id SP new-id SP name + + old-id = obj-id + new-id = obj-id + + pack-file = "PACK" 28*(OCTET) +---- + +If the receiving end does not support delete-refs, the sending end MUST +NOT ask for delete command. + +The pack-file MUST NOT be sent if the only command used is 'delete'. + +A pack-file MUST be sent if either create or update command is used, +even if the server already has all the necessary objects. In this +case the client MUST send an empty pack-file. The only time this +is likely to happen is if the client is creating +a new branch or a tag that points to an existing obj-id. + +The server will receive the packfile, unpack it, then validate each +reference that is being updated that it hasn't changed while the request +was being processed (the obj-id is still the same as the old-id), and +it will run any update hooks to make sure that the update is acceptable. +If all of that is fine, the server will then update the references. + +Report Status +------------- + +After receiving the pack data from the sender, the receiver sends a +report if 'report-status' capability is in effect. +It is a short listing of what happened in that update. It will first +list the status of the packfile unpacking as either 'unpack ok' or +'unpack [error]'. Then it will list the status for each of the references +that it tried to update. Each line is either 'ok [refname]' if the +update was successful, or 'ng [refname] [error]' if the update was not. + +---- + report-status = unpack-status + 1*(command-status) + flush-pkt + + unpack-status = PKT-LINE("unpack" SP unpack-result LF) + unpack-result = "ok" / error-msg + + command-status = command-ok / command-fail + command-ok = PKT-LINE("ok" SP refname LF) + command-fail = PKT-LINE("ng" SP refname SP error-msg LF) + + error-msg = 1*(OCTECT) ; where not "ok" +---- + +Updates can be unsuccessful for a number of reasons. The reference can have +changed since the reference discovery phase was originally sent, meaning +someone pushed in the meantime. The reference being pushed could be a +non-fast-forward reference and the update hooks or configuration could be +set to not allow that, etc. Also, some references can be updated while others +can be rejected. + +An example client/server communication might look like this: + +---- + S: 007c74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/local\0report-status delete-refs ofs-delta\n + S: 003e7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug\n + S: 003f74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/master\n + S: 003f74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/team\n + S: 0000 + + C: 003e7d1665144a3a975c05f1f43902ddaf084e784dbe 74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/debug\n + C: 003e74730d410fcb6603ace96f1dc55ea6196122532d 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a refs/heads/master\n + C: 0000 + C: [PACKDATA] + + S: 000aunpack ok\n + S: 0014ok refs/heads/debug\n + S: 0026ng refs/heads/master non-fast-forward\n +---- diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt new file mode 100644 index 0000000000..1892d3eeac --- /dev/null +++ b/Documentation/technical/protocol-capabilities.txt @@ -0,0 +1,187 @@ +Git Protocol Capabilities +========================= + +Servers SHOULD support all capabilities defined in this document. + +On the very first line of the initial server response of either +receive-pack and upload-pack the first reference is followed by +a NUL byte and then a list of space delimited server capabilities. +These allow the server to declare what it can and cannot support +to the client. + +Client will then send a space separated list of capabilities it wants +to be in effect. The client MUST NOT ask for capabilities the server +did not say it supports. + +Server MUST diagnose and abort if capabilities it does not understand +was sent. Server MUST NOT ignore capabilities that client requested +and server advertised. As a consequence of these rules, server MUST +NOT advertise capabilities it does not understand. + +The 'report-status' and 'delete-refs' capabilities are sent and +recognized by the receive-pack (push to server) process. + +The 'ofs-delta' capability is sent and recognized by both upload-pack +and receive-pack protocols. + +All other capabilities are only recognized by the upload-pack (fetch +from server) process. + +multi_ack +--------- + +The 'multi_ack' capability allows the server to return "ACK obj-id +continue" as soon as it finds a commit that it can use as a common +base, between the client's wants and the client's have set. + +By sending this early, the server can potentially head off the client +from walking any further down that particular branch of the client's +repository history. The client may still need to walk down other +branches, sending have lines for those, until the server has a +complete cut across the DAG, or the client has said "done". + +Without multi_ack, a client sends have lines in --date-order until +the server has found a common base. That means the client will send +have lines that are already known by the server to be common, because +they overlap in time with another branch that the server hasn't found +a common base on yet. + +For example suppose the client has commits in caps that the server +doesn't and the server has commits in lower case that the client +doesn't, as in the following diagram: + + +---- u ---------------------- x + / +----- y + / / + a -- b -- c -- d -- E -- F + \ + +--- Q -- R -- S + +If the client wants x,y and starts out by saying have F,S, the server +doesn't know what F,S is. Eventually the client says "have d" and +the server sends "ACK d continue" to let the client know to stop +walking down that line (so don't send c-b-a), but its not done yet, +it needs a base for x. The client keeps going with S-R-Q, until a +gets reached, at which point the server has a clear base and it all +ends. + +Without multi_ack the client would have sent that c-b-a chain anyway, +interleaved with S-R-Q. + +thin-pack +--------- + +This capability means that the server can send a 'thin' pack, a pack +which does not contain base objects; if those base objects are available +on client side. Client requests 'thin-pack' capability when it +understands how to "thicken" it by adding required delta bases making +it self-contained. + +Client MUST NOT request 'thin-pack' capability if it cannot turn a thin +pack into a self-contained pack. + + +side-band, side-band-64k +------------------------ + +This capability means that server can send, and client understand multiplexed +progress reports and error info interleaved with the packfile itself. + +These two options are mutually exclusive. A modern client always +favors 'side-band-64k'. + +Either mode indicates that the packfile data will be streamed broken +up into packets of up to either 1000 bytes in the case of 'side_band', +or 65520 bytes in the case of 'side_band_64k'. Each packet is made up +of a leading 4-byte pkt-line length of how much data is in the packet, +followed by a 1-byte stream code, followed by the actual data. + +The stream code can be one of: + + 1 - pack data + 2 - progress messages + 3 - fatal error message just before stream aborts + +The "side-band-64k" capability came about as a way for newer clients +that can handle much larger packets to request packets that are +actually crammed nearly full, while maintaining backward compatibility +for the older clients. + +Further, with side-band and its up to 1000-byte messages, it's actually +999 bytes of payload and 1 byte for the stream code. With side-band-64k, +same deal, you have up to 65519 bytes of data and 1 byte for the stream +code. + +The client MUST send only maximum of one of "side-band" and "side- +band-64k". Server MUST diagnose it as an error if client requests +both. + +ofs-delta +--------- + +Server can send, and client understand PACKv2 with delta refering to +its base by position in pack rather than by an obj-id. That is, they can +send/read OBJ_OFS_DELTA (aka type 6) in a packfile. + +shallow +------- + +This capability adds "deepen", "shallow" and "unshallow" commands to +the fetch-pack/upload-pack protocol so clients can request shallow +clones. + +no-progress +----------- + +The client was started with "git clone -q" or something, and doesn't +want that side band 2. Basically the client just says "I do not +wish to receive stream 2 on sideband, so do not send it to me, and if +you did, I will drop it on the floor anyway". However, the sideband +channel 3 is still used for error responses. + +include-tag +----------- + +The 'include-tag' capability is about sending annotated tags if we are +sending objects they point to. If we pack an object to the client, and +a tag object points exactly at that object, we pack the tag object too. +In general this allows a client to get all new annotated tags when it +fetches a branch, in a single network connection. + +Clients MAY always send include-tag, hardcoding it into a request when +the server advertises this capability. The decision for a client to +request include-tag only has to do with the client's desires for tag +data, whether or not a server had advertised objects in the +refs/tags/* namespace. + +Servers MUST pack the tags if their referrant is packed and the client +has requested include-tags. + +Clients MUST be prepared for the case where a server has ignored +include-tag and has not actually sent tags in the pack. In such +cases the client SHOULD issue a subsequent fetch to acquire the tags +that include-tag would have otherwise given the client. + +The server SHOULD send include-tag, if it supports it, regardless +of whether or not there are tags available. + +report-status +------------- + +The upload-pack process can receive a 'report-status' capability, +which tells it that the client wants a report of what happened after +a packfile upload and reference update. If the pushing client requests +this capability, after unpacking and updating references the server +will respond with whether the packfile unpacked successfully and if +each reference was updated successfully. If any of those were not +successful, it will send back an error message. See pack-protocol.txt +for example messages. + +delete-refs +----------- + +If the server sends back the 'delete-refs' capability, it means that +it is capable of accepting an zero-id value as the target +value of a reference update. It is not sent back by the client, it +simply informs the client that it can be sent zero-id values +to delete references. diff --git a/Documentation/technical/protocol-common.txt b/Documentation/technical/protocol-common.txt new file mode 100644 index 0000000000..d30a1b9510 --- /dev/null +++ b/Documentation/technical/protocol-common.txt @@ -0,0 +1,96 @@ +Documentation Common to Pack and Http Protocols +=============================================== + +ABNF Notation +------------- + +ABNF notation as described by RFC 5234 is used within the protocol documents, +except the following replacement core rules are used: +---- + HEXDIG = DIGIT / "a" / "b" / "c" / "d" / "e" / "f" +---- + +We also define the following common rules: +---- + NUL = %x00 + zero-id = 40*"0" + obj-id = 40*(HEXDIGIT) + + refname = "HEAD" + refname /= "refs/" <see discussion below> +---- + +A refname is a hierarchical octet string beginning with "refs/" and +not violating the 'git-check-ref-format' command's validation rules. +More specifically, they: + +. They can include slash `/` for hierarchical (directory) + grouping, but no slash-separated component can begin with a + dot `.`. + +. They must contain at least one `/`. This enforces the presence of a + category like `heads/`, `tags/` etc. but the actual names are not + restricted. + +. They cannot have two consecutive dots `..` anywhere. + +. They cannot have ASCII control characters (i.e. bytes whose + values are lower than \040, or \177 `DEL`), space, tilde `~`, + caret `{caret}`, colon `:`, question-mark `?`, asterisk `*`, + or open bracket `[` anywhere. + +. They cannot end with a slash `/` nor a dot `.`. + +. They cannot end with the sequence `.lock`. + +. They cannot contain a sequence `@{`. + +. They cannot contain a `\\`. + + +pkt-line Format +--------------- + +Much (but not all) of the payload is described around pkt-lines. + +A pkt-line is a variable length binary string. The first four bytes +of the line, the pkt-len, indicates the total length of the line, +in hexadecimal. The pkt-len includes the 4 bytes used to contain +the length's hexadecimal representation. + +A pkt-line MAY contain binary data, so implementors MUST ensure +pkt-line parsing/formatting routines are 8-bit clean. + +A non-binary line SHOULD BE terminated by an LF, which if present +MUST be included in the total length. + +The maximum length of a pkt-line's data component is 65520 bytes. +Implementations MUST NOT send pkt-line whose length exceeds 65524 +(65520 bytes of payload + 4 bytes of length data). + +Implementations SHOULD NOT send an empty pkt-line ("0004"). + +A pkt-line with a length field of 0 ("0000"), called a flush-pkt, +is a special case and MUST be handled differently than an empty +pkt-line ("0004"). + +---- + pkt-line = data-pkt / flush-pkt + + data-pkt = pkt-len pkt-payload + pkt-len = 4*(HEXDIG) + pkt-payload = (pkt-len - 4)*(OCTET) + + flush-pkt = "0000" +---- + +Examples (as C-style strings): + +---- + pkt-line actual value + --------------------------------- + "0006a\n" "a\n" + "0005a" "a" + "000bfoobar\n" "foobar\n" + "0004" "" +---- diff --git a/Documentation/technical/racy-git.txt b/Documentation/technical/racy-git.txt index 48bb97f0b1..53aa0c82c2 100644 --- a/Documentation/technical/racy-git.txt +++ b/Documentation/technical/racy-git.txt @@ -42,10 +42,12 @@ compared, but this is not enabled by default because this member is not stable on network filesystems. With `USE_NSEC` compile-time option, `st_mtim.tv_nsec` and `st_ctim.tv_nsec` members are also compared, but this is not enabled by default -because the value of this member becomes meaningless once the -inode is evicted from the inode cache on filesystems that do not -store it on disk. - +because in-core timestamps can have finer granularity than +on-disk timestamps, resulting in meaningless changes when an +inode is evicted from the inode cache. See commit 8ce13b0 +of git://git.kernel.org/pub/scm/linux/kernel/git/tglx/history.git +([PATCH] Sync in core time granuality with filesystems, +2005-01-04). Racy git -------- diff --git a/Documentation/urls.txt b/Documentation/urls.txt index 5355ebc0f3..d813ceb723 100644 --- a/Documentation/urls.txt +++ b/Documentation/urls.txt @@ -67,3 +67,21 @@ For example, with this: a URL like "work:repo.git" or like "host.xz:/path/to/repo.git" will be rewritten in any context that takes a URL to be "git://git.host.xz/repo.git". +If you want to rewrite URLs for push only, you can create a +configuration section of the form: + +------------ + [url "<actual url base>"] + pushInsteadOf = <other url base> +------------ + +For example, with this: + +------------ + [url "ssh://example.org/"] + pushInsteadOf = git://example.org/ +------------ + +a URL like "git://example.org/path/to/repo.git" will be rewritten to +"ssh://example.org/path/to/repo.git" for pushes, but pulls will still +use the original URL. diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 67ebffa568..269ec475e6 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -1384,7 +1384,7 @@ were merged. However, if the current branch is a descendant of the other--so every commit present in the one is already contained in the other--then git -just performs a "fast forward"; the head of the current branch is moved +just performs a "fast-forward"; the head of the current branch is moved forward to point at the head of the merged-in branch, without any new commits being created. @@ -1719,7 +1719,7 @@ producing a default commit message documenting the branch and repository that you pulled from. (But note that no such commit will be created in the case of a -<<fast-forwards,fast forward>>; instead, your branch will just be +<<fast-forwards,fast-forward>>; instead, your branch will just be updated to point to the latest commit from the upstream branch.) The `git pull` command can also be given "." as the "remote" repository, @@ -1943,7 +1943,7 @@ $ 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 +<<fast-forwards,fast-forward>>; see the following section for details on handling this case. Note that the target of a "push" is normally a @@ -1976,7 +1976,7 @@ details. What to do when a push fails ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If a push would not result in a <<fast-forwards,fast forward>> of the +If a push would not result in a <<fast-forwards,fast-forward>> of the remote branch, then it will fail with an error like: ------------------------------------------------- @@ -2115,7 +2115,7 @@ $ git checkout release && git pull Important note! If you have any local changes in these branches, then this merge will create a commit object in the history (with no local -changes git will simply do a "Fast forward" merge). Many people dislike +changes git will simply do a "fast-forward" merge). Many people dislike the "noise" that this creates in the Linux history, so you should avoid doing this capriciously in the "release" branch, as these noisy commits will become part of the permanent history when you ask Linus to pull @@ -2729,9 +2729,9 @@ In the previous example, when updating an existing branch, "git fetch" checks to make sure that the most recent commit on the remote branch is a descendant of the most recent commit on your copy of the branch before updating your copy of the branch to point at the new -commit. Git calls this process a <<fast-forwards,fast forward>>. +commit. Git calls this process a <<fast-forwards,fast-forward>>. -A fast forward looks something like this: +A fast-forward looks something like this: ................................................ o--o--o--o <-- old head of the branch diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index d7d9a9a063..710d361233 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.4.GIT +DEF_VER=v1.6.5.GIT LF=' ' @@ -13,6 +13,10 @@ that uses $prefix, the built results have some paths encoded, which are derived from $prefix, so "make all; make prefix=/usr install" would not work. +The beginning of the Makefile documents many variables that affect the way +git is built. You can override them either from the command line, or in a +config.mak file. + Alternatively you can use autoconf generated ./configure script to set up install paths (via config.mak.autogen), so you can write instead @@ -48,32 +52,42 @@ Issues of note: export GIT_EXEC_PATH PATH GITPERLLIB - Git is reasonably self-sufficient, but does depend on a few external - programs and libraries: + programs and libraries. Git can be used without most of them by adding + the approriate "NO_<LIBRARY>=YesPlease" to the make command line or + config.mak file. - "zlib", the compression library. Git won't build without it. - - "openssl". Unless you specify otherwise, you'll get the SHA1 - library from here. + - "ssh" is used to push and pull over the net. - If you don't have openssl, you can use one of the SHA1 libraries - that come with git (git includes the one from Mozilla, and has - its own PowerPC and ARM optimized ones too - see the Makefile). + - A POSIX-compliant shell is required to run many scripts needed + for everyday use (e.g. "bisect", "pull"). - - libcurl library; git-http-fetch and git-fetch use them. You - might also want the "curl" executable for debugging purposes. - If you do not use http transfer, you are probably OK if you - do not have them. + - "Perl" is needed to use some of the features (e.g. preparing a + partial commit using "git add -i/-p", interacting with svn + repositories with "git svn"). If you can live without these, use + NO_PERL. - - expat library; git-http-push uses it for remote lock - management over DAV. Similar to "curl" above, this is optional. + - "openssl" library is used by git-imap-send to use IMAP over SSL. + If you don't need it, use NO_OPENSSL. - - "wish", the Tcl/Tk windowing shell is used in gitk to show the - history graphically, and in git-gui. + By default, git uses OpenSSL for SHA1 but it will use it's own + library (inspired by Mozilla's) with either NO_OPENSSL or + BLK_SHA1. Also included is a version optimized for PowerPC + (PPC_SHA1). + + - "libcurl" library is used by git-http-fetch and git-fetch. You + might also want the "curl" executable for debugging purposes. + If you do not use http:// or https:// repositories, you do not + have to have them (use NO_CURL). - - "ssh" is used to push and pull over the net + - "expat" library; git-http-push uses it for remote lock + management over DAV. Similar to "curl" above, this is optional + (with NO_EXPAT). - - "perl" and POSIX-compliant shells are needed to use most of - the bare-bones Porcelainish scripts. + - "wish", the Tcl/Tk windowing shell is used in gitk to show the + history graphically, and in git-gui. If you don't want gitk or + git-gui, you can use NO_TCLTK. - Some platform specific issues are dealt with Makefile rules, but depending on your specific installation, you may not @@ -91,7 +91,9 @@ all:: # Define PPC_SHA1 environment variable when running make to make use of # a bundled SHA1 routine optimized for PowerPC. # -# Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin). +# Define NEEDS_CRYPTO_WITH_SSL if you need -lcrypto when using -lssl (Darwin). +# +# Define NEEDS_SSL_WITH_CRYPTO if you need -lssl when using -lcrypto (Darwin). # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). # @@ -151,7 +153,15 @@ all:: # # Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8 # -# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72. +# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72 +# (not v1.73 or v1.71). +# +# Define ASCIIDOC_NO_ROFF if your DocBook XSL escapes raw roff directives +# (versions 1.72 and later and 1.68.1 and earlier). +# +# Define GNU_ROFF if your target system uses GNU groff. This forces +# apostrophes to be ASCII so that cut&pasting examples to the shell +# will work. # # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's # MakeMaker (e.g. using ActiveState under Cygwin). @@ -194,6 +204,18 @@ all:: # memory allocators with the nedmalloc allocator written by Niall Douglas. # # Define NO_REGEX if you have no or inferior regex support in your C library. +# +# Define DEFAULT_PAGER to a sensible pager command (defaults to "less") if +# you want to use something different. The value will be interpreted by the +# shell at runtime when it is used. +# +# Define DEFAULT_EDITOR to a sensible editor command (defaults to "vi") if you +# want to use something different. The value will be interpreted by the shell +# if necessary when it is used. Examples: +# +# DEFAULT_EDITOR='~/bin/vi', +# DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR', +# DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork' GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -206,6 +228,12 @@ uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not') uname_V := $(shell sh -c 'uname -v 2>/dev/null || echo not') +ifdef MSVC + # avoid the MingW and Cygwin configuration sections + uname_S := Windows + uname_O := Windows +endif + # CFLAGS and LDFLAGS are for the users to override from the command line. CFLAGS = -g -O2 -Wall @@ -315,6 +343,7 @@ SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-mergetool--lib.sh +SCRIPT_SH += git-notes.sh SCRIPT_SH += git-parse-remote.sh SCRIPT_SH += git-pull.sh SCRIPT_SH += git-quiltimport.sh @@ -348,6 +377,7 @@ EXTRA_PROGRAMS = PROGRAMS += $(EXTRA_PROGRAMS) PROGRAMS += git-fast-import$X PROGRAMS += git-hash-object$X +PROGRAMS += git-imap-send$X PROGRAMS += git-index-pack$X PROGRAMS += git-merge-index$X PROGRAMS += git-merge-tree$X @@ -359,6 +389,7 @@ PROGRAMS += git-show-index$X PROGRAMS += git-unpack-file$X PROGRAMS += git-upload-pack$X PROGRAMS += git-var$X +PROGRAMS += git-http-backend$X # List built-in command $C whose implementation cmd_$C() is not in # builtin-$C.o but is linked in as part of some other command. @@ -378,7 +409,8 @@ BUILT_INS += git-stage$X BUILT_INS += git-status$X BUILT_INS += git-whatchanged$X -# what 'all' will build and 'install' will install, in gitexecdir +# what 'all' will build and 'install' will install in gitexecdir, +# excluding programs for built-in commands ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) # what 'all' will build but not install in gitexecdir @@ -397,6 +429,7 @@ export PERL_PATH LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a +LIB_H += advice.h LIB_H += archive.h LIB_H += attr.h LIB_H += blob.h @@ -404,6 +437,7 @@ LIB_H += builtin.h LIB_H += cache.h LIB_H += cache-tree.h LIB_H += commit.h +LIB_H += compat/bswap.h LIB_H += compat/cygwin.h LIB_H += compat/mingw.h LIB_H += csum-file.h @@ -424,6 +458,7 @@ LIB_H += ll-merge.h LIB_H += log-tree.h LIB_H += mailmap.h LIB_H += merge-recursive.h +LIB_H += notes.h LIB_H += object.h LIB_H += pack.h LIB_H += pack-refs.h @@ -444,6 +479,7 @@ LIB_H += sideband.h LIB_H += sigchain.h LIB_H += strbuf.h LIB_H += string-list.h +LIB_H += submodule.h LIB_H += tag.h LIB_H += transport.h LIB_H += tree.h @@ -454,6 +490,7 @@ LIB_H += utf8.h LIB_H += wt-status.h LIB_OBJS += abspath.o +LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.o LIB_OBJS += archive.o @@ -507,6 +544,7 @@ LIB_OBJS += match-trees.o LIB_OBJS += merge-file.o LIB_OBJS += merge-recursive.o LIB_OBJS += name-hash.o +LIB_OBJS += notes.o LIB_OBJS += object.o LIB_OBJS += pack-check.o LIB_OBJS += pack-refs.o @@ -541,10 +579,12 @@ LIB_OBJS += sideband.o LIB_OBJS += sigchain.o LIB_OBJS += strbuf.o LIB_OBJS += string-list.o +LIB_OBJS += submodule.o LIB_OBJS += symlinks.o LIB_OBJS += tag.o LIB_OBJS += trace.o LIB_OBJS += transport.o +LIB_OBJS += transport-helper.o LIB_OBJS += tree-diff.o LIB_OBJS += tree.o LIB_OBJS += tree-walk.o @@ -584,7 +624,6 @@ BUILTIN_OBJS += builtin-diff-index.o BUILTIN_OBJS += builtin-diff-tree.o BUILTIN_OBJS += builtin-diff.o BUILTIN_OBJS += builtin-fast-export.o -BUILTIN_OBJS += builtin-fetch--tool.o BUILTIN_OBJS += builtin-fetch-pack.o BUILTIN_OBJS += builtin-fetch.o BUILTIN_OBJS += builtin-fmt-merge-msg.o @@ -704,6 +743,7 @@ ifeq ($(uname_S),SCO_SV) TAR = gtar endif ifeq ($(uname_S),Darwin) + NEEDS_CRYPTO_WITH_SSL = YesPlease NEEDS_SSL_WITH_CRYPTO = YesPlease NEEDS_LIBICONV = YesPlease ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2) @@ -727,6 +767,7 @@ ifeq ($(uname_S),SunOS) NO_MKSTEMPS = YesPlease NO_REGEX = YesPlease NO_EXTERNAL_GREP = YesPlease + THREADED_DELTA_SEARCH = YesPlease ifeq ($(uname_R),5.7) NEEDS_RESOLV = YesPlease NO_IPV6 = YesPlease @@ -770,6 +811,8 @@ ifeq ($(uname_O),Cygwin) NO_MMAP = YesPlease NO_IPV6 = YesPlease X = .exe + COMPAT_OBJS += compat/cygwin.o + UNRELIABLE_FSTAT = UnfortunatelyYes endif ifeq ($(uname_S),FreeBSD) NEEDS_LIBICONV = YesPlease @@ -833,11 +876,18 @@ ifeq ($(uname_S),IRIX) NO_MEMMEM = YesPlease NO_MKSTEMPS = YesPlease NO_MKDTEMP = YesPlease + # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads + # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set), + # git dies with a segmentation fault when trying to access the first + # entry of a reflog. The conservative choice is made to always set + # NO_MMAP. If you suspect that your compiler is not affected by this + # issue, comment out the NO_MMAP statement. NO_MMAP = YesPlease NO_EXTERNAL_GREP = UnfortunatelyYes SNPRINTF_RETURNS_BOGUS = YesPlease SHELL_PATH = /usr/gnu/bin/bash NEEDS_LIBGEN = YesPlease + THREADED_DELTA_SEARCH = YesPlease endif ifeq ($(uname_S),IRIX64) NO_SETENV=YesPlease @@ -846,11 +896,18 @@ ifeq ($(uname_S),IRIX64) NO_MEMMEM = YesPlease NO_MKSTEMPS = YesPlease NO_MKDTEMP = YesPlease + # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads + # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set), + # git dies with a segmentation fault when trying to access the first + # entry of a reflog. The conservative choice is made to always set + # NO_MMAP. If you suspect that your compiler is not affected by this + # issue, comment out the NO_MMAP statement. NO_MMAP = YesPlease NO_EXTERNAL_GREP = UnfortunatelyYes SNPRINTF_RETURNS_BOGUS = YesPlease SHELL_PATH=/usr/gnu/bin/bash NEEDS_LIBGEN = YesPlease + THREADED_DELTA_SEARCH = YesPlease endif ifeq ($(uname_S),HP-UX) NO_IPV6=YesPlease @@ -865,14 +922,64 @@ ifeq ($(uname_S),HP-UX) NO_SYS_SELECT_H = YesPlease SNPRINTF_RETURNS_BOGUS = YesPlease endif -ifneq (,$(findstring CYGWIN,$(uname_S))) - COMPAT_OBJS += compat/cygwin.o +ifeq ($(uname_S),Windows) + GIT_VERSION := $(GIT_VERSION).MSVC + pathsep = ; + NO_PREAD = YesPlease + NEEDS_CRYPTO_WITH_SSL = YesPlease + NO_LIBGEN_H = YesPlease + NO_SYMLINK_HEAD = YesPlease + NO_IPV6 = YesPlease + NO_SETENV = YesPlease + NO_UNSETENV = YesPlease + NO_STRCASESTR = YesPlease + NO_STRLCPY = YesPlease + NO_MEMMEM = YesPlease + # NEEDS_LIBICONV = YesPlease + NO_ICONV = YesPlease + NO_C99_FORMAT = YesPlease + NO_STRTOUMAX = YesPlease + NO_STRTOULL = YesPlease + NO_MKDTEMP = YesPlease + NO_MKSTEMPS = YesPlease + SNPRINTF_RETURNS_BOGUS = YesPlease + NO_SVN_TESTS = YesPlease + NO_PERL_MAKEMAKER = YesPlease + RUNTIME_PREFIX = YesPlease + NO_POSIX_ONLY_PROGRAMS = YesPlease + NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + NO_NSEC = YesPlease + USE_WIN32_MMAP = YesPlease + # USE_NED_ALLOCATOR = YesPlease UNRELIABLE_FSTAT = UnfortunatelyYes + OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo + NO_REGEX = YesPlease + NO_CURL = YesPlease + NO_PTHREADS = YesPlease + BLK_SHA1 = YesPlease + + CC = compat/vcbuild/scripts/clink.pl + AR = compat/vcbuild/scripts/lib.pl + CFLAGS = + BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE + COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o + COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -DSTRIP_EXTENSION=\".exe\" + BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib + EXTLIBS = advapi32.lib shell32.lib wininet.lib ws2_32.lib + lib = +ifndef DEBUG + BASIC_CFLAGS += -GL -Os -MT + BASIC_LDFLAGS += -LTCG + AR += -LTCG +else + BASIC_CFLAGS += -Zi -MTd +endif + X = .exe endif ifneq (,$(findstring MINGW,$(uname_S))) pathsep = ; NO_PREAD = YesPlease - NO_OPENSSL = YesPlease + NEEDS_CRYPTO_WITH_SSL = YesPlease NO_LIBGEN_H = YesPlease NO_SYMLINK_HEAD = YesPlease NO_IPV6 = YesPlease @@ -899,6 +1006,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) UNRELIABLE_FSTAT = UnfortunatelyYes OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo NO_REGEX = YesPlease + BLK_SHA1 = YesPlease COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o @@ -970,9 +1078,7 @@ else else CURL_LIBCURL = -lcurl endif - BUILTIN_OBJS += builtin-http-fetch.o - EXTLIBS += $(CURL_LIBCURL) - LIB_OBJS += http.o http-walker.o + PROGRAMS += git-remote-curl$X git-http-fetch$X curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p) ifeq "$(curl_check)" "070908" ifndef NO_EXPAT @@ -997,7 +1103,6 @@ EXTLIBS += -lz ifndef NO_POSIX_ONLY_PROGRAMS PROGRAMS += git-daemon$X - PROGRAMS += git-imap-send$X endif ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl @@ -1007,6 +1112,9 @@ ifndef NO_OPENSSL else OPENSSL_LINK = endif + ifdef NEEDS_CRYPTO_WITH_SSL + OPENSSL_LINK += -lcrypto + endif else BASIC_CFLAGS += -DNO_OPENSSL BLK_SHA1 = 1 @@ -1243,6 +1351,7 @@ ifndef V QUIET_LINK = @echo ' ' LINK $@; QUIET_BUILT_IN = @echo ' ' BUILTIN $@; QUIET_GEN = @echo ' ' GEN $@; + QUIET_LNCP = @echo ' ' LN/CP $@; QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir @@ -1281,6 +1390,22 @@ BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \ $(COMPAT_CFLAGS) LIB_OBJS += $(COMPAT_OBJS) +# Quote for C + +ifdef DEFAULT_EDITOR +DEFAULT_EDITOR_CQ = "$(subst ",\",$(subst \,\\,$(DEFAULT_EDITOR)))" +DEFAULT_EDITOR_CQ_SQ = $(subst ','\'',$(DEFAULT_EDITOR_CQ)) + +BASIC_CFLAGS += -DDEFAULT_EDITOR='$(DEFAULT_EDITOR_CQ_SQ)' +endif + +ifdef DEFAULT_PAGER +DEFAULT_PAGER_CQ = "$(subst ",\",$(subst \,\\,$(DEFAULT_PAGER)))" +DEFAULT_PAGER_CQ_SQ = $(subst ','\'',$(DEFAULT_PAGER_CQ)) + +BASIC_CFLAGS += -DDEFAULT_PAGER='$(DEFAULT_PAGER_CQ_SQ)' +endif + ALL_CFLAGS += $(BASIC_CFLAGS) ALL_LDFLAGS += $(BASIC_LDFLAGS) @@ -1293,7 +1418,7 @@ SHELL = $(SHELL_PATH) all:: shell_compatibility_test $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS ifneq (,$X) - $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$p' -ef '$p$X' || $(RM) '$p';) + $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';) endif all:: @@ -1317,7 +1442,7 @@ strip: $(PROGRAMS) git$X git.o: git.c common-cmds.h GIT-CFLAGS $(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \ '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ - $(ALL_CFLAGS) -c $(filter %.c,$^) + $(ALL_CFLAGS) -o $@ -c $(filter %.c,$^) git$X: git.o $(BUILTIN_OBJS) $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \ @@ -1470,12 +1595,21 @@ git-imap-send$X: imap-send.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) -http.o http-walker.o http-push.o transport.o: http.h +http.o http-walker.o http-push.o: http.h +http.o http-walker.o: $(LIB_H) + +git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ + $(LIBS) $(CURL_LIBCURL) git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) +git-remote-curl$X: remote-curl.o http.o http-walker.o $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ + $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) + $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)) git.o: $(LIB_H) $(wildcard */*.h) builtin-revert.o wt-status.o: wt-status.h @@ -1708,7 +1842,10 @@ dist: git.spec git-archive$(X) configure gzip -f -9 $(GIT_TARNAME).tar rpm: dist - $(RPMBUILD) -ta $(GIT_TARNAME).tar.gz + $(RPMBUILD) \ + --define "_source_filedigest_algorithm md5" \ + --define "_binary_filedigest_algorithm md5" \ + -ta $(GIT_TARNAME).tar.gz htmldocs = git-htmldocs-$(GIT_VERSION) manpages = git-manpages-$(GIT_VERSION) @@ -1736,7 +1873,7 @@ distclean: clean $(RM) configure clean: - $(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \ + $(RM) *.o block-sha1/*.o arm/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \ $(LIB_FILE) $(XDIFF_LIB) $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) @@ -37,7 +37,7 @@ CVS users may also want to read Documentation/gitcvs-migration.txt ("man gitcvs-migration" or "git help cvs-migration" if git is installed). -Many Git online resources are accessible from http://git.or.cz/ +Many Git online resources are accessible from http://git-scm.com/ including full documentation and Git related tools. The user discussion and development of Git take place on the Git @@ -1 +1 @@ -Documentation/RelNotes-1.6.5.txt
\ No newline at end of file +Documentation/RelNotes-1.6.6.txt
\ No newline at end of file diff --git a/advice.c b/advice.c new file mode 100644 index 0000000000..ae4b1e81df --- /dev/null +++ b/advice.c @@ -0,0 +1,27 @@ +#include "cache.h" + +int advice_push_nonfastforward = 1; +int advice_status_hints = 1; + +static struct { + const char *name; + int *preference; +} advice_config[] = { + { "pushnonfastforward", &advice_push_nonfastforward }, + { "statushints", &advice_status_hints }, +}; + +int git_default_advice_config(const char *var, const char *value) +{ + const char *k = skip_prefix(var, "advice."); + int i; + + for (i = 0; i < ARRAY_SIZE(advice_config); i++) { + if (strcmp(k, advice_config[i].name)) + continue; + *advice_config[i].preference = git_config_bool(var, value); + return 0; + } + + return 0; +} diff --git a/advice.h b/advice.h new file mode 100644 index 0000000000..e9df8e026c --- /dev/null +++ b/advice.h @@ -0,0 +1,9 @@ +#ifndef ADVICE_H +#define ADVICE_H + +extern int advice_push_nonfastforward; +extern int advice_status_hints; + +int git_default_advice_config(const char *var, const char *value); + +#endif /* ADVICE_H */ @@ -31,6 +31,8 @@ static void format_subst(const struct commit *commit, { char *to_free = NULL; struct strbuf fmt = STRBUF_INIT; + struct pretty_print_context ctx = {0}; + ctx.date_mode = DATE_NORMAL; if (src == buf->buf) to_free = strbuf_detach(buf, NULL); @@ -48,7 +50,7 @@ static void format_subst(const struct commit *commit, strbuf_add(&fmt, b + 8, c - b - 8); strbuf_add(buf, src, b - src); - format_commit_message(commit, fmt.buf, buf, DATE_NORMAL); + format_commit_message(commit, fmt.buf, buf, &ctx); len -= c + 1 - src; src = c + 1; } @@ -115,6 +117,7 @@ static int write_archive_entry(const unsigned char *sha1, const char *base, strbuf_reset(&path); strbuf_grow(&path, PATH_MAX); + strbuf_add(&path, args->base, args->baselen); strbuf_add(&path, base, baselen); strbuf_addstr(&path, filename); path_without_prefix = path.buf + args->baselen; @@ -187,8 +190,8 @@ int write_archive_entries(struct archiver_args *args, git_attr_set_direction(GIT_ATTR_INDEX, &the_index); } - err = read_tree_recursive(args->tree, args->base, args->baselen, 0, - args->pathspec, write_archive_entry, &context); + err = read_tree_recursive(args->tree, "", 0, 0, args->pathspec, + write_archive_entry, &context); if (err == READ_TREE_RECURSIVE) err = 0; return err; @@ -211,7 +214,7 @@ static const struct archiver *lookup_archiver(const char *name) static void parse_pathspec_arg(const char **pathspec, struct archiver_args *ar_args) { - ar_args->pathspec = get_pathspec(ar_args->base, pathspec); + ar_args->pathspec = get_pathspec("", pathspec); } static void parse_treeish_arg(const char **argv, @@ -283,7 +286,7 @@ static int parse_archive_args(int argc, const char **argv, OPT_STRING(0, "format", &format, "fmt", "archive format"), OPT_STRING(0, "prefix", &base, "prefix", "prepend prefix to each pathname in the archive"), - OPT_STRING(0, "output", &output, "file", + OPT_STRING('o', "output", &output, "file", "write the archive to this file"), OPT_BOOLEAN(0, "worktree-attributes", &worktree_attributes, "read .gitattributes in working directory"), diff --git a/builtin-add.c b/builtin-add.c index 006fd08769..cb6e5906fb 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -131,27 +131,27 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p return pathspec; } -int interactive_add(int argc, const char **argv, const char *prefix) +int run_add_interactive(const char *revision, const char *patch_mode, + const char **pathspec) { - int status, ac; + int status, ac, pc = 0; const char **args; - const char **pathspec = NULL; - if (argc) { - pathspec = validate_pathspec(argc, argv, prefix); - if (!pathspec) - return -1; - } + if (pathspec) + while (pathspec[pc]) + pc++; - args = xcalloc(sizeof(const char *), (argc + 4)); + args = xcalloc(sizeof(const char *), (pc + 5)); ac = 0; args[ac++] = "add--interactive"; - if (patch_interactive) - args[ac++] = "--patch"; + if (patch_mode) + args[ac++] = patch_mode; + if (revision) + args[ac++] = revision; args[ac++] = "--"; - if (argc) { - memcpy(&(args[ac]), pathspec, sizeof(const char *) * argc); - ac += argc; + if (pc) { + memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc); + ac += pc; } args[ac] = NULL; @@ -160,6 +160,21 @@ int interactive_add(int argc, const char **argv, const char *prefix) return status; } +int interactive_add(int argc, const char **argv, const char *prefix) +{ + const char **pathspec = NULL; + + if (argc) { + pathspec = validate_pathspec(argc, argv, prefix); + if (!pathspec) + return -1; + } + + return run_add_interactive(NULL, + patch_interactive ? "--patch" : NULL, + pathspec); +} + static int edit_patch(int argc, const char **argv, const char *prefix) { char *file = xstrdup(git_path("ADD_EDIT.patch")); @@ -183,7 +198,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) out = open(file, O_CREAT | O_WRONLY, 0644); if (out < 0) die ("Could not open '%s' for writing.", file); - rev.diffopt.file = fdopen(out, "w"); + rev.diffopt.file = xfdopen(out, "w"); rev.diffopt.close_file = 1; if (run_diff_files(&rev, 0)) die ("Could not write patch"); diff --git a/builtin-apply.c b/builtin-apply.c index c8372a0a80..f667368d16 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -153,6 +153,7 @@ struct fragment { const char *patch; int size; int rejected; + int linenr; struct fragment *next; }; @@ -1227,23 +1228,29 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc return -1; } -static void check_whitespace(const char *line, int len, unsigned ws_rule) +static void record_ws_error(unsigned result, const char *line, int len, int linenr) { char *err; - unsigned result = ws_check(line + 1, len - 1, ws_rule); + if (!result) return; whitespace_error++; if (squelch_whitespace_errors && squelch_whitespace_errors < whitespace_error) - ; - else { - err = whitespace_error_string(result); - fprintf(stderr, "%s:%d: %s.\n%.*s\n", - patch_input_file, linenr, err, len - 2, line + 1); - free(err); - } + return; + + err = whitespace_error_string(result); + fprintf(stderr, "%s:%d: %s.\n%.*s\n", + patch_input_file, linenr, err, len, line); + free(err); +} + +static void check_whitespace(const char *line, int len, unsigned ws_rule) +{ + unsigned result = ws_check(line + 1, len - 1, ws_rule); + + record_ws_error(result, line + 1, len - 2, linenr); } /* @@ -1359,6 +1366,7 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc int len; fragment = xcalloc(1, sizeof(*fragment)); + fragment->linenr = linenr; len = parse_fragment(line, size, patch, fragment); if (len <= 0) die("corrupt patch at line %d", linenr); @@ -2142,6 +2150,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, int len = linelen(patch, size); int plen, added; int added_blank_line = 0; + int is_blank_context = 0; if (!len) break; @@ -2174,8 +2183,12 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, *new++ = '\n'; add_line_info(&preimage, "\n", 1, LINE_COMMON); add_line_info(&postimage, "\n", 1, LINE_COMMON); + is_blank_context = 1; break; case ' ': + if (plen && (ws_rule & WS_BLANK_AT_EOF) && + ws_blank_line(patch + 1, plen, ws_rule)) + is_blank_context = 1; case '-': memcpy(old, patch + 1, plen); add_line_info(&preimage, old, plen, @@ -2202,7 +2215,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, (first == '+' ? 0 : LINE_COMMON)); new += added; if (first == '+' && - added == 1 && new[-1] == '\n') + (ws_rule & WS_BLANK_AT_EOF) && + ws_blank_line(patch + 1, plen, ws_rule)) added_blank_line = 1; break; case '@': case '\\': @@ -2215,6 +2229,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, } if (added_blank_line) new_blank_lines_at_end++; + else if (is_blank_context) + ; else new_blank_lines_at_end = 0; patch += len; @@ -2296,17 +2312,24 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, } if (applied_pos >= 0) { - if (ws_error_action == correct_ws_error && - new_blank_lines_at_end && - postimage.nr + applied_pos == img->nr) { + if (new_blank_lines_at_end && + preimage.nr + applied_pos == img->nr && + (ws_rule & WS_BLANK_AT_EOF) && + ws_error_action != nowarn_ws_error) { + record_ws_error(WS_BLANK_AT_EOF, "+", 1, frag->linenr); + if (ws_error_action == correct_ws_error) { + while (new_blank_lines_at_end--) + remove_last_line(&postimage); + } /* - * If the patch application adds blank lines - * at the end, and if the patch applies at the - * end of the image, remove those added blank - * lines. + * We would want to prevent write_out_results() + * from taking place in apply_patch() that follows + * the callchain led us here, which is: + * apply_patch->check_patch_list->check_patch-> + * apply_data->apply_fragments->apply_one_fragment */ - while (new_blank_lines_at_end--) - remove_last_line(&postimage); + if (ws_error_action == die_on_ws_error) + apply = 0; } /* diff --git a/builtin-archive.c b/builtin-archive.c index f9a4bea41e..12351e9dd5 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -60,6 +60,17 @@ static int run_remote_archiver(int argc, const char **argv, return !!rv; } +static const char *format_from_name(const char *filename) +{ + const char *ext = strrchr(filename, '.'); + if (!ext) + return NULL; + ext++; + if (!strcasecmp(ext, "zip")) + return "zip"; + return NULL; +} + #define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \ PARSE_OPT_KEEP_ARGV0 | \ PARSE_OPT_KEEP_UNKNOWN | \ @@ -70,21 +81,39 @@ int cmd_archive(int argc, const char **argv, const char *prefix) const char *exec = "git-upload-archive"; const char *output = NULL; const char *remote = NULL; + const char *format = NULL; struct option local_opts[] = { - OPT_STRING(0, "output", &output, "file", + OPT_STRING('o', "output", &output, "file", "write the archive to this file"), OPT_STRING(0, "remote", &remote, "repo", "retrieve the archive from remote repository <repo>"), OPT_STRING(0, "exec", &exec, "cmd", "path to the remote git-upload-archive command"), + OPT_STRING(0, "format", &format, "fmt", "archive format"), OPT_END() }; + char fmt_opt[32]; argc = parse_options(argc, argv, prefix, local_opts, NULL, PARSE_OPT_KEEP_ALL); - if (output) + if (output) { create_output_file(output); + if (!format) + format = format_from_name(output); + } + + if (format) { + sprintf(fmt_opt, "--format=%s", format); + /* + * This is safe because either --format and/or --output must + * have been given on the original command line if we get to + * this point, and parse_options() must have eaten at least + * one argument, i.e. we have enough room to append to argv[]. + */ + argv[argc++] = fmt_opt; + argv[argc] = NULL; + } if (remote) return run_remote_archiver(argc, argv, remote, exec); diff --git a/builtin-blame.c b/builtin-blame.c index 7512773b40..dd16b22297 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1604,6 +1604,9 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) } while (ch != '\n' && cp < sb->final_buf + sb->final_buf_size); } + + if (sb->final_buf_size && cp[-1] != '\n') + putchar('\n'); } static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) @@ -1667,6 +1670,9 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) } while (ch != '\n' && cp < sb->final_buf + sb->final_buf_size); } + + if (sb->final_buf_size && cp[-1] != '\n') + putchar('\n'); } static void output(struct scoreboard *sb, int option) diff --git a/builtin-branch.c b/builtin-branch.c index 9f57992062..05e876e285 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -387,8 +387,9 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, commit = item->commit; if (commit && !parse_commit(commit)) { + struct pretty_print_context ctx = {0}; pretty_print_commit(CMIT_FMT_ONELINE, commit, - &subject, 0, NULL, NULL, 0, 0); + &subject, &ctx); sub = subject.buf; } diff --git a/builtin-bundle.c b/builtin-bundle.c index 9b58152047..2006cc5cd5 100644 --- a/builtin-bundle.c +++ b/builtin-bundle.c @@ -9,7 +9,11 @@ * bundle supporting "fetch", "pull", and "ls-remote". */ -static const char *bundle_usage="git bundle (create <bundle> <git rev-list args> | verify <bundle> | list-heads <bundle> [refname]... | unbundle <bundle> [refname]... )"; +static const char builtin_bundle_usage[] = + "git bundle create <file> <git-rev-list args>\n" + " or: git bundle verify <file>\n" + " or: git bundle list-heads <file> [refname...]\n" + " or: git bundle unbundle <file> [refname...]"; int cmd_bundle(int argc, const char **argv, const char *prefix) { @@ -20,7 +24,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) char buffer[PATH_MAX]; if (argc < 3) - usage(bundle_usage); + usage(builtin_bundle_usage); cmd = argv[1]; bundle_file = argv[2]; @@ -59,5 +63,5 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) return !!unbundle(&header, bundle_fd) || list_bundle_refs(&header, argc, argv); } else - usage(bundle_usage); + usage(builtin_bundle_usage); } diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c index f9381e07ea..b106c65d80 100644 --- a/builtin-check-ref-format.c +++ b/builtin-check-ref-format.c @@ -7,8 +7,37 @@ #include "builtin.h" #include "strbuf.h" +static const char builtin_check_ref_format_usage[] = +"git check-ref-format [--print] <refname>\n" +" or: git check-ref-format --branch <branchname-shorthand>"; + +/* + * Replace each run of adjacent slashes in src with a single slash, + * and write the result to dst. + * + * This function is similar to normalize_path_copy(), but stripped down + * to meet check_ref_format's simpler needs. + */ +static void collapse_slashes(char *dst, const char *src) +{ + char ch; + char prev = '\0'; + + while ((ch = *src++) != '\0') { + if (prev == '/' && ch == prev) + continue; + + *dst++ = ch; + prev = ch; + } + *dst = '\0'; +} + int cmd_check_ref_format(int argc, const char **argv, const char *prefix) { + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(builtin_check_ref_format_usage); + if (argc == 3 && !strcmp(argv[1], "--branch")) { struct strbuf sb = STRBUF_INIT; @@ -17,7 +46,16 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix) printf("%s\n", sb.buf + 11); exit(0); } + if (argc == 3 && !strcmp(argv[1], "--print")) { + char *refname = xmalloc(strlen(argv[2]) + 1); + + if (check_ref_format(argv[2])) + exit(1); + collapse_slashes(refname, argv[2]); + printf("%s\n", refname); + exit(0); + } if (argc != 2) - usage("git check-ref-format refname"); + usage(builtin_check_ref_format_usage); return !!check_ref_format(argv[1]); } diff --git a/builtin-checkout.c b/builtin-checkout.c index 36e2116ea2..64f3a11ae1 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -302,8 +302,9 @@ static void show_local_changes(struct object *head) static void describe_detached_head(char *msg, struct commit *commit) { struct strbuf sb = STRBUF_INIT; + struct pretty_print_context ctx = {0}; parse_commit(commit); - pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, NULL, NULL, 0, 0); + pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx); fprintf(stderr, "%s %s... %s\n", msg, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf); strbuf_release(&sb); @@ -566,6 +567,47 @@ static int git_checkout_config(const char *var, const char *value, void *cb) return git_xmerge_config(var, value, cb); } +static int interactive_checkout(const char *revision, const char **pathspec, + struct checkout_opts *opts) +{ + return run_add_interactive(revision, "--patch=checkout", pathspec); +} + +struct tracking_name_data { + const char *name; + char *remote; + int unique; +}; + +static int check_tracking_name(const char *refname, const unsigned char *sha1, + int flags, void *cb_data) +{ + struct tracking_name_data *cb = cb_data; + const char *slash; + + if (prefixcmp(refname, "refs/remotes/")) + return 0; + slash = strchr(refname + 13, '/'); + if (!slash || strcmp(slash + 1, cb->name)) + return 0; + if (cb->remote) { + cb->unique = 0; + return 0; + } + cb->remote = xstrdup(refname); + return 0; +} + +static const char *unique_tracking_name(const char *name) +{ + struct tracking_name_data cb_data = { name, NULL, 1 }; + for_each_ref(check_tracking_name, &cb_data); + if (cb_data.unique) + return cb_data.remote; + free(cb_data.remote); + return NULL; +} + int cmd_checkout(int argc, const char **argv, const char *prefix) { struct checkout_opts opts; @@ -574,6 +616,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) struct branch_info new; struct tree *source_tree = NULL; char *conflict_style = NULL; + int patch_mode = 0; + int dwim_new_local_branch = 1; struct option options[] = { OPT__QUIET(&opts.quiet), OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"), @@ -588,6 +632,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT_BOOLEAN('m', "merge", &opts.merge, "merge"), OPT_STRING(0, "conflict", &conflict_style, "style", "conflict style (merge or diff3)"), + OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), + { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL, + "second guess 'git checkout no-such-branch'", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, OPT_END(), }; int has_dash_dash; @@ -602,6 +650,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); + if (patch_mode && (opts.track > 0 || opts.new_branch + || opts.new_branch_log || opts.merge || opts.force)) + die ("--patch is incompatible with all other options"); + /* --track without -b should DWIM */ if (0 < opts.track && !opts.new_branch) { const char *argv0 = argv[0]; @@ -617,8 +669,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.new_branch = argv0 + 1; } - if (opts.track == BRANCH_TRACK_UNSPECIFIED) - opts.track = git_branch_track; if (conflict_style) { opts.merge = 1; /* implied */ git_xmerge_config("merge.conflictstyle", conflict_style, NULL); @@ -642,6 +692,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) * With no paths, if <something> is a commit, that is to * switch to the branch or detach HEAD at it. * + * With no paths, if <something> is _not_ a commit, no -t nor -b + * was given, and there is a tracking branch whose name is + * <something> in one and only one remote, then this is a short-hand + * to fork local <something> from that remote tracking branch. + * * Otherwise <something> shall not be ambiguous. * - If it's *only* a reference, treat it like case (1). * - If it's only a path, treat it like case (2). @@ -664,7 +719,21 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (get_sha1(arg, rev)) { if (has_dash_dash) /* case (1) */ die("invalid reference: %s", arg); - goto no_reference; /* case (3 -> 2) */ + if (!patch_mode && + dwim_new_local_branch && + opts.track == BRANCH_TRACK_UNSPECIFIED && + !opts.new_branch && + !check_filename(NULL, arg) && + argc == 1) { + const char *remote = unique_tracking_name(arg); + if (!remote || get_sha1(remote, rev)) + goto no_reference; + opts.new_branch = arg; + arg = remote; + /* DWIMmed to create local branch */ + } + else + goto no_reference; } /* we can't end up being in (2) anymore, eat the argument */ @@ -702,12 +771,19 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) } no_reference: + + if (opts.track == BRANCH_TRACK_UNSPECIFIED) + opts.track = git_branch_track; + if (argc) { const char **pathspec = get_pathspec(prefix, argv); if (!pathspec) die("invalid path specification"); + if (patch_mode) + return interactive_checkout(new.name, pathspec, &opts); + /* Checkout paths */ if (opts.new_branch) { if (argc == 1) { @@ -723,6 +799,9 @@ no_reference: return checkout_paths(source_tree, pathspec, &opts); } + if (patch_mode) + return interactive_checkout(new.name, NULL, &opts); + if (opts.new_branch) { struct strbuf buf = STRBUF_INIT; if (strbuf_check_branch_ref(&buf, opts.new_branch)) diff --git a/builtin-clone.c b/builtin-clone.c index 0f231d8af5..caf3025031 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -41,6 +41,7 @@ static int option_quiet, option_no_checkout, option_bare, option_mirror; static int option_local, option_no_hardlinks, option_shared, option_recursive; static char *option_template, *option_reference, *option_depth; static char *option_origin = NULL; +static char *option_branch = NULL; static char *option_upload_pack = "git-upload-pack"; static int option_verbose; @@ -50,7 +51,9 @@ static struct option builtin_clone_options[] = { OPT_BOOLEAN('n', "no-checkout", &option_no_checkout, "don't create a checkout"), OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"), - OPT_BOOLEAN(0, "naked", &option_bare, "create a bare repository"), + { OPTION_BOOLEAN, 0, "naked", &option_bare, NULL, + "create a bare repository", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, OPT_BOOLEAN(0, "mirror", &option_mirror, "create a mirror repository (implies bare)"), OPT_BOOLEAN('l', "local", &option_local, @@ -60,13 +63,15 @@ static struct option builtin_clone_options[] = { OPT_BOOLEAN('s', "shared", &option_shared, "setup as shared repository"), OPT_BOOLEAN(0, "recursive", &option_recursive, - "setup as shared repository"), + "initialize submodules in the clone"), OPT_STRING(0, "template", &option_template, "path", "path the template repository"), OPT_STRING(0, "reference", &option_reference, "repo", "reference repository"), OPT_STRING('o', "origin", &option_origin, "branch", "use <branch> instead of 'origin' to track upstream"), + OPT_STRING('b', "branch", &option_branch, "branch", + "checkout <branch> instead of the remote's HEAD"), OPT_STRING('u', "upload-pack", &option_upload_pack, "path", "path to git-upload-pack on the remote"), OPT_STRING(0, "depth", &option_depth, "depth", @@ -266,7 +271,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) die_errno("failed to create link '%s'", dest->buf); option_no_hardlinks = 1; } - if (copy_file(dest->buf, src->buf, 0666)) + if (copy_file_with_time(dest->buf, src->buf, 0666)) die_errno("failed to copy file to '%s'", dest->buf); } closedir(dir); @@ -326,24 +331,28 @@ static void remove_junk_on_signal(int signo) raise(signo); } -static struct ref *write_remote_refs(const struct ref *refs, - struct refspec *refspec, const char *reflog) +static struct ref *wanted_peer_refs(const struct ref *refs, + struct refspec *refspec) { struct ref *local_refs = NULL; struct ref **tail = &local_refs; - struct ref *r; get_fetch_map(refs, refspec, &tail, 0); if (!option_mirror) get_fetch_map(refs, tag_refspec, &tail, 0); + return local_refs; +} + +static void write_remote_refs(const struct ref *local_refs) +{ + const struct ref *r; + for (r = local_refs; r; r = r->next) add_extra_ref(r->peer_ref->name, r->old_sha1, 0); pack_refs(PACK_REFS_ALL); clear_extra_refs(); - - return local_refs; } int cmd_clone(int argc, const char **argv, const char *prefix) @@ -353,7 +362,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) const char *repo_name, *repo, *work_tree, *git_dir; char *path, *dir; int dest_exists; - const struct ref *refs, *head_points_at, *remote_head, *mapped_refs; + const struct ref *refs, *remote_head, *mapped_refs; + const struct ref *remote_head_points_at; + const struct ref *our_head_points_at; struct strbuf key = STRBUF_INIT, value = STRBUF_INIT; struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; struct transport *transport = NULL; @@ -368,8 +379,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_clone_options, builtin_clone_usage, 0); + if (argc > 2) + usage_msg_opt("Too many arguments.", + builtin_clone_usage, builtin_clone_options); + if (argc == 0) - die("You must specify a repository to clone."); + usage_msg_opt("You must specify a repository to clone.", + builtin_clone_usage, builtin_clone_options); if (option_mirror) option_bare = 1; @@ -490,9 +506,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) strbuf_reset(&value); - if (path && !is_bundle) + if (path && !is_bundle) { refs = clone_local(path, git_dir); - else { + mapped_refs = wanted_peer_refs(refs, refspec); + } else { struct remote *remote = remote_get(argv[0]); transport = transport_get(remote, remote->url[0]); @@ -515,21 +532,43 @@ int cmd_clone(int argc, const char **argv, const char *prefix) option_upload_pack); refs = transport_get_remote_refs(transport); - if (refs) - transport_fetch_refs(transport, refs); + if (refs) { + mapped_refs = wanted_peer_refs(refs, refspec); + transport_fetch_refs(transport, mapped_refs); + } } if (refs) { clear_extra_refs(); - mapped_refs = write_remote_refs(refs, refspec, reflog_msg.buf); + write_remote_refs(mapped_refs); remote_head = find_ref_by_name(refs, "HEAD"); - head_points_at = guess_remote_head(remote_head, mapped_refs, 0); + remote_head_points_at = + guess_remote_head(remote_head, mapped_refs, 0); + + if (option_branch) { + struct strbuf head = STRBUF_INIT; + strbuf_addstr(&head, src_ref_prefix); + strbuf_addstr(&head, option_branch); + our_head_points_at = + find_ref_by_name(mapped_refs, head.buf); + strbuf_release(&head); + + if (!our_head_points_at) { + warning("Remote branch %s not found in " + "upstream %s, using HEAD instead", + option_branch, option_origin); + our_head_points_at = remote_head_points_at; + } + } + else + our_head_points_at = remote_head_points_at; } else { warning("You appear to have cloned an empty repository."); - head_points_at = NULL; + our_head_points_at = NULL; + remote_head_points_at = NULL; remote_head = NULL; option_no_checkout = 1; if (!option_bare) @@ -537,41 +576,35 @@ int cmd_clone(int argc, const char **argv, const char *prefix) "refs/heads/master"); } - if (head_points_at) { - /* Local default branch link */ - create_symref("HEAD", head_points_at->name, NULL); + if (remote_head_points_at && !option_bare) { + struct strbuf head_ref = STRBUF_INIT; + strbuf_addstr(&head_ref, branch_top.buf); + strbuf_addstr(&head_ref, "HEAD"); + create_symref(head_ref.buf, + remote_head_points_at->peer_ref->name, + reflog_msg.buf); + } + if (our_head_points_at) { + /* Local default branch link */ + create_symref("HEAD", our_head_points_at->name, NULL); if (!option_bare) { - struct strbuf head_ref = STRBUF_INIT; - const char *head = head_points_at->name; - - if (!prefixcmp(head, "refs/heads/")) - head += 11; - - /* Set up the initial local branch */ - - /* Local branch initial value */ + const char *head = skip_prefix(our_head_points_at->name, + "refs/heads/"); update_ref(reflog_msg.buf, "HEAD", - head_points_at->old_sha1, + our_head_points_at->old_sha1, NULL, 0, DIE_ON_ERR); - - strbuf_addstr(&head_ref, branch_top.buf); - strbuf_addstr(&head_ref, "HEAD"); - - /* Remote branch link */ - create_symref(head_ref.buf, - head_points_at->peer_ref->name, - reflog_msg.buf); - install_branch_config(0, head, option_origin, - head_points_at->name); + our_head_points_at->name); } } else if (remote_head) { /* Source had detached HEAD pointing somewhere. */ - if (!option_bare) + if (!option_bare) { update_ref(reflog_msg.buf, "HEAD", remote_head->old_sha1, NULL, REF_NODEREF, DIE_ON_ERR); + our_head_points_at = remote_head; + } } else { /* Nothing to checkout out */ if (!option_no_checkout) @@ -605,7 +638,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) opts.src_index = &the_index; opts.dst_index = &the_index; - tree = parse_tree_indirect(remote_head->old_sha1); + tree = parse_tree_indirect(our_head_points_at->old_sha1); parse_tree(tree); init_tree_desc(&t, tree->buffer, tree->size); unpack_trees(1, &t, &opts); @@ -615,7 +648,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) die("unable to write new index file"); err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1), - sha1_to_hex(remote_head->old_sha1), "1", NULL); + sha1_to_hex(our_head_points_at->old_sha1), "1", + NULL); if (!err && option_recursive) err = run_command_v_opt(argv_submodule, RUN_GIT_CMD); diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index 6467077731..ddcb7a4bbb 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -105,7 +105,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); - if (argc < 2) + if (argc < 2 || !strcmp(argv[1], "-h")) usage(commit_tree_usage); if (get_sha1(argv[1], tree_sha1)) die("Not a valid object name %s", argv[1]); diff --git a/builtin-commit.c b/builtin-commit.c index 200ffdaad4..d525b894ec 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -414,6 +414,47 @@ static void determine_author_info(void) author_date = date; } +static int ends_rfc2822_footer(struct strbuf *sb) +{ + int ch; + int hit = 0; + int i, j, k; + int len = sb->len; + int first = 1; + const char *buf = sb->buf; + + for (i = len - 1; i > 0; i--) { + if (hit && buf[i] == '\n') + break; + hit = (buf[i] == '\n'); + } + + while (i < len - 1 && buf[i] == '\n') + i++; + + for (; i < len; i = k) { + for (k = i; k < len && buf[k] != '\n'; k++) + ; /* do nothing */ + k++; + + if ((buf[k] == ' ' || buf[k] == '\t') && !first) + continue; + + first = 0; + + for (j = 0; i + j < len; j++) { + ch = buf[i + j]; + if (ch == ':') + break; + if (isalnum(ch) || + (ch == '-')) + continue; + return 0; + } + } + return 1; +} + static int prepare_to_commit(const char *index_file, const char *prefix, struct wt_status *s) { @@ -489,7 +530,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--) ; /* do nothing */ if (prefixcmp(sb.buf + i, sob.buf)) { - if (prefixcmp(sb.buf + i, sign_off_header)) + if (!i || !ends_rfc2822_footer(&sb)) strbuf_addch(&sb, '\n'); strbuf_addbuf(&sb, &sob); } @@ -684,8 +725,10 @@ static const char *find_author_by_nickname(const char *name) prepare_revision_walk(&revs); commit = get_revision(&revs); if (commit) { + struct pretty_print_context ctx = {0}; + ctx.date_mode = DATE_NORMAL; strbuf_release(&buf); - format_commit_message(commit, "%an <%ae>", &buf, DATE_NORMAL); + format_commit_message(commit, "%an <%ae>", &buf, &ctx); return strbuf_detach(&buf, NULL); } die("No existing author found with '%s'", name); @@ -942,8 +985,10 @@ static void print_summary(const char *prefix, const unsigned char *sha1) initial_commit ? " (root-commit)" : ""); if (!log_tree_commit(&rev, commit)) { + struct pretty_print_context ctx = {0}; struct strbuf buf = STRBUF_INIT; - format_commit_message(commit, format + 7, &buf, DATE_NORMAL); + ctx.date_mode = DATE_NORMAL; + format_commit_message(commit, format + 7, &buf, &ctx); printf("%s\n", buf.buf); strbuf_release(&buf); } diff --git a/builtin-describe.c b/builtin-describe.c index df67a733ae..71be2a9364 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -5,12 +5,14 @@ #include "builtin.h" #include "exec_cmd.h" #include "parse-options.h" +#include "diff.h" #define SEEN (1u<<0) #define MAX_TAGS (FLAG_BITS - 1) static const char * const describe_usage[] = { "git describe [options] <committish>*", + "git describe [options] --dirty", NULL }; @@ -23,6 +25,13 @@ static int max_candidates = 10; static int found_names; static const char *pattern; static int always; +static const char *dirty; + +/* diff-index command arguments to check if working tree is dirty. */ +static const char *diff_index_args[] = { + "diff-index", "--quiet", "HEAD", "--", NULL +}; + struct commit_name { struct tag *tag; @@ -96,8 +105,6 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void if (!all) { if (!prio) return 0; - if (!tags && prio < 2) - return 0; } add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1); return 0; @@ -180,11 +187,11 @@ static void describe(const char *arg, int last_one) unsigned char sha1[20]; struct commit *cmit, *gave_up_on = NULL; struct commit_list *list; - static int initialized = 0; struct commit_name *n; struct possible_tag all_matches[MAX_TAGS]; unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; unsigned long seen_commits = 0; + unsigned int unannotated_cnt = 0; if (get_sha1(arg, sha1)) die("Not a valid object name %s", arg); @@ -192,22 +199,16 @@ static void describe(const char *arg, int last_one) if (!cmit) die("%s is not a valid '%s' object", arg, commit_type); - if (!initialized) { - initialized = 1; - for_each_ref(get_name, NULL); - } - - if (!found_names) - die("cannot describe '%s'", sha1_to_hex(sha1)); - n = cmit->util; - if (n) { + if (n && (tags || all || n->prio == 2)) { /* * Exact match to an existing ref. */ display_name(n); if (longformat) show_suffix(0, n->tag ? n->tag->tagged->sha1 : sha1); + if (dirty) + printf("%s", dirty); printf("\n"); return; } @@ -226,7 +227,9 @@ static void describe(const char *arg, int last_one) seen_commits++; n = c->util; if (n) { - if (match_cnt < max_candidates) { + if (!tags && !all && n->prio < 2) { + unannotated_cnt++; + } else if (match_cnt < max_candidates) { struct possible_tag *t = &all_matches[match_cnt++]; t->name = n; t->depth = seen_commits - 1; @@ -265,10 +268,20 @@ static void describe(const char *arg, int last_one) if (!match_cnt) { const unsigned char *sha1 = cmit->object.sha1; if (always) { - printf("%s\n", find_unique_abbrev(sha1, abbrev)); + printf("%s", find_unique_abbrev(sha1, abbrev)); + if (dirty) + printf("%s", dirty); + printf("\n"); return; } - die("cannot describe '%s'", sha1_to_hex(sha1)); + if (unannotated_cnt) + die("No annotated tags can describe '%s'.\n" + "However, there were unannotated tags: try --tags.", + sha1_to_hex(sha1)); + else + die("No tags can describe '%s'.\n" + "Try --always, or create some tags.", + sha1_to_hex(sha1)); } qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt); @@ -300,6 +313,8 @@ static void describe(const char *arg, int last_one) display_name(all_matches[0].name); if (abbrev) show_suffix(all_matches[0].depth, cmit->object.sha1); + if (dirty) + printf("%s", dirty); printf("\n"); if (!last_one) @@ -324,6 +339,9 @@ int cmd_describe(int argc, const char **argv, const char *prefix) "only consider tags matching <pattern>"), OPT_BOOLEAN(0, "always", &always, "show abbreviated commit object as fallback"), + {OPTION_STRING, 0, "dirty", &dirty, "mark", + "append <mark> on dirty working tree (default: \"-dirty\")", + PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"}, OPT_END(), }; @@ -359,8 +377,16 @@ int cmd_describe(int argc, const char **argv, const char *prefix) return cmd_name_rev(i + argc, args, prefix); } + for_each_ref(get_name, NULL); + if (!found_names && !always) + die("No names found, cannot describe anything."); + if (argc == 0) { + if (dirty && !cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, diff_index_args, prefix)) + dirty = NULL; describe("HEAD", 1); + } else if (dirty) { + die("--dirty is incompatible with committishes"); } else { while (argc-- > 0) { describe(*argv++, argc == 0); diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 629735f547..8ed4a6feaa 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -157,6 +157,66 @@ static const unsigned char *get_rev(void) return commit->object.sha1; } +enum ack_type { + NAK = 0, + ACK, + ACK_continue, + ACK_common, + ACK_ready +}; + +static void consume_shallow_list(int fd) +{ + if (args.stateless_rpc && args.depth > 0) { + /* If we sent a depth we will get back "duplicate" + * shallow and unshallow commands every time there + * is a block of have lines exchanged. + */ + char line[1000]; + while (packet_read_line(fd, line, sizeof(line))) { + if (!prefixcmp(line, "shallow ")) + continue; + if (!prefixcmp(line, "unshallow ")) + continue; + die("git fetch-pack: expected shallow list"); + } + } +} + +static enum ack_type get_ack(int fd, unsigned char *result_sha1) +{ + static char line[1000]; + int len = packet_read_line(fd, line, sizeof(line)); + + if (!len) + die("git fetch-pack: expected ACK/NAK, got EOF"); + if (line[len-1] == '\n') + line[--len] = 0; + if (!strcmp(line, "NAK")) + return NAK; + if (!prefixcmp(line, "ACK ")) { + if (!get_sha1_hex(line+4, result_sha1)) { + if (strstr(line+45, "continue")) + return ACK_continue; + if (strstr(line+45, "common")) + return ACK_common; + if (strstr(line+45, "ready")) + return ACK_ready; + return ACK; + } + } + die("git fetch_pack: expected ACK/NAK, got '%s'", line); +} + +static void send_request(int fd, struct strbuf *buf) +{ + if (args.stateless_rpc) { + send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX); + packet_flush(fd); + } else + safe_write(fd, buf->buf, buf->len); +} + static int find_common(int fd[2], unsigned char *result_sha1, struct ref *refs) { @@ -165,7 +225,11 @@ static int find_common(int fd[2], unsigned char *result_sha1, const unsigned char *sha1; unsigned in_vain = 0; int got_continue = 0; + struct strbuf req_buf = STRBUF_INIT; + size_t state_len = 0; + if (args.stateless_rpc && multi_ack == 1) + die("--stateless-rpc requires multi_ack_detailed"); if (marked) for_each_ref(clear_marks, NULL); marked = 1; @@ -175,6 +239,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, fetching = 0; for ( ; refs ; refs = refs->next) { unsigned char *remote = refs->old_sha1; + const char *remote_hex; struct object *o; /* @@ -192,32 +257,42 @@ static int find_common(int fd[2], unsigned char *result_sha1, continue; } - if (!fetching) - packet_write(fd[1], "want %s%s%s%s%s%s%s%s\n", - sha1_to_hex(remote), - (multi_ack ? " multi_ack" : ""), - (use_sideband == 2 ? " side-band-64k" : ""), - (use_sideband == 1 ? " side-band" : ""), - (args.use_thin_pack ? " thin-pack" : ""), - (args.no_progress ? " no-progress" : ""), - (args.include_tag ? " include-tag" : ""), - (prefer_ofs_delta ? " ofs-delta" : "")); - else - packet_write(fd[1], "want %s\n", sha1_to_hex(remote)); + remote_hex = sha1_to_hex(remote); + if (!fetching) { + struct strbuf c = STRBUF_INIT; + if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed"); + if (multi_ack == 1) strbuf_addstr(&c, " multi_ack"); + if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k"); + if (use_sideband == 1) strbuf_addstr(&c, " side-band"); + if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack"); + if (args.no_progress) strbuf_addstr(&c, " no-progress"); + if (args.include_tag) strbuf_addstr(&c, " include-tag"); + if (prefer_ofs_delta) strbuf_addstr(&c, " ofs-delta"); + packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf); + strbuf_release(&c); + } else + packet_buf_write(&req_buf, "want %s\n", remote_hex); fetching++; } + + if (!fetching) { + strbuf_release(&req_buf); + packet_flush(fd[1]); + return 1; + } + if (is_repository_shallow()) - write_shallow_commits(fd[1], 1); + write_shallow_commits(&req_buf, 1); if (args.depth > 0) - packet_write(fd[1], "deepen %d", args.depth); - packet_flush(fd[1]); - if (!fetching) - return 1; + packet_buf_write(&req_buf, "deepen %d", args.depth); + packet_buf_flush(&req_buf); + state_len = req_buf.len; if (args.depth > 0) { char line[1024]; unsigned char sha1[20]; + send_request(fd[1], &req_buf); while (packet_read_line(fd[0], line, sizeof(line))) { if (!prefixcmp(line, "shallow ")) { if (get_sha1_hex(line + 8, sha1)) @@ -239,45 +314,73 @@ static int find_common(int fd[2], unsigned char *result_sha1, } die("expected shallow/unshallow, got %s", line); } + } else if (!args.stateless_rpc) + send_request(fd[1], &req_buf); + + if (!args.stateless_rpc) { + /* If we aren't using the stateless-rpc interface + * we don't need to retain the headers. + */ + strbuf_setlen(&req_buf, 0); + state_len = 0; } flushes = 0; retval = -1; while ((sha1 = get_rev())) { - packet_write(fd[1], "have %s\n", sha1_to_hex(sha1)); + packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1)); if (args.verbose) fprintf(stderr, "have %s\n", sha1_to_hex(sha1)); in_vain++; if (!(31 & ++count)) { int ack; - packet_flush(fd[1]); + packet_buf_flush(&req_buf); + send_request(fd[1], &req_buf); + strbuf_setlen(&req_buf, state_len); flushes++; /* * We keep one window "ahead" of the other side, and * will wait for an ACK only on the next one */ - if (count == 32) + if (!args.stateless_rpc && count == 32) continue; + consume_shallow_list(fd[0]); do { ack = get_ack(fd[0], result_sha1); if (args.verbose && ack) fprintf(stderr, "got ack %d %s\n", ack, sha1_to_hex(result_sha1)); - if (ack == 1) { + switch (ack) { + case ACK: flushes = 0; multi_ack = 0; retval = 0; goto done; - } else if (ack == 2) { + case ACK_common: + case ACK_ready: + case ACK_continue: { struct commit *commit = lookup_commit(result_sha1); + if (args.stateless_rpc + && ack == ACK_common + && !(commit->object.flags & COMMON)) { + /* We need to replay the have for this object + * on the next RPC request so the peer knows + * it is in common with us. + */ + const char *hex = sha1_to_hex(result_sha1); + packet_buf_write(&req_buf, "have %s\n", hex); + state_len = req_buf.len; + } mark_common(commit, 0, 1); retval = 0; in_vain = 0; got_continue = 1; + break; + } } } while (ack); flushes--; @@ -289,20 +392,24 @@ static int find_common(int fd[2], unsigned char *result_sha1, } } done: - packet_write(fd[1], "done\n"); + packet_buf_write(&req_buf, "done\n"); + send_request(fd[1], &req_buf); if (args.verbose) fprintf(stderr, "done\n"); if (retval != 0) { multi_ack = 0; flushes++; } + strbuf_release(&req_buf); + + consume_shallow_list(fd[0]); while (flushes || multi_ack) { int ack = get_ack(fd[0], result_sha1); if (ack) { if (args.verbose) fprintf(stderr, "got ack (%d) %s\n", ack, sha1_to_hex(result_sha1)); - if (ack == 1) + if (ack == ACK) return 0; multi_ack = 1; continue; @@ -584,7 +691,12 @@ static struct ref *do_fetch_pack(int fd[2], if (is_repository_shallow() && !server_supports("shallow")) die("Server does not support shallow clients"); - if (server_supports("multi_ack")) { + if (server_supports("multi_ack_detailed")) { + if (args.verbose) + fprintf(stderr, "Server supports multi_ack_detailed\n"); + multi_ack = 2; + } + else if (server_supports("multi_ack")) { if (args.verbose) fprintf(stderr, "Server supports multi_ack\n"); multi_ack = 1; @@ -615,6 +727,8 @@ static struct ref *do_fetch_pack(int fd[2], */ warning("no common commits"); + if (args.stateless_rpc) + packet_flush(fd[1]); if (get_pack(fd, pack_lockfile)) die("git fetch-pack: fetch failed."); @@ -685,6 +799,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) struct ref *ref = NULL; char *dest = NULL, **heads; int fd[2]; + char *pack_lockfile = NULL; + char **pack_lockfile_ptr = NULL; struct child_process *conn; nr_heads = 0; @@ -734,6 +850,15 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) args.no_progress = 1; continue; } + if (!strcmp("--stateless-rpc", arg)) { + args.stateless_rpc = 1; + continue; + } + if (!strcmp("--lock-pack", arg)) { + args.lock_pack = 1; + pack_lockfile_ptr = &pack_lockfile; + continue; + } usage(fetch_pack_usage); } dest = (char *)arg; @@ -744,19 +869,27 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) if (!dest) usage(fetch_pack_usage); - conn = git_connect(fd, (char *)dest, args.uploadpack, - args.verbose ? CONNECT_VERBOSE : 0); - if (conn) { - get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL); - - ref = fetch_pack(&args, fd, conn, ref, dest, nr_heads, heads, NULL); - close(fd[0]); - close(fd[1]); - if (finish_connect(conn)) - ref = NULL; + if (args.stateless_rpc) { + conn = NULL; + fd[0] = 0; + fd[1] = 1; } else { - ref = NULL; + conn = git_connect(fd, (char *)dest, args.uploadpack, + args.verbose ? CONNECT_VERBOSE : 0); } + + get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL); + + ref = fetch_pack(&args, fd, conn, ref, dest, + nr_heads, heads, pack_lockfile_ptr); + if (pack_lockfile) { + printf("lock %s\n", pack_lockfile); + fflush(stdout); + } + close(fd[0]); + close(fd[1]); + if (finish_connect(conn)) + ref = NULL; ret = !ref; if (!ret && nr_heads) { @@ -809,6 +942,7 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args, if (args.depth > 0) { struct cache_time mtime; + struct strbuf sb = STRBUF_INIT; char *shallow = git_path("shallow"); int fd; @@ -826,12 +960,14 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args, fd = hold_lock_file_for_update(&lock, shallow, LOCK_DIE_ON_ERROR); - if (!write_shallow_commits(fd, 0)) { + if (!write_shallow_commits(&sb, 0) + || write_in_full(fd, sb.buf, sb.len) != sb.len) { unlink_or_warn(shallow); rollback_lock_file(&lock); } else { commit_lock_file(&lock); } + strbuf_release(&sb); } reprepare_packed_git(); diff --git a/builtin-fetch.c b/builtin-fetch.c index 817dd6bff0..cd0bcf7140 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -269,7 +269,7 @@ static int update_local_ref(struct ref *ref, strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); strcat(quickref, ".."); strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); - r = s_update_ref("fast forward", ref, 1); + r = s_update_ref("fast-forward", ref, 1); sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ', SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, pretty_ref, r ? " (unable to update local ref)" : ""); @@ -287,7 +287,7 @@ static int update_local_ref(struct ref *ref, r ? "unable to update local ref" : "forced update"); return r; } else { - sprintf(display, "! %-*s %-*s -> %s (non fast forward)", + sprintf(display, "! %-*s %-*s -> %s (non-fast-forward)", SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, pretty_ref); return 1; @@ -454,7 +454,7 @@ static int quickfetch(struct ref *ref_map) for (ref = ref_map; ref; ref = ref->next) { if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 || - write_in_full(revlist.in, "\n", 1) < 0) { + write_str_in_full(revlist.in, "\n") < 0) { if (errno != EPIPE && errno != EINVAL) error("failed write to rev-list: %s", strerror(errno)); err = -1; @@ -489,7 +489,8 @@ static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata) { struct string_list *list = (struct string_list *)cbdata; - string_list_insert(refname, list); + struct string_list_item *item = string_list_insert(refname, list); + item->util = (void *)sha1; return 0; } @@ -504,57 +505,98 @@ static int will_fetch(struct ref **head, const unsigned char *sha1) return 0; } +struct tag_data { + struct ref **head; + struct ref ***tail; +}; + +static int add_to_tail(struct string_list_item *item, void *cb_data) +{ + struct tag_data *data = (struct tag_data *)cb_data; + struct ref *rm = NULL; + + /* We have already decided to ignore this item */ + if (!item->util) + return 0; + + rm = alloc_ref(item->string); + rm->peer_ref = alloc_ref(item->string); + hashcpy(rm->old_sha1, item->util); + + **data->tail = rm; + *data->tail = &rm->next; + + return 0; +} + static void find_non_local_tags(struct transport *transport, struct ref **head, struct ref ***tail) { struct string_list existing_refs = { NULL, 0, 0, 0 }; - struct string_list new_refs = { NULL, 0, 0, 1 }; - char *ref_name; - int ref_name_len; - const unsigned char *ref_sha1; - const struct ref *tag_ref; - struct ref *rm = NULL; + struct string_list remote_refs = { NULL, 0, 0, 0 }; + struct tag_data data = {head, tail}; const struct ref *ref; + struct string_list_item *item = NULL; for_each_ref(add_existing, &existing_refs); for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) { if (prefixcmp(ref->name, "refs/tags")) continue; - ref_name = xstrdup(ref->name); - ref_name_len = strlen(ref_name); - ref_sha1 = ref->old_sha1; - - if (!strcmp(ref_name + ref_name_len - 3, "^{}")) { - ref_name[ref_name_len - 3] = 0; - tag_ref = transport_get_remote_refs(transport); - while (tag_ref) { - if (!strcmp(tag_ref->name, ref_name)) { - ref_sha1 = tag_ref->old_sha1; - break; - } - tag_ref = tag_ref->next; - } + /* + * The peeled ref always follows the matching base + * ref, so if we see a peeled ref that we don't want + * to fetch then we can mark the ref entry in the list + * as one to ignore by setting util to NULL. + */ + if (!strcmp(ref->name + strlen(ref->name) - 3, "^{}")) { + if (item && !has_sha1_file(ref->old_sha1) && + !will_fetch(head, ref->old_sha1) && + !has_sha1_file(item->util) && + !will_fetch(head, item->util)) + item->util = NULL; + item = NULL; + continue; } - if (!string_list_has_string(&existing_refs, ref_name) && - !string_list_has_string(&new_refs, ref_name) && - (has_sha1_file(ref->old_sha1) || - will_fetch(head, ref->old_sha1))) { - string_list_insert(ref_name, &new_refs); + /* + * If item is non-NULL here, then we previously saw a + * ref not followed by a peeled reference, so we need + * to check if it is a lightweight tag that we want to + * fetch. + */ + if (item && !has_sha1_file(item->util) && + !will_fetch(head, item->util)) + item->util = NULL; - rm = alloc_ref(ref_name); - rm->peer_ref = alloc_ref(ref_name); - hashcpy(rm->old_sha1, ref_sha1); + item = NULL; - **tail = rm; - *tail = &rm->next; - } - free(ref_name); + /* skip duplicates and refs that we already have */ + if (string_list_has_string(&remote_refs, ref->name) || + string_list_has_string(&existing_refs, ref->name)) + continue; + + item = string_list_insert(ref->name, &remote_refs); + item->util = (void *)ref->old_sha1; } string_list_clear(&existing_refs, 0); - string_list_clear(&new_refs, 0); + + /* + * We may have a final lightweight tag that needs to be + * checked to see if it needs fetching. + */ + if (item && !has_sha1_file(item->util) && + !will_fetch(head, item->util)) + item->util = NULL; + + /* + * For all the tags in the remote_refs string list, call + * add_to_tail to add them to the list of refs to be fetched + */ + for_each_string_list(add_to_tail, &remote_refs, &data); + + string_list_clear(&remote_refs, 0); } static void check_not_current_branch(struct ref *ref_map) @@ -574,9 +616,14 @@ static void check_not_current_branch(struct ref *ref_map) static int do_fetch(struct transport *transport, struct refspec *refs, int ref_count) { + struct string_list existing_refs = { NULL, 0, 0, 0 }; + struct string_list_item *peer_item = NULL; struct ref *ref_map; struct ref *rm; int autotags = (transport->remote->fetch_tags == 1); + + for_each_ref(add_existing, &existing_refs); + if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET) tags = TAGS_SET; if (transport->remote->fetch_tags == -1) @@ -599,8 +646,13 @@ static int do_fetch(struct transport *transport, check_not_current_branch(ref_map); for (rm = ref_map; rm; rm = rm->next) { - if (rm->peer_ref) - read_ref(rm->peer_ref->name, rm->peer_ref->old_sha1); + if (rm->peer_ref) { + peer_item = string_list_lookup(rm->peer_ref->name, + &existing_refs); + if (peer_item) + hashcpy(rm->peer_ref->old_sha1, + peer_item->util); + } } if (tags == TAGS_DEFAULT && autotags) @@ -665,7 +717,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) transport = transport_get(remote, remote->url[0]); if (verbosity >= 2) - transport->verbose = 1; + transport->verbose = verbosity <= 3 ? verbosity : 3; if (verbosity < 0) transport->verbose = -1; if (upload_pack) diff --git a/builtin-fsck.c b/builtin-fsck.c index c58b0e337e..2d88e4570f 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -19,7 +19,7 @@ static int show_root; static int show_tags; static int show_unreachable; static int include_reflogs = 1; -static int check_full; +static int check_full = 1; static int check_strict; static int keep_cache_objects; static unsigned char head_sha1[20]; diff --git a/builtin-gc.c b/builtin-gc.c index 7d3e9cc7a0..093517e390 100644 --- a/builtin-gc.c +++ b/builtin-gc.c @@ -216,10 +216,13 @@ int cmd_gc(int argc, const char **argv, const char *prefix) */ if (!need_to_gc()) return 0; - fprintf(stderr, "Auto packing your repository for optimum " - "performance. You may also\n" - "run \"git gc\" manually. See " - "\"git help gc\" for more information.\n"); + fprintf(stderr, + "Auto packing the repository for optimum performance.%s\n", + quiet + ? "" + : (" You may also\n" + "run \"git gc\" manually. See " + "\"git help gc\" for more information.")); } else append_option(argv_repack, prune_expire && !strcmp(prune_expire, "now") diff --git a/builtin-grep.c b/builtin-grep.c index ad0e0a5385..01be9bf7ff 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -13,6 +13,7 @@ #include "parse-options.h" #include "userdiff.h" #include "grep.h" +#include "quote.h" #ifndef NO_EXTERNAL_GREP #ifdef __unix__ @@ -157,8 +158,8 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char unsigned long size; char *data; enum object_type type; - char *to_free = NULL; int hit; + struct strbuf pathbuf = STRBUF_INIT; data = read_sha1_file(sha1, &type, &size); if (!data) { @@ -166,26 +167,13 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char return 0; } if (opt->relative && opt->prefix_length) { - static char name_buf[PATH_MAX]; - char *cp; - int name_len = strlen(name) - opt->prefix_length + 1; - - if (!tree_name_len) - name += opt->prefix_length; - else { - if (ARRAY_SIZE(name_buf) <= name_len) - cp = to_free = xmalloc(name_len); - else - cp = name_buf; - memcpy(cp, name, tree_name_len); - strcpy(cp + tree_name_len, - name + tree_name_len + opt->prefix_length); - name = cp; - } + quote_path_relative(name + tree_name_len, -1, &pathbuf, opt->prefix); + strbuf_insert(&pathbuf, 0, name, tree_name_len); + name = pathbuf.buf; } hit = grep_buffer(opt, name, data, size); + strbuf_release(&pathbuf); free(data); - free(to_free); return hit; } @@ -195,6 +183,7 @@ static int grep_file(struct grep_opt *opt, const char *filename) int i; char *data; size_t sz; + struct strbuf buf = STRBUF_INIT; if (lstat(filename, &st) < 0) { err_ret: @@ -219,8 +208,9 @@ static int grep_file(struct grep_opt *opt, const char *filename) } close(i); if (opt->relative && opt->prefix_length) - filename += opt->prefix_length; + filename = quote_path_relative(filename, -1, &buf, opt->prefix); i = grep_buffer(opt, filename, data, sz); + strbuf_release(&buf); free(data); return i; } @@ -503,6 +493,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached, hit = external_grep(opt, paths, cached); if (hit >= 0) return hit; + hit = 0; } #endif @@ -640,7 +631,7 @@ static int file_callback(const struct option *opt, const char *arg, int unset) struct grep_opt *grep_opt = opt->value; FILE *patterns; int lno = 0; - struct strbuf sb; + struct strbuf sb = STRBUF_INIT; patterns = fopen(arg, "r"); if (!patterns) @@ -797,7 +788,15 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_END() }; + /* + * 'git grep -h', unlike 'git grep -h <pattern>', is a request + * to show usage information and exit. + */ + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(grep_usage, options); + memset(&opt, 0, sizeof(opt)); + opt.prefix = prefix; opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; opt.relative = 1; opt.pathname = 1; @@ -868,15 +867,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) verify_filename(prefix, argv[j]); } - if (i < argc) { + if (i < argc) paths = get_pathspec(prefix, argv + i); - if (opt.prefix_length && opt.relative) { - /* Make sure we do not get outside of paths */ - for (i = 0; paths[i]; i++) - if (strncmp(prefix, paths[i], opt.prefix_length)) - die("git grep: cannot generate relative filenames containing '..'"); - } - } else if (prefix) { paths = xcalloc(2, sizeof(const char *)); paths[0] = prefix; diff --git a/builtin-help.c b/builtin-help.c index e1eba778a5..ca08519d9d 100644 --- a/builtin-help.c +++ b/builtin-help.c @@ -372,6 +372,7 @@ static void show_info_page(const char *git_cmd) const char *page = cmd_to_page(git_cmd); setenv("INFOPATH", system_path(GIT_INFO_PATH), 1); execlp("info", "info", "gitman", page, NULL); + die("no info viewer handled the request"); } static void get_html_page_path(struct strbuf *page_path, const char *page) @@ -416,9 +417,6 @@ int cmd_help(int argc, const char **argv, const char *prefix) const char *alias; load_command_list("git-", &main_cmds, &other_cmds); - setup_git_directory_gently(&nongit); - git_config(git_help_config, NULL); - argc = parse_options(argc, argv, prefix, builtin_help_options, builtin_help_usage, 0); @@ -429,6 +427,9 @@ int cmd_help(int argc, const char **argv, const char *prefix) return 0; } + setup_git_directory_gently(&nongit); + git_config(git_help_config, NULL); + if (!argv[0]) { printf("usage: %s\n\n", git_usage_string); list_common_cmds_help(); diff --git a/builtin-log.c b/builtin-log.c index 25e21ed415..33fa6ea6c8 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -50,6 +50,12 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, if (default_date_mode) rev->date_mode = parse_date_format(default_date_mode); + /* + * Check for -h before setup_revisions(), or "git log -h" will + * fail when run without a git directory. + */ + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(builtin_log_usage); argc = setup_revisions(argc, argv, rev, "HEAD"); if (rev->diffopt.pickaxe || rev->diffopt.filter) @@ -891,6 +897,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct patch_ids ids; char *add_signoff = NULL; struct strbuf buf = STRBUF_INIT; + int use_patch_format = 0; const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, "use [PATCH n/m] even with a single patch", @@ -922,6 +929,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) "don't output binary diffs"), OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream, "don't include a patch matching a commit upstream"), + { OPTION_BOOLEAN, 'p', "no-stat", &use_patch_format, NULL, + "show patch format instead of default (patch + stat)", + PARSE_OPT_NONEG | PARSE_OPT_NOARG }, OPT_GROUP("Messaging"), { OPTION_CALLBACK, 0, "add-header", NULL, "header", "add email header", PARSE_OPT_NONEG, @@ -1027,9 +1037,20 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (argc > 1) die ("unrecognized argument: %s", argv[1]); - if (!rev.diffopt.output_format - || rev.diffopt.output_format == DIFF_FORMAT_PATCH) - rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH; + if (rev.diffopt.output_format & DIFF_FORMAT_NAME) + die("--name-only does not make sense"); + if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS) + die("--name-status does not make sense"); + if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF) + die("--check does not make sense"); + + if (!use_patch_format && + (!rev.diffopt.output_format || + rev.diffopt.output_format == DIFF_FORMAT_PATCH)) + rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY; + + /* Always generate a patch */ + rev.diffopt.output_format |= DIFF_FORMAT_PATCH; if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff) DIFF_OPT_SET(&rev.diffopt, BINARY); @@ -1237,6 +1258,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) argv++; } + if (argc > 1 && !strcmp(argv[1], "-h")) + usage(cherry_usage); + switch (argc) { case 4: limit = argv[3]; @@ -1304,8 +1328,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) if (verbose) { struct strbuf buf = STRBUF_INIT; + struct pretty_print_context ctx = {0}; pretty_print_commit(CMIT_FMT_ONELINE, commit, - &buf, 0, NULL, NULL, 0, 0); + &buf, &ctx); printf("%c %s %s\n", sign, sha1_to_hex(commit->object.sha1), buf.buf); strbuf_release(&buf); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index f473220502..c9a03e5427 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -171,8 +171,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; int dtype = ce_to_dtype(ce); - if (excluded(dir, ce->name, &dtype) != - !!(dir->flags & DIR_SHOW_IGNORED)) + if (dir->flags & DIR_SHOW_IGNORED && + !excluded(dir, ce->name, &dtype)) continue; if (show_unmerged && !ce_stage(ce)) continue; @@ -187,8 +187,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) struct stat st; int err; int dtype = ce_to_dtype(ce); - if (excluded(dir, ce->name, &dtype) != - !!(dir->flags & DIR_SHOW_IGNORED)) + if (dir->flags & DIR_SHOW_IGNORED && + !excluded(dir, ce->name, &dtype)) continue; if (ce->ce_flags & CE_UPDATE) continue; @@ -524,11 +524,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) ps_matched = xcalloc(1, num); } - if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given) { - fprintf(stderr, "%s: --ignored needs some exclude pattern\n", - argv[0]); - exit(1); - } + if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given) + die("ls-files --ignored needs some exclude pattern"); /* With no flags, we default to showing the cached files */ if (!(show_stage | show_deleted | show_others | show_unmerged | diff --git a/builtin-ls-remote.c b/builtin-ls-remote.c index 78a88f7476..b5bad0c184 100644 --- a/builtin-ls-remote.c +++ b/builtin-ls-remote.c @@ -86,10 +86,10 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) pattern[j - i] = p; } } - remote = nongit ? NULL : remote_get(dest); - if (remote && !remote->url_nr) + remote = remote_get(dest); + if (!remote->url_nr) die("remote %s has no configured URL", dest); - transport = transport_get(remote, remote ? remote->url[0] : dest); + transport = transport_get(remote, remote->url[0]); if (uploadpack != NULL) transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c index 22008dfa8f..4484185afc 100644 --- a/builtin-ls-tree.c +++ b/builtin-ls-tree.c @@ -9,6 +9,7 @@ #include "commit.h" #include "quote.h" #include "builtin.h" +#include "parse-options.h" static int line_termination = '\n'; #define LS_RECURSIVE 1 @@ -22,8 +23,10 @@ static const char **pathspec; static int chomp_prefix; static const char *ls_tree_prefix; -static const char ls_tree_usage[] = - "git ls-tree [-d] [-r] [-t] [-l] [-z] [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]] <tree-ish> [path...]"; +static const char * const ls_tree_usage[] = { + "git ls-tree [<options>] <tree-ish> [path...]", + NULL +}; static int show_recursive(const char *base, int baselen, const char *pathname) { @@ -117,76 +120,53 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) { unsigned char sha1[20]; struct tree *tree; + int full_tree = 0; + const struct option ls_tree_options[] = { + OPT_BIT('d', NULL, &ls_options, "only show trees", + LS_TREE_ONLY), + OPT_BIT('r', NULL, &ls_options, "recurse into subtrees", + LS_RECURSIVE), + OPT_BIT('t', NULL, &ls_options, "show trees when recursing", + LS_SHOW_TREES), + OPT_SET_INT('z', NULL, &line_termination, + "terminate entries with NUL byte", 0), + OPT_BIT('l', "long", &ls_options, "include object size", + LS_SHOW_SIZE), + OPT_BIT(0, "name-only", &ls_options, "list only filenames", + LS_NAME_ONLY), + OPT_BIT(0, "name-status", &ls_options, "list only filenames", + LS_NAME_ONLY), + OPT_SET_INT(0, "full-name", &chomp_prefix, + "use full path names", 0), + OPT_BOOLEAN(0, "full-tree", &full_tree, + "list entire tree; not just current directory " + "(implies --full-name)"), + OPT__ABBREV(&abbrev), + OPT_END() + }; git_config(git_default_config, NULL); ls_tree_prefix = prefix; if (prefix && *prefix) chomp_prefix = strlen(prefix); - while (1 < argc && argv[1][0] == '-') { - switch (argv[1][1]) { - case 'z': - line_termination = 0; - break; - case 'r': - ls_options |= LS_RECURSIVE; - break; - case 'd': - ls_options |= LS_TREE_ONLY; - break; - case 't': - ls_options |= LS_SHOW_TREES; - break; - case 'l': - ls_options |= LS_SHOW_SIZE; - break; - case '-': - if (!strcmp(argv[1]+2, "name-only") || - !strcmp(argv[1]+2, "name-status")) { - ls_options |= LS_NAME_ONLY; - break; - } - if (!strcmp(argv[1]+2, "long")) { - ls_options |= LS_SHOW_SIZE; - break; - } - if (!strcmp(argv[1]+2, "full-name")) { - chomp_prefix = 0; - break; - } - if (!strcmp(argv[1]+2, "full-tree")) { - ls_tree_prefix = prefix = NULL; - chomp_prefix = 0; - break; - } - if (!prefixcmp(argv[1]+2, "abbrev=")) { - abbrev = strtoul(argv[1]+9, NULL, 10); - if (abbrev && abbrev < MINIMUM_ABBREV) - abbrev = MINIMUM_ABBREV; - else if (abbrev > 40) - abbrev = 40; - break; - } - if (!strcmp(argv[1]+2, "abbrev")) { - abbrev = DEFAULT_ABBREV; - break; - } - /* otherwise fallthru */ - default: - usage(ls_tree_usage); - } - argc--; argv++; + + argc = parse_options(argc, argv, prefix, ls_tree_options, + ls_tree_usage, 0); + if (full_tree) { + ls_tree_prefix = prefix = NULL; + chomp_prefix = 0; } /* -d -r should imply -t, but -d by itself should not have to. */ if ( (LS_TREE_ONLY|LS_RECURSIVE) == ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options)) ls_options |= LS_SHOW_TREES; - if (argc < 2) - usage(ls_tree_usage); - if (get_sha1(argv[1], sha1)) - die("Not a valid object name %s", argv[1]); + if (argc < 1) + usage_with_options(ls_tree_usage, ls_tree_options); + if (get_sha1(argv[0], sha1)) + die("Not a valid object name %s", argv[0]); - pathspec = get_pathspec(prefix, argv + 2); + pathspec = get_pathspec(prefix, argv + 1); tree = parse_tree_indirect(sha1); if (!tree) die("not a tree object"); diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index b0b5d8f6cb..c90cd312ac 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -25,6 +25,7 @@ static enum { static struct strbuf charset = STRBUF_INIT; static int patch_lines; static struct strbuf **p_hdr_data, **s_hdr_data; +static int use_scissors; #define MAX_HDR_PARSED 10 #define MAX_BOUNDARIES 5 @@ -712,6 +713,56 @@ static inline int patchbreak(const struct strbuf *line) return 0; } +static int is_scissors_line(const struct strbuf *line) +{ + size_t i, len = line->len; + int scissors = 0, gap = 0; + int first_nonblank = -1; + int last_nonblank = 0, visible, perforation = 0, in_perforation = 0; + const char *buf = line->buf; + + for (i = 0; i < len; i++) { + if (isspace(buf[i])) { + if (in_perforation) { + perforation++; + gap++; + } + continue; + } + last_nonblank = i; + if (first_nonblank < 0) + first_nonblank = i; + if (buf[i] == '-') { + in_perforation = 1; + perforation++; + continue; + } + if (i + 1 < len && + (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2))) { + in_perforation = 1; + perforation += 2; + scissors += 2; + i++; + continue; + } + in_perforation = 0; + } + + /* + * The mark must be at least 8 bytes long (e.g. "-- >8 --"). + * Even though there can be arbitrary cruft on the same line + * (e.g. "cut here"), in order to avoid misidentification, the + * perforation must occupy more than a third of the visible + * width of the line, and dashes and scissors must occupy more + * than half of the perforation. + */ + + visible = last_nonblank - first_nonblank + 1; + return (scissors && 8 <= visible && + visible < perforation * 3 && + gap * 2 < perforation); +} + static int handle_commit_msg(struct strbuf *line) { static int still_looking = 1; @@ -723,7 +774,8 @@ static int handle_commit_msg(struct strbuf *line) strbuf_ltrim(line); if (!line->len) return 0; - if ((still_looking = check_header(line, s_hdr_data, 0)) != 0) + still_looking = check_header(line, s_hdr_data, 0); + if (still_looking) return 0; } @@ -731,6 +783,26 @@ static int handle_commit_msg(struct strbuf *line) if (metainfo_charset) convert_to_utf8(line, charset.buf); + if (use_scissors && is_scissors_line(line)) { + int i; + if (fseek(cmitmsg, 0L, SEEK_SET)) + die_errno("Could not rewind output message file"); + if (ftruncate(fileno(cmitmsg), 0)) + die_errno("Could not truncate output message file at scissors"); + still_looking = 1; + + /* + * We may have already read "secondary headers"; purge + * them to give ourselves a clean restart. + */ + for (i = 0; header[i]; i++) { + if (s_hdr_data[i]) + strbuf_release(s_hdr_data[i]); + s_hdr_data[i] = NULL; + } + return 0; + } + if (patchbreak(line)) { fclose(cmitmsg); cmitmsg = NULL; @@ -885,12 +957,9 @@ static void handle_info(void) fprintf(fout, "\n"); } -static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, - const char *msg, const char *patch) +static int mailinfo(FILE *in, FILE *out, const char *msg, const char *patch) { int peek; - keep_subject = ks; - metainfo_charset = encoding; fin = in; fout = out; @@ -924,8 +993,20 @@ static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, return 0; } +static int git_mailinfo_config(const char *var, const char *value, void *unused) +{ + if (prefixcmp(var, "mailinfo.")) + return git_default_config(var, value, unused); + if (!strcmp(var, "mailinfo.scissors")) { + use_scissors = git_config_bool(var, value); + return 0; + } + /* perhaps others here */ + return 0; +} + static const char mailinfo_usage[] = - "git mailinfo [-k] [-u | --encoding=<encoding> | -n] msg patch <mail >info"; + "git mailinfo [-k] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info"; int cmd_mailinfo(int argc, const char **argv, const char *prefix) { @@ -934,7 +1015,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) /* NEEDSWORK: might want to do the optional .git/ directory * discovery */ - git_config(git_default_config, NULL); + git_config(git_mailinfo_config, NULL); def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8"); metainfo_charset = def_charset; @@ -948,6 +1029,10 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) metainfo_charset = NULL; else if (!prefixcmp(argv[1], "--encoding=")) metainfo_charset = argv[1] + 11; + else if (!strcmp(argv[1], "--scissors")) + use_scissors = 1; + else if (!strcmp(argv[1], "--no-scissors")) + use_scissors = 0; else usage(mailinfo_usage); argc--; argv++; @@ -956,5 +1041,5 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) if (argc != 3) usage(mailinfo_usage); - return !!mailinfo(stdin, stdout, keep_subject, metainfo_charset, argv[1], argv[2]); + return !!mailinfo(stdin, stdout, argv[1], argv[2]); } diff --git a/builtin-mailsplit.c b/builtin-mailsplit.c index ee6ca0ebcd..207e358ed1 100644 --- a/builtin-mailsplit.c +++ b/builtin-mailsplit.c @@ -64,7 +64,7 @@ static int split_one(FILE *mbox, const char *name, int allow_bare) fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666); if (fd < 0) die_errno("cannot open output file '%s'", name); - output = fdopen(fd, "w"); + output = xfdopen(fd, "w"); /* Copy it out, while searching for a line that begins with * "From " and having something that looks like a date format. @@ -231,6 +231,8 @@ int cmd_mailsplit(int argc, const char **argv, const char *prefix) continue; } else if ( arg[1] == 'f' ) { nr = strtol(arg+2, NULL, 10); + } else if ( arg[1] == 'h' ) { + usage(git_mailsplit_usage); } else if ( arg[1] == 'b' && !arg[2] ) { allow_bare = 1; } else if (!strcmp(arg, "--keep-cr")) { diff --git a/builtin-merge-ours.c b/builtin-merge-ours.c index 8f5bbaf402..684411694f 100644 --- a/builtin-merge-ours.c +++ b/builtin-merge-ours.c @@ -10,6 +10,9 @@ #include "git-compat-util.h" #include "builtin.h" +static const char builtin_merge_ours_usage[] = + "git merge-ours <base>... -- HEAD <remote>..."; + static const char *diff_index_args[] = { "diff-index", "--quiet", "--cached", "HEAD", "--", NULL }; @@ -17,6 +20,9 @@ static const char *diff_index_args[] = { int cmd_merge_ours(int argc, const char **argv, const char *prefix) { + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(builtin_merge_ours_usage); + /* * We need to exit with 2 if the index does not match our HEAD tree, * because the current index is what we will be committing as the diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index d26a96e486..710674c6b2 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -33,7 +33,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) } if (argc < 4) - die("Usage: %s <base>... -- <head> <remote> ...", argv[0]); + usagef("%s <base>... -- <head> <remote> ...", argv[0]); for (i = 1; i < argc; ++i) { if (!strcmp(argv[i], "--")) diff --git a/builtin-merge.c b/builtin-merge.c index b6b84286b2..57eedd447d 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -43,6 +43,7 @@ static const char * const builtin_merge_usage[] = { static int show_diffstat = 1, option_log, squash; static int option_commit = 1, allow_fast_forward = 1; +static int fast_forward_only; static int allow_trivial = 1, have_message; static struct strbuf merge_msg; static struct commit_list *remoteheads; @@ -166,7 +167,9 @@ static struct option builtin_merge_options[] = { OPT_BOOLEAN(0, "commit", &option_commit, "perform a commit if the merge succeeds (default)"), OPT_BOOLEAN(0, "ff", &allow_fast_forward, - "allow fast forward (default)"), + "allow fast-forward (default)"), + OPT_BOOLEAN(0, "ff-only", &fast_forward_only, + "abort if fast-forward is not possible"), OPT_CALLBACK('s', "strategy", &use_strategies, "strategy", "merge strategy to use", option_parse_strategy), OPT_CALLBACK('m', "message", &merge_msg, "message", @@ -264,6 +267,7 @@ static void squash_message(void) struct strbuf out = STRBUF_INIT; struct commit_list *j; int fd; + struct pretty_print_context ctx = {0}; printf("Squash commit -- not updating HEAD\n"); fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666); @@ -285,13 +289,15 @@ static void squash_message(void) if (prepare_revision_walk(&rev)) die("revision walk setup failed"); + ctx.abbrev = rev.abbrev; + ctx.date_mode = rev.date_mode; + strbuf_addstr(&out, "Squashed commit of the following:\n"); while ((commit = get_revision(&rev)) != NULL) { strbuf_addch(&out, '\n'); strbuf_addf(&out, "commit %s\n", sha1_to_hex(commit->object.sha1)); - pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev, - NULL, NULL, rev.date_mode, 0); + pretty_print_commit(rev.commit_format, commit, &out, &ctx); } if (write(fd, out.buf, out.len) < 0) die_errno("Writing SQUASH_MSG"); @@ -840,7 +846,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix) const char *best_strategy = NULL, *wt_strategy = NULL; struct commit_list **remotes = &remoteheads; - setup_work_tree(); if (file_exists(git_path("MERGE_HEAD"))) die("You have not concluded your merge. (MERGE_HEAD exists)"); if (read_cache_unmerged()) @@ -874,6 +879,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) option_commit = 0; } + if (!allow_fast_forward && fast_forward_only) + die("You cannot combine --no-ff with --ff-only."); + if (!argc) usage_with_options(builtin_merge_usage, builtin_merge_options); @@ -1013,7 +1021,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) hex, find_unique_abbrev(remoteheads->item->object.sha1, DEFAULT_ABBREV)); - strbuf_addstr(&msg, "Fast forward"); + strbuf_addstr(&msg, "Fast-forward"); if (have_message) strbuf_addstr(&msg, " (no commit created; -m option ignored)"); @@ -1031,16 +1039,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } else if (!remoteheads->next && common->next) ; /* - * We are not doing octopus and not fast forward. Need + * We are not doing octopus and not fast-forward. Need * a real merge. */ else if (!remoteheads->next && !common->next && option_commit) { /* - * We are not doing octopus, not fast forward, and have + * We are not doing octopus, not fast-forward, and have * only one common. */ refresh_cache(REFRESH_QUIET); - if (allow_trivial) { + if (allow_trivial && !fast_forward_only) { /* See if it is really trivial. */ git_committer_info(IDENT_ERROR_ON_NO_NAME); printf("Trying really trivial in-index merge...\n"); @@ -1079,6 +1087,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } } + if (fast_forward_only) + die("Not possible to fast-forward, aborting."); + /* We are going to make a new commit. */ git_committer_info(IDENT_ERROR_ON_NO_NAME); diff --git a/builtin-mv.c b/builtin-mv.c index 1b20028c67..f633d81424 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -64,15 +64,15 @@ int cmd_mv(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); - newfd = hold_locked_index(&lock_file, 1); - if (read_cache() < 0) - die("index file corrupt"); - argc = parse_options(argc, argv, prefix, builtin_mv_options, builtin_mv_usage, 0); if (--argc < 1) usage_with_options(builtin_mv_usage, builtin_mv_options); + newfd = hold_locked_index(&lock_file, 1); + if (read_cache() < 0) + die("index file corrupt"); + source = copy_pathspec(prefix, argv, argc, 0); modes = xcalloc(argc, sizeof(enum update_mode)); dest_path = copy_pathspec(prefix, argv + argc, 1, 0); diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 1ff2ce6df4..4c91e944c2 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -22,15 +22,15 @@ #include <pthread.h> #endif -static const char pack_usage[] = "\ -git pack-objects [{ -q | --progress | --all-progress }] \n\ - [--max-pack-size=N] [--local] [--incremental] \n\ - [--window=N] [--window-memory=N] [--depth=N] \n\ - [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset] \n\ - [--threads=N] [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\ - [--stdout | base-name] [--include-tag] \n\ - [--keep-unreachable | --unpack-unreachable] \n\ - [<ref-list | <object-list]"; +static const char pack_usage[] = + "git pack-objects [{ -q | --progress | --all-progress }]\n" + " [--max-pack-size=N] [--local] [--incremental]\n" + " [--window=N] [--window-memory=N] [--depth=N]\n" + " [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset]\n" + " [--threads=N] [--non-empty] [--revs [--unpacked | --all]*]\n" + " [--reflog] [--stdout | base-name] [--include-tag]\n" + " [--keep-unreachable | --unpack-unreachable \n" + " [<ref-list | <object-list]"; struct object_entry { struct pack_idx_entry idx; @@ -1008,6 +1008,33 @@ static void add_preferred_base(unsigned char *sha1) it->pcache.tree_size = size; } +static void cleanup_preferred_base(void) +{ + struct pbase_tree *it; + unsigned i; + + it = pbase_tree; + pbase_tree = NULL; + while (it) { + struct pbase_tree *this = it; + it = this->next; + free(this->pcache.tree_data); + free(this); + } + + for (i = 0; i < ARRAY_SIZE(pbase_tree_cache); i++) { + if (!pbase_tree_cache[i]) + continue; + free(pbase_tree_cache[i]->tree_data); + free(pbase_tree_cache[i]); + pbase_tree_cache[i] = NULL; + } + + free(done_pbase_paths); + done_pbase_paths = NULL; + done_pbase_paths_num = done_pbase_paths_alloc = 0; +} + static void check_object(struct object_entry *entry) { if (entry->in_pack) { @@ -1602,6 +1629,8 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, struct thread_params *p; int i, ret, active_threads = 0; + if (!delta_search_threads) /* --threads=0 means autodetect */ + delta_search_threads = online_cpus(); if (delta_search_threads <= 1) { find_deltas(list, &list_size, window, depth, processed); return; @@ -2297,11 +2326,6 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (keep_unreachable && unpack_unreachable) die("--keep-unreachable and --unpack-unreachable are incompatible."); -#ifdef THREADED_DELTA_SEARCH - if (!delta_search_threads) /* --threads=0 means autodetect */ - delta_search_threads = online_cpus(); -#endif - prepare_packed_git(); if (progress) @@ -2312,6 +2336,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) rp_av[rp_ac] = NULL; get_object_list(rp_ac, rp_av); } + cleanup_preferred_base(); if (include_tag && nr_result) for_each_ref(add_ref_tag, NULL); stop_progress(&progress_state); diff --git a/builtin-push.c b/builtin-push.c index 67f6d96fbe..356d7c1fd3 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 | --mirror] [--dry-run] [--porcelain] [--tags] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [-v] [<repository> <refspec>...]", + "git push [<options>] [<repository> <refspec>...]", NULL, }; @@ -66,7 +66,6 @@ static void setup_push_tracking(void) static void setup_default_push_refspecs(void) { - git_config(git_default_config, NULL); switch (push_default) { default: case PUSH_DEFAULT_MATCHING: @@ -157,10 +156,10 @@ static int do_push(const char *repo, int flags) continue; error("failed to push some refs to '%s'", url[i]); - if (nonfastforward) { - printf("To prevent you from losing history, non-fast-forward updates were rejected.\n" - "Merge the remote changes before pushing again.\n" - "See 'non-fast forward' section of 'git push --help' for details.\n"); + if (nonfastforward && advice_push_nonfastforward) { + printf("To prevent you from losing history, non-fast-forward updates were rejected\n" + "Merge the remote changes before pushing again. See the 'non-fast-forward'\n" + "section of 'git push --help' for details.\n"); } errs++; } @@ -173,7 +172,6 @@ int cmd_push(int argc, const char **argv, const char *prefix) int tags = 0; int rc; const char *repo = NULL; /* default repository */ - struct option options[] = { OPT_BIT('q', "quiet", &flags, "be quiet", TRANSPORT_PUSH_QUIET), OPT_BIT('v', "verbose", &flags, "be verbose", TRANSPORT_PUSH_VERBOSE), @@ -181,8 +179,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL), OPT_BIT( 0 , "mirror", &flags, "mirror all refs", (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)), - OPT_BOOLEAN( 0 , "tags", &tags, "push tags"), - OPT_BIT( 0 , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), + OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"), + OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE), OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"), @@ -191,6 +189,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_END() }; + git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, push_usage, 0); if (tags) diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 14c836b169..2a3a32cbfe 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -108,11 +108,11 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) git_config(git_default_config, NULL); - newfd = hold_locked_index(&lock_file, 1); - argc = parse_options(argc, argv, unused_prefix, read_tree_options, read_tree_usage, 0); + newfd = hold_locked_index(&lock_file, 1); + prefix_set = opts.prefix ? 1 : 0; if (1 < opts.merge + opts.reset + prefix_set) die("Which one? -m, --reset, or --prefix?"); diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index b771fe9b20..78c0e69cdc 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -28,6 +28,8 @@ static int transfer_unpack_limit = -1; static int unpack_limit = 100; static int report_status; static int prefer_ofs_delta = 1; +static int auto_update_server_info; +static int auto_gc = 1; static const char *head_name; static char *capabilities_to_send; @@ -88,6 +90,16 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.updateserverinfo") == 0) { + auto_update_server_info = git_config_bool(var, value); + return 0; + } + + if (strcmp(var, "receive.autogc") == 0) { + auto_gc = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); } @@ -329,9 +341,9 @@ static const char *update(struct command *cmd) break; free_commit_list(bases); if (!ent) { - error("denying non-fast forward %s" + error("denying non-fast-forward %s" " (you should pull first)", name); - return "non-fast forward"; + return "non-fast-forward"; } } if (run_update_hook(cmd)) { @@ -615,6 +627,8 @@ static void add_alternate_refs(void) int cmd_receive_pack(int argc, const char **argv, const char *prefix) { + int advertise_refs = 0; + int stateless_rpc = 0; int i; char *dir = NULL; @@ -623,7 +637,15 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) const char *arg = *argv++; if (*arg == '-') { - /* Do flag handling here */ + if (!strcmp(arg, "--advertise-refs")) { + advertise_refs = 1; + continue; + } + if (!strcmp(arg, "--stateless-rpc")) { + stateless_rpc = 1; + continue; + } + usage(receive_pack_usage); } if (dir) @@ -652,12 +674,16 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) " report-status delete-refs ofs-delta " : " report-status delete-refs "; - add_alternate_refs(); - write_head_info(); - clear_extra_refs(); + if (advertise_refs || !stateless_rpc) { + add_alternate_refs(); + write_head_info(); + clear_extra_refs(); - /* EOF */ - packet_flush(1); + /* EOF */ + packet_flush(1); + } + if (advertise_refs) + return 0; read_head_info(); if (commands) { @@ -672,6 +698,14 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) report(unpack_status); run_receive_hook(post_receive_hook); run_update_post_hook(commands); + if (auto_gc) { + const char *argv_gc_auto[] = { + "gc", "--auto", "--quiet", NULL, + }; + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + } + if (auto_update_server_info) + update_server_info(0); } return 0; } diff --git a/builtin-reflog.c b/builtin-reflog.c index 95198c5de4..749821078d 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -362,7 +362,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, } else if (cmd->updateref && (write_in_full(lock->lock_fd, sha1_to_hex(cb.last_kept_sha1), 40) != 40 || - write_in_full(lock->lock_fd, "\n", 1) != 1 || + write_str_in_full(lock->lock_fd, "\n") != 1 || close_ref(lock) < 0)) { status |= error("Couldn't write %s", lock->lk->filename); @@ -698,6 +698,9 @@ static const char reflog_usage[] = int cmd_reflog(int argc, const char **argv, const char *prefix) { + if (argc > 1 && !strcmp(argv[1], "-h")) + usage(reflog_usage); + /* With no command, we default to showing it. */ if (argc < 2 || *argv[1] == '-') return cmd_log_reflog(argc, argv, prefix); diff --git a/builtin-remote.c b/builtin-remote.c index 0777dd719b..9aafc19c4d 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -953,7 +953,7 @@ static int show_push_info_item(struct string_list_item *item, void *cb_data) status = "up to date"; break; case PUSH_STATUS_FASTFORWARD: - status = "fast forwardable"; + status = "fast-forwardable"; break; case PUSH_STATUS_OUTOFDATE: status = "local out of date"; diff --git a/builtin-rerere.c b/builtin-rerere.c index adfb7b5f48..343d6cde48 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -106,6 +106,9 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) if (argc < 2) return rerere(); + if (!strcmp(argv[1], "-h")) + usage(git_rerere_usage); + fd = setup_rerere(&merge_rr); if (fd < 0) return 0; diff --git a/builtin-reset.c b/builtin-reset.c index 0fc0b0743f..73e60223db 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -143,6 +143,17 @@ static void update_index_from_diff(struct diff_queue_struct *q, } } +static int interactive_reset(const char *revision, const char **argv, + const char *prefix) +{ + const char **pathspec = NULL; + + if (*argv) + pathspec = get_pathspec(prefix, argv); + + return run_add_interactive(revision, "--patch=reset", pathspec); +} + static int read_from_tree(const char *prefix, const char **argv, unsigned char *tree_sha1, int refresh_flags) { @@ -184,6 +195,7 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size) int cmd_reset(int argc, const char **argv, const char *prefix) { int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0; + int patch_mode = 0; const char *rev = "HEAD"; unsigned char sha1[20], *orig = NULL, sha1_orig[20], *old_orig = NULL, sha1_old_orig[20]; @@ -199,6 +211,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) "reset HEAD, index and working tree", MERGE), OPT_BOOLEAN('q', NULL, &quiet, "disable showing new HEAD in hard reset and progress message"), + OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), OPT_END() }; @@ -252,6 +265,12 @@ int cmd_reset(int argc, const char **argv, const char *prefix) die("Could not parse object '%s'.", rev); hashcpy(sha1, commit->object.sha1); + if (patch_mode) { + if (reset_type != NONE) + die("--patch is incompatible with --{hard,mixed,soft}"); + return interactive_reset(rev, argv + i, prefix); + } + /* git reset tree [--] paths... can be used to * load chosen paths from the tree into the index without * affecting the working tree nor HEAD. */ diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 4ba1c12e0b..ac1136a3f5 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -96,9 +96,10 @@ static void show_commit(struct commit *commit, void *data) if (revs->verbose_header && commit->buffer) { struct strbuf buf = STRBUF_INIT; - pretty_print_commit(revs->commit_format, commit, - &buf, revs->abbrev, NULL, NULL, - revs->date_mode, 0); + struct pretty_print_context ctx = {0}; + ctx.abbrev = revs->abbrev; + ctx.date_mode = revs->date_mode; + pretty_print_commit(revs->commit_format, commit, &buf, &ctx); if (revs->graph) { if (buf.len) { if (revs->commit_format != CMIT_FMT_ONELINE) @@ -319,6 +320,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) memset(&info, 0, sizeof(info)); info.revs = &revs; + if (revs.bisect) + bisect_list = 1; quiet = DIFF_OPT_TST(&revs.diffopt, QUIET); for (i = 1 ; i < argc; i++) { diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 45bead6545..37d0233521 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -180,6 +180,12 @@ static int show_reference(const char *refname, const unsigned char *sha1, int fl return 0; } +static int anti_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +{ + show_rev(REVERSED, sha1, refname); + return 0; +} + static void show_datestring(const char *flag, const char *datestr) { static char buffer[100]; @@ -426,6 +432,13 @@ static void die_no_single_rev(int quiet) die("Needed a single revision"); } +static const char builtin_rev_parse_usage[] = +"git rev-parse --parseopt [options] -- [<args>...]\n" +" or: git rev-parse --sq-quote [<arg>...]\n" +" or: git rev-parse [options] [<arg>...]\n" +"\n" +"Run \"git rev-parse --parseopt -h\" for more information on the first usage."; + int cmd_rev_parse(int argc, const char **argv, const char *prefix) { int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0; @@ -438,6 +451,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (argc > 1 && !strcmp("--sq-quote", argv[1])) return cmd_sq_quote(argc - 2, argv + 2); + if (argc > 1 && !strcmp("-h", argv[1])) + usage(builtin_rev_parse_usage); + prefix = setup_git_directory(); git_config(git_default_config, NULL); for (i = 1; i < argc; i++) { @@ -548,6 +564,11 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for_each_ref(show_reference, NULL); continue; } + if (!strcmp(arg, "--bisect")) { + for_each_ref_in("refs/bisect/bad", show_reference, NULL); + for_each_ref_in("refs/bisect/good", anti_reference, NULL); + continue; + } if (!strcmp(arg, "--branches")) { for_each_branch_ref(show_reference, NULL); continue; diff --git a/builtin-send-pack.c b/builtin-send-pack.c index 37e528e283..d26997bcfe 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -2,9 +2,11 @@ #include "commit.h" #include "refs.h" #include "pkt-line.h" +#include "sideband.h" #include "run-command.h" #include "remote.h" #include "send-pack.h" +#include "quote.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" @@ -59,7 +61,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext memset(&po, 0, sizeof(po)); po.argv = argv; po.in = -1; - po.out = fd; + po.out = args->stateless_rpc ? -1 : fd; po.git_cmd = 1; if (start_command(&po)) die_errno("git pack-objects failed"); @@ -83,6 +85,20 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext } close(po.in); + + if (args->stateless_rpc) { + char *buf = xmalloc(LARGE_PACKET_MAX); + while (1) { + ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX); + if (n <= 0) + break; + send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX); + } + free(buf); + close(po.out); + po.out = -1; + } + if (finish_command(&po)) return error("pack-objects died with strange error"); return 0; @@ -246,7 +262,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count) break; case REF_STATUS_REJECT_NONFASTFORWARD: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "non-fast forward"); + "non-fast-forward"); break; case REF_STATUS_REMOTE_REJECT: print_ref_status('!', "[remote rejected]", ref, @@ -303,6 +319,59 @@ static int refs_pushed(struct ref *ref) return 0; } +static void print_helper_status(struct ref *ref) +{ + struct strbuf buf = STRBUF_INIT; + + for (; ref; ref = ref->next) { + const char *msg = NULL; + const char *res; + + switch(ref->status) { + case REF_STATUS_NONE: + res = "error"; + msg = "no match"; + break; + + case REF_STATUS_OK: + res = "ok"; + break; + + case REF_STATUS_UPTODATE: + res = "ok"; + msg = "up to date"; + break; + + case REF_STATUS_REJECT_NONFASTFORWARD: + res = "error"; + msg = "non-fast forward"; + break; + + case REF_STATUS_REJECT_NODELETE: + case REF_STATUS_REMOTE_REJECT: + res = "error"; + break; + + case REF_STATUS_EXPECTING_REPORT: + default: + continue; + } + + strbuf_reset(&buf); + strbuf_addf(&buf, "%s %s", res, ref->name); + if (ref->remote_status) + msg = ref->remote_status; + if (msg) { + strbuf_addch(&buf, ' '); + quote_two_c_style(&buf, "", msg, 0); + } + strbuf_addch(&buf, '\n'); + + safe_write(1, buf.buf, buf.len); + } + strbuf_release(&buf); +} + int send_pack(struct send_pack_args *args, int fd[], struct child_process *conn, struct ref *remote_refs, @@ -310,6 +379,7 @@ int send_pack(struct send_pack_args *args, { int in = fd[0]; int out = fd[1]; + struct strbuf req_buf = STRBUF_INIT; struct ref *ref; int new_refs; int ask_for_status_report = 0; @@ -391,14 +461,14 @@ int send_pack(struct send_pack_args *args, char *new_hex = sha1_to_hex(ref->new_sha1); if (ask_for_status_report) { - packet_write(out, "%s %s %s%c%s", + packet_buf_write(&req_buf, "%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", + packet_buf_write(&req_buf, "%s %s %s", old_hex, new_hex, ref->name); } ref->status = expect_status_report ? @@ -406,7 +476,17 @@ int send_pack(struct send_pack_args *args, REF_STATUS_OK; } - packet_flush(out); + if (args->stateless_rpc) { + if (!args->dry_run) { + packet_buf_flush(&req_buf); + send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX); + } + } else { + safe_write(out, req_buf.buf, req_buf.len); + packet_flush(out); + } + strbuf_release(&req_buf); + if (new_refs && !args->dry_run) { if (pack_objects(out, remote_refs, extra_have, args) < 0) { for (ref = remote_refs; ref; ref = ref->next) @@ -414,11 +494,15 @@ int send_pack(struct send_pack_args *args, return -1; } } + if (args->stateless_rpc && !args->dry_run) + packet_flush(out); if (expect_status_report) ret = receive_status(in, remote_refs); else ret = 0; + if (args->stateless_rpc) + packet_flush(out); if (ret < 0) return ret; @@ -478,6 +562,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) struct extra_have_objects extra_have; struct ref *remote_refs, *local_refs; int ret; + int helper_status = 0; int send_all = 0; const char *receivepack = "git-receive-pack"; int flags; @@ -523,6 +608,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) args.use_thin_pack = 1; continue; } + if (!strcmp(arg, "--stateless-rpc")) { + args.stateless_rpc = 1; + continue; + } + if (!strcmp(arg, "--helper-status")) { + helper_status = 1; + continue; + } usage(send_pack_usage); } if (!dest) { @@ -551,7 +644,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) } } - conn = git_connect(fd, dest, receivepack, args.verbose ? CONNECT_VERBOSE : 0); + if (args.stateless_rpc) { + conn = NULL; + fd[0] = 0; + fd[1] = 1; + } else { + conn = git_connect(fd, dest, receivepack, + args.verbose ? CONNECT_VERBOSE : 0); + } memset(&extra_have, 0, sizeof(extra_have)); @@ -575,12 +675,16 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) ret = send_pack(&args, fd, conn, remote_refs, &extra_have); + if (helper_status) + print_helper_status(remote_refs); + close(fd[1]); close(fd[0]); ret |= finish_connect(conn); - print_push_status(dest, remote_refs); + if (!helper_status) + print_push_status(dest, remote_refs); if (!args.dry_run && remote) { struct ref *ref; diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 4d4a3c82d6..8aa63c7857 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -158,9 +158,12 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) sha1_to_hex(commit->object.sha1)); if (log->user_format) { struct strbuf buf = STRBUF_INIT; - - pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &buf, - DEFAULT_ABBREV, "", "", DATE_NORMAL, 0); + struct pretty_print_context ctx = {0}; + ctx.abbrev = DEFAULT_ABBREV; + ctx.subject = ""; + ctx.after_subject = ""; + ctx.date_mode = DATE_NORMAL; + pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &buf, &ctx); insert_one_record(log, author, buf.buf); strbuf_release(&buf); return; diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 3510a86e38..9f13caa76d 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -293,8 +293,8 @@ static void show_one_commit(struct commit *commit, int no_name) struct commit_name *name = commit->util; if (commit->object.parsed) { - pretty_print_commit(CMIT_FMT_ONELINE, commit, - &pretty, 0, NULL, NULL, 0, 0); + struct pretty_print_context ctx = {0}; + pretty_print_commit(CMIT_FMT_ONELINE, commit, &pretty, &ctx); pretty_str = pretty.buf; } if (!prefixcmp(pretty_str, "[PATCH] ")) @@ -565,7 +565,15 @@ static int git_show_branch_config(const char *var, const char *value, void *cb) if (!strcmp(var, "showbranch.default")) { if (!value) return config_error_nonbool(var); - if (default_alloc <= default_num + 1) { + /* + * default_arg is now passed to parse_options(), so we need to + * mimick the real argv a bit better. + */ + if (!default_num) { + default_alloc = 20; + default_arg = xcalloc(default_alloc, sizeof(*default_arg)); + default_arg[default_num++] = "show-branch"; + } else if (default_alloc <= default_num + 1) { default_alloc = default_alloc * 3 / 2 + 20; default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc); } @@ -692,8 +700,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) /* If nothing is specified, try the default first */ if (ac == 1 && default_num) { - ac = default_num + 1; - av = default_arg - 1; /* ick; we would not address av[0] */ + ac = default_num; + av = default_arg; } ac = parse_options(ac, av, prefix, builtin_show_branch_options, diff --git a/builtin-show-ref.c b/builtin-show-ref.c index c46550c9c0..17ada88dfb 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -7,7 +7,7 @@ #include "parse-options.h" static const char * const show_ref_usage[] = { - "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [pattern*] ", + "git show-ref [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [pattern*] ", "git show-ref --exclude-existing[=pattern] < ref-list", NULL }; @@ -183,7 +183,10 @@ static const struct option show_ref_options[] = { OPT_BOOLEAN(0, "heads", &heads_only, "only show heads (can be combined with tags)"), OPT_BOOLEAN(0, "verify", &verify, "stricter reference checking, " "requires exact ref path"), - OPT_BOOLEAN('h', "head", &show_head, "show the HEAD reference"), + { OPTION_BOOLEAN, 'h', NULL, &show_head, NULL, + "show the HEAD reference", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + OPT_BOOLEAN(0, "head", &show_head, "show the HEAD reference"), OPT_BOOLEAN('d', "dereference", &deref_tags, "dereference tags into object IDs"), { OPTION_CALLBACK, 's', "hash", &abbrev, "n", @@ -201,6 +204,9 @@ static const struct option show_ref_options[] = { int cmd_show_ref(int argc, const char **argv, const char *prefix) { + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(show_ref_usage, show_ref_options); + argc = parse_options(argc, argv, prefix, show_ref_options, show_ref_usage, PARSE_OPT_NO_INTERNAL_HELP); diff --git a/builtin-stripspace.c b/builtin-stripspace.c index 1fd2205d53..4d3b93fedb 100644 --- a/builtin-stripspace.c +++ b/builtin-stripspace.c @@ -73,9 +73,11 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) struct strbuf buf = STRBUF_INIT; int strip_comments = 0; - if (argc > 1 && (!strcmp(argv[1], "-s") || + if (argc == 2 && (!strcmp(argv[1], "-s") || !strcmp(argv[1], "--strip-comments"))) strip_comments = 1; + else if (argc > 1) + usage("git stripspace [-s | --strip-comments] < <stream>"); if (strbuf_read(&buf, 0, 1024) < 0) die_errno("could not read the input"); diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index 8b3a35e12d..3f1e7012db 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -11,6 +11,9 @@ static const char tar_tree_usage[] = "git tar-tree [--remote=<repo>] <tree-ish> [basedir]\n" "*** Note that this command is now deprecated; use \"git archive\" instead."; +static const char builtin_get_tar_commit_id_usage[] = +"git get-tar-commit-id < <tarfile>"; + int cmd_tar_tree(int argc, const char **argv, const char *prefix) { /* @@ -81,6 +84,9 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) char *content = buffer + RECORDSIZE; ssize_t n; + if (argc != 1) + usage(builtin_get_tar_commit_id_usage); + n = read_in_full(0, buffer, HEADERSIZE); if (n < HEADERSIZE) die("git get-tar-commit-id: read error"); diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c index c4cd1e1327..29446e84cc 100644 --- a/builtin-upload-archive.c +++ b/builtin-upload-archive.c @@ -132,7 +132,6 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) while (1) { struct pollfd pfd[2]; - ssize_t processed[2] = { 0, 0 }; int status; pfd[0].fd = fd1[0]; @@ -147,15 +146,14 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) } continue; } - if (pfd[0].revents & POLLIN) - /* Data stream ready */ - processed[0] = process_input(pfd[0].fd, 1); if (pfd[1].revents & POLLIN) /* Status stream ready */ - processed[1] = process_input(pfd[1].fd, 2); - /* Always finish to read data when available */ - if (processed[0] || processed[1]) - continue; + if (process_input(pfd[1].fd, 2)) + continue; + if (pfd[0].revents & POLLIN) + /* Data stream ready */ + if (process_input(pfd[0].fd, 1)) + continue; if (waitpid(writer, &status, 0) < 0) error_clnt("%s", lostchild); @@ -48,7 +48,6 @@ 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); extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix); extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix); extern int cmd_format_patch(int argc, const char **argv, const char *prefix); @@ -234,7 +234,7 @@ int create_bundle(struct bundle_header *header, const char *path, rls.git_cmd = 1; if (start_command(&rls)) return -1; - rls_fout = fdopen(rls.out, "r"); + rls_fout = xfdopen(rls.out, "r"); while (fgets(buffer, sizeof(buffer), rls_fout)) { unsigned char sha1[20]; if (buffer[0] == '-') { @@ -4,6 +4,7 @@ #include "git-compat-util.h" #include "strbuf.h" #include "hash.h" +#include "advice.h" #include SHA1_HEADER #ifndef git_SHA_CTX @@ -371,6 +372,8 @@ static inline enum object_type object_type(unsigned int mode) #define GITATTRIBUTES_FILE ".gitattributes" #define INFOATTRIBUTES_FILE "info/attributes" #define ATTRIBUTE_MACRO_PREFIX "[attr]" +#define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF" +#define GIT_NOTES_DEFAULT_REF "refs/notes/commits" extern int is_bare_repository_cfg; extern int is_bare_repository(void); @@ -395,6 +398,7 @@ extern const char *setup_git_directory_gently(int *); extern const char *setup_git_directory(void); extern const char *prefix_path(const char *prefix, int len, const char *path); extern const char *prefix_filename(const char *prefix, int len, const char *path); +extern int check_filename(const char *prefix, const char *name); extern void verify_filename(const char *prefix, const char *name); extern void verify_non_filename(const char *prefix, const char *name); @@ -488,6 +492,7 @@ struct lock_file { }; #define LOCK_DIE_ON_ERROR 1 #define LOCK_NODEREF 2 +extern int unable_to_lock_error(const char *path, int err); extern NORETURN void unable_to_lock_index_die(const char *path, int err); extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); extern int hold_lock_file_for_append(struct lock_file *, const char *path, int); @@ -565,6 +570,8 @@ enum object_creation_mode { extern enum object_creation_mode object_creation_mode; +extern char *notes_ref_name; + extern int grafts_replace_parents; #define GIT_REPO_VERSION 0 @@ -654,6 +661,7 @@ const char *make_relative_path(const char *abs, const char *base); int normalize_path_copy(char *dst, const char *src); int longest_ancestor_length(const char *path, const char *prefix_list); char *strip_path_suffix(const char *path, const char *suffix); +int daemon_avoid_alias(const char *path); /* Read and unpack a sha1 file into memory, write memory to a sha1 file */ extern int sha1_object_info(const unsigned char *, unsigned long *); @@ -748,6 +756,8 @@ extern const char *git_author_info(int); extern const char *git_committer_info(int); extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int); extern const char *fmt_name(const char *name, const char *email); +extern const char *git_editor(void); +extern const char *git_pager(void); struct checkout { const char *base_dir; @@ -854,7 +864,6 @@ extern struct ref *find_ref_by_name(const struct ref *list, const char *name); 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); struct extra_have_objects { int nr, alloc; unsigned char (*array)[20]; @@ -923,13 +932,19 @@ extern const char *git_mailmap_file; extern void maybe_flush_or_die(FILE *, const char *); extern int copy_fd(int ifd, int ofd); extern int copy_file(const char *dst, const char *src, int mode); -extern ssize_t read_in_full(int fd, void *buf, size_t count); -extern ssize_t write_in_full(int fd, const void *buf, size_t count); +extern int copy_file_with_time(const char *dst, const char *src, int mode); extern void write_or_die(int fd, const void *buf, size_t count); extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg); extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg); extern void fsync_or_die(int fd, const char *); +extern ssize_t read_in_full(int fd, void *buf, size_t count); +extern ssize_t write_in_full(int fd, const void *buf, size_t count); +static inline ssize_t write_str_in_full(int fd, const char *str) +{ + return write_in_full(fd, str, strlen(str)); +} + /* pager.c */ extern void setup_pager(void); extern const char *pager_program; @@ -978,10 +993,12 @@ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, i * whitespace rules. * used by both diff and apply */ -#define WS_TRAILING_SPACE 01 +#define WS_BLANK_AT_EOL 01 #define WS_SPACE_BEFORE_TAB 02 #define WS_INDENT_WITH_NON_TAB 04 #define WS_CR_AT_EOL 010 +#define WS_BLANK_AT_EOF 020 +#define WS_TRAILING_SPACE (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF) #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB) extern unsigned whitespace_rule_cfg; extern unsigned whitespace_rule(const char *); diff --git a/command-list.txt b/command-list.txt index fb03a2ebb5..cc5d48b385 100644 --- a/command-list.txt +++ b/command-list.txt @@ -74,6 +74,7 @@ git-mktag plumbingmanipulators git-mktree plumbingmanipulators git-mv mainporcelain common git-name-rev plumbinginterrogators +git-notes mainporcelain git-pack-objects plumbingmanipulators git-pack-redundant plumbinginterrogators git-pack-refs ancillarymanipulators @@ -92,6 +93,7 @@ git-reflog ancillarymanipulators git-relink ancillarymanipulators git-remote ancillarymanipulators git-repack ancillarymanipulators +git-replace ancillarymanipulators git-repo-config ancillarymanipulators deprecated git-request-pull foreignscminterface git-rerere ancillaryinterrogators @@ -5,6 +5,7 @@ #include "utf8.h" #include "diff.h" #include "revision.h" +#include "notes.h" int save_commit_buffer = 1; @@ -132,8 +133,8 @@ struct commit_graft *read_graft_line(char *buf, int len) int i; struct commit_graft *graft = NULL; - if (buf[len-1] == '\n') - buf[--len] = 0; + while (len && isspace(buf[len-1])) + buf[--len] = '\0'; if (buf[0] == '#' || buf[0] == '\0') return NULL; if ((len + 1) % 41) { @@ -199,7 +200,7 @@ struct commit_graft *lookup_commit_graft(const unsigned char *sha1) return commit_graft[pos]; } -int write_shallow_commits(int fd, int use_pack_protocol) +int write_shallow_commits(struct strbuf *out, int use_pack_protocol) { int i, count = 0; for (i = 0; i < commit_graft_nr; i++) @@ -208,12 +209,10 @@ int write_shallow_commits(int fd, int use_pack_protocol) sha1_to_hex(commit_graft[i]->sha1); count++; if (use_pack_protocol) - packet_write(fd, "shallow %s", hex); + packet_buf_write(out, "shallow %s", hex); else { - if (write_in_full(fd, hex, 40) != 40) - break; - if (write_in_full(fd, "\n", 1) != 1) - break; + strbuf_addstr(out, hex); + strbuf_addch(out, '\n'); } } return count; @@ -63,6 +63,16 @@ enum cmit_fmt { CMIT_FMT_UNSPECIFIED, }; +struct pretty_print_context +{ + int abbrev; + const char *subject; + const char *after_subject; + enum date_mode date_mode; + int need_8bit_cte; + struct reflog_walk_info *reflog_info; +}; + extern int non_ascii(int); extern int has_non_ascii(const char *text); struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ @@ -70,13 +80,11 @@ extern char *reencode_commit_message(const struct commit *commit, const char **encoding_p); extern void get_commit_format(const char *arg, struct rev_info *); extern void format_commit_message(const struct commit *commit, - const void *format, struct strbuf *sb, - enum date_mode dmode); -extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*, - struct strbuf *, - int abbrev, const char *subject, - const char *after_subject, enum date_mode, - int need_8bit_cte); + const char *format, struct strbuf *sb, + const struct pretty_print_context *context); +extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, + struct strbuf *sb, + const struct pretty_print_context *context); void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb, const char *line, enum date_mode dmode, const char *encoding); @@ -131,7 +139,7 @@ extern struct commit_list *get_octopus_merge_bases(struct commit_list *in); extern int register_shallow(const unsigned char *sha1); extern int unregister_shallow(const unsigned char *sha1); -extern int write_shallow_commits(int fd, int use_pack_protocol); +extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol); extern int is_repository_shallow(void); extern struct commit_list *get_shallow_commits(struct object_array *heads, int depth, int shallow_flag, int not_shallow_flag); @@ -140,6 +148,8 @@ int is_descendant_of(struct commit *, struct commit_list *); int in_merge_bases(struct commit *, struct commit **, int); extern int interactive_add(int argc, const char **argv, const char *prefix); +extern int run_add_interactive(const char *revision, const char *patch_mode, + const char **pathspec); static inline int single_parent(struct commit *commit) { diff --git a/compat/bswap.h b/compat/bswap.h index 7246a12c6e..279e0b48b1 100644 --- a/compat/bswap.h +++ b/compat/bswap.h @@ -9,7 +9,7 @@ * Default version that the compiler ought to optimize properly with * constant values. */ -static inline unsigned int default_swab32(unsigned int val) +static inline uint32_t default_swab32(uint32_t val) { return (((val & 0xff000000) >> 24) | ((val & 0x00ff0000) >> 8) | @@ -20,7 +20,7 @@ static inline unsigned int default_swab32(unsigned int val) #if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) #define bswap32(x) ({ \ - unsigned int __res; \ + uint32_t __res; \ if (__builtin_constant_p(x)) { \ __res = default_swab32(x); \ } else { \ @@ -28,6 +28,16 @@ static inline unsigned int default_swab32(unsigned int val) } \ __res; }) +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + +#include <stdlib.h> + +#define bswap32(x) _byteswap_ulong(x) + +#endif + +#ifdef bswap32 + #undef ntohl #undef htonl #define ntohl(x) bswap32(x) diff --git a/compat/mingw.c b/compat/mingw.c index bed417875e..15fe33eaa0 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -3,7 +3,7 @@ #include <conio.h> #include "../strbuf.h" -unsigned int _CRT_fmode = _O_BINARY; +#include <shellapi.h> static int err_win_to_posix(DWORD winerr) { @@ -123,13 +123,17 @@ int mingw_open (const char *filename, int oflags, ...) { va_list args; unsigned mode; + int fd; + va_start(args, oflags); mode = va_arg(args, int); va_end(args); if (!strcmp(filename, "/dev/null")) filename = "nul"; - int fd = open(filename, oflags, mode); + + fd = open(filename, oflags, mode); + if (fd < 0 && (oflags & O_CREAT) && errno == EACCES) { DWORD attrs = GetFileAttributes(filename); if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) @@ -580,10 +584,11 @@ static char **get_path_split(void) static void free_path_split(char **path) { + char **p = path; + if (!path) return; - char **p = path; while (*p) free(*p++); free(path); @@ -824,7 +829,7 @@ void mingw_execvp(const char *cmd, char *const *argv) free_path_split(path); } -char **copy_environ() +static char **copy_environ(void) { char **env; int i = 0; @@ -861,7 +866,7 @@ static int lookup_env(char **env, const char *name, size_t nmln) /* * If name contains '=', then sets the variable, otherwise it unsets it */ -char **env_setenv(char **env, const char *name) +static char **env_setenv(char **env, const char *name) { char *eq = strchrnul(name, '='); int i = lookup_env(env, name, eq-name); @@ -886,6 +891,18 @@ char **env_setenv(char **env, const char *name) return env; } +/* + * Copies global environ and adjusts variables as specified by vars. + */ +char **make_augmented_environ(const char *const *vars) +{ + char **env = copy_environ(); + + while (*vars) + env = env_setenv(env, *vars++); + return env; +} + /* this is the first function to call into WS_32; initialize it */ #undef gethostbyname struct hostent *mingw_gethostbyname(const char *host) @@ -983,6 +1000,18 @@ repeat: return -1; } +/* + * Note that this doesn't return the actual pagesize, but + * the allocation granularity. If future Windows specific git code + * needs the real getpagesize function, we need to find another solution. + */ +int mingw_getpagesize(void) +{ + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwAllocationGranularity; +} + struct passwd *getpwuid(int uid) { static char user_name[100]; @@ -1012,7 +1041,7 @@ static sig_handler_t timer_fn = SIG_DFL; * length to call the signal handler. */ -static __stdcall unsigned ticktack(void *dummy) +static unsigned __stdcall ticktack(void *dummy) { while (WaitForSingleObject(timer_event, timer_interval) == WAIT_TIMEOUT) { if (timer_fn == SIG_DFL) @@ -1108,9 +1137,9 @@ int sigaction(int sig, struct sigaction *in, struct sigaction *out) #undef signal sig_handler_t mingw_signal(int sig, sig_handler_t handler) { + sig_handler_t old = timer_fn; if (sig != SIGALRM) return signal(sig, handler); - sig_handler_t old = timer_fn; timer_fn = handler; return old; } @@ -1139,7 +1168,7 @@ void mingw_open_html(const char *unixpath) int link(const char *oldpath, const char *newpath) { - typedef BOOL WINAPI (*T)(const char*, const char*, LPSECURITY_ATTRIBUTES); + typedef BOOL (WINAPI *T)(const char*, const char*, LPSECURITY_ATTRIBUTES); static T create_hard_link = NULL; if (!create_hard_link) { create_hard_link = (T) GetProcAddress( @@ -1197,8 +1226,9 @@ struct dirent *mingw_readdir(DIR *dir) if (dir->dd_handle == (long)INVALID_HANDLE_VALUE && dir->dd_stat == 0) { + DWORD lasterr; handle = FindFirstFileA(dir->dd_name, &buf); - DWORD lasterr = GetLastError(); + lasterr = GetLastError(); dir->dd_handle = (long)handle; if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) { errno = err_win_to_posix(lasterr); diff --git a/compat/mingw.h b/compat/mingw.h index 948de66eb5..51993ef114 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -41,6 +41,7 @@ struct passwd { extern char *getpass(const char *prompt); +#ifndef POLLIN struct pollfd { int fd; /* file descriptor */ short events; /* requested events */ @@ -48,6 +49,7 @@ struct pollfd { }; #define POLLIN 1 #define POLLHUP 2 +#endif typedef void (__cdecl *sig_handler_t)(int); struct sigaction { @@ -122,6 +124,27 @@ static inline int waitpid(pid_t pid, int *status, unsigned options) return -1; } +#ifndef NO_OPENSSL +#include <openssl/ssl.h> +static inline int mingw_SSL_set_fd(SSL *ssl, int fd) +{ + return SSL_set_fd(ssl, _get_osfhandle(fd)); +} +#define SSL_set_fd mingw_SSL_set_fd + +static inline int mingw_SSL_set_rfd(SSL *ssl, int fd) +{ + return SSL_set_rfd(ssl, _get_osfhandle(fd)); +} +#define SSL_set_rfd mingw_SSL_set_rfd + +static inline int mingw_SSL_set_wfd(SSL *ssl, int fd) +{ + return SSL_set_wfd(ssl, _get_osfhandle(fd)); +} +#define SSL_set_wfd mingw_SSL_set_wfd +#endif + /* * implementations of missing functions */ @@ -164,7 +187,7 @@ int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz); int mingw_rename(const char*, const char*); #define rename mingw_rename -#ifdef USE_WIN32_MMAP +#if defined(USE_WIN32_MMAP) || defined(_MSC_VER) int mingw_getpagesize(void); #define getpagesize mingw_getpagesize #endif @@ -222,18 +245,22 @@ void mingw_open_html(const char *path); * helpers */ -char **copy_environ(void); +char **make_augmented_environ(const char *const *vars); void free_environ(char **env); -char **env_setenv(char **env, const char *name); /* * A replacement of main() that ensures that argv[0] has a path + * and that default fmode and std(in|out|err) are in binary mode */ #define main(c,v) dummy_decl_mingw_main(); \ static int mingw_main(); \ int main(int argc, const char **argv) \ { \ + _fmode = _O_BINARY; \ + _setmode(_fileno(stdin), _O_BINARY); \ + _setmode(_fileno(stdout), _O_BINARY); \ + _setmode(_fileno(stderr), _O_BINARY); \ argv[0] = xstrdup(_pgmptr); \ return mingw_main(argc, argv); \ } \ diff --git a/compat/msvc.c b/compat/msvc.c new file mode 100644 index 0000000000..ac04a4ccbd --- /dev/null +++ b/compat/msvc.c @@ -0,0 +1,35 @@ +#include "../git-compat-util.h" +#include "win32.h" +#include <conio.h> +#include "../strbuf.h" + +DIR *opendir(const char *name) +{ + int len; + DIR *p; + p = (DIR*)malloc(sizeof(DIR)); + memset(p, 0, sizeof(DIR)); + strncpy(p->dd_name, name, PATH_MAX); + len = strlen(p->dd_name); + p->dd_name[len] = '/'; + p->dd_name[len+1] = '*'; + + if (p == NULL) + return NULL; + + p->dd_handle = _findfirst(p->dd_name, &p->dd_dta); + + if (p->dd_handle == -1) { + free(p); + return NULL; + } + return p; +} +int closedir(DIR *dir) +{ + _findclose(dir->dd_handle); + free(dir); + return 0; +} + +#include "mingw.c" diff --git a/compat/msvc.h b/compat/msvc.h new file mode 100644 index 0000000000..9c753a560f --- /dev/null +++ b/compat/msvc.h @@ -0,0 +1,50 @@ +#ifndef __MSVC__HEAD +#define __MSVC__HEAD + +#include <direct.h> +#include <process.h> +#include <malloc.h> + +/* porting function */ +#define inline __inline +#define __inline__ __inline +#define __attribute__(x) +#define va_copy(dst, src) ((dst) = (src)) +#define strncasecmp _strnicmp +#define ftruncate _chsize + +static __inline int strcasecmp (const char *s1, const char *s2) +{ + int size1 = strlen(s1); + int sisz2 = strlen(s2); + return _strnicmp(s1, s2, sisz2 > size1 ? sisz2 : size1); +} + +#undef ERROR +#undef stat +#undef _stati64 +#include "compat/mingw.h" +#undef stat +#define stat _stati64 +#define _stat64(x,y) mingw_lstat(x,y) + +/* + Even though _stati64 is normally just defined at _stat64 + on Windows, we specify it here as a proper struct to avoid + compiler warnings about macro redefinition due to magic in + mingw.h. Struct taken from ReactOS (GNU GPL license). +*/ +struct _stati64 { + _dev_t st_dev; + _ino_t st_ino; + unsigned short st_mode; + short st_nlink; + short st_uid; + short st_gid; + _dev_t st_rdev; + __int64 st_size; + time_t st_atime; + time_t st_mtime; + time_t st_ctime; +}; +#endif diff --git a/compat/regex/regex.c b/compat/regex/regex.c index 5ea007567d..67d5c370a0 100644 --- a/compat/regex/regex.c +++ b/compat/regex/regex.c @@ -4852,11 +4852,8 @@ regexec (preg, string, nmatch, pmatch, eflags) from either regcomp or regexec. We don't use PREG here. */ size_t -regerror (errcode, preg, errbuf, errbuf_size) - int errcode; - const regex_t *preg; - char *errbuf; - size_t errbuf_size; +regerror(int errcode, const regex_t *preg, + char *errbuf, size_t errbuf_size) { const char *msg; size_t msg_size; diff --git a/compat/snprintf.c b/compat/snprintf.c index 4d07087abd..e1e0e7543d 100644 --- a/compat/snprintf.c +++ b/compat/snprintf.c @@ -2,12 +2,14 @@ /* * The size parameter specifies the available space, i.e. includes - * the trailing NUL byte; but Windows's vsnprintf expects the - * number of characters to write, and does not necessarily write the - * trailing NUL. + * the trailing NUL byte; but Windows's vsnprintf uses the entire + * buffer and avoids the trailing NUL, should the buffer be exactly + * big enough for the result. Defining SNPRINTF_SIZE_CORR to 1 will + * therefore remove 1 byte from the reported buffer size, so we + * always have room for a trailing NUL byte. */ #ifndef SNPRINTF_SIZE_CORR -#if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ < 4 +#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4) #define SNPRINTF_SIZE_CORR 1 #else #define SNPRINTF_SIZE_CORR 0 diff --git a/compat/vcbuild/README b/compat/vcbuild/README new file mode 100644 index 0000000000..df8a6574c9 --- /dev/null +++ b/compat/vcbuild/README @@ -0,0 +1,50 @@ +The Steps of Build Git with VS2008 + +1. You need the build environment, which contains the Git dependencies + to be able to compile, link and run Git with MSVC. + + You can either use the binary repository: + + WWW: http://repo.or.cz/w/msvcgit.git + Git: git clone git://repo.or.cz/msvcgit.git + Zip: http://repo.or.cz/w/msvcgit.git?a=snapshot;h=master;sf=zip + + and call the setup_32bit_env.cmd batch script before compiling Git, + (see repo/package README for details), or the source repository: + + WWW: http://repo.or.cz/w/gitbuild.git + Git: git clone git://repo.or.cz/gitbuild.git + Zip: (None, as it's a project with submodules) + + and build the support libs as instructed in that repo/package. + +2. Ensure you have the msysgit environment in your path, so you have + GNU Make, bash and perl available. + + WWW: http://repo.or.cz/w/msysgit.git + Git: git clone git://repo.or.cz/msysgit.git + Zip: http://repo.or.cz/w/msysgit.git?a=snapshot;h=master;sf=zip + + This environment is also needed when you use the resulting + executables, since Git might need to run scripts which are part of + the git operations. + +3. Inside Git's directory run the command: + make common-cmds.h + to generate the common-cmds.h file needed to compile git. + +4. Then either build Git with the GNU Make Makefile in the Git projects + root + make MSVC=1 + or generate Visual Studio solution/projects (.sln/.vcproj) with the + command + perl contrib/buildsystems/generate -g Vcproj + and open and build the solution with the IDE + devenv git.sln /useenv + or build with the IDE build engine directly from the command line + devenv git.sln /useenv /build "Release|Win32" + The /useenv option is required, so Visual Studio picks up the + environment variables for the support libraries required to build + Git, which you set up in step 1. + +Done! diff --git a/compat/vcbuild/include/alloca.h b/compat/vcbuild/include/alloca.h new file mode 100644 index 0000000000..c0d7985b7e --- /dev/null +++ b/compat/vcbuild/include/alloca.h @@ -0,0 +1 @@ +#include <malloc.h> diff --git a/compat/vcbuild/include/arpa/inet.h b/compat/vcbuild/include/arpa/inet.h new file mode 100644 index 0000000000..0d8552a2c6 --- /dev/null +++ b/compat/vcbuild/include/arpa/inet.h @@ -0,0 +1 @@ +/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/dirent.h b/compat/vcbuild/include/dirent.h new file mode 100644 index 0000000000..440618db0d --- /dev/null +++ b/compat/vcbuild/include/dirent.h @@ -0,0 +1,128 @@ +/* + * DIRENT.H (formerly DIRLIB.H) + * This file has no copyright assigned and is placed in the Public Domain. + * This file is a part of the mingw-runtime package. + * + * The mingw-runtime package and its code is distributed in the hope that it + * will be useful but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESSED OR + * IMPLIED ARE HEREBY DISCLAIMED. This includes but is not limited to + * warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You are free to use this package and its code without limitation. + */ +#ifndef _DIRENT_H_ +#define _DIRENT_H_ +#include <io.h> + +#define PATH_MAX 512 + +#define __MINGW_NOTHROW + +#ifndef RC_INVOKED + +#ifdef __cplusplus +extern "C" { +#endif + +struct dirent +{ + long d_ino; /* Always zero. */ + unsigned short d_reclen; /* Always zero. */ + unsigned short d_namlen; /* Length of name in d_name. */ + char d_name[FILENAME_MAX]; /* File name. */ +}; + +/* + * This is an internal data structure. Good programmers will not use it + * except as an argument to one of the functions below. + * dd_stat field is now int (was short in older versions). + */ +typedef struct +{ + /* disk transfer area for this dir */ + struct _finddata_t dd_dta; + + /* dirent struct to return from dir (NOTE: this makes this thread + * safe as long as only one thread uses a particular DIR struct at + * a time) */ + struct dirent dd_dir; + + /* _findnext handle */ + long dd_handle; + + /* + * Status of search: + * 0 = not started yet (next entry to read is first entry) + * -1 = off the end + * positive = 0 based index of next entry + */ + int dd_stat; + + /* given path for dir with search pattern (struct is extended) */ + char dd_name[PATH_MAX+3]; +} DIR; + +DIR* __cdecl __MINGW_NOTHROW opendir (const char*); +struct dirent* __cdecl __MINGW_NOTHROW readdir (DIR*); +int __cdecl __MINGW_NOTHROW closedir (DIR*); +void __cdecl __MINGW_NOTHROW rewinddir (DIR*); +long __cdecl __MINGW_NOTHROW telldir (DIR*); +void __cdecl __MINGW_NOTHROW seekdir (DIR*, long); + + +/* wide char versions */ + +struct _wdirent +{ + long d_ino; /* Always zero. */ + unsigned short d_reclen; /* Always zero. */ + unsigned short d_namlen; /* Length of name in d_name. */ + wchar_t d_name[FILENAME_MAX]; /* File name. */ +}; + +/* + * This is an internal data structure. Good programmers will not use it + * except as an argument to one of the functions below. + */ +typedef struct +{ + /* disk transfer area for this dir */ + //struct _wfinddata_t dd_dta; + + /* dirent struct to return from dir (NOTE: this makes this thread + * safe as long as only one thread uses a particular DIR struct at + * a time) */ + struct _wdirent dd_dir; + + /* _findnext handle */ + long dd_handle; + + /* + * Status of search: + * 0 = not started yet (next entry to read is first entry) + * -1 = off the end + * positive = 0 based index of next entry + */ + int dd_stat; + + /* given path for dir with search pattern (struct is extended) */ + wchar_t dd_name[1]; +} _WDIR; + + + +_WDIR* __cdecl __MINGW_NOTHROW _wopendir (const wchar_t*); +struct _wdirent* __cdecl __MINGW_NOTHROW _wreaddir (_WDIR*); +int __cdecl __MINGW_NOTHROW _wclosedir (_WDIR*); +void __cdecl __MINGW_NOTHROW _wrewinddir (_WDIR*); +long __cdecl __MINGW_NOTHROW _wtelldir (_WDIR*); +void __cdecl __MINGW_NOTHROW _wseekdir (_WDIR*, long); + + +#ifdef __cplusplus +} +#endif + +#endif /* Not RC_INVOKED */ + +#endif /* Not _DIRENT_H_ */ diff --git a/compat/vcbuild/include/grp.h b/compat/vcbuild/include/grp.h new file mode 100644 index 0000000000..0d8552a2c6 --- /dev/null +++ b/compat/vcbuild/include/grp.h @@ -0,0 +1 @@ +/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/inttypes.h b/compat/vcbuild/include/inttypes.h new file mode 100644 index 0000000000..0d8552a2c6 --- /dev/null +++ b/compat/vcbuild/include/inttypes.h @@ -0,0 +1 @@ +/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/netdb.h b/compat/vcbuild/include/netdb.h new file mode 100644 index 0000000000..0d8552a2c6 --- /dev/null +++ b/compat/vcbuild/include/netdb.h @@ -0,0 +1 @@ +/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/netinet/in.h b/compat/vcbuild/include/netinet/in.h new file mode 100644 index 0000000000..0d8552a2c6 --- /dev/null +++ b/compat/vcbuild/include/netinet/in.h @@ -0,0 +1 @@ +/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/netinet/tcp.h b/compat/vcbuild/include/netinet/tcp.h new file mode 100644 index 0000000000..0d8552a2c6 --- /dev/null +++ b/compat/vcbuild/include/netinet/tcp.h @@ -0,0 +1 @@ +/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/pwd.h b/compat/vcbuild/include/pwd.h new file mode 100644 index 0000000000..0d8552a2c6 --- /dev/null +++ b/compat/vcbuild/include/pwd.h @@ -0,0 +1 @@ +/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/sys/ioctl.h b/compat/vcbuild/include/sys/ioctl.h new file mode 100644 index 0000000000..0d8552a2c6 --- /dev/null +++ b/compat/vcbuild/include/sys/ioctl.h @@ -0,0 +1 @@ +/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/sys/param.h b/compat/vcbuild/include/sys/param.h new file mode 100644 index 0000000000..0d8552a2c6 --- /dev/null +++ b/compat/vcbuild/include/sys/param.h @@ -0,0 +1 @@ +/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/sys/poll.h b/compat/vcbuild/include/sys/poll.h new file mode 100644 index 0000000000..0d8552a2c6 --- /dev/null +++ b/compat/vcbuild/include/sys/poll.h @@ -0,0 +1 @@ +/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/sys/select.h b/compat/vcbuild/include/sys/select.h new file mode 100644 index 0000000000..0d8552a2c6 --- /dev/null +++ b/compat/vcbuild/include/sys/select.h @@ -0,0 +1 @@ +/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/sys/socket.h b/compat/vcbuild/include/sys/socket.h new file mode 100644 index 0000000000..0d8552a2c6 --- /dev/null +++ b/compat/vcbuild/include/sys/socket.h @@ -0,0 +1 @@ +/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/sys/time.h b/compat/vcbuild/include/sys/time.h new file mode 100644 index 0000000000..0d8552a2c6 --- /dev/null +++ b/compat/vcbuild/include/sys/time.h @@ -0,0 +1 @@ +/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/sys/utime.h b/compat/vcbuild/include/sys/utime.h new file mode 100644 index 0000000000..582589c70a --- /dev/null +++ b/compat/vcbuild/include/sys/utime.h @@ -0,0 +1,34 @@ +#ifndef _UTIME_H_ +#define _UTIME_H_ +/* + * UTIME.H + * This file has no copyright assigned and is placed in the Public Domain. + * This file is a part of the mingw-runtime package. + * + * The mingw-runtime package and its code is distributed in the hope that it + * will be useful but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESSED OR + * IMPLIED ARE HEREBY DISCLAIMED. This includes but is not limited to + * warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You are free to use this package and its code without limitation. + */ + +/* + * Structure used by _utime function. + */ +struct _utimbuf +{ + time_t actime; /* Access time */ + time_t modtime; /* Modification time */ +}; + +#ifndef _NO_OLDNAMES +/* NOTE: Must be the same as _utimbuf above. */ +struct utimbuf +{ + time_t actime; + time_t modtime; +}; +#endif /* Not _NO_OLDNAMES */ + +#endif diff --git a/compat/vcbuild/include/sys/wait.h b/compat/vcbuild/include/sys/wait.h new file mode 100644 index 0000000000..0d8552a2c6 --- /dev/null +++ b/compat/vcbuild/include/sys/wait.h @@ -0,0 +1 @@ +/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/unistd.h b/compat/vcbuild/include/unistd.h new file mode 100644 index 0000000000..2a4f276869 --- /dev/null +++ b/compat/vcbuild/include/unistd.h @@ -0,0 +1,92 @@ +#ifndef _UNISTD_ +#define _UNISTD_ + +/* Win32 define for porting git*/ + +#ifndef _MODE_T_ +#define _MODE_T_ +typedef unsigned short _mode_t; + +#ifndef _NO_OLDNAMES +typedef _mode_t mode_t; +#endif +#endif /* Not _MODE_T_ */ + +#ifndef _SSIZE_T_ +#define _SSIZE_T_ +typedef long _ssize_t; + +#ifndef _OFF_T_ +#define _OFF_T_ +typedef long _off_t; + +#ifndef _NO_OLDNAMES +typedef _off_t off_t; +#endif +#endif /* Not _OFF_T_ */ + + +#ifndef _NO_OLDNAMES +typedef _ssize_t ssize_t; +#endif +#endif /* Not _SSIZE_T_ */ + +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned uint32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; + +typedef long long intmax_t; +typedef unsigned long long uintmax_t; + +typedef int64_t off64_t; + +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +/* Some defines for _access nAccessMode (MS doesn't define them, but + * it doesn't seem to hurt to add them). */ +#define F_OK 0 /* Check for file existence */ +/* Well maybe it does hurt. On newer versions of MSVCRT, an access mode + of 1 causes invalid parameter error. */ +#define X_OK 0 /* MS access() doesn't check for execute permission. */ +#define W_OK 2 /* Check for write permission */ +#define R_OK 4 /* Check for read permission */ + +#define _S_IFIFO 0x1000 /* FIFO */ +#define _S_IFCHR 0x2000 /* Character */ +#define _S_IFBLK 0x3000 /* Block: Is this ever set under w32? */ +#define _S_IFDIR 0x4000 /* Directory */ +#define _S_IFREG 0x8000 /* Regular */ + +#define _S_IFMT 0xF000 /* File type mask */ + +#define _S_IXUSR _S_IEXEC +#define _S_IWUSR _S_IWRITE +#define _S_IRUSR _S_IREAD +#define _S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) + +#define S_IFIFO _S_IFIFO +#define S_IFCHR _S_IFCHR +#define S_IFBLK _S_IFBLK +#define S_IFDIR _S_IFDIR +#define S_IFREG _S_IFREG +#define S_IFMT _S_IFMT +#define S_IEXEC _S_IEXEC +#define S_IWRITE _S_IWRITE +#define S_IREAD _S_IREAD +#define S_IRWXU _S_IRWXU +#define S_IXUSR _S_IXUSR +#define S_IWUSR _S_IWUSR +#define S_IRUSR _S_IRUSR + + +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) + +#endif diff --git a/compat/vcbuild/include/utime.h b/compat/vcbuild/include/utime.h new file mode 100644 index 0000000000..8285f38fde --- /dev/null +++ b/compat/vcbuild/include/utime.h @@ -0,0 +1 @@ +#include <sys/utime.h> diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl new file mode 100644 index 0000000000..8a2112f22f --- /dev/null +++ b/compat/vcbuild/scripts/clink.pl @@ -0,0 +1,51 @@ +#!/usr/bin/perl -w +###################################################################### +# Compiles or links files +# +# This is a wrapper to facilitate the compilation of Git with MSVC +# using GNU Make as the build system. So, instead of manipulating the +# Makefile into something nasty, just to support non-space arguments +# etc, we use this wrapper to fix the command line options +# +# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com> +###################################################################### +use strict; +my @args = (); +my @cflags = (); +my $is_linking = 0; +while (@ARGV) { + my $arg = shift @ARGV; + if ("$arg" =~ /^-[DIMGO]/) { + push(@cflags, $arg); + } elsif ("$arg" eq "-o") { + my $file_out = shift @ARGV; + if ("$file_out" =~ /exe$/) { + $is_linking = 1; + push(@args, "-OUT:$file_out"); + } else { + push(@args, "-Fo$file_out"); + } + } elsif ("$arg" eq "-lz") { + push(@args, "zlib.lib"); + } elsif ("$arg" eq "-liconv") { + push(@args, "iconv.lib"); + } elsif ("$arg" eq "-lcrypto") { + push(@args, "libeay32.lib"); + push(@args, "ssleay32.lib"); + } elsif ("$arg" =~ /^-L/ && "$arg" ne "-LTCG") { + $arg =~ s/^-L/-LIBPATH:/; + push(@args, $arg); + } elsif ("$arg" =~ /^-R/) { + # eat + } else { + push(@args, $arg); + } +} +if ($is_linking) { + unshift(@args, "link.exe"); +} else { + unshift(@args, "cl.exe"); + push(@args, @cflags); +} +#printf("**** @args\n"); +exit (system(@args) != 0); diff --git a/compat/vcbuild/scripts/lib.pl b/compat/vcbuild/scripts/lib.pl new file mode 100644 index 0000000000..d8054e469f --- /dev/null +++ b/compat/vcbuild/scripts/lib.pl @@ -0,0 +1,26 @@ +#!/usr/bin/perl -w +###################################################################### +# Libifies files on Windows +# +# This is a wrapper to facilitate the compilation of Git with MSVC +# using GNU Make as the build system. So, instead of manipulating the +# Makefile into something nasty, just to support non-space arguments +# etc, we use this wrapper to fix the command line options +# +# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com> +###################################################################### +use strict; +my @args = (); +while (@ARGV) { + my $arg = shift @ARGV; + if ("$arg" eq "rcs") { + # Consume the rcs option + } elsif ("$arg" =~ /\.a$/) { + push(@args, "-OUT:$arg"); + } else { + push(@args, $arg); + } +} +unshift(@args, "lib.exe"); +# printf("**** @args\n"); +exit (system(@args) != 0); diff --git a/compat/win32.h b/compat/win32.h index c26384e595..8ce91048de 100644 --- a/compat/win32.h +++ b/compat/win32.h @@ -1,5 +1,10 @@ +#ifndef WIN32_H +#define WIN32_H + /* common Win32 functions for MinGW and Cygwin */ +#ifndef WIN32 /* Not defined by Cygwin */ #include <windows.h> +#endif static inline int file_attr_to_st_mode (DWORD attr) { @@ -32,3 +37,5 @@ static inline int get_file_attr(const char *fname, WIN32_FILE_ATTRIBUTE_DATA *fd return ENOENT; } } + +#endif diff --git a/compat/win32mmap.c b/compat/win32mmap.c index 779d796cd5..1c5a14922f 100644 --- a/compat/win32mmap.c +++ b/compat/win32mmap.c @@ -1,17 +1,5 @@ #include "../git-compat-util.h" -/* - * Note that this doesn't return the actual pagesize, but - * the allocation granularity. If future Windows specific git code - * needs the real getpagesize function, we need to find another solution. - */ -int mingw_getpagesize(void) -{ - SYSTEM_INFO si; - GetSystemInfo(&si); - return si.dwAllocationGranularity; -} - void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset) { HANDLE hmap; diff --git a/compat/winansi.c b/compat/winansi.c index 9217c24b43..dedce2104e 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -2,7 +2,6 @@ * Copyright 2008 Peter Harris <git@peter.is-a-geek.org> */ -#include <windows.h> #include "../git-compat-util.h" /* @@ -467,6 +467,11 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.notesref")) { + notes_ref_name = xstrdup(value); + return 0; + } + if (!strcmp(var, "core.pager")) return git_config_string(&pager_program, var, value); @@ -627,6 +632,9 @@ int git_default_config(const char *var, const char *value, void *dummy) if (!prefixcmp(var, "mailmap.")) return git_default_mailmap_config(var, value); + if (!prefixcmp(var, "advice.")) + return git_default_advice_config(var, value); + if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) { pager_use_color = git_config_bool(var,value); return 0; @@ -1116,7 +1124,7 @@ int git_config_set_multivar(const char *key, const char *value, copy_end - copy_begin) goto write_err_out; if (new_line && - write_in_full(fd, "\n", 1) != 1) + write_str_in_full(fd, "\n") != 1) goto write_err_out; } copy_begin = store.offset[i]; diff --git a/configure.ac b/configure.ac index b09b8e446f..4625b8672b 100644 --- a/configure.ac +++ b/configure.ac @@ -68,6 +68,26 @@ else \ GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \ fi \ ])# GIT_PARSE_WITH +# +# GIT_PARSE_WITH_SET_MAKE_VAR(WITHNAME, VAR, HELP_TEXT) +# --------------------- +# Set VAR to the value specied by --with-WITHNAME. +# No verification of arguments is performed, but warnings are issued +# if either 'yes' or 'no' is specified. +# HELP_TEXT is presented when --help is called. +# This is a direct way to allow setting variables in the Makefile. +AC_DEFUN([GIT_PARSE_WITH_SET_MAKE_VAR], +[AC_ARG_WITH([$1], + [AS_HELP_STRING([--with-$1=VALUE], $3)], + if test -n "$withval"; then \ + if test "$withval" = "yes" -o "$withval" = "no"; then \ + AC_MSG_WARN([You likely do not want either 'yes' or 'no' as] + [a value for $1 ($2). Maybe you do...?]); \ + fi; \ + \ + AC_MSG_NOTICE([Setting $2 to $withval]); \ + GIT_CONF_APPEND_LINE($2=$withval); \ + fi)])# GIT_PARSE_WITH_SET_MAKE_VAR dnl dnl GIT_CHECK_FUNC(FUNCTION, IFTRUE, IFFALSE) @@ -227,6 +247,29 @@ GIT_PARSE_WITH(iconv)) # change being considered an inode change from the update-index perspective. # +# Allow user to set ETC_GITCONFIG variable +GIT_PARSE_WITH_SET_MAKE_VAR(gitconfig, ETC_GITCONFIG, + Use VALUE instead of /etc/gitconfig as the + global git configuration file. + If VALUE is not fully qualified it will be interpretted + as a path relative to the computed prefix at runtime.) + +# +# Allow user to set the default pager +GIT_PARSE_WITH_SET_MAKE_VAR(pager, DEFAULT_PAGER, + Use VALUE as the fall-back pager instead of 'less'. + This is used by things like 'git log' when the user + does not specify a pager to use through alternate + methods. eg: /usr/bin/pager) +# +# Allow user to set the default editor +GIT_PARSE_WITH_SET_MAKE_VAR(editor, DEFAULT_EDITOR, + Use VALUE as the fall-back editor instead of 'vi'. + This is used by things like 'git commit' when the user + does not specify a preferred editor through other + methods. eg: /usr/bin/editor) + +# # Define SHELL_PATH to provide path to shell. GIT_ARG_SET_PATH(shell) # @@ -107,27 +107,6 @@ int server_supports(const char *feature) strstr(server_capabilities, feature) != NULL; } -int get_ack(int fd, unsigned char *result_sha1) -{ - static char line[1000]; - int len = packet_read_line(fd, line, sizeof(line)); - - if (!len) - die("git fetch-pack: expected ACK/NAK, got EOF"); - if (line[len-1] == '\n') - line[--len] = 0; - if (!strcmp(line, "NAK")) - return 0; - if (!prefixcmp(line, "ACK ")) { - if (!get_sha1_hex(line+4, result_sha1)) { - if (strstr(line+45, "continue")) - return 2; - return 1; - } - } - die("git fetch_pack: expected ACK/NAK, got '%s'", line); -} - int path_match(const char *path, int nr, char **match) { int i; diff --git a/contrib/buildsystems/Generators.pm b/contrib/buildsystems/Generators.pm new file mode 100644 index 0000000000..408ef714b8 --- /dev/null +++ b/contrib/buildsystems/Generators.pm @@ -0,0 +1,42 @@ +package Generators; +require Exporter; + +use strict; +use File::Basename; +no strict 'refs'; +use vars qw($VERSION @AVAILABLE); + +our $VERSION = '1.00'; +our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE); +@ISA = qw(Exporter); + +BEGIN { + local(*D); + my $me = $INC{"Generators.pm"}; + die "Couldn't find myself in \@INC, which is required to load the generators!" if ("$me" eq ""); + $me = dirname($me); + if (opendir(D,"$me/Generators")) { + foreach my $gen (readdir(D)) { + next if ($gen =~ /^\.\.?$/); + require "${me}/Generators/$gen"; + $gen =~ s,\.pm,,; + push(@AVAILABLE, $gen); + } + closedir(D); + my $gens = join(', ', @AVAILABLE); + } + + push @EXPORT_OK, qw(available); +} + +sub available { + return @AVAILABLE; +} + +sub generate { + my ($gen, $git_dir, $out_dir, $rel_dir, %build_structure) = @_; + return eval("Generators::${gen}::generate(\$git_dir, \$out_dir, \$rel_dir, \%build_structure)") if grep(/^$gen$/, @AVAILABLE); + die "Generator \"${gen}\" is not available!\nAvailable generators are: @AVAILABLE\n"; +} + +1; diff --git a/contrib/buildsystems/Generators/QMake.pm b/contrib/buildsystems/Generators/QMake.pm new file mode 100644 index 0000000000..ff3b657e61 --- /dev/null +++ b/contrib/buildsystems/Generators/QMake.pm @@ -0,0 +1,189 @@ +package Generators::QMake; +require Exporter; + +use strict; +use vars qw($VERSION); + +our $VERSION = '1.00'; +our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE); +@ISA = qw(Exporter); + +BEGIN { + push @EXPORT_OK, qw(generate); +} + +sub generate { + my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_; + + my @libs = @{$build_structure{"LIBS"}}; + foreach (@libs) { + createLibProject($_, $git_dir, $out_dir, $rel_dir, %build_structure); + } + + my @apps = @{$build_structure{"APPS"}}; + foreach (@apps) { + createAppProject($_, $git_dir, $out_dir, $rel_dir, %build_structure); + } + + createGlueProject($git_dir, $out_dir, $rel_dir, %build_structure); + return 0; +} + +sub createLibProject { + my ($libname, $git_dir, $out_dir, $rel_dir, %build_structure) = @_; + print "Generate $libname lib project\n"; + $rel_dir = "../$rel_dir"; + + my $sources = join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"LIBS_${libname}_SOURCES"}}))); + my $defines = join(" \\\n\t", sort(@{$build_structure{"LIBS_${libname}_DEFINES"}})); + my $includes= join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"LIBS_${libname}_INCLUDES"}}))); + my $cflags = join(" ", sort(@{$build_structure{"LIBS_${libname}_CFLAGS"}})); + + my $cflags_debug = $cflags; + $cflags_debug =~ s/-MT/-MTd/; + $cflags_debug =~ s/-O.//; + + my $cflags_release = $cflags; + $cflags_release =~ s/-MTd/-MT/; + + my @tmp = @{$build_structure{"LIBS_${libname}_LFLAGS"}}; + my @tmp2 = (); + foreach (@tmp) { + if (/^-LTCG/) { + } elsif (/^-L/) { + $_ =~ s/^-L/-LIBPATH:$rel_dir\//; + } + push(@tmp2, $_); + } + my $lflags = join(" ", sort(@tmp)); + + my $target = $libname; + $target =~ s/\//_/g; + $defines =~ s/-D//g; + $defines =~ s/"/\\\\"/g; + $includes =~ s/-I//g; + mkdir "$target" || die "Could not create the directory $target for lib project!\n"; + open F, ">$target/$target.pro" || die "Could not open $target/$target.pro for writing!\n"; + print F << "EOM"; +TEMPLATE = lib +TARGET = $target +DESTDIR = $rel_dir + +CONFIG -= qt +CONFIG += static + +QMAKE_CFLAGS = +QMAKE_CFLAGS_RELEASE = $cflags_release +QMAKE_CFLAGS_DEBUG = $cflags_debug +QMAKE_LIBFLAGS = $lflags + +DEFINES += \\ + $defines + +INCLUDEPATH += \\ + $includes + +SOURCES += \\ + $sources +EOM + close F; +} + +sub createAppProject { + my ($appname, $git_dir, $out_dir, $rel_dir, %build_structure) = @_; + print "Generate $appname app project\n"; + $rel_dir = "../$rel_dir"; + + my $sources = join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"APPS_${appname}_SOURCES"}}))); + my $defines = join(" \\\n\t", sort(@{$build_structure{"APPS_${appname}_DEFINES"}})); + my $includes= join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"APPS_${appname}_INCLUDES"}}))); + my $cflags = join(" ", sort(@{$build_structure{"APPS_${appname}_CFLAGS"}})); + + my $cflags_debug = $cflags; + $cflags_debug =~ s/-MT/-MTd/; + $cflags_debug =~ s/-O.//; + + my $cflags_release = $cflags; + $cflags_release =~ s/-MTd/-MT/; + + my $libs; + foreach (sort(@{$build_structure{"APPS_${appname}_LIBS"}})) { + $_ =~ s/\//_/g; + $libs .= " $_"; + } + my @tmp = @{$build_structure{"APPS_${appname}_LFLAGS"}}; + my @tmp2 = (); + foreach (@tmp) { + # next if ($_ eq "-NODEFAULTLIB:MSVCRT.lib"); + if (/^-LTCG/) { + } elsif (/^-L/) { + $_ =~ s/^-L/-LIBPATH:$rel_dir\//; + } + push(@tmp2, $_); + } + my $lflags = join(" ", sort(@tmp)); + + my $target = $appname; + $target =~ s/\.exe//; + $target =~ s/\//_/g; + $defines =~ s/-D//g; + $defines =~ s/"/\\\\"/g; + $includes =~ s/-I//g; + mkdir "$target" || die "Could not create the directory $target for app project!\n"; + open F, ">$target/$target.pro" || die "Could not open $target/$target.pro for writing!\n"; + print F << "EOM"; +TEMPLATE = app +TARGET = $target +DESTDIR = $rel_dir + +CONFIG -= qt embed_manifest_exe +CONFIG += console + +QMAKE_CFLAGS = +QMAKE_CFLAGS_RELEASE = $cflags_release +QMAKE_CFLAGS_DEBUG = $cflags_debug +QMAKE_LFLAGS = $lflags +LIBS = $libs + +DEFINES += \\ + $defines + +INCLUDEPATH += \\ + $includes + +win32:QMAKE_LFLAGS += -LIBPATH:$rel_dir +else: QMAKE_LFLAGS += -L$rel_dir + +SOURCES += \\ + $sources +EOM + close F; +} + +sub createGlueProject { + my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_; + my $libs = join(" \\ \n", map("\t$_|$_.pro", @{$build_structure{"LIBS"}})); + my $apps = join(" \\ \n", map("\t$_|$_.pro", @{$build_structure{"APPS"}})); + $libs =~ s/\.a//g; + $libs =~ s/\//_/g; + $libs =~ s/\|/\//g; + $apps =~ s/\.exe//g; + $apps =~ s/\//_/g; + $apps =~ s/\|/\//g; + + my $filename = $out_dir; + $filename =~ s/.*\/([^\/]+)$/$1/; + $filename =~ s/\/$//; + print "Generate glue project $filename.pro\n"; + open F, ">$filename.pro" || die "Could not open $filename.pro for writing!\n"; + print F << "EOM"; +TEMPLATE = subdirs +CONFIG += ordered +SUBDIRS += \\ +$libs \\ +$apps +EOM + close F; +} + +1; diff --git a/contrib/buildsystems/Generators/Vcproj.pm b/contrib/buildsystems/Generators/Vcproj.pm new file mode 100644 index 0000000000..cfa74adcc2 --- /dev/null +++ b/contrib/buildsystems/Generators/Vcproj.pm @@ -0,0 +1,626 @@ +package Generators::Vcproj; +require Exporter; + +use strict; +use vars qw($VERSION); + +our $VERSION = '1.00'; +our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE); +@ISA = qw(Exporter); + +BEGIN { + push @EXPORT_OK, qw(generate); +} + +my $guid_index = 0; +my @GUIDS = ( + "{E07B9989-2BF7-4F21-8918-BE22BA467AC3}", + "{278FFB51-0296-4A44-A81A-22B87B7C3592}", + "{7346A2C4-F0FD-444F-9EBE-1AF23B2B5650}", + "{67F421AC-EB34-4D49-820B-3196807B423F}", + "{385DCFE1-CC8C-4211-A451-80FCFC31CA51}", + "{97CC46C5-D2CC-4D26-B634-E75792B79916}", + "{C7CE21FE-6EF8-4012-A5C7-A22BCEDFBA11}", + "{51575134-3FDF-42D1-BABD-3FB12669C6C9}", + "{0AE195E4-9823-4B87-8E6F-20C5614AF2FF}", + "{4B918255-67CA-43BB-A46C-26704B666E6B}", + "{18CCFEEF-C8EE-4CC1-A265-26F95C9F4649}", + "{5D5D90FA-01B7-4973-AFE5-CA88C53AC197}", + "{1F054320-036D-49E1-B384-FB5DF0BC8AC0}", + "{7CED65EE-F2D9-4171-825B-C7D561FE5786}", + "{8D341679-0F07-4664-9A56-3BA0DE88B9BC}", + "{C189FEDC-2957-4BD7-9FA4-7622241EA145}", + "{66844203-1B9F-4C53-9274-164FFF95B847}", + "{E4FEA145-DECC-440D-AEEA-598CF381FD43}", + "{73300A8E-C8AC-41B0-B555-4F596B681BA7}", + "{873FDEB1-D01D-40BF-A1BF-8BBC58EC0F51}", + "{7922C8BE-76C5-4AC6-8BF7-885C0F93B782}", + "{E245D370-308B-4A49-BFC1-1E527827975F}", + "{F6FA957B-66FC-4ED7-B260-E59BBE4FE813}", + "{E6055070-0198-431A-BC49-8DB6CEE770AE}", + "{54159234-C3EB-43DA-906B-CE5DA5C74654}", + "{594CFC35-0B60-46F6-B8EF-9983ACC1187D}", + "{D93FCAB7-1F01-48D2-B832-F761B83231A5}", + "{DBA5E6AC-E7BE-42D3-8703-4E787141526E}", + "{6171953F-DD26-44C7-A3BE-CC45F86FC11F}", + "{9E19DDBE-F5E4-4A26-A2FE-0616E04879B8}", + "{AE81A615-99E3-4885-9CE0-D9CAA193E867}", + "{FBF4067E-1855-4F6C-8BCD-4D62E801A04D}", + "{17007948-6593-4AEB-8106-F7884B4F2C19}", + "{199D4C8D-8639-4DA6-82EF-08668C35DEE0}", + "{E085E50E-C140-4CF3-BE4B-094B14F0DDD6}", + "{00785268-A9CC-4E40-AC29-BAC0019159CE}", + "{4C06F56A-DCDB-46A6-B67C-02339935CF12}", + "{3A62D3FD-519E-4EC9-8171-D2C1BFEA022F}", + "{3A62D3FD-519E-4EC9-8171-D2C1BFEA022F}", + "{9392EB58-D7BA-410B-B1F0-B2FAA6BC89A7}", + "{2ACAB2D5-E0CE-4027-BCA0-D78B2D7A6C66}", + "{86E216C3-43CE-481A-BCB2-BE5E62850635}", + "{FB631291-7923-4B91-9A57-7B18FDBB7A42}", + "{0A176EC9-E934-45B8-B87F-16C7F4C80039}", + "{DF55CA80-46E8-4C53-B65B-4990A23DD444}", + "{3A0F9895-55D2-4710-BE5E-AD7498B5BF44}", + "{294BDC5A-F448-48B6-8110-DD0A81820F8C}", + "{4B9F66E9-FAC9-47AB-B1EF-C16756FBFD06}", + "{72EA49C6-2806-48BD-B81B-D4905102E19C}", + "{5728EB7E-8929-486C-8CD5-3238D060E768}" +); + +sub generate { + my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_; + my @libs = @{$build_structure{"LIBS"}}; + foreach (@libs) { + createLibProject($_, $git_dir, $out_dir, $rel_dir, \%build_structure); + } + + my @apps = @{$build_structure{"APPS"}}; + foreach (@apps) { + createAppProject($_, $git_dir, $out_dir, $rel_dir, \%build_structure); + } + + createGlueProject($git_dir, $out_dir, $rel_dir, %build_structure); + return 0; +} + +sub createLibProject { + my ($libname, $git_dir, $out_dir, $rel_dir, $build_structure) = @_; + print "Generate $libname vcproj lib project\n"; + $rel_dir = "..\\$rel_dir"; + $rel_dir =~ s/\//\\/g; + + my $target = $libname; + $target =~ s/\//_/g; + $target =~ s/\.a//; + + my $uuid = $GUIDS[$guid_index]; + $$build_structure{"LIBS_${target}_GUID"} = $uuid; + $guid_index += 1; + + my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"LIBS_${libname}_SOURCES"}})); + my @sources; + foreach (@srcs) { + $_ =~ s/\//\\/g; + push(@sources, $_); + } + my $defines = join(",", sort(@{$$build_structure{"LIBS_${libname}_DEFINES"}})); + my $includes= join(";", sort(map(""$rel_dir\\$_"", @{$$build_structure{"LIBS_${libname}_INCLUDES"}}))); + my $cflags = join(" ", sort(@{$$build_structure{"LIBS_${libname}_CFLAGS"}})); + $cflags =~ s/\"/"/g; + + my $cflags_debug = $cflags; + $cflags_debug =~ s/-MT/-MTd/; + $cflags_debug =~ s/-O.//; + + my $cflags_release = $cflags; + $cflags_release =~ s/-MTd/-MT/; + + my @tmp = @{$$build_structure{"LIBS_${libname}_LFLAGS"}}; + my @tmp2 = (); + foreach (@tmp) { + if (/^-LTCG/) { + } elsif (/^-L/) { + $_ =~ s/^-L/-LIBPATH:$rel_dir\//; + } + push(@tmp2, $_); + } + my $lflags = join(" ", sort(@tmp)); + + $defines =~ s/-D//g; + $defines =~ s/\"/\\"/g; + $defines =~ s/\'//g; + $includes =~ s/-I//g; + mkdir "$target" || die "Could not create the directory $target for lib project!\n"; + open F, ">$target/$target.vcproj" || die "Could not open $target/$target.pro for writing!\n"; + binmode F, ":crlf"; + print F << "EOM"; +<?xml version="1.0" encoding = "Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="9,00" + Name="$target" + ProjectGUID="$uuid"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="$rel_dir" + ConfigurationType="4" + CharacterSet="0" + IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="$cflags_debug" + Optimization="0" + InlineFunctionExpansion="1" + AdditionalIncludeDirectories="$includes" + PreprocessorDefinitions="WIN32,_DEBUG,$defines" + MinimalRebuild="true" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" + WarningLevel="3" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + SuppressStartupBanner="true" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="$rel_dir" + ConfigurationType="4" + CharacterSet="0" + WholeProgramOptimization="1" + IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="$cflags_release" + Optimization="2" + InlineFunctionExpansion="1" + EnableIntrinsicFunctions="true" + AdditionalIncludeDirectories="$includes" + PreprocessorDefinitions="WIN32,NDEBUG,$defines" + RuntimeLibrary="0" + EnableFunctionLevelLinking="true" + UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" + WarningLevel="3" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + SuppressStartupBanner="true" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> +EOM + foreach(@sources) { + print F << "EOM"; + <File + RelativePath="$_"/> +EOM + } + print F << "EOM"; + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> +EOM + close F; +} + +sub createAppProject { + my ($appname, $git_dir, $out_dir, $rel_dir, $build_structure) = @_; + print "Generate $appname vcproj app project\n"; + $rel_dir = "..\\$rel_dir"; + $rel_dir =~ s/\//\\/g; + + my $target = $appname; + $target =~ s/\//_/g; + $target =~ s/\.exe//; + + my $uuid = $GUIDS[$guid_index]; + $$build_structure{"APPS_${target}_GUID"} = $uuid; + $guid_index += 1; + + my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"APPS_${appname}_SOURCES"}})); + my @sources; + foreach (@srcs) { + $_ =~ s/\//\\/g; + push(@sources, $_); + } + my $defines = join(",", sort(@{$$build_structure{"APPS_${appname}_DEFINES"}})); + my $includes= join(";", sort(map(""$rel_dir\\$_"", @{$$build_structure{"APPS_${appname}_INCLUDES"}}))); + my $cflags = join(" ", sort(@{$$build_structure{"APPS_${appname}_CFLAGS"}})); + $cflags =~ s/\"/"/g; + + my $cflags_debug = $cflags; + $cflags_debug =~ s/-MT/-MTd/; + $cflags_debug =~ s/-O.//; + + my $cflags_release = $cflags; + $cflags_release =~ s/-MTd/-MT/; + + my $libs; + foreach (sort(@{$$build_structure{"APPS_${appname}_LIBS"}})) { + $_ =~ s/\//_/g; + $libs .= " $_"; + } + my @tmp = @{$$build_structure{"APPS_${appname}_LFLAGS"}}; + my @tmp2 = (); + foreach (@tmp) { + if (/^-LTCG/) { + } elsif (/^-L/) { + $_ =~ s/^-L/-LIBPATH:$rel_dir\//; + } + push(@tmp2, $_); + } + my $lflags = join(" ", sort(@tmp)) . " -LIBPATH:$rel_dir"; + + $defines =~ s/-D//g; + $defines =~ s/\"/\\"/g; + $defines =~ s/\'//g; + $defines =~ s/\\\\/\\/g; + $includes =~ s/-I//g; + mkdir "$target" || die "Could not create the directory $target for lib project!\n"; + open F, ">$target/$target.vcproj" || die "Could not open $target/$target.pro for writing!\n"; + binmode F, ":crlf"; + print F << "EOM"; +<?xml version="1.0" encoding = "Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="9,00" + Name="$target" + ProjectGUID="$uuid"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="$rel_dir" + ConfigurationType="1" + CharacterSet="0" + IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="$cflags_debug" + Optimization="0" + InlineFunctionExpansion="1" + AdditionalIncludeDirectories="$includes" + PreprocessorDefinitions="WIN32,_DEBUG,$defines" + MinimalRebuild="true" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" + WarningLevel="3" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="$libs" + AdditionalOptions="$lflags" + LinkIncremental="2" + GenerateDebugInformation="true" + SubSystem="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="$rel_dir" + ConfigurationType="1" + CharacterSet="0" + WholeProgramOptimization="1" + IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="$cflags_release" + Optimization="2" + InlineFunctionExpansion="1" + EnableIntrinsicFunctions="true" + AdditionalIncludeDirectories="$includes" + PreprocessorDefinitions="WIN32,NDEBUG,$defines" + RuntimeLibrary="0" + EnableFunctionLevelLinking="true" + UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" + WarningLevel="3" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="$libs" + AdditionalOptions="$lflags" + LinkIncremental="1" + GenerateDebugInformation="true" + SubSystem="1" + TargetMachine="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> +EOM + foreach(@sources) { + print F << "EOM"; + <File + RelativePath="$_"/> +EOM + } + print F << "EOM"; + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> +EOM + close F; +} + +sub createGlueProject { + my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_; + print "Generate solutions file\n"; + $rel_dir = "..\\$rel_dir"; + $rel_dir =~ s/\//\\/g; + my $SLN_HEAD = "Microsoft Visual Studio Solution File, Format Version 10.00\n# Visual Studio 2008\n"; + my $SLN_PRE = "Project(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = "; + my $SLN_POST = "\nEndProject\n"; + + my @libs = @{$build_structure{"LIBS"}}; + my @tmp; + foreach (@libs) { + $_ =~ s/\//_/g; + $_ =~ s/\.a//; + push(@tmp, $_); + } + @libs = @tmp; + + my @apps = @{$build_structure{"APPS"}}; + @tmp = (); + foreach (@apps) { + $_ =~ s/\//_/g; + $_ =~ s/\.exe//; + push(@tmp, $_); + } + @apps = @tmp; + + open F, ">git.sln" || die "Could not open git.sln for writing!\n"; + binmode F, ":crlf"; + print F "$SLN_HEAD"; + foreach (@libs) { + my $libname = $_; + my $uuid = $build_structure{"LIBS_${libname}_GUID"}; + print F "$SLN_PRE"; + print F "\"${libname}\", \"${libname}\\${libname}.vcproj\", \"${uuid}\""; + print F "$SLN_POST"; + } + my $uuid_libgit = $build_structure{"LIBS_libgit_GUID"}; + my $uuid_xdiff_lib = $build_structure{"LIBS_xdiff_lib_GUID"}; + foreach (@apps) { + my $appname = $_; + my $uuid = $build_structure{"APPS_${appname}_GUID"}; + print F "$SLN_PRE"; + print F "\"${appname}\", \"${appname}\\${appname}.vcproj\", \"${uuid}\"\n"; + print F " ProjectSection(ProjectDependencies) = postProject\n"; + print F " ${uuid_libgit} = ${uuid_libgit}\n"; + print F " ${uuid_xdiff_lib} = ${uuid_xdiff_lib}\n"; + print F " EndProjectSection"; + print F "$SLN_POST"; + } + + print F << "EOM"; +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection +EOM + print F << "EOM"; + GlobalSection(ProjectConfigurationPlatforms) = postSolution +EOM + foreach (@libs) { + my $libname = $_; + my $uuid = $build_structure{"LIBS_${libname}_GUID"}; + print F "\t\t${uuid}.Debug|Win32.ActiveCfg = Debug|Win32\n"; + print F "\t\t${uuid}.Debug|Win32.Build.0 = Debug|Win32\n"; + print F "\t\t${uuid}.Release|Win32.ActiveCfg = Release|Win32\n"; + print F "\t\t${uuid}.Release|Win32.Build.0 = Release|Win32\n"; + } + foreach (@apps) { + my $appname = $_; + my $uuid = $build_structure{"APPS_${appname}_GUID"}; + print F "\t\t${uuid}.Debug|Win32.ActiveCfg = Debug|Win32\n"; + print F "\t\t${uuid}.Debug|Win32.Build.0 = Debug|Win32\n"; + print F "\t\t${uuid}.Release|Win32.ActiveCfg = Release|Win32\n"; + print F "\t\t${uuid}.Release|Win32.Build.0 = Release|Win32\n"; + } + + print F << "EOM"; + EndGlobalSection +EndGlobal +EOM + close F; +} + +1; diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl new file mode 100644 index 0000000000..d506717bfd --- /dev/null +++ b/contrib/buildsystems/engine.pl @@ -0,0 +1,356 @@ +#!/usr/bin/perl -w +###################################################################### +# Do not call this script directly! +# +# The generate script ensures that @INC is correct before the engine +# is executed. +# +# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com> +###################################################################### +use strict; +use File::Basename; +use File::Spec; +use Cwd; +use Generators; + +my (%build_structure, %compile_options, @makedry); +my $out_dir = getcwd(); +my $git_dir = $out_dir; +$git_dir =~ s=\\=/=g; +$git_dir = dirname($git_dir) while (!-e "$git_dir/git.c" && "$git_dir" ne ""); +die "Couldn't find Git repo" if ("$git_dir" eq ""); + +my @gens = Generators::available(); +my $gen = "Vcproj"; + +sub showUsage +{ + my $genlist = join(', ', @gens); + print << "EOM"; +generate usage: + -g <GENERATOR> --gen <GENERATOR> Specify the buildsystem generator (default: $gen) + Available: $genlist + -o <PATH> --out <PATH> Specify output directory generation (default: .) + -i <FILE> --in <FILE> Specify input file, instead of running GNU Make + -h,-? --help This help +EOM + exit 0; +} + +# Parse command-line options +while (@ARGV) { + my $arg = shift @ARGV; + if ("$arg" eq "-h" || "$arg" eq "--help" || "$arg" eq "-?") { + showUsage(); + exit(0); + } elsif("$arg" eq "--out" || "$arg" eq "-o") { + $out_dir = shift @ARGV; + } elsif("$arg" eq "--gen" || "$arg" eq "-g") { + $gen = shift @ARGV; + } elsif("$arg" eq "--in" || "$arg" eq "-i") { + my $infile = shift @ARGV; + open(F, "<$infile") || die "Couldn't open file $infile"; + @makedry = <F>; + close(F); + } +} + +# NOT using File::Spec->rel2abs($path, $base) here, as +# it fails badly for me in the msysgit environment +$git_dir = File::Spec->rel2abs($git_dir); +$out_dir = File::Spec->rel2abs($out_dir); +my $rel_dir = makeOutRel2Git($git_dir, $out_dir); + +# Print some information so the user feels informed +print << "EOM"; +----- +Generator: $gen +Git dir: $git_dir +Out dir: $out_dir +----- +Running GNU Make to figure out build structure... +EOM + +# Pipe a make --dry-run into a variable, if not already loaded from file +@makedry = `cd $git_dir && make -n MSVC=1 V=1 2>/dev/null` if !@makedry; + +# Parse the make output into usable info +parseMakeOutput(); + +# Finally, ask the generator to start generating.. +Generators::generate($gen, $git_dir, $out_dir, $rel_dir, %build_structure); + +# main flow ends here +# ------------------------------------------------------------------------------------------------- + + +# 1) path: /foo/bar/baz 2) path: /foo/bar/baz 3) path: /foo/bar/baz +# base: /foo/bar/baz/temp base: /foo/bar base: /tmp +# rel: .. rel: baz rel: ../foo/bar/baz +sub makeOutRel2Git +{ + my ($path, $base) = @_; + my $rel; + if ("$path" eq "$base") { + return "."; + } elsif ($base =~ /^$path/) { + # case 1 + my $tmp = $base; + $tmp =~ s/^$path//; + foreach (split('/', $tmp)) { + $rel .= "../" if ("$_" ne ""); + } + } elsif ($path =~ /^$base/) { + # case 2 + $rel = $path; + $rel =~ s/^$base//; + $rel = "./$rel"; + } else { + my $tmp = $base; + foreach (split('/', $tmp)) { + $rel .= "../" if ("$_" ne ""); + } + $rel .= $path; + } + $rel =~ s/\/\//\//g; # simplify + $rel =~ s/\/$//; # don't end with / + return $rel; +} + +sub parseMakeOutput +{ + print "Parsing GNU Make output to figure out build structure...\n"; + my $line = 0; + while (my $text = shift @makedry) { + my $ate_next; + do { + $ate_next = 0; + $line++; + chomp $text; + chop $text if ($text =~ /\r$/); + if ($text =~ /\\$/) { + $text =~ s/\\$//; + $text .= shift @makedry; + $ate_next = 1; + } + } while($ate_next); + + if($text =~ / -c /) { + # compilation + handleCompileLine($text, $line); + + } elsif ($text =~ / -o /) { + # linking executable + handleLinkLine($text, $line); + + } elsif ($text =~ /\.o / && $text =~ /\.a /) { + # libifying + handleLibLine($text, $line); +# +# } elsif ($text =~ /^cp /) { +# # copy file around +# +# } elsif ($text =~ /^rm -f /) { +# # shell command +# +# } elsif ($text =~ /^make[ \[]/) { +# # make output +# +# } elsif ($text =~ /^echo /) { +# # echo to file +# +# } elsif ($text =~ /^if /) { +# # shell conditional +# +# } elsif ($text =~ /^tclsh /) { +# # translation stuff +# +# } elsif ($text =~ /^umask /) { +# # handling boilerplates +# +# } elsif ($text =~ /\$\(\:\)/) { +# # ignore +# +# } elsif ($text =~ /^FLAGS=/) { +# # flags check for dependencies +# +# } elsif ($text =~ /^'\/usr\/bin\/perl' -MError -e/) { +# # perl commands for copying files +# +# } elsif ($text =~ /generate-cmdlist\.sh/) { +# # command for generating list of commands +# +# } elsif ($text =~ /^test / && $text =~ /|| rm -f /) { +# # commands removing executables, if they exist +# +# } elsif ($text =~ /new locations or Tcl/) { +# # command for detecting Tcl/Tk changes +# +# } elsif ($text =~ /mkdir -p/) { +# # command creating path +# +# } elsif ($text =~ /: no custom templates yet/) { +# # whatever +# +# } else { +# print "Unhandled (line: $line): $text\n"; + } + } + +# use Data::Dumper; +# print "Parsed build structure:\n"; +# print Dumper(%build_structure); +} + +# variables for the compilation part of each step +my (@defines, @incpaths, @cflags, @sources); + +sub clearCompileStep +{ + @defines = (); + @incpaths = (); + @cflags = (); + @sources = (); +} + +sub removeDuplicates +{ + my (%dupHash, $entry); + %dupHash = map { $_, 1 } @defines; + @defines = keys %dupHash; + + %dupHash = map { $_, 1 } @incpaths; + @incpaths = keys %dupHash; + + %dupHash = map { $_, 1 } @cflags; + @cflags = keys %dupHash; +} + +sub handleCompileLine +{ + my ($line, $lineno) = @_; + my @parts = split(' ', $line); + my $sourcefile; + shift(@parts); # ignore cmd + while (my $part = shift @parts) { + if ("$part" eq "-o") { + # ignore object file + shift @parts; + } elsif ("$part" eq "-c") { + # ignore compile flag + } elsif ("$part" eq "-c") { + } elsif ($part =~ /^.?-I/) { + push(@incpaths, $part); + } elsif ($part =~ /^.?-D/) { + push(@defines, $part); + } elsif ($part =~ /^-/) { + push(@cflags, $part); + } elsif ($part =~ /\.(c|cc|cpp)$/) { + $sourcefile = $part; + } else { + die "Unhandled compiler option @ line $lineno: $part"; + } + } + @{$compile_options{"${sourcefile}_CFLAGS"}} = @cflags; + @{$compile_options{"${sourcefile}_DEFINES"}} = @defines; + @{$compile_options{"${sourcefile}_INCPATHS"}} = @incpaths; + clearCompileStep(); +} + +sub handleLibLine +{ + my ($line, $lineno) = @_; + my (@objfiles, @lflags, $libout, $part); + # kill cmd and rm 'prefix' + $line =~ s/^rm -f .* && .* rcs //; + my @parts = split(' ', $line); + while ($part = shift @parts) { + if ($part =~ /^-/) { + push(@lflags, $part); + } elsif ($part =~ /\.(o|obj)$/) { + push(@objfiles, $part); + } elsif ($part =~ /\.(a|lib)$/) { + $libout = $part; + $libout =~ s/\.a$//; + } else { + die "Unhandled lib option @ line $lineno: $part"; + } + } +# print "LibOut: '$libout'\nLFlags: @lflags\nOfiles: @objfiles\n"; +# exit(1); + foreach (@objfiles) { + my $sourcefile = $_; + $sourcefile =~ s/\.o/.c/; + push(@sources, $sourcefile); + push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}}); + push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}}); + push(@incpaths, @{$compile_options{"${sourcefile}_INCPATHS"}}); + } + removeDuplicates(); + + push(@{$build_structure{"LIBS"}}, $libout); + @{$build_structure{"LIBS_${libout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_SOURCES", + "_OBJECTS"); + @{$build_structure{"LIBS_${libout}_DEFINES"}} = @defines; + @{$build_structure{"LIBS_${libout}_INCLUDES"}} = @incpaths; + @{$build_structure{"LIBS_${libout}_CFLAGS"}} = @cflags; + @{$build_structure{"LIBS_${libout}_LFLAGS"}} = @lflags; + @{$build_structure{"LIBS_${libout}_SOURCES"}} = @sources; + @{$build_structure{"LIBS_${libout}_OBJECTS"}} = @objfiles; + clearCompileStep(); +} + +sub handleLinkLine +{ + my ($line, $lineno) = @_; + my (@objfiles, @lflags, @libs, $appout, $part); + my @parts = split(' ', $line); + shift(@parts); # ignore cmd + while ($part = shift @parts) { + if ($part =~ /^-IGNORE/) { + push(@lflags, $part); + } elsif ($part =~ /^-[GRIMDO]/) { + # eat compiler flags + } elsif ("$part" eq "-o") { + $appout = shift @parts; + } elsif ("$part" eq "-lz") { + push(@libs, "zlib.lib"); + } elsif ("$part" eq "-lcrypto") { + push(@libs, "libeay32.lib"); + push(@libs, "ssleay32.lib"); + } elsif ($part =~ /^-/) { + push(@lflags, $part); + } elsif ($part =~ /\.(a|lib)$/) { + $part =~ s/\.a$/.lib/; + push(@libs, $part); + } elsif ($part =~ /\.(o|obj)$/) { + push(@objfiles, $part); + } else { + die "Unhandled lib option @ line $lineno: $part"; + } + } +# print "AppOut: '$appout'\nLFlags: @lflags\nLibs : @libs\nOfiles: @objfiles\n"; +# exit(1); + foreach (@objfiles) { + my $sourcefile = $_; + $sourcefile =~ s/\.o/.c/; + push(@sources, $sourcefile); + push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}}); + push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}}); + push(@incpaths, @{$compile_options{"${sourcefile}_INCPATHS"}}); + } + removeDuplicates(); + + removeDuplicates(); + push(@{$build_structure{"APPS"}}, $appout); + @{$build_structure{"APPS_${appout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_LFLAGS", + "_SOURCES", "_OBJECTS", "_LIBS"); + @{$build_structure{"APPS_${appout}_DEFINES"}} = @defines; + @{$build_structure{"APPS_${appout}_INCLUDES"}} = @incpaths; + @{$build_structure{"APPS_${appout}_CFLAGS"}} = @cflags; + @{$build_structure{"APPS_${appout}_LFLAGS"}} = @lflags; + @{$build_structure{"APPS_${appout}_SOURCES"}} = @sources; + @{$build_structure{"APPS_${appout}_OBJECTS"}} = @objfiles; + @{$build_structure{"APPS_${appout}_LIBS"}} = @libs; + clearCompileStep(); +} diff --git a/contrib/buildsystems/generate b/contrib/buildsystems/generate new file mode 100644 index 0000000000..bc10f25ff2 --- /dev/null +++ b/contrib/buildsystems/generate @@ -0,0 +1,29 @@ +#!/usr/bin/perl -w +###################################################################### +# Generate buildsystem files +# +# This script generate buildsystem files based on the output of a +# GNU Make --dry-run, enabling Windows users to develop Git with their +# trusted IDE with native projects. +# +# Note: +# It is not meant as *the* way of building Git with MSVC, but merely a +# convenience. The correct way of building Git with MSVC is to use the +# GNU Make tool to build with the maintained Makefile in the root of +# the project. If you have the msysgit environment installed and +# available in your current console, together with the Visual Studio +# environment you wish to build for, all you have to do is run the +# command: +# make MSVC=1 +# +# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com> +###################################################################### +use strict; +use File::Basename; +use Cwd; + +my $git_dir = getcwd(); +$git_dir =~ s=\\=/=g; +$git_dir = dirname($git_dir) while (!-e "$git_dir/git.c" && "$git_dir" ne ""); +die "Couldn't find Git repo" if ("$git_dir" eq ""); +exec join(" ", ("PERL5LIB=${git_dir}/contrib/buildsystems ${git_dir}/contrib/buildsystems/engine.pl", @ARGV)); diff --git a/contrib/buildsystems/parse.pl b/contrib/buildsystems/parse.pl new file mode 100644 index 0000000000..c9656ece99 --- /dev/null +++ b/contrib/buildsystems/parse.pl @@ -0,0 +1,228 @@ +#!/usr/bin/perl -w +###################################################################### +# Do not call this script directly! +# +# The generate script ensures that @INC is correct before the engine +# is executed. +# +# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com> +###################################################################### +use strict; +use File::Basename; +use Cwd; + +my $file = $ARGV[0]; +die "No file provided!" if !defined $file; + +my ($cflags, $target, $type, $line); + +open(F, "<$file") || die "Couldn't open file $file"; +my @data = <F>; +close(F); + +while (my $text = shift @data) { + my $ate_next; + do { + $ate_next = 0; + $line++; + chomp $text; + chop $text if ($text =~ /\r$/); + if ($text =~ /\\$/) { + $text =~ s/\\$//; + $text .= shift @data; + $ate_next = 1; + } + } while($ate_next); + + if($text =~ / -c /) { + # compilation + handleCompileLine($text, $line); + + } elsif ($text =~ / -o /) { + # linking executable + handleLinkLine($text, $line); + + } elsif ($text =~ /\.o / && $text =~ /\.a /) { + # libifying + handleLibLine($text, $line); + +# } elsif ($text =~ /^cp /) { +# # copy file around +# +# } elsif ($text =~ /^rm -f /) { +# # shell command +# +# } elsif ($text =~ /^make[ \[]/) { +# # make output +# +# } elsif ($text =~ /^echo /) { +# # echo to file +# +# } elsif ($text =~ /^if /) { +# # shell conditional +# +# } elsif ($text =~ /^tclsh /) { +# # translation stuff +# +# } elsif ($text =~ /^umask /) { +# # handling boilerplates +# +# } elsif ($text =~ /\$\(\:\)/) { +# # ignore +# +# } elsif ($text =~ /^FLAGS=/) { +# # flags check for dependencies +# +# } elsif ($text =~ /^'\/usr\/bin\/perl' -MError -e/) { +# # perl commands for copying files +# +# } elsif ($text =~ /generate-cmdlist\.sh/) { +# # command for generating list of commands +# +# } elsif ($text =~ /^test / && $text =~ /|| rm -f /) { +# # commands removing executables, if they exist +# +# } elsif ($text =~ /new locations or Tcl/) { +# # command for detecting Tcl/Tk changes +# +# } elsif ($text =~ /mkdir -p/) { +# # command creating path +# +# } elsif ($text =~ /: no custom templates yet/) { +# # whatever + + } else { +# print "Unhandled (line: $line): $text\n"; + } +} +close(F); + +# use Data::Dumper; +# print "Parsed build structure:\n"; +# print Dumper(%build_structure); + +# ------------------------------------------------------------------- +# Functions under here +# ------------------------------------------------------------------- +my (%build_structure, @defines, @incpaths, @cflags, @sources); + +sub clearCompileStep +{ + @defines = (); + @incpaths = (); + @cflags = (); + @sources = (); +} + +sub removeDuplicates +{ + my (%dupHash, $entry); + %dupHash = map { $_, 1 } @defines; + @defines = keys %dupHash; + + %dupHash = map { $_, 1 } @incpaths; + @incpaths = keys %dupHash; + + %dupHash = map { $_, 1 } @cflags; + @cflags = keys %dupHash; + + %dupHash = map { $_, 1 } @sources; + @sources = keys %dupHash; +} + +sub handleCompileLine +{ + my ($line, $lineno) = @_; + my @parts = split(' ', $line); + shift(@parts); # ignore cmd + while (my $part = shift @parts) { + if ("$part" eq "-o") { + # ignore object file + shift @parts; + } elsif ("$part" eq "-c") { + # ignore compile flag + } elsif ("$part" eq "-c") { + } elsif ($part =~ /^.?-I/) { + push(@incpaths, $part); + } elsif ($part =~ /^.?-D/) { + push(@defines, $part); + } elsif ($part =~ /^-/) { + push(@cflags, $part); + } elsif ($part =~ /\.(c|cc|cpp)$/) { + push(@sources, $part); + } else { + die "Unhandled compiler option @ line $lineno: $part"; + } + } + #print "Sources: @sources\nCFlags: @cflags\nDefine: @defines\nIncpat: @incpaths\n"; + #exit(1); +} + +sub handleLibLine +{ + my ($line, $lineno) = @_; + my (@objfiles, @lflags, $libout, $part); + # kill cmd and rm 'prefix' + $line =~ s/^rm -f .* && .* rcs //; + my @parts = split(' ', $line); + while ($part = shift @parts) { + if ($part =~ /^-/) { + push(@lflags, $part); + } elsif ($part =~ /\.(o|obj)$/) { + push(@objfiles, $part); + } elsif ($part =~ /\.(a|lib)$/) { + $libout = $part; + } else { + die "Unhandled lib option @ line $lineno: $part"; + } + } + #print "LibOut: '$libout'\nLFlags: @lflags\nOfiles: @objfiles\n"; + #exit(1); + removeDuplicates(); + push(@{$build_structure{"LIBS"}}, $libout); + @{$build_structure{"LIBS_${libout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_SOURCES", + "_OBJECTS"); + @{$build_structure{"LIBS_${libout}_DEFINES"}} = @defines; + @{$build_structure{"LIBS_${libout}_INCLUDES"}} = @incpaths; + @{$build_structure{"LIBS_${libout}_CFLAGS"}} = @cflags; + @{$build_structure{"LIBS_${libout}_SOURCES"}} = @sources; + @{$build_structure{"LIBS_${libout}_OBJECTS"}} = @objfiles; + clearCompileStep(); +} + +sub handleLinkLine +{ + my ($line, $lineno) = @_; + my (@objfiles, @lflags, @libs, $appout, $part); + my @parts = split(' ', $line); + shift(@parts); # ignore cmd + while ($part = shift @parts) { + if ($part =~ /^-[GRIDO]/) { + # eat compiler flags + } elsif ("$part" eq "-o") { + $appout = shift @parts; + } elsif ($part =~ /^-/) { + push(@lflags, $part); + } elsif ($part =~ /\.(a|lib)$/) { + push(@libs, $part); + } elsif ($part =~ /\.(o|obj)$/) { + push(@objfiles, $part); + } else { + die "Unhandled lib option @ line $lineno: $part"; + } + } + #print "AppOut: '$appout'\nLFlags: @lflags\nLibs : @libs\nOfiles: @objfiles\n"; + #exit(1); + removeDuplicates(); + push(@{$build_structure{"APPS"}}, $appout); + @{$build_structure{"APPS_${appout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_LFLAGS", + "_SOURCES", "_OBJECTS", "_LIBS"); + @{$build_structure{"APPS_${appout}_DEFINES"}} = @defines; + @{$build_structure{"APPS_${appout}_INCLUDES"}} = @incpaths; + @{$build_structure{"APPS_${appout}_CFLAGS"}} = @cflags; + @{$build_structure{"APPS_${appout}_LFLAGS"}} = @lflags; + @{$build_structure{"APPS_${appout}_SOURCES"}} = @sources; + @{$build_structure{"APPS_${appout}_OBJECTS"}} = @objfiles; + @{$build_structure{"APPS_${appout}_LIBS"}} = @libs; + clearCompileStep(); +} diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index bf688e12e6..bd66639d48 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -318,13 +318,9 @@ __git_remotes () echo ${i#$d/remotes/} done [ "$ngoff" ] && shopt -u nullglob - for i in $(git --git-dir="$d" config --list); do - case "$i" in - remote.*.url=*) - i="${i#remote.}" - echo "${i/.url=*/}" - ;; - esac + for i in $(git --git-dir="$d" config --get-regexp 'remote\..*\.url' 2>/dev/null); do + i="${i#remote.}" + echo "${i/.url*/}" done } @@ -500,7 +496,7 @@ __git_all_commands () return fi local i IFS=" "$'\n' - for i in $(git help -a|egrep '^ ') + for i in $(git help -a|egrep '^ [a-zA-Z0-9]') do case $i in *--*) : helper pattern;; @@ -605,11 +601,11 @@ __git_porcelain_commandlist="$(__git_porcelain_commands 2>/dev/null)" __git_aliases () { local i IFS=$'\n' - for i in $(git --git-dir="$(__gitdir)" config --list); do + for i in $(git --git-dir="$(__gitdir)" config --get-regexp "alias\..*" 2>/dev/null); do case "$i" in alias.*) i="${i#alias.}" - echo "${i/=*/}" + echo "${i/ */}" ;; esac done @@ -628,8 +624,8 @@ __git_aliased_command () done } -# __git_find_subcommand requires 1 argument -__git_find_subcommand () +# __git_find_on_cmdline requires 1 argument +__git_find_on_cmdline () { local word subcommand c=1 @@ -676,7 +672,7 @@ _git_am () --3way --committer-date-is-author-date --ignore-date --ignore-whitespace --ignore-space-change --interactive --keep --no-utf8 --signoff --utf8 - --whitespace= + --whitespace= --scissors " return esac @@ -748,7 +744,7 @@ _git_bisect () __git_has_doubledash && return local subcommands="start bad good skip reset visualize replay log run" - local subcommand="$(__git_find_subcommand "$subcommands")" + local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands" return @@ -818,7 +814,21 @@ _git_checkout () { __git_has_doubledash && return - __gitcomp "$(__git_refs)" + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --conflict=*) + __gitcomp "diff3 merge" "" "${cur##--conflict=}" + ;; + --*) + __gitcomp " + --quiet --ours --theirs --track --no-track --merge + --conflict= --patch + " + ;; + *) + __gitcomp "$(__git_refs)" + ;; + esac } _git_cherry () @@ -888,6 +898,7 @@ _git_commit () __gitcomp " --all --author= --signoff --verify --no-verify --edit --amend --include --only --interactive + --dry-run " return esac @@ -920,6 +931,8 @@ __git_diff_common_options="--stat --numstat --shortstat --summary --inter-hunk-context= --patience --raw + --dirstat --dirstat= --dirstat-by-file + --dirstat-by-file= --cumulative " _git_diff () @@ -940,11 +953,13 @@ _git_diff () } __git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff - tkdiff vimdiff gvimdiff xxdiff araxis + tkdiff vimdiff gvimdiff xxdiff araxis p4merge " _git_difftool () { + __git_has_doubledash && return + local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --tool=*) @@ -952,11 +967,15 @@ _git_difftool () return ;; --*) - __gitcomp "--tool=" + __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex + --base --ours --theirs + --no-renames --diff-filter= --find-copies-harder + --relative --ignore-submodules + --tool=" return ;; esac - COMPREPLY=() + __git_complete_file } __git_fetch_options=" @@ -1056,7 +1075,8 @@ _git_grep () return ;; esac - COMPREPLY=() + + __gitcomp "$(__git_refs)" } _git_help () @@ -1173,6 +1193,10 @@ _git_log () __gitcomp "$__git_log_date_formats" "" "${cur##--date=}" return ;; + --decorate=*) + __gitcomp "long short" "" "${cur##--decorate=}" + return + ;; --*) __gitcomp " $__git_log_common_options @@ -1185,7 +1209,7 @@ _git_log () --pretty= --format= --oneline --cherry-pick --graph - --decorate + --decorate --decorate= --walk-reflogs --parents --children $merge @@ -1200,7 +1224,7 @@ _git_log () __git_merge_options=" --no-commit --no-stat --log --no-log --squash --strategy - --commit --stat --no-squash --ff --no-ff + --commit --stat --no-squash --ff --no-ff --ff-only " _git_merge () @@ -1305,8 +1329,18 @@ _git_rebase () fi __git_complete_strategy && return case "$cur" in + --whitespace=*) + __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}" + return + ;; --*) - __gitcomp "--onto --merge --strategy --interactive" + __gitcomp " + --onto --merge --strategy --interactive + --preserve-merges --stat --no-stat + --committer-date-is-author-date --ignore-date + --ignore-whitespace --whitespace= + " + return esac __gitcomp "$(__git_refs)" @@ -1532,7 +1566,7 @@ _git_config () url.*.*) local pfx="${cur%.*}." cur="${cur##*.}" - __gitcomp "insteadof" "$pfx" "$cur" + __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur" return ;; esac @@ -1757,7 +1791,7 @@ _git_config () _git_remote () { local subcommands="add rename rm show prune update set-head" - local subcommand="$(__git_find_subcommand "$subcommands")" + local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands" return @@ -1769,13 +1803,9 @@ _git_remote () ;; update) local i c='' IFS=$'\n' - for i in $(git --git-dir="$(__gitdir)" config --list); do - case "$i" in - remotes.*) - i="${i#remotes.}" - c="$c ${i/=*/}" - ;; - esac + for i in $(git --git-dir="$(__gitdir)" config --get-regexp "remotes\..*" 2>/dev/null); do + i="${i#remotes.}" + c="$c ${i/ */}" done __gitcomp "$c" ;; @@ -1785,6 +1815,11 @@ _git_remote () esac } +_git_replace () +{ + __gitcomp "$(__git_refs)" +} + _git_reset () { __git_has_doubledash && return @@ -1792,7 +1827,7 @@ _git_reset () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --*) - __gitcomp "--merge --mixed --hard --soft" + __gitcomp "--merge --mixed --hard --soft --patch" return ;; esac @@ -1888,18 +1923,30 @@ _git_show_branch () _git_stash () { + local cur="${COMP_WORDS[COMP_CWORD]}" + local save_opts='--keep-index --no-keep-index --quiet --patch' local subcommands='save list show apply clear drop pop create branch' - local subcommand="$(__git_find_subcommand "$subcommands")" + local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then - __gitcomp "$subcommands" + case "$cur" in + --*) + __gitcomp "$save_opts" + ;; + *) + if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then + __gitcomp "$subcommands" + else + COMPREPLY=() + fi + ;; + esac else - local cur="${COMP_WORDS[COMP_CWORD]}" case "$subcommand,$cur" in save,--*) - __gitcomp "--keep-index" + __gitcomp "$save_opts" ;; apply,--*|pop,--*) - __gitcomp "--index" + __gitcomp "--index --quiet" ;; show,--*|drop,--*|branch,--*) COMPREPLY=() @@ -1920,7 +1967,7 @@ _git_submodule () __git_has_doubledash && return local subcommands="add status init update summary foreach sync" - if [ -z "$(__git_find_subcommand "$subcommands")" ]; then + if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --*) @@ -1942,7 +1989,7 @@ _git_svn () proplist show-ignore show-externals branch tag blame migrate " - local subcommand="$(__git_find_subcommand "$subcommands")" + local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands" else @@ -2141,6 +2188,7 @@ _git () push) _git_push ;; rebase) _git_rebase ;; remote) _git_remote ;; + replace) _git_replace ;; reset) _git_reset ;; revert) _git_revert ;; rm) _git_rm ;; diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el index 4fa70c5ad4..7f4c792978 100644 --- a/contrib/emacs/git-blame.el +++ b/contrib/emacs/git-blame.el @@ -80,6 +80,57 @@ (eval-when-compile (require 'cl)) ; to use `push', `pop' +(defface git-blame-prefix-face + '((((background dark)) (:foreground "gray" + :background "black")) + (((background light)) (:foreground "gray" + :background "white")) + (t (:weight bold))) + "The face used for the hash prefix." + :group 'git-blame) + +(defgroup git-blame nil + "A minor mode showing Git blame information." + :group 'git + :link '(function-link git-blame-mode)) + + +(defcustom git-blame-use-colors t + "Use colors to indicate commits in `git-blame-mode'." + :type 'boolean + :group 'git-blame) + +(defcustom git-blame-prefix-format + "%h %20A:" + "The format of the prefix added to each line in `git-blame' +mode. The format is passed to `format-spec' with the following format keys: + + %h - the abbreviated hash + %H - the full hash + %a - the author name + %A - the author email + %c - the committer name + %C - the committer email + %s - the commit summary +" + :group 'git-blame) + +(defcustom git-blame-mouseover-format + "%h %a %A: %s" + "The format of the description shown when pointing at a line in +`git-blame' mode. The format string is passed to `format-spec' +with the following format keys: + + %h - the abbreviated hash + %H - the full hash + %a - the author name + %A - the author email + %c - the committer name + %C - the committer email + %s - the commit summary +" + :group 'git-blame) + (defun git-blame-color-scale (&rest elements) "Given a list, returns a list of triples formed with each @@ -302,72 +353,69 @@ See also function `git-blame-mode'." (src-line (string-to-number (match-string 2))) (res-line (string-to-number (match-string 3))) (num-lines (string-to-number (match-string 4)))) - (setq git-blame-current - (if (string= hash "0000000000000000000000000000000000000000") - nil - (git-blame-new-commit - hash src-line res-line num-lines)))) - (delete-region (point) (match-end 0)) - t) - ((looking-at "filename \\(.+\\)\n") - (let ((filename (match-string 1))) - (git-blame-add-info "filename" filename)) - (delete-region (point) (match-end 0)) + (delete-region (point) (match-end 0)) + (setq git-blame-current (list (git-blame-new-commit hash) + src-line res-line num-lines))) t) ((looking-at "\\([a-z-]+\\) \\(.+\\)\n") (let ((key (match-string 1)) (value (match-string 2))) - (git-blame-add-info key value)) - (delete-region (point) (match-end 0)) - t) - ((looking-at "boundary\n") - (setq git-blame-current nil) - (delete-region (point) (match-end 0)) + (delete-region (point) (match-end 0)) + (git-blame-add-info (car git-blame-current) key value) + (when (string= key "filename") + (git-blame-create-overlay (car git-blame-current) + (caddr git-blame-current) + (cadddr git-blame-current)) + (setq git-blame-current nil))) t) (t nil))) -(defun git-blame-new-commit (hash src-line res-line num-lines) +(defun git-blame-new-commit (hash) + (with-current-buffer git-blame-file + (or (gethash hash git-blame-cache) + ;; Assign a random color to each new commit info + ;; Take care not to select the same color multiple times + (let* ((color (if git-blame-colors + (git-blame-random-pop git-blame-colors) + git-blame-ancient-color)) + (info `(,hash (color . ,color)))) + (puthash hash info git-blame-cache) + info)))) + +(defun git-blame-create-overlay (info start-line num-lines) (save-excursion (set-buffer git-blame-file) - (let ((info (gethash hash git-blame-cache)) - (inhibit-point-motion-hooks t) + (let ((inhibit-point-motion-hooks t) (inhibit-modification-hooks t)) - (when (not info) - ;; Assign a random color to each new commit info - ;; Take care not to select the same color multiple times - (let ((color (if git-blame-colors - (git-blame-random-pop git-blame-colors) - git-blame-ancient-color))) - (setq info (list hash src-line res-line num-lines - (git-describe-commit hash) - (cons 'color color)))) - (puthash hash info git-blame-cache)) - (goto-line res-line) - (while (> num-lines 0) - (if (get-text-property (point) 'git-blame) - (forward-line) - (let* ((start (point)) - (end (progn (forward-line 1) (point))) - (ovl (make-overlay start end))) - (push ovl git-blame-overlays) - (overlay-put ovl 'git-blame info) - (overlay-put ovl 'help-echo hash) + (goto-line start-line) + (let* ((start (point)) + (end (progn (forward-line num-lines) (point))) + (ovl (make-overlay start end)) + (hash (car info)) + (spec `((?h . ,(substring hash 0 6)) + (?H . ,hash) + (?a . ,(git-blame-get-info info 'author)) + (?A . ,(git-blame-get-info info 'author-mail)) + (?c . ,(git-blame-get-info info 'committer)) + (?C . ,(git-blame-get-info info 'committer-mail)) + (?s . ,(git-blame-get-info info 'summary))))) + (push ovl git-blame-overlays) + (overlay-put ovl 'git-blame info) + (overlay-put ovl 'help-echo + (format-spec git-blame-mouseover-format spec)) + (if git-blame-use-colors (overlay-put ovl 'face (list :background - (cdr (assq 'color (nthcdr 5 info))))) - ;; the point-entered property doesn't seem to work in overlays - ;;(overlay-put ovl 'point-entered - ;; `(lambda (x y) (git-blame-identify ,hash))) - (let ((modified (buffer-modified-p))) - (put-text-property (if (= start 1) start (1- start)) (1- end) - 'point-entered - `(lambda (x y) (git-blame-identify ,hash))) - (set-buffer-modified-p modified)))) - (setq num-lines (1- num-lines)))))) - -(defun git-blame-add-info (key value) - (if git-blame-current - (nconc git-blame-current (list (cons (intern key) value))))) + (cdr (assq 'color (cdr info)))))) + (overlay-put ovl 'line-prefix + (propertize (format-spec git-blame-prefix-format spec) + 'face 'git-blame-prefix-face)))))) + +(defun git-blame-add-info (info key value) + (nconc info (list (cons (intern key) value)))) + +(defun git-blame-get-info (info key) + (cdr (assq key (cdr info)))) (defun git-blame-current-commit () (let ((info (get-char-property (point) 'git-blame))) diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 8c70ad8b7f..214930a021 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -1046,7 +1046,7 @@ The FILES list must be sorted." (defun git-add-file () "Add marked file(s) to the index cache." (interactive) - (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored)))) + (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored 'unmerged)))) ;; FIXME: add support for directories (unless files (push (file-relative-name (read-file-name "File to add: " nil nil t)) files)) @@ -1119,15 +1119,6 @@ The FILES list must be sorted." (when buffer (with-current-buffer buffer (revert-buffer t t t))))) (git-success-message "Reverted" names)))))) -(defun git-resolve-file () - "Resolve conflicts in marked file(s)." - (interactive) - (let ((files (git-get-filenames (git-marked-files-state 'unmerged)))) - (when files - (when (apply 'git-call-process-display-error "update-index" "--" files) - (git-update-status-files files) - (git-success-message "Resolved" files))))) - (defun git-remove-handled () "Remove handled files from the status list." (interactive) @@ -1556,7 +1547,6 @@ amended version of it." (define-key map "P" 'git-prev-unmerged-file) (define-key map "q" 'git-status-quit) (define-key map "r" 'git-remove-file) - (define-key map "R" 'git-resolve-file) (define-key map "t" toggle-map) (define-key map "T" 'git-toggle-all-marks) (define-key map "u" 'git-unmark-file) @@ -1598,7 +1588,6 @@ amended version of it." ("Merge" ["Next Unmerged File" git-next-unmerged-file t] ["Prev Unmerged File" git-prev-unmerged-file t] - ["Mark as Resolved" git-resolve-file t] ["Interactive Merge File" git-find-file-imerge t] ["Diff Against Common Base File" git-diff-file-base t] ["Diff Combined" git-diff-file-combined t] diff --git a/builtin-fetch--tool.c b/contrib/examples/builtin-fetch--tool.c index 3dbdf7a288..cd10dbcbc9 100644 --- a/builtin-fetch--tool.c +++ b/contrib/examples/builtin-fetch--tool.c @@ -97,21 +97,21 @@ static int update_local_ref(const char *name, strcpy(newh, find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); if (in_merge_bases(current, &updated, 1)) { - fprintf(stderr, "* %s: fast forward to %s\n", + fprintf(stderr, "* %s: fast-forward to %s\n", name, note); fprintf(stderr, " old..new: %s..%s\n", oldh, newh); - return update_ref_env("fast forward", name, sha1_new, sha1_old); + return update_ref_env("fast-forward", name, sha1_new, sha1_old); } if (!force) { fprintf(stderr, - "* %s: not updating to non-fast forward %s\n", + "* %s: not updating to non-fast-forward %s\n", name, note); fprintf(stderr, " old...new: %s...%s\n", oldh, newh); return 1; } fprintf(stderr, - "* %s: forcing update to non-fast forward %s\n", + "* %s: forcing update to non-fast-forward %s\n", name, note); fprintf(stderr, " old...new: %s...%s\n", oldh, newh); return update_ref_env("forced-update", name, sha1_new, sha1_old); diff --git a/contrib/examples/git-merge.sh b/contrib/examples/git-merge.sh index e9588eec33..500635fe4b 100755 --- a/contrib/examples/git-merge.sh +++ b/contrib/examples/git-merge.sh @@ -14,7 +14,7 @@ summary (synonym to --stat) log add list of one-line log to merge commit message squash create a single commit instead of doing a merge commit perform a commit if the merge succeeds (default) -ff allow fast forward (default) +ff allow fast-forward (default) s,strategy= merge strategy to use m,message= message to be used for the merge commit (if any) " @@ -353,7 +353,7 @@ t,1,"$head",*) # Again the most common case of merging one remote. echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)" git update-index --refresh 2>/dev/null - msg="Fast forward" + msg="Fast-forward" if test -n "$have_message" then msg="$msg (no commit created; -m option ignored)" @@ -365,11 +365,11 @@ t,1,"$head",*) exit 0 ;; ?,1,?*"$LF"?*,*) - # We are not doing octopus and not fast forward. Need a + # We are not doing octopus and not fast-forward. Need a # real merge. ;; ?,1,*,) - # We are not doing octopus, not fast forward, and have only + # We are not doing octopus, not fast-forward, and have only # one common. git update-index --refresh 2>/dev/null case "$allow_trivial_merge" in diff --git a/contrib/examples/git-resolve.sh b/contrib/examples/git-resolve.sh index 0ee1bd898e..8f98142f77 100755 --- a/contrib/examples/git-resolve.sh +++ b/contrib/examples/git-resolve.sh @@ -48,7 +48,7 @@ case "$common" in "$head") echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $merge)" git read-tree -u -m $head $merge || exit 1 - git update-ref -m "resolve $merge_name: Fast forward" \ + git update-ref -m "resolve $merge_name: Fast-forward" \ HEAD "$merge" "$head" git diff-tree -p $head $merge | git apply --stat dropheads diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index 38438f3c4a..48059d0aa7 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -8,12 +8,10 @@ # License: MIT <http://www.opensource.org/licenses/mit-license.php> # -import optparse, sys, os, marshal, popen2, subprocess, shelve -import tempfile, getopt, sha, os.path, time, platform +import optparse, sys, os, marshal, subprocess, shelve +import tempfile, getopt, os.path, time, platform import re -from sets import Set; - verbose = False @@ -731,13 +729,10 @@ class P4Submit(Command): tmpFile.write(submitTemplate + separatorLine + diff + newdiff) tmpFile.close() mtime = os.stat(fileName).st_mtime - defaultEditor = "vi" - if platform.system() == "Windows": - defaultEditor = "notepad" if os.environ.has_key("P4EDITOR"): editor = os.environ.get("P4EDITOR") else: - editor = os.environ.get("EDITOR", defaultEditor); + editor = read_pipe("git var GIT_EDITOR") system(editor + " " + fileName) response = "y" @@ -864,8 +859,8 @@ class P4Sync(Command): self.usage += " //depot/path[@revRange]" self.silent = False - self.createdBranches = Set() - self.committedChanges = Set() + self.createdBranches = set() + self.committedChanges = set() self.branch = "" self.detectBranches = False self.detectLabels = False @@ -1662,7 +1657,7 @@ class P4Sync(Command): if len(self.changesFile) > 0: output = open(self.changesFile).readlines() - changeSet = Set() + changeSet = set() for line in output: changeSet.add(int(line)) diff --git a/contrib/fast-import/import-directories.perl b/contrib/fast-import/import-directories.perl new file mode 100755 index 0000000000..5782d80e26 --- /dev/null +++ b/contrib/fast-import/import-directories.perl @@ -0,0 +1,416 @@ +#!/usr/bin/perl -w +# +# Copyright 2008-2009 Peter Krefting <peter@softwolves.pp.se> +# +# ------------------------------------------------------------------------ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# ------------------------------------------------------------------------ + +=pod + +=head1 NAME + +import-directories - Import bits and pieces to Git. + +=head1 SYNOPSIS + +B<import-directories.perl> F<configfile> F<outputfile> + +=head1 DESCRIPTION + +Script to import arbitrary projects version controlled by the "copy the +source directory to a new location and edit it there"-version controlled +projects into version control. Handles projects with arbitrary branching +and version trees, taking a file describing the inputs and generating a +file compatible with the L<git-fast-import(1)> format. + +=head1 CONFIGURATION FILE + +=head2 Format + +The configuration file is based on the standard I<.ini> format. + + ; Comments start with semi-colons + [section] + key=value + +Please see below for information on how to escape special characters. + +=head2 Global configuration + +Global configuration is done in the B<[config]> section, which should be +the first section in the file. Configuration can be changed by +repeating configuration sections later on. + + [config] + ; configure conversion of CRLFs. "convert" means that all CRLFs + ; should be converted into LFs (suitable for the core.autocrlf + ; setting set to true in Git). "none" means that all data is + ; treated as binary. + crlf=convert + +=head2 Revision configuration + +Each revision that is to be imported is described in three +sections. Revisions should be defined in topological order, so +that a revision's parent has always been defined when a new revision +is introduced. All the sections for one revision must be defined +before defining the next revision. + +Each revision is assigned a unique numerical identifier. The +numbers do not need to be consecutive, nor monotonically +increasing. + +For instance, if your configuration file contains only the two +revisions 4711 and 42, where 4711 is the initial commit, the +only requirement is that 4711 is completely defined before 42. + +=pod + +=head3 Revision description section + +A section whose section name is just an integer gives meta-data +about the revision. + + [3] + ; author sets the author of the revisions + author=Peter Krefting <peter@softwolves.pp.se> + ; branch sets the branch that the revision should be committed to + branch=master + ; parent describes the revision that is the parent of this commit + ; (optional) + parent=1 + ; merges describes a revision that is merged into this commit + ; (optional; can be repeated) + merges=2 + ; selects one file to take the timestamp from + ; (optional; if unspecified, the most recent file from the .files + ; section is used) + timestamp=3/source.c + +=head3 Revision contents section + +A section whose section name is an integer followed by B<.files> +describe all the files included in this revision. If a file that +was available previously is not included in this revision, it will +be removed. + +If an on-disk revision is incomplete, you can point to files from +a previous revision. There are no restriction as to where the source +files are located, nor to the names of them. + + [3.files] + ; the key is the path inside the repository, the value is the path + ; as seen from the importer script. + source.c=ver-3.00/source.c + source.h=ver-2.99/source.h + readme.txt=ver-3.00/introduction to the project.txt + +File names are treated as byte strings (but please see below on +quoting rules), and should be stored in the configuration file in +the encoding that should be used in the generated repository. + +=head3 Revision commit message section + +A section whose section name is an integer followed by B<.message> +gives the commit message. This section is read verbatim, up until +the beginning of the next section. As such, a commit message may not +contain a line that begins with an opening square bracket ("[") and +ends with a closing square bracket ("]"), unless they are surrounded +by whitespace or other characters. + + [3.message] + Implement foobar. + ; trailing blank lines are ignored. + +=cut + +# Globals +use strict; +use integer; +my $crlfmode = 0; +my @revs; +my (%revmap, %message, %files, %author, %branch, %parent, %merges, %time, %timesource); +my $sectiontype = 0; +my $rev = 0; +my $mark = 1; + +# Check command line +if ($#ARGV < 1 || $ARGV[0] =~ /^--?h/) +{ + exec('perldoc', $0); + exit 1; +} + +# Open configuration +my $config = $ARGV[0]; +open CFG, '<', $config or die "Cannot open configuration file \"$config\": "; + +# Open output +my $output = $ARGV[1]; +open OUT, '>', $output or die "Cannot create output file \"$output\": "; +binmode OUT; + +LINE: while (my $line = <CFG>) +{ + $line =~ s/\r?\n$//; + next LINE if $sectiontype != 4 && $line eq ''; + next LINE if $line =~ /^;/; + my $oldsectiontype = $sectiontype; + my $oldrev = $rev; + + # Sections + if ($line =~ m"^\[(config|(\d+)(|\.files|\.message))\]$") + { + if ($1 eq 'config') + { + $sectiontype = 1; + } + elsif ($3 eq '') + { + $sectiontype = 2; + $rev = $2; + # Create a new revision + die "Duplicate rev: $line\n " if defined $revmap{$rev}; + print "Reading revision $rev\n"; + push @revs, $rev; + $revmap{$rev} = $mark ++; + $time{$revmap{$rev}} = 0; + } + elsif ($3 eq '.files') + { + $sectiontype = 3; + $rev = $2; + die "Revision mismatch: $line\n " unless $rev == $oldrev; + } + elsif ($3 eq '.message') + { + $sectiontype = 4; + $rev = $2; + die "Revision mismatch: $line\n " unless $rev == $oldrev; + } + else + { + die "Internal parse error: $line\n "; + } + next LINE; + } + + # Parse data + if ($sectiontype != 4) + { + # Key and value + if ($line =~ m"^\s*([^\s].*=.*[^\s])\s*$") + { + my ($key, $value) = &parsekeyvaluepair($1); + # Global configuration + if (1 == $sectiontype) + { + if ($key eq 'crlf') + { + $crlfmode = 1, next LINE if $value eq 'convert'; + $crlfmode = 0, next LINE if $value eq 'none'; + } + die "Unknown configuration option: $line\n "; + } + # Revision specification + if (2 == $sectiontype) + { + my $current = $revmap{$rev}; + $author{$current} = $value, next LINE if $key eq 'author'; + $branch{$current} = $value, next LINE if $key eq 'branch'; + $parent{$current} = $value, next LINE if $key eq 'parent'; + $timesource{$current} = $value, next LINE if $key eq 'timestamp'; + push(@{$merges{$current}}, $value), next LINE if $key eq 'merges'; + die "Unknown revision option: $line\n "; + } + # Filespecs + if (3 == $sectiontype) + { + # Add the file and create a marker + die "File not found: $line\n " unless -f $value; + my $current = $revmap{$rev}; + ${$files{$current}}{$key} = $mark; + my $time = &fileblob($value, $crlfmode, $mark ++); + + # Update revision timestamp if more recent than other + # files seen, or if this is the file we have selected + # to take the time stamp from using the "timestamp" + # directive. + if ((defined $timesource{$current} && $timesource{$current} eq $value) + || $time > $time{$current}) + { + $time{$current} = $time; + } + } + } + else + { + die "Parse error: $line\n "; + } + } + else + { + # Commit message + my $current = $revmap{$rev}; + if (defined $message{$current}) + { + $message{$current} .= "\n"; + } + $message{$current} .= $line; + } +} +close CFG; + +# Start spewing out data for git-fast-import +foreach my $commit (@revs) +{ + # Progress + print OUT "progress Creating revision $commit\n"; + + # Create commit header + my $mark = $revmap{$commit}; + + # Branch and commit id + print OUT "commit refs/heads/", $branch{$mark}, "\nmark :", $mark, "\n"; + + # Author and timestamp + die "No timestamp defined for $commit (no files?)\n" unless defined $time{$mark}; + print OUT "committer ", $author{$mark}, " ", $time{$mark}, " +0100\n"; + + # Commit message + die "No message defined for $commit\n" unless defined $message{$mark}; + my $message = $message{$mark}; + $message =~ s/\n$//; # Kill trailing empty line + print OUT "data ", length($message), "\n", $message, "\n"; + + # Parent and any merges + print OUT "from :", $revmap{$parent{$mark}}, "\n" if defined $parent{$mark}; + if (defined $merges{$mark}) + { + foreach my $merge (@{$merges{$mark}}) + { + print OUT "merge :", $revmap{$merge}, "\n"; + } + } + + # Output file marks + print OUT "deleteall\n"; # start from scratch + foreach my $file (sort keys %{$files{$mark}}) + { + print OUT "M 644 :", ${$files{$mark}}{$file}, " $file\n"; + } + print OUT "\n"; +} + +# Create one file blob +sub fileblob +{ + my ($filename, $crlfmode, $mark) = @_; + + # Import the file + print OUT "progress Importing $filename\nblob\nmark :$mark\n"; + open FILE, '<', $filename or die "Cannot read $filename\n "; + binmode FILE; + my ($size, $mtime) = (stat(FILE))[7,9]; + my $file; + read FILE, $file, $size; + close FILE; + $file =~ s/\r\n/\n/g if $crlfmode; + print OUT "data ", length($file), "\n", $file, "\n"; + + return $mtime; +} + +# Parse a key=value pair +sub parsekeyvaluepair +{ +=pod + +=head2 Escaping special characters + +Key and value strings may be enclosed in quotes, in which case +whitespace inside the quotes is preserved. Additionally, an equal +sign may be included in the key by preceeding it with a backslash. +For example: + + "key1 "=value1 + key2=" value2" + key\=3=value3 + key4=value=4 + "key5""=value5 + +Here the first key is "key1 " (note the trailing white-space) and the +second value is " value2" (note the leading white-space). The third +key contains an equal sign "key=3" and so does the fourth value, which +does not need to be escaped. The fifth key contains a trailing quote, +which does not need to be escaped since it is inside a surrounding +quote. + +=cut + my $pair = shift; + + # Separate key and value by the first non-quoted equal sign + my ($key, $value); + if ($pair =~ /^(.*[^\\])=(.*)$/) + { + ($key, $value) = ($1, $2) + } + else + { + die "Parse error: $pair\n "; + } + + # Unquote and unescape the key and value separately + return (&unescape($key), &unescape($value)); +} + +# Unquote and unescape +sub unescape +{ + my $string = shift; + + # First remove enclosing quotes. Backslash before the trailing + # quote leaves both. + if ($string =~ /^"(.*[^\\])"$/) + { + $string = $1; + } + + # Second remove any backslashes inside the unquoted string. + # For later: Handle special sequences like \t ? + $string =~ s/\\(.)/$1/g; + + return $string; +} + +__END__ + +=pod + +=head1 EXAMPLES + +B<import-directories.perl> F<project.import> + +=head1 AUTHOR + +Copyright 2008-2009 Peter Krefting E<lt>peter@softwolves.pp.se> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation. + +=cut diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl index 78e40d2a13..95438e1ed4 100755 --- a/contrib/fast-import/import-tars.perl +++ b/contrib/fast-import/import-tars.perl @@ -8,9 +8,20 @@ ## perl import-tars.perl *.tar.bz2 ## git whatchanged import-tars ## +## Use --metainfo to specify the extension for a meta data file, where +## import-tars can read the commit message and optionally author and +## committer information. +## +## echo 'This is the commit message' > myfile.tar.bz2.msg +## perl import-tars.perl --metainfo=msg myfile.tar.bz2 use strict; -die "usage: import-tars *.tar.{gz,bz2,Z}\n" unless @ARGV; +use Getopt::Long; + +my $metaext = ''; + +die "usage: import-tars [--metainfo=extension] *.tar.{gz,bz2,lzma,xz,Z}\n" + unless GetOptions('metainfo=s' => \$metaext) && @ARGV; my $branch_name = 'import-tars'; my $branch_ref = "refs/heads/$branch_name"; @@ -38,6 +49,9 @@ foreach my $tar_file (@ARGV) } elsif ($tar_name =~ s/\.tar\.Z$//) { open(I, '-|', 'uncompress', '-c', $tar_file) or die "Unable to uncompress -c $tar_file: $!\n"; + } elsif ($tar_name =~ s/\.(tar\.(lzma|xz)|(tlz|txz))$//) { + open(I, '-|', 'xz', '-dc', $tar_file) + or die "Unable to xz -dc $tar_file: $!\n"; } elsif ($tar_name =~ s/\.tar$//) { open(I, $tar_file) or die "Unable to open $tar_file: $!\n"; } else { @@ -109,12 +123,43 @@ foreach my $tar_file (@ARGV) $have_top_dir = 0 if $top_dir ne $1; } + my $commit_msg = "Imported from $tar_file."; + my $this_committer_name = $committer_name; + my $this_committer_email = $committer_email; + my $this_author_name = $author_name; + my $this_author_email = $author_email; + if ($metaext ne '') { + # Optionally read a commit message from <filename.tar>.msg + # Add a line on the form "Committer: name <e-mail>" to override + # the committer and "Author: name <e-mail>" to override the + # author for this tar ball. + if (open MSG, '<', "${tar_file}.${metaext}") { + my $header_done = 0; + $commit_msg = ''; + while (<MSG>) { + if (!$header_done && /^Committer:\s+([^<>]*)\s+<(.*)>\s*$/i) { + $this_committer_name = $1; + $this_committer_email = $2; + } elsif (!$header_done && /^Author:\s+([^<>]*)\s+<(.*)>\s*$/i) { + $this_author_name = $1; + $this_author_email = $2; + } elsif (!$header_done && /^$/) { # empty line ends header. + $header_done = 1; + } else { + $commit_msg .= $_; + $header_done = 1; + } + } + close MSG; + } + } + print FI <<EOF; commit $branch_ref -author $author_name <$author_email> $author_time +0000 -committer $committer_name <$committer_email> $commit_time +0000 +author $this_author_name <$this_author_email> $author_time +0000 +committer $this_committer_name <$this_committer_email> $commit_time +0000 data <<END_OF_COMMIT_MESSAGE -Imported from $tar_file. +$commit_msg END_OF_COMMIT_MESSAGE deleteall diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 2a66063e44..58a35c8287 100755 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -315,8 +315,8 @@ generate_update_branch_email() # "remotes/" will be ignored as well. # List all of the revisions that were removed by this update, in a - # fast forward update, this list will be empty, because rev-list O - # ^N is empty. For a non fast forward, O ^N is the list of removed + # fast-forward update, this list will be empty, because rev-list O + # ^N is empty. For a non-fast-forward, O ^N is the list of removed # revisions fast_forward="" rev="" @@ -411,7 +411,7 @@ generate_update_branch_email() # revision because the base is effectively a random revision at this # point - the user will be interested in what this revision changed # - including the undoing of previous revisions in the case of - # non-fast forward updates. + # non-fast-forward updates. echo "" echo "Summary of changes:" git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev @@ -35,6 +35,19 @@ int copy_fd(int ifd, int ofd) return 0; } +static int copy_times(const char *dst, const char *src) +{ + struct stat st; + struct utimbuf times; + if (stat(src, &st) < 0) + return -1; + times.actime = st.st_atime; + times.modtime = st.st_mtime; + if (utime(dst, ×) < 0) + return -1; + return 0; +} + int copy_file(const char *dst, const char *src, int mode) { int fdi, fdo, status; @@ -55,3 +68,11 @@ int copy_file(const char *dst, const char *src, int mode) return status; } + +int copy_file_with_time(const char *dst, const char *src, int mode) +{ + int status = copy_file(dst, src, mode); + if (!status) + return copy_times(dst, src); + return status; +} @@ -101,53 +101,6 @@ static void NORETURN daemon_die(const char *err, va_list params) exit(1); } -static int avoid_alias(char *p) -{ - int sl, ndot; - - /* - * This resurrects the belts and suspenders paranoia check by HPA - * done in <435560F7.4080006@zytor.com> thread, now enter_repo() - * does not do getcwd() based path canonicalizations. - * - * sl becomes true immediately after seeing '/' and continues to - * be true as long as dots continue after that without intervening - * non-dot character. - */ - if (!p || (*p != '/' && *p != '~')) - return -1; - sl = 1; ndot = 0; - p++; - - while (1) { - char ch = *p++; - if (sl) { - if (ch == '.') - ndot++; - else if (ch == '/') { - if (ndot < 3) - /* reject //, /./ and /../ */ - return -1; - ndot = 0; - } - else if (ch == 0) { - if (0 < ndot && ndot < 3) - /* reject /.$ and /..$ */ - return -1; - return 0; - } - else - sl = ndot = 0; - } - else if (ch == 0) - return 0; - else if (ch == '/') { - sl = 1; - ndot = 0; - } - } -} - static char *path_ok(char *directory) { static char rpath[PATH_MAX]; @@ -157,7 +110,7 @@ static char *path_ok(char *directory) dir = directory; - if (avoid_alias(dir)) { + if (daemon_avoid_alias(dir)) { logerror("'%s': aliased", dir); return NULL; } @@ -123,7 +123,7 @@ const char *show_date_relative(unsigned long time, int tz, return timebuf; } /* Say months for the past 12 months or so */ - if (diff < 360) { + if (diff < 365) { snprintf(timebuf, timebuf_size, "%lu months ago", (diff + 15) / 30); return timebuf; } diff --git a/diff-delta.c b/diff-delta.c index a4e28df714..464ac3ffc0 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -4,7 +4,7 @@ * This code was greatly inspired by parts of LibXDiff from Davide Libenzi * http://www.xmailserver.org/xdiff-lib.html * - * Rewritten for GIT by Nicolas Pitre <nico@cam.org>, (C) 2005-2007 + * Rewritten for GIT by Nicolas Pitre <nico@fluxnic.net>, (C) 2005-2007 * * This code is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as diff --git a/diff-no-index.c b/diff-no-index.c index 4ebc1dbd87..aae8e7accc 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -201,8 +201,8 @@ void diff_no_index(struct rev_info *revs, return; } if (argc != i + 2) - die("git diff %s takes two paths", - no_index ? "--no-index" : "[--no-index]"); + usagef("git diff %s <path> <path>", + no_index ? "--no-index" : "[--no-index]"); diff_setup(&revs->diffopt); for (i = 1; i < argc - 2; ) { @@ -13,6 +13,7 @@ #include "utf8.h" #include "userdiff.h" #include "sigchain.h" +#include "submodule.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -174,6 +175,175 @@ static struct diff_tempfile { char tmp_path[PATH_MAX]; } diff_temp[2]; +typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); + +struct emit_callback { + int color_diff; + unsigned ws_rule; + int blank_at_eof_in_preimage; + int blank_at_eof_in_postimage; + int lno_in_preimage; + int lno_in_postimage; + sane_truncate_fn truncate; + const char **label_path; + struct diff_words_data *diff_words; + int *found_changesp; + FILE *file; +}; + +static int count_lines(const char *data, int size) +{ + int count, ch, completely_empty = 1, nl_just_seen = 0; + count = 0; + while (0 < size--) { + ch = *data++; + if (ch == '\n') { + count++; + nl_just_seen = 1; + completely_empty = 0; + } + else { + nl_just_seen = 0; + completely_empty = 0; + } + } + if (completely_empty) + return 0; + if (!nl_just_seen) + count++; /* no trailing newline */ + return count; +} + +static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) +{ + if (!DIFF_FILE_VALID(one)) { + mf->ptr = (char *)""; /* does not matter */ + mf->size = 0; + return 0; + } + else if (diff_populate_filespec(one, 0)) + return -1; + + mf->ptr = one->data; + mf->size = one->size; + return 0; +} + +static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule) +{ + char *ptr = mf->ptr; + long size = mf->size; + int cnt = 0; + + if (!size) + return cnt; + ptr += size - 1; /* pointing at the very end */ + if (*ptr != '\n') + ; /* incomplete line */ + else + ptr--; /* skip the last LF */ + while (mf->ptr < ptr) { + char *prev_eol; + for (prev_eol = ptr; mf->ptr <= prev_eol; prev_eol--) + if (*prev_eol == '\n') + break; + if (!ws_blank_line(prev_eol + 1, ptr - prev_eol, ws_rule)) + break; + cnt++; + ptr = prev_eol - 1; + } + return cnt; +} + +static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, + struct emit_callback *ecbdata) +{ + int l1, l2, at; + unsigned ws_rule = ecbdata->ws_rule; + l1 = count_trailing_blank(mf1, ws_rule); + l2 = count_trailing_blank(mf2, ws_rule); + if (l2 <= l1) { + ecbdata->blank_at_eof_in_preimage = 0; + ecbdata->blank_at_eof_in_postimage = 0; + return; + } + at = count_lines(mf1->ptr, mf1->size); + ecbdata->blank_at_eof_in_preimage = (at - l1) + 1; + + at = count_lines(mf2->ptr, mf2->size); + ecbdata->blank_at_eof_in_postimage = (at - l2) + 1; +} + +static void emit_line_0(FILE *file, const char *set, const char *reset, + int first, const char *line, int len) +{ + int has_trailing_newline, has_trailing_carriage_return; + int nofirst; + + if (len == 0) { + has_trailing_newline = (first == '\n'); + has_trailing_carriage_return = (!has_trailing_newline && + (first == '\r')); + nofirst = has_trailing_newline || has_trailing_carriage_return; + } else { + has_trailing_newline = (len > 0 && line[len-1] == '\n'); + if (has_trailing_newline) + len--; + has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); + if (has_trailing_carriage_return) + len--; + nofirst = 0; + } + + fputs(set, file); + + if (!nofirst) + fputc(first, file); + fwrite(line, len, 1, file); + fputs(reset, file); + if (has_trailing_carriage_return) + fputc('\r', file); + if (has_trailing_newline) + fputc('\n', file); +} + +static void emit_line(FILE *file, const char *set, const char *reset, + const char *line, int len) +{ + emit_line_0(file, set, reset, line[0], line+1, len-1); +} + +static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) +{ + if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && + ecbdata->blank_at_eof_in_preimage && + ecbdata->blank_at_eof_in_postimage && + ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage && + ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage)) + return 0; + return ws_blank_line(line, len, ecbdata->ws_rule); +} + +static void emit_add_line(const char *reset, + struct emit_callback *ecbdata, + const char *line, int len) +{ + const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); + const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); + + if (!*ws) + emit_line_0(ecbdata->file, set, reset, '+', line, len); + else if (new_blank_line_at_eof(ecbdata, line, len)) + /* Blank line at EOF - paint '+' as well */ + emit_line_0(ecbdata->file, ws, reset, '+', line, len); + else { + /* Emit just the prefix, then the rest. */ + emit_line_0(ecbdata->file, set, reset, '+', "", 0); + ws_check_emit(line, len, ecbdata->ws_rule, + ecbdata->file, set, reset, ws); + } +} + static struct diff_tempfile *claim_diff_tempfile(void) { int i; for (i = 0; i < ARRAY_SIZE(diff_temp); i++) @@ -201,29 +371,6 @@ static void remove_tempfile_on_signal(int signo) raise(signo); } -static int count_lines(const char *data, int size) -{ - int count, ch, completely_empty = 1, nl_just_seen = 0; - count = 0; - while (0 < size--) { - ch = *data++; - if (ch == '\n') { - count++; - nl_just_seen = 1; - completely_empty = 0; - } - else { - nl_just_seen = 0; - completely_empty = 0; - } - } - if (completely_empty) - return 0; - if (!nl_just_seen) - count++; /* no trailing newline */ - return count; -} - static void print_line_count(FILE *file, int count) { switch (count) { @@ -239,26 +386,36 @@ static void print_line_count(FILE *file, int count) } } -static void copy_file_with_prefix(FILE *file, - int prefix, const char *data, int size, - const char *set, const char *reset) +static void emit_rewrite_lines(struct emit_callback *ecb, + int prefix, const char *data, int size) { - int ch, nl_just_seen = 1; - while (0 < size--) { - ch = *data++; - if (nl_just_seen) { - fputs(set, file); - putc(prefix, file); + const char *endp = NULL; + static const char *nneof = " No newline at end of file\n"; + const char *old = diff_get_color(ecb->color_diff, DIFF_FILE_OLD); + const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET); + + while (0 < size) { + int len; + + endp = memchr(data, '\n', size); + len = endp ? (endp - data + 1) : size; + if (prefix != '+') { + ecb->lno_in_preimage++; + emit_line_0(ecb->file, old, reset, '-', + data, len); + } else { + ecb->lno_in_postimage++; + emit_add_line(reset, ecb, data, len); } - if (ch == '\n') { - nl_just_seen = 1; - fputs(reset, file); - } else - nl_just_seen = 0; - putc(ch, file); + size -= len; + data += len; + } + if (!endp) { + const char *plain = diff_get_color(ecb->color_diff, + DIFF_PLAIN); + emit_line_0(ecb->file, plain, reset, '\\', + nneof, strlen(nneof)); } - if (!nl_just_seen) - fprintf(file, "%s\n\\ No newline at end of file\n", reset); } static void emit_rewrite_diff(const char *name_a, @@ -274,13 +431,12 @@ static void emit_rewrite_diff(const char *name_a, const char *name_a_tab, *name_b_tab; const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO); const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO); - const char *old = diff_get_color(color_diff, DIFF_FILE_OLD); - const char *new = diff_get_color(color_diff, DIFF_FILE_NEW); const char *reset = diff_get_color(color_diff, DIFF_RESET); static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT; const char *a_prefix, *b_prefix; const char *data_one, *data_two; size_t size_one, size_two; + struct emit_callback ecbdata; if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) { a_prefix = o->b_prefix; @@ -321,6 +477,22 @@ static void emit_rewrite_diff(const char *name_a, size_two = two->size; } + memset(&ecbdata, 0, sizeof(ecbdata)); + ecbdata.color_diff = color_diff; + ecbdata.found_changesp = &o->found_changes; + ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); + ecbdata.file = o->file; + if (ecbdata.ws_rule & WS_BLANK_AT_EOF) { + mmfile_t mf1, mf2; + mf1.ptr = (char *)data_one; + mf2.ptr = (char *)data_two; + mf1.size = size_one; + mf2.size = size_two; + check_blank_at_eof(&mf1, &mf2, &ecbdata); + } + ecbdata.lno_in_preimage = 1; + ecbdata.lno_in_postimage = 1; + lc_a = count_lines(data_one, size_one); lc_b = count_lines(data_two, size_two); fprintf(o->file, @@ -332,24 +504,9 @@ static void emit_rewrite_diff(const char *name_a, print_line_count(o->file, lc_b); fprintf(o->file, " @@%s\n", reset); if (lc_a) - copy_file_with_prefix(o->file, '-', data_one, size_one, old, reset); + emit_rewrite_lines(&ecbdata, '-', data_one, size_one); if (lc_b) - copy_file_with_prefix(o->file, '+', data_two, size_two, new, reset); -} - -static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) -{ - if (!DIFF_FILE_VALID(one)) { - mf->ptr = (char *)""; /* does not matter */ - mf->size = 0; - return 0; - } - else if (diff_populate_filespec(one, 0)) - return -1; - - mf->ptr = one->data; - mf->size = one->size; - return 0; + emit_rewrite_lines(&ecbdata, '+', data_two, size_two); } struct diff_words_buffer { @@ -529,26 +686,18 @@ static void diff_words_show(struct diff_words_data *diff_words) diff_words->minus.text.size = diff_words->plus.text.size = 0; } -typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); - -struct emit_callback { - int nparents, color_diff; - unsigned ws_rule; - sane_truncate_fn truncate; - const char **label_path; - struct diff_words_data *diff_words; - int *found_changesp; - FILE *file; -}; +/* In "color-words" mode, show word-diff of words accumulated in the buffer */ +static void diff_words_flush(struct emit_callback *ecbdata) +{ + if (ecbdata->diff_words->minus.text.size || + ecbdata->diff_words->plus.text.size) + diff_words_show(ecbdata->diff_words); +} static void free_diff_words_data(struct emit_callback *ecbdata) { if (ecbdata->diff_words) { - /* flush buffers */ - if (ecbdata->diff_words->minus.text.size || - ecbdata->diff_words->plus.text.size) - diff_words_show(ecbdata->diff_words); - + diff_words_flush(ecbdata); free (ecbdata->diff_words->minus.text.ptr); free (ecbdata->diff_words->minus.orig); free (ecbdata->diff_words->plus.text.ptr); @@ -566,42 +715,6 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix) return ""; } -static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len) -{ - int has_trailing_newline, has_trailing_carriage_return; - - has_trailing_newline = (len > 0 && line[len-1] == '\n'); - if (has_trailing_newline) - len--; - has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); - if (has_trailing_carriage_return) - len--; - - fputs(set, file); - fwrite(line, len, 1, file); - fputs(reset, file); - if (has_trailing_carriage_return) - fputc('\r', file); - if (has_trailing_newline) - fputc('\n', file); -} - -static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) -{ - const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); - const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); - - if (!*ws) - emit_line(ecbdata->file, set, reset, line, len); - else { - /* Emit just the prefix, then the rest. */ - emit_line(ecbdata->file, set, reset, line, ecbdata->nparents); - ws_check_emit(line + ecbdata->nparents, - len - ecbdata->nparents, ecbdata->ws_rule, - ecbdata->file, set, reset, ws); - } -} - static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len) { const char *cp; @@ -620,10 +733,23 @@ static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, u return allot - l; } +static void find_lno(const char *line, struct emit_callback *ecbdata) +{ + const char *p; + ecbdata->lno_in_preimage = 0; + ecbdata->lno_in_postimage = 0; + p = strchr(line, '-'); + if (!p) + return; /* cannot happen */ + ecbdata->lno_in_preimage = strtol(p + 1, NULL, 10); + p = strchr(p, '+'); + if (!p) + return; /* cannot happen */ + ecbdata->lno_in_postimage = strtol(p + 1, NULL, 10); +} + static void fn_out_consume(void *priv, char *line, unsigned long len) { - int i; - int color; struct emit_callback *ecbdata = priv; const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO); const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN); @@ -650,14 +776,11 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) len = 1; } - /* This is not really necessary for now because - * this codepath only deals with two-way diffs. - */ - for (i = 0; i < len && line[i] == '@'; i++) - ; - if (2 <= i && i < len && line[i] == ' ') { - ecbdata->nparents = i - 1; + if (line[0] == '@') { + if (ecbdata->diff_words) + diff_words_flush(ecbdata); len = sane_truncate_line(ecbdata, line, len); + find_lno(line, ecbdata); emit_line(ecbdata->file, diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), reset, line, len); @@ -666,15 +789,11 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) return; } - if (len < ecbdata->nparents) { + if (len < 1) { emit_line(ecbdata->file, reset, reset, line, len); return; } - color = DIFF_PLAIN; - if (ecbdata->diff_words && ecbdata->nparents != 1) - /* fall back to normal diff */ - free_diff_words_data(ecbdata); if (ecbdata->diff_words) { if (line[0] == '-') { diff_words_append(line, len, @@ -685,28 +804,25 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) &ecbdata->diff_words->plus); return; } - if (ecbdata->diff_words->minus.text.size || - ecbdata->diff_words->plus.text.size) - diff_words_show(ecbdata->diff_words); + diff_words_flush(ecbdata); line++; len--; emit_line(ecbdata->file, plain, reset, line, len); return; } - for (i = 0; i < ecbdata->nparents && len; i++) { - if (line[i] == '-') - color = DIFF_FILE_OLD; - else if (line[i] == '+') - color = DIFF_FILE_NEW; - } - if (color != DIFF_FILE_NEW) { - emit_line(ecbdata->file, - diff_get_color(ecbdata->color_diff, color), - reset, line, len); - return; + if (line[0] != '+') { + const char *color = + diff_get_color(ecbdata->color_diff, + line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN); + ecbdata->lno_in_preimage++; + if (line[0] == ' ') + ecbdata->lno_in_postimage++; + emit_line(ecbdata->file, color, reset, line, len); + } else { + ecbdata->lno_in_postimage++; + emit_add_line(reset, ecbdata, line + 1, len - 1); } - emit_add_line(reset, ecbdata, line, len); } static char *pprint_rename(const char *a, const char *b) @@ -999,7 +1115,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) total_files, adds, dels); } -static void show_shortstats(struct diffstat_t* data, struct diff_options *options) +static void show_shortstats(struct diffstat_t *data, struct diff_options *options) { int i, adds = 0, dels = 0, total_files = data->nr; @@ -1211,7 +1327,6 @@ struct checkdiff_t { struct diff_options *o; unsigned ws_rule; unsigned status; - int trailing_blanks_start; }; static int is_conflict_marker(const char *line, unsigned long len) @@ -1255,10 +1370,6 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) if (line[0] == '+') { unsigned bad; data->lineno++; - if (!ws_blank_line(line + 1, len - 1, data->ws_rule)) - data->trailing_blanks_start = 0; - else if (!data->trailing_blanks_start) - data->trailing_blanks_start = data->lineno; if (is_conflict_marker(line + 1, len - 1)) { data->status |= 1; fprintf(data->o->file, @@ -1278,14 +1389,12 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) data->o->file, set, reset, ws); } else if (line[0] == ' ') { data->lineno++; - data->trailing_blanks_start = 0; } else if (line[0] == '@') { char *plus = strchr(line, '+'); if (plus) data->lineno = strtol(plus, NULL, 10) - 1; else die("invalid diff"); - data->trailing_blanks_start = 0; } } @@ -1453,6 +1562,17 @@ static void builtin_diff(const char *name_a, const char *a_prefix, *b_prefix; const char *textconv_one = NULL, *textconv_two = NULL; + if (DIFF_OPT_TST(o, SUBMODULE_LOG) && + (!one->mode || S_ISGITLINK(one->mode)) && + (!two->mode || S_ISGITLINK(two->mode))) { + const char *del = diff_get_color_opt(o, DIFF_FILE_OLD); + const char *add = diff_get_color_opt(o, DIFF_FILE_NEW); + show_submodule_summary(o->file, one ? one->path : two->path, + one->sha1, two->sha1, + del, add, reset); + return; + } + if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) { textconv_one = get_textconv(one); textconv_two = get_textconv(two); @@ -1562,6 +1682,8 @@ static void builtin_diff(const char *name_a, ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF); ecbdata.found_changesp = &o->found_changes; ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); + if (ecbdata.ws_rule & WS_BLANK_AT_EOF) + check_blank_at_eof(&mf1, &mf2, &ecbdata); ecbdata.file = o->file; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; @@ -1704,11 +1826,22 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data, &xpp, &xecfg, &ecb); - if ((data.ws_rule & WS_TRAILING_SPACE) && - data.trailing_blanks_start) { - fprintf(o->file, "%s:%d: ends with blank lines.\n", - data.filename, data.trailing_blanks_start); - data.status = 1; /* report errors */ + if (data.ws_rule & WS_BLANK_AT_EOF) { + struct emit_callback ecbdata; + int blank_at_eof; + + ecbdata.ws_rule = data.ws_rule; + check_blank_at_eof(&mf1, &mf2, &ecbdata); + blank_at_eof = ecbdata.blank_at_eof_in_preimage; + + if (blank_at_eof) { + static char *err; + if (!err) + err = whitespace_error_string(WS_BLANK_AT_EOF); + fprintf(o->file, "%s:%d: %s.\n", + data.filename, blank_at_eof, err); + data.status = 1; /* report errors */ + } } } free_and_return: @@ -2640,6 +2773,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_CLR(options, ALLOW_TEXTCONV); else if (!strcmp(arg, "--ignore-submodules")) DIFF_OPT_SET(options, IGNORE_SUBMODULES); + else if (!strcmp(arg, "--submodule")) + DIFF_OPT_SET(options, SUBMODULE_LOG); + else if (!prefixcmp(arg, "--submodule=")) { + if (!strcmp(arg + 12, "log")) + DIFF_OPT_SET(options, SUBMODULE_LOG); + } /* misc options */ else if (!strcmp(arg, "-z")) @@ -66,6 +66,9 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_DIRSTAT_CUMULATIVE (1 << 19) #define DIFF_OPT_DIRSTAT_BY_FILE (1 << 20) #define DIFF_OPT_ALLOW_TEXTCONV (1 << 21) + +#define DIFF_OPT_SUBMODULE_LOG (1 << 23) + #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) #define DIFF_OPT_CLR(opts, flag) ((opts)->flags &= ~DIFF_OPT_##flag) @@ -2,24 +2,38 @@ #include "strbuf.h" #include "run-command.h" -int launch_editor(const char *path, struct strbuf *buffer, const char *const *env) +#ifndef DEFAULT_EDITOR +#define DEFAULT_EDITOR "vi" +#endif + +const char *git_editor(void) { - const char *editor, *terminal; + const char *editor = getenv("GIT_EDITOR"); + const char *terminal = getenv("TERM"); + int terminal_is_dumb = !terminal || !strcmp(terminal, "dumb"); - editor = getenv("GIT_EDITOR"); if (!editor && editor_program) editor = editor_program; - if (!editor) + if (!editor && !terminal_is_dumb) editor = getenv("VISUAL"); if (!editor) editor = getenv("EDITOR"); - terminal = getenv("TERM"); - if (!editor && (!terminal || !strcmp(terminal, "dumb"))) - return error("Terminal is dumb but no VISUAL nor EDITOR defined."); + if (!editor && terminal_is_dumb) + return NULL; + + if (!editor) + editor = DEFAULT_EDITOR; + + return editor; +} + +int launch_editor(const char *path, struct strbuf *buffer, const char *const *env) +{ + const char *editor = git_editor(); if (!editor) - editor = "vi"; + return error("Terminal is dumb, but EDITOR unset"); if (strcmp(editor, ":")) { size_t len = strlen(editor); @@ -28,7 +42,7 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en const char *args[6]; struct strbuf arg0 = STRBUF_INIT; - if (strcspn(editor, "$ \t'") != len) { + if (strcspn(editor, "|&;<>()$`\\\"' \t\n*?[#~=%") != len) { /* there are specials */ strbuf_addf(&arg0, "%s \"$@\"", editor); args[i++] = "sh"; diff --git a/environment.c b/environment.c index 5de6837840..571ab56b7e 100644 --- a/environment.c +++ b/environment.c @@ -49,6 +49,7 @@ enum push_default_type push_default = PUSH_DEFAULT_MATCHING; #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS #endif enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE; +char *notes_ref_name; int grafts_replace_parents = 1; /* Parallel index stat data preload? */ diff --git a/fast-import.c b/fast-import.c index 7ef9865aa6..dd3c99d60d 100644 --- a/fast-import.c +++ b/fast-import.c @@ -22,8 +22,8 @@ Format of STDIN stream: ('author' sp name sp '<' email '>' sp when lf)? 'committer' sp name sp '<' email '>' sp when lf commit_msg - ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? - ('merge' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)* + ('from' sp committish lf)? + ('merge' sp committish lf)* file_change* lf?; commit_msg ::= data; @@ -41,15 +41,18 @@ Format of STDIN stream: file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf; file_inm ::= 'M' sp mode sp 'inline' sp path_str lf data; + note_obm ::= 'N' sp (hexsha1 | idnum) sp committish lf; + note_inm ::= 'N' sp 'inline' sp committish lf + data; new_tag ::= 'tag' sp tag_str lf - 'from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf + 'from' sp committish lf ('tagger' sp name sp '<' email '>' sp when lf)? tag_msg; tag_msg ::= data; reset_branch ::= 'reset' sp ref_str lf - ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? + ('from' sp committish lf)? lf?; checkpoint ::= 'checkpoint' lf @@ -88,6 +91,7 @@ Format of STDIN stream: # stream formatting is: \, " and LF. Otherwise these values # are UTF8. # + committish ::= (ref_str | hexsha1 | sha1exp_str | idnum); ref_str ::= ref; sha1exp_str ::= sha1exp; tag_str ::= tag; @@ -1744,10 +1748,12 @@ static int validate_raw_date(const char *src, char *result, int maxlen) { const char *orig_src = src; char *endp; + unsigned long num; errno = 0; - strtoul(src, &endp, 10); + num = strtoul(src, &endp, 10); + /* NEEDSWORK: perhaps check for reasonable values? */ if (errno || endp == src || *endp != ' ') return -1; @@ -1755,8 +1761,9 @@ static int validate_raw_date(const char *src, char *result, int maxlen) if (*src != '-' && *src != '+') return -1; - strtoul(src + 1, &endp, 10); - if (errno || endp == src || *endp || (endp - orig_src) >= maxlen) + num = strtoul(src + 1, &endp, 10); + if (errno || endp == src + 1 || *endp || (endp - orig_src) >= maxlen || + 1400 < num) return -1; strcpy(result, orig_src); @@ -2003,6 +2010,80 @@ static void file_change_cr(struct branch *b, int rename) leaf.tree); } +static void note_change_n(struct branch *b) +{ + const char *p = command_buf.buf + 2; + static struct strbuf uq = STRBUF_INIT; + struct object_entry *oe = oe; + struct branch *s; + unsigned char sha1[20], commit_sha1[20]; + uint16_t inline_data = 0; + + /* <dataref> or 'inline' */ + if (*p == ':') { + char *x; + oe = find_mark(strtoumax(p + 1, &x, 10)); + hashcpy(sha1, oe->sha1); + p = x; + } else if (!prefixcmp(p, "inline")) { + inline_data = 1; + p += 6; + } else { + if (get_sha1_hex(p, sha1)) + die("Invalid SHA1: %s", command_buf.buf); + oe = find_object(sha1); + p += 40; + } + if (*p++ != ' ') + die("Missing space after SHA1: %s", command_buf.buf); + + /* <committish> */ + s = lookup_branch(p); + if (s) { + hashcpy(commit_sha1, s->sha1); + } else if (*p == ':') { + uintmax_t commit_mark = strtoumax(p + 1, NULL, 10); + struct object_entry *commit_oe = find_mark(commit_mark); + if (commit_oe->type != OBJ_COMMIT) + die("Mark :%" PRIuMAX " not a commit", commit_mark); + hashcpy(commit_sha1, commit_oe->sha1); + } else if (!get_sha1(p, commit_sha1)) { + unsigned long size; + char *buf = read_object_with_reference(commit_sha1, + commit_type, &size, commit_sha1); + if (!buf || size < 46) + die("Not a valid commit: %s", p); + free(buf); + } else + die("Invalid ref name or SHA1 expression: %s", p); + + if (inline_data) { + static struct strbuf buf = STRBUF_INIT; + + if (p != uq.buf) { + strbuf_addstr(&uq, p); + p = uq.buf; + } + read_next_command(); + parse_data(&buf); + store_object(OBJ_BLOB, &buf, &last_blob, sha1, 0); + } else if (oe) { + if (oe->type != OBJ_BLOB) + die("Not a blob (actually a %s): %s", + typename(oe->type), command_buf.buf); + } else { + enum object_type type = sha1_object_info(sha1, NULL); + if (type < 0) + die("Blob not found: %s", command_buf.buf); + if (type != OBJ_BLOB) + die("Not a blob (actually a %s): %s", + typename(type), command_buf.buf); + } + + tree_content_set(&b->branch_tree, sha1_to_hex(commit_sha1), sha1, + S_IFREG | 0644, NULL); +} + static void file_change_deleteall(struct branch *b) { release_tree_content_recursive(b->branch_tree.tree); @@ -2172,6 +2253,8 @@ static void parse_new_commit(void) file_change_cr(b, 1); else if (!prefixcmp(command_buf.buf, "C ")) file_change_cr(b, 0); + else if (!prefixcmp(command_buf.buf, "N ")) + note_change_n(b); else if (!strcmp("deleteall", command_buf.buf)) file_change_deleteall(b); else { @@ -2402,6 +2485,9 @@ int main(int argc, const char **argv) git_extract_argv0_path(argv[0]); + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(fast_import_usage); + setup_git_directory(); git_config(git_pack_config, NULL); if (!pack_compression_seen && core_compression_seen) diff --git a/fetch-pack.h b/fetch-pack.h index 8bd9c32561..fbe85ac05f 100644 --- a/fetch-pack.h +++ b/fetch-pack.h @@ -13,7 +13,8 @@ struct fetch_pack_args fetch_all:1, verbose:1, no_progress:1, - include_tag:1; + include_tag:1, + stateless_rpc:1; }; struct ref *fetch_pack(struct fetch_pack_args *args, diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 06f70602cc..f813ffdaa1 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -72,6 +72,79 @@ sub colored { # command line options my $patch_mode; +my $patch_mode_revision; + +sub apply_patch; +sub apply_patch_for_checkout_commit; +sub apply_patch_for_stash; + +my %patch_modes = ( + 'stage' => { + DIFF => 'diff-files -p', + APPLY => sub { apply_patch 'apply --cached', @_; }, + APPLY_CHECK => 'apply --cached', + VERB => 'Stage', + TARGET => '', + PARTICIPLE => 'staging', + FILTER => 'file-only', + }, + 'stash' => { + DIFF => 'diff-index -p HEAD', + APPLY => sub { apply_patch 'apply --cached', @_; }, + APPLY_CHECK => 'apply --cached', + VERB => 'Stash', + TARGET => '', + PARTICIPLE => 'stashing', + FILTER => undef, + }, + 'reset_head' => { + DIFF => 'diff-index -p --cached', + APPLY => sub { apply_patch 'apply -R --cached', @_; }, + APPLY_CHECK => 'apply -R --cached', + VERB => 'Unstage', + TARGET => '', + PARTICIPLE => 'unstaging', + FILTER => 'index-only', + }, + 'reset_nothead' => { + DIFF => 'diff-index -R -p --cached', + APPLY => sub { apply_patch 'apply --cached', @_; }, + APPLY_CHECK => 'apply --cached', + VERB => 'Apply', + TARGET => ' to index', + PARTICIPLE => 'applying', + FILTER => 'index-only', + }, + 'checkout_index' => { + DIFF => 'diff-files -p', + APPLY => sub { apply_patch 'apply -R', @_; }, + APPLY_CHECK => 'apply -R', + VERB => 'Discard', + TARGET => ' from worktree', + PARTICIPLE => 'discarding', + FILTER => 'file-only', + }, + 'checkout_head' => { + DIFF => 'diff-index -p', + APPLY => sub { apply_patch_for_checkout_commit '-R', @_ }, + APPLY_CHECK => 'apply -R', + VERB => 'Discard', + TARGET => ' from index and worktree', + PARTICIPLE => 'discarding', + FILTER => undef, + }, + 'checkout_nothead' => { + DIFF => 'diff-index -R -p', + APPLY => sub { apply_patch_for_checkout_commit '', @_ }, + APPLY_CHECK => 'apply', + VERB => 'Apply', + TARGET => ' to index and worktree', + PARTICIPLE => 'applying', + FILTER => undef, + }, +); + +my %patch_mode_flavour = %{$patch_modes{stage}}; sub run_cmd_pipe { if ($^O eq 'MSWin32' || $^O eq 'msys') { @@ -186,11 +259,18 @@ sub list_modified { @tracked = map { chomp $_; unquote_path($_); - } run_cmd_pipe(qw(git ls-files --exclude-standard --), @ARGV); + } run_cmd_pipe(qw(git ls-files --), @ARGV); return if (!@tracked); } - my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD'; + my $reference; + if (defined $patch_mode_revision and $patch_mode_revision ne 'HEAD') { + $reference = $patch_mode_revision; + } elsif (is_initial_commit()) { + $reference = get_empty_tree(); + } else { + $reference = 'HEAD'; + } for (run_cmd_pipe(qw(git diff-index --cached --numstat --summary), $reference, '--', @tracked)) { @@ -613,12 +693,24 @@ sub add_untracked_cmd { print "\n"; } +sub run_git_apply { + my $cmd = shift; + my $fh; + open $fh, '| git ' . $cmd; + print $fh @_; + return close $fh; +} + sub parse_diff { my ($path) = @_; - my @diff = run_cmd_pipe(qw(git diff-files -p --), $path); + my @diff_cmd = split(" ", $patch_mode_flavour{DIFF}); + if (defined $patch_mode_revision) { + push @diff_cmd, $patch_mode_revision; + } + my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path); my @colored = (); if ($diff_use_color) { - @colored = run_cmd_pipe(qw(git diff-files -p --color --), $path); + @colored = run_cmd_pipe("git", @diff_cmd, qw(--color --), $path); } my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' }; @@ -639,14 +731,17 @@ sub parse_diff_header { my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' }; my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' }; + my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' }; for (my $i = 0; $i < @{$src->{TEXT}}; $i++) { - my $dest = $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? - $mode : $head; + my $dest = + $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode : + $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion : + $head; push @{$dest->{TEXT}}, $src->{TEXT}->[$i]; push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i]; } - return ($head, $mode); + return ($head, $mode, $deletion); } sub hunk_splittable { @@ -881,6 +976,7 @@ sub edit_hunk_manually { or die "failed to open hunk edit file for writing: " . $!; print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n"; print $fh @$oldtext; + my $participle = $patch_mode_flavour{PARTICIPLE}; print $fh <<EOF; # --- # To remove '-' lines, make them ' ' lines (context). @@ -888,14 +984,13 @@ sub edit_hunk_manually { # Lines starting with # will be removed. # # If the patch applies cleanly, the edited hunk will immediately be -# marked for staging. If it does not apply cleanly, you will be given +# marked for $participle. If it does not apply cleanly, you will be given # an opportunity to edit again. If all lines of the hunk are removed, # then the edit is aborted and the hunk is left unchanged. EOF close $fh; - my $editor = $ENV{GIT_EDITOR} || $repo->config("core.editor") - || $ENV{VISUAL} || $ENV{EDITOR} || "vi"; + chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR))); system('sh', '-c', $editor.' "$@"', $editor, $hunkfile); if ($? != 0) { @@ -922,11 +1017,8 @@ EOF sub diff_applies { my $fh; - open $fh, '| git apply --recount --cached --check'; - for my $h (@_) { - print $fh @{$h->{TEXT}}; - } - return close $fh; + return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --recount --check', + map { @{$_->{TEXT}} } @_); } sub _restore_terminal_and_die { @@ -992,12 +1084,14 @@ sub edit_hunk_loop { } sub help_patch_cmd { - print colored $help_color, <<\EOF ; -y - stage this hunk -n - do not stage this hunk -q - quit, do not stage this hunk nor any of the remaining ones -a - stage this and all the remaining hunks in the file -d - do not stage this hunk nor any of the remaining hunks in the file + my $verb = lc $patch_mode_flavour{VERB}; + my $target = $patch_mode_flavour{TARGET}; + print colored $help_color, <<EOF ; +y - $verb this hunk$target +n - do not $verb this hunk$target +q - quit, do not $verb this hunk nor any of the remaining ones +a - $verb this and all the remaining hunks in the file +d - do not $verb this hunk nor any of the remaining hunks in the file g - select a hunk to go to / - search for a hunk matching the given regex j - leave this hunk undecided, see next undecided hunk @@ -1010,8 +1104,40 @@ e - manually edit the current hunk EOF } +sub apply_patch { + my $cmd = shift; + my $ret = run_git_apply $cmd . ' --recount', @_; + if (!$ret) { + print STDERR @_; + } + return $ret; +} + +sub apply_patch_for_checkout_commit { + my $reverse = shift; + my $applies_index = run_git_apply 'apply '.$reverse.' --cached --recount --check', @_; + my $applies_worktree = run_git_apply 'apply '.$reverse.' --recount --check', @_; + + if ($applies_worktree && $applies_index) { + run_git_apply 'apply '.$reverse.' --cached --recount', @_; + run_git_apply 'apply '.$reverse.' --recount', @_; + return 1; + } elsif (!$applies_index) { + print colored $error_color, "The selected hunks do not apply to the index!\n"; + if (prompt_yesno "Apply them to the worktree anyway? ") { + return run_git_apply 'apply '.$reverse.' --recount', @_; + } else { + print colored $error_color, "Nothing was applied.\n"; + return 0; + } + } else { + print STDERR @_; + return 0; + } +} + sub patch_update_cmd { - my @all_mods = list_modified('file-only'); + my @all_mods = list_modified($patch_mode_flavour{FILTER}); my @mods = grep { !($_->{BINARY}) } @all_mods; my @them; @@ -1082,7 +1208,7 @@ sub patch_update_file { my ($ix, $num); my $path = shift; my ($head, @hunk) = parse_diff($path); - ($head, my $mode) = parse_diff_header($head); + ($head, my $mode, my $deletion) = parse_diff_header($head); for (@{$head->{DISPLAY}}) { print; } @@ -1090,6 +1216,9 @@ sub patch_update_file { if (@{$mode->{TEXT}}) { unshift @hunk, $mode; } + if (@{$deletion->{TEXT}} && !@hunk) { + @hunk = ($deletion); + } $num = scalar @hunk; $ix = 0; @@ -1142,8 +1271,11 @@ sub patch_update_file { for (@{$hunk[$ix]{DISPLAY}}) { print; } - print colored $prompt_color, 'Stage ', - ($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'), + print colored $prompt_color, $patch_mode_flavour{VERB}, + ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : + $hunk[$ix]{TYPE} eq 'deletion' ? ' deletion' : + ' this hunk'), + $patch_mode_flavour{TARGET}, " [y,n,q,a,d,/$other,?]? "; my $line = prompt_single_character; if ($line) { @@ -1317,16 +1449,9 @@ sub patch_update_file { if (@result) { my $fh; - - open $fh, '| git apply --cached --recount'; - for (@{$head->{TEXT}}, @result) { - print $fh $_; - } - if (!close $fh) { - for (@{$head->{TEXT}}, @result) { - print STDERR $_; - } - } + my @patch = (@{$head->{TEXT}}, @result); + my $apply_routine = $patch_mode_flavour{APPLY}; + &$apply_routine(@patch); refresh(); } @@ -1367,11 +1492,41 @@ EOF sub process_args { return unless @ARGV; my $arg = shift @ARGV; - if ($arg eq "--patch") { - $patch_mode = 1; - $arg = shift @ARGV or die "missing --"; + if ($arg =~ /--patch(?:=(.*))?/) { + if (defined $1) { + if ($1 eq 'reset') { + $patch_mode = 'reset_head'; + $patch_mode_revision = 'HEAD'; + $arg = shift @ARGV or die "missing --"; + if ($arg ne '--') { + $patch_mode_revision = $arg; + $patch_mode = ($arg eq 'HEAD' ? + 'reset_head' : 'reset_nothead'); + $arg = shift @ARGV or die "missing --"; + } + } elsif ($1 eq 'checkout') { + $arg = shift @ARGV or die "missing --"; + if ($arg eq '--') { + $patch_mode = 'checkout_index'; + } else { + $patch_mode_revision = $arg; + $patch_mode = ($arg eq 'HEAD' ? + 'checkout_head' : 'checkout_nothead'); + $arg = shift @ARGV or die "missing --"; + } + } elsif ($1 eq 'stage' or $1 eq 'stash') { + $patch_mode = $1; + $arg = shift @ARGV or die "missing --"; + } else { + die "unknown --patch mode: $1"; + } + } else { + $patch_mode = 'stage'; + $arg = shift @ARGV or die "missing --"; + } die "invalid argument $arg, expecting --" unless $arg eq "--"; + %patch_mode_flavour = %{$patch_modes{$patch_mode}}; } elsif ($arg ne "--") { die "invalid argument $arg, expecting --"; @@ -15,6 +15,7 @@ q,quiet be quiet s,signoff add a Signed-off-by line to the commit message u,utf8 recode into utf8 (default) k,keep pass -k flag to git-mailinfo +c,scissors strip everything before a scissors line whitespace= pass it through git-apply ignore-space-change pass it through git-apply ignore-whitespace pass it through git-apply @@ -204,7 +205,7 @@ check_patch_format () { # and see if it looks like that they all begin with the # header field names... sed -n -e '/^$/q' -e '/^[ ]/d' -e p "$1" | - egrep -v '^[A-Za-z]+(-[A-Za-z]+)*:' >/dev/null || + LC_ALL=C egrep -v '^[!-9;-~]+:' >/dev/null || patch_format=mbox fi } < "$1" || clean_abort @@ -288,7 +289,7 @@ split_patches () { prec=4 dotest="$GIT_DIR/rebase-apply" sign= utf8=t keep= skip= interactive= resolved= rebasing= abort= -resolvemsg= resume= +resolvemsg= resume= scissors= git_apply_opt= committer_date_is_author_date= ignore_date= @@ -310,6 +311,10 @@ do utf8= ;; -k|--keep) keep=t ;; + -c|--scissors) + scissors=t ;; + --no-scissors) + scissors=f ;; -r|--resolved) resolved=t ;; --skip) @@ -317,7 +322,7 @@ do --abort) abort=t ;; --rebasing) - rebasing=t threeway=t keep=t ;; + rebasing=t threeway=t keep=t scissors=f ;; -d|--dotest) die "-d option is no longer supported. Do not use." ;; @@ -435,14 +440,14 @@ else split_patches "$@" - # -s, -u, -k, --whitespace, -3, -C, -q and -p flags are kept - # for the resuming session after a patch failure. - # -i can and must be given when resuming. + # -i can and must be given when resuming; everything + # else is kept echo " $git_apply_opt" >"$dotest/apply-opt" echo "$threeway" >"$dotest/threeway" echo "$sign" >"$dotest/sign" echo "$utf8" >"$dotest/utf8" echo "$keep" >"$dotest/keep" + echo "$scissors" >"$dotest/scissors" echo "$GIT_QUIET" >"$dotest/quiet" echo 1 >"$dotest/next" if test -n "$rebasing" @@ -484,6 +489,12 @@ if test "$(cat "$dotest/keep")" = t then keep=-k fi +case "$(cat "$dotest/scissors")" in +t) + scissors=--scissors ;; +f) + scissors=--no-scissors ;; +esac if test "$(cat "$dotest/quiet")" = t then GIT_QUIET=t @@ -538,7 +549,7 @@ do # by the user, or the user can tell us to do so by --resolved flag. case "$resume" in '') - git mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \ + git mailinfo $keep $scissors $utf8 "$dotest/msg" "$dotest/patch" \ <"$dotest/$msgnum" >"$dotest/info" || stop_here $this @@ -638,7 +649,10 @@ do [eE]*) git_editor "$dotest/final-commit" action=again ;; [vV]*) action=again - LESS=-S ${PAGER:-less} "$dotest/patch" ;; + : ${GIT_PAGER=$(git var GIT_PAGER)} + : ${LESS=-FRSX} + export LESS + $GIT_PAGER "$dotest/patch" ;; *) action=again ;; esac done diff --git a/git-bisect.sh b/git-bisect.sh index 6f6f03966f..8b3c5858a9 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -13,8 +13,8 @@ git bisect skip [(<rev>|<range>)...] mark <rev>... untestable revisions. git bisect next find next bisection to test and check it out. -git bisect reset [<branch>] - finish bisection search and go back to branch. +git bisect reset [<commit>] + finish bisection search and go back to commit. git bisect visualize show bisect status in gitk. git bisect replay <logfile> @@ -311,8 +311,8 @@ bisect_reset() { } case "$#" in 0) branch=$(cat "$GIT_DIR/BISECT_START") ;; - 1) git show-ref --verify --quiet -- "refs/heads/$1" || - die "$1 does not seem to be a valid branch" + 1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null || + die "'$1' is not a valid commit" branch="$1" ;; *) usage ;; diff --git a/git-compat-util.h b/git-compat-util.h index e5e9f39c56..5c596875c2 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -65,6 +65,12 @@ #define _NETBSD_SOURCE 1 #define _SGI_SOURCE 1 +#ifdef WIN32 /* Both MinGW and MSVC */ +#define WIN32_LEAN_AND_MEAN /* stops windows.h including winsock.h */ +#include <winsock2.h> +#include <windows.h> +#endif + #include <unistd.h> #include <stdio.h> #include <sys/stat.h> @@ -113,6 +119,9 @@ /* pull in Windows compatibility stuff */ #include "compat/mingw.h" #endif /* __MINGW32__ */ +#ifdef _MSC_VER +#include "compat/msvc.h" +#endif #ifndef NO_LIBGEN_H #include <libgen.h> @@ -167,8 +176,10 @@ extern char *gitbasename(char *); #ifdef __GNUC__ #define NORETURN __attribute__((__noreturn__)) +#define NORETURN_PTR __attribute__((__noreturn__)) #else #define NORETURN +#define NORETURN_PTR #ifndef __attribute__ #define __attribute__(x) #endif @@ -177,13 +188,14 @@ extern char *gitbasename(char *); #include "compat/bswap.h" /* General helper functions */ -extern void usage(const char *err) NORETURN; -extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2))); -extern void die_errno(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2))); +extern NORETURN void usage(const char *err); +extern NORETURN void usagef(const char *err, ...) __attribute__((format (printf, 1, 2))); +extern NORETURN void die(const char *err, ...) __attribute__((format (printf, 1, 2))); +extern NORETURN void die_errno(const char *err, ...) __attribute__((format (printf, 1, 2))); extern int error(const char *err, ...) __attribute__((format (printf, 1, 2))); extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2))); -extern void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN); +extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params)); extern int prefixcmp(const char *str, const char *prefix); extern time_t tm_to_time_t(const struct tm *tm); diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 593832d813..a7d215c8aa 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -238,7 +238,9 @@ sub conn { } my $rr = ":pserver:$user\@$serv:$port$repo"; - unless ($pass) { + if ($pass) { + $pass = $self->_scramble($pass); + } else { open(H,$ENV{'HOME'}."/.cvspass") and do { # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z while (<H>) { @@ -251,10 +253,9 @@ sub conn { } } }; + $pass = "A" unless $pass; } - $pass = $self->_scramble($pass); - my ($s, $rep); if ($proxyhost) { @@ -578,10 +579,21 @@ sub get_headref ($) { return $r; } +my $user_filename_prepend = ''; +sub munge_user_filename { + my $name = shift; + return File::Spec->file_name_is_absolute($name) ? + $name : + $user_filename_prepend . $name; +} + -d $git_tree or mkdir($git_tree,0777) or die "Could not create $git_tree: $!"; -chdir($git_tree); +if ($git_tree ne '.') { + $user_filename_prepend = getwd() . '/'; + chdir($git_tree); +} my $last_branch = ""; my $orig_branch = ""; @@ -643,7 +655,7 @@ unless (-d $git_dir) { -f "$git_dir/cvs-authors" and read_author_info("$git_dir/cvs-authors"); if ($opt_A) { - read_author_info($opt_A); + read_author_info(munge_user_filename($opt_A)); write_author_info("$git_dir/cvs-authors"); } @@ -678,7 +690,7 @@ unless ($opt_P) { $? == 0 or die "git-cvsimport: fatal: cvsps reported error\n"; close $cvspsfh; } else { - $cvspsfile = $opt_P; + $cvspsfile = munge_user_filename($opt_P); } open(CVS, "<$cvspsfile") or die $!; diff --git a/git-filter-branch.sh b/git-filter-branch.sh index a480d6fc70..6b8b6a4f26 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -125,6 +125,7 @@ filter_subdir= orig_namespace=refs/original/ force= prune_empty= +remap_to_ancestor= while : do case "$1" in @@ -137,6 +138,11 @@ do force=t continue ;; + --remap-to-ancestor) + shift + remap_to_ancestor=t + continue + ;; --prune-empty) shift prune_empty=t @@ -182,6 +188,7 @@ do ;; --subdirectory-filter) filter_subdir="$OPTARG" + remap_to_ancestor=t ;; --original) orig_namespace=$(expr "$OPTARG/" : '\(.*[^/]\)/*$')/ @@ -257,15 +264,24 @@ git read-tree || die "Could not seed the index" # map old->new commit ids for rewriting parents mkdir ../map || die "Could not create map/ directory" +# we need "--" only if there are no path arguments in $@ +nonrevs=$(git rev-parse --no-revs "$@") || exit +test -z "$nonrevs" && dashdash=-- || dashdash= +rev_args=$(git rev-parse --revs-only "$@") + case "$filter_subdir" in "") - git rev-list --reverse --topo-order --default HEAD \ - --parents --simplify-merges "$@" + eval set -- "$(git rev-parse --sq --no-revs "$@")" ;; *) - git rev-list --reverse --topo-order --default HEAD \ - --parents --simplify-merges "$@" -- "$filter_subdir" -esac > ../revs || die "Could not get the commits" + eval set -- "$(git rev-parse --sq --no-revs "$@" $dashdash \ + "$filter_subdir")" + ;; +esac + +git rev-list --reverse --topo-order --default HEAD \ + --parents --simplify-merges $rev_args "$@" > ../revs || + die "Could not get the commits" commits=$(wc -l <../revs | tr -d " ") test $commits -eq 0 && die "Found nothing to rewrite" @@ -345,19 +361,19 @@ while read commit parents; do die "could not write rewritten commit" done <../revs -# In case of a subdirectory filter, it is possible that a specified head -# is not in the set of rewritten commits, because it was pruned by the -# revision walker. Fix it by mapping these heads to the unique nearest -# ancestor that survived the pruning. +# If we are filtering for paths, as in the case of a subdirectory +# filter, it is possible that a specified head is not in the set of +# rewritten commits, because it was pruned by the revision walker. +# Ancestor remapping fixes this by mapping these heads to the unique +# nearest ancestor that survived the pruning. -if test "$filter_subdir" +if test "$remap_to_ancestor" = t then while read ref do sha1=$(git rev-parse "$ref"^0) test -f "$workdir"/../map/$sha1 && continue - ancestor=$(git rev-list --simplify-merges -1 \ - $ref -- "$filter_subdir") + ancestor=$(git rev-list --simplify-merges -1 "$ref" "$@") test "$ancestor" && echo $(map $ancestor) >> "$workdir"/../map/$sha1 done < "$tempdir"/heads fi diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 14b92ba786..037a1f2c21 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -745,6 +745,8 @@ set default_config(gui.newbranchtemplate) {} set default_config(gui.spellingdictionary) {} set default_config(gui.fontui) [font configure font_ui] set default_config(gui.fontdiff) [font configure font_diff] +# TODO: this option should be added to the git-config documentation +set default_config(gui.maxfilesdisplayed) 5000 set font_descs { {fontui font_ui {mc "Main Font"}} {fontdiff font_diff {mc "Diff/Console Font"}} @@ -1132,6 +1134,7 @@ set current_branch {} set is_detached 0 set current_diff_path {} set is_3way_diff 0 +set is_submodule_diff 0 set is_conflict_diff 0 set selected_commit_type new set diff_empty_count 0 @@ -1698,10 +1701,12 @@ proc display_all_files_helper {w path icon_name m} { $w insert end "[escape_path $path]\n" } +set files_warning 0 proc display_all_files {} { global ui_index ui_workdir global file_states file_lists global last_clicked + global files_warning $ui_index conf -state normal $ui_workdir conf -state normal @@ -1713,7 +1718,18 @@ proc display_all_files {} { set file_lists($ui_index) [list] set file_lists($ui_workdir) [list] - foreach path [lsort [array names file_states]] { + set to_display [lsort [array names file_states]] + set display_limit [get_config gui.maxfilesdisplayed] + if {[llength $to_display] > $display_limit} { + if {!$files_warning} { + # do not repeatedly warn: + set files_warning 1 + info_popup [mc "Displaying only %s of %s files." \ + $display_limit [llength $to_display]] + } + set to_display [lrange $to_display 0 [expr {$display_limit-1}]] + } + foreach path $to_display { set s $file_states($path) set m [lindex $s 0] set icon_name [lindex $s 1] @@ -2010,6 +2026,19 @@ proc do_quit {{rc {1}}} { # -- Stash our current window geometry into this repository. # + set cfg_wmstate [wm state .] + if {[catch {set rc_wmstate $repo_config(gui.wmstate)}]} { + set rc_wmstate {} + } + if {$cfg_wmstate ne $rc_wmstate} { + catch {git config gui.wmstate $cfg_wmstate} + } + if {$cfg_wmstate eq {zoomed}} { + # on Windows wm geometry will lie about window + # position (but not size) when window is zoomed + # restore the window before querying wm geometry + wm state . normal + } set cfg_geometry [list] lappend cfg_geometry [wm geometry .] lappend cfg_geometry [lindex [.vpane sash coord 0] 0] @@ -3054,7 +3083,7 @@ frame .vpane.lower.diff.body set ui_diff .vpane.lower.diff.body.t text $ui_diff -background white -foreground black \ -borderwidth 0 \ - -width 80 -height 15 -wrap none \ + -width 80 -height 5 -wrap none \ -font font_diff \ -xscrollcommand {.vpane.lower.diff.body.sbx set} \ -yscrollcommand {.vpane.lower.diff.body.sby set} \ @@ -3212,7 +3241,7 @@ proc popup_diff_menu {ctxm ctxmmg x y X Y} { set l [mc "Stage Hunk For Commit"] set t [mc "Stage Line For Commit"] } - if {$::is_3way_diff + if {$::is_3way_diff || $::is_submodule_diff || $current_diff_path eq {} || {__} eq $state || {_O} eq $state @@ -3249,6 +3278,14 @@ wm geometry . [lindex $gm 0] unset gm } +# -- Load window state +# +catch { +set gws $repo_config(gui.wmstate) +wm state . $gws +unset gws +} + # -- Key Bindings # bind $ui_comm <$M1B-Key-Return> {do_commit;break} diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index 925b3f56c1..bd5d189ed1 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -255,7 +255,7 @@ proc show_other_diff {path w m cont_info} { proc start_show_diff {cont_info {add_opts {}}} { global file_states file_lists - global is_3way_diff diff_active repo_config + global is_3way_diff is_submodule_diff diff_active repo_config global ui_diff ui_index ui_workdir global current_diff_path current_diff_side current_diff_header @@ -265,6 +265,7 @@ proc start_show_diff {cont_info {add_opts {}}} { set s $file_states($path) set m [lindex $s 0] set is_3way_diff 0 + set is_submodule_diff 0 set diff_active 1 set current_diff_header {} @@ -295,6 +296,16 @@ proc start_show_diff {cont_info {add_opts {}}} { lappend cmd $path } + if {[string match {160000 *} [lindex $s 2]] + || [string match {160000 *} [lindex $s 3]]} { + set is_submodule_diff 1 + if {$w eq $ui_index} { + set cmd [list submodule summary --cached -- $path] + } else { + set cmd [list submodule summary --files -- $path] + } + } + if {[catch {set fd [eval git_read --nice $cmd]} err]} { set diff_active 0 unlock_index @@ -312,7 +323,7 @@ proc start_show_diff {cont_info {add_opts {}}} { } proc read_diff {fd cont_info} { - global ui_diff diff_active + global ui_diff diff_active is_submodule_diff global is_3way_diff is_conflict_diff current_diff_header global current_diff_queue global diff_empty_count @@ -374,6 +385,23 @@ proc read_diff {fd cont_info} { set tags {} } } + } elseif {$is_submodule_diff} { + if {$line == ""} continue + if {[regexp {^\* } $line]} { + set line [string replace $line 0 1 {Submodule }] + set tags d_@ + } else { + set op [string range $line 0 2] + switch -- $op { + { <} {set tags d_-} + { >} {set tags d_+} + { W} {set tags {}} + default { + puts "error: Unhandled submodule diff marker: {$op}" + set tags {} + } + } + } } else { set op [string index $line 0] switch -- $op { diff --git a/git-gui/lib/remote_branch_delete.tcl b/git-gui/lib/remote_branch_delete.tcl index 4e02fc0d39..31e0947488 100644 --- a/git-gui/lib/remote_branch_delete.tcl +++ b/git-gui/lib/remote_branch_delete.tcl @@ -208,13 +208,15 @@ method _delete {} { return } - if {[tk_messageBox \ - -icon warning \ - -type yesno \ - -title [wm title $w] \ - -parent $w \ - -message [mc "Recovering deleted branches is difficult.\n\nDelete the selected branches?"]] ne yes} { - return + if {$checktype ne {head}} { + if {[tk_messageBox \ + -icon warning \ + -type yesno \ + -title [wm title $w] \ + -parent $w \ + -message [mc "Recovering deleted branches is difficult.\n\nDelete the selected branches?"]] ne yes} { + return + } } destroy $w diff --git a/git-gui/po/el.po b/git-gui/po/el.po new file mode 100644 index 0000000000..3634ba469d --- /dev/null +++ b/git-gui/po/el.po @@ -0,0 +1,2005 @@ +# Translation of git-gui to Greek +# Copyright (C) 2009 Jimmy Angelakos +# This file is distributed under the same license as the git-gui package. +# Jimmy Angelakos <vyruss@hellug.gr>, 2009. +msgid "" +msgstr "" +"Project-Id-Version: el\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2008-03-14 07:18+0100\n" +"PO-Revision-Date: 2009-06-23 21:33+0300\n" +"Last-Translator: Jimmy Angelakos <vyruss@hellug.gr>\n" +"Language-Team: Greek <i18n@lists.hellug.gr>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 0.3\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: git-gui.sh:41 git-gui.sh:634 git-gui.sh:648 git-gui.sh:661 git-gui.sh:744 +#: git-gui.sh:763 +msgid "git-gui: fatal error" +msgstr "git-gui: κρίσιμο σφάλμα" + +#: git-gui.sh:593 +#, tcl-format +msgid "Invalid font specified in %s:" +msgstr "Μη έγκυρη γραμματοσειρά στο %s:" + +#: git-gui.sh:620 +msgid "Main Font" +msgstr "Κύρια Γραμματοσειρά" + +#: git-gui.sh:621 +msgid "Diff/Console Font" +msgstr "Γραμματοσειρά Διαφοράς/Κονσόλας" + +#: git-gui.sh:635 +msgid "Cannot find git in PATH." +msgstr "Δε βρέθηκε το git στο PATH." + +#: git-gui.sh:662 +msgid "Cannot parse Git version string:" +msgstr "Αδύνατη η ανάγνωση στοιχειοσειράς έκδοσης Git:" + +#: git-gui.sh:680 +#, tcl-format +msgid "" +"Git version cannot be determined.\n" +"\n" +"%s claims it is version '%s'.\n" +"\n" +"%s requires at least Git 1.5.0 or later.\n" +"\n" +"Assume '%s' is version 1.5.0?\n" +msgstr "" +"Δε μπορεί να ανιχνευτεί η έκδοση του Git. \n" +"\n" +"Το %s υποστηρίζει πως είναι η έκδοση '%s'.\n" +"\n" +"Το %s απαιτεί τουλάχιστον Git 1.5.0 ή πιό πρόσφατη.\n" +"\n" +"Να υποτεθεί πως το '%s' είναι η έκδοση 1.5.0;\n" + +#: git-gui.sh:918 +msgid "Git directory not found:" +msgstr "Δε βρέθηκε φάκελος Git:" + +#: git-gui.sh:925 +msgid "Cannot move to top of working directory:" +msgstr "Δεν είναι δυνατή η μετακίνηση στην κορυφή του φακέλου εργασίας:" + +#: git-gui.sh:932 +msgid "Cannot use funny .git directory:" +msgstr "Δεν είναι δυνατή η χρήση περίεργου φακέλου .git:" + +#: git-gui.sh:937 +msgid "No working directory" +msgstr "Δεν υπάρχει φάκελος εργασίας" + +#: git-gui.sh:1084 lib/checkout_op.tcl:283 +msgid "Refreshing file status..." +msgstr "Ανανέωση κατάστασης αρχείου..." + +#: git-gui.sh:1149 +msgid "Scanning for modified files ..." +msgstr "Ανίχνευση για τροποποιημένα αρχεία..." + +#: git-gui.sh:1324 lib/browser.tcl:246 +msgid "Ready." +msgstr "Έτοιμο." + +#: git-gui.sh:1590 +msgid "Unmodified" +msgstr "Μη τροποποιημένο" + +#: git-gui.sh:1592 +msgid "Modified, not staged" +msgstr "Τροποποιημένο, μη σταδιοποιημένο" + +#: git-gui.sh:1593 git-gui.sh:1598 +msgid "Staged for commit" +msgstr "Σταδιοποιημένο προς υποβολή" + +#: git-gui.sh:1594 git-gui.sh:1599 +msgid "Portions staged for commit" +msgstr "Μέρη σταδιοποιημένα προς υποβολή" + +#: git-gui.sh:1595 git-gui.sh:1600 +msgid "Staged for commit, missing" +msgstr "Σταδιοποιημένο προς υποβολή, λείπει" + +#: git-gui.sh:1597 +msgid "Untracked, not staged" +msgstr "Μη παρακολουθούμενο, μη σταδιοποιημένο" + +#: git-gui.sh:1602 +msgid "Missing" +msgstr "Λείπει" + +#: git-gui.sh:1603 +msgid "Staged for removal" +msgstr "Σταδιοποιημένο προς αφαίρεση" + +#: git-gui.sh:1604 +msgid "Staged for removal, still present" +msgstr "Σταδιοποιημένο προς αφαίρεση, ακόμα παρόν" + +#: git-gui.sh:1606 git-gui.sh:1607 git-gui.sh:1608 git-gui.sh:1609 +msgid "Requires merge resolution" +msgstr "Απαιτεί επίλυση συγχώνευσης" + +#: git-gui.sh:1644 +msgid "Starting gitk... please wait..." +msgstr "Γίνεται εκκίνηση του gitk... παρακαλώ περιμένετε..." + +#: git-gui.sh:1653 +#, tcl-format +msgid "" +"Unable to start gitk:\n" +"\n" +"%s does not exist" +msgstr "" +"Αδυναμία εκκίνησης του gitk:\n" +"\n" +"Το %s δεν υπάρχει" + +#: git-gui.sh:1860 lib/choose_repository.tcl:36 +msgid "Repository" +msgstr "Αποθετήριο" + +#: git-gui.sh:1861 +msgid "Edit" +msgstr "Επεξεργασία" + +#: git-gui.sh:1863 lib/choose_rev.tcl:561 +msgid "Branch" +msgstr "Κλάδος" + +#: git-gui.sh:1866 lib/choose_rev.tcl:548 +msgid "Commit@@noun" +msgstr "Υποβολή@@noun" + +#: git-gui.sh:1869 lib/merge.tcl:120 lib/merge.tcl:149 lib/merge.tcl:167 +msgid "Merge" +msgstr "Συγχώνευση" + +#: git-gui.sh:1870 lib/choose_rev.tcl:557 +msgid "Remote" +msgstr "Απομακρυσμένο" + +#: git-gui.sh:1879 +msgid "Browse Current Branch's Files" +msgstr "Περιήγηση Αρχείων Τρέχοντα Κλάδου" + +#: git-gui.sh:1883 +msgid "Browse Branch Files..." +msgstr "Περιήγηση Αρχείων Κλάδου..." + +#: git-gui.sh:1888 +msgid "Visualize Current Branch's History" +msgstr "Απεικόνιση Ιστορικού Τρέχοντα Κλάδου" + +#: git-gui.sh:1892 +msgid "Visualize All Branch History" +msgstr "Απεικόνιση Ιστορικού Όλων των Κλάδων" + +#: git-gui.sh:1899 +#, tcl-format +msgid "Browse %s's Files" +msgstr "Περιήγηση Αρχείων του %s" + +#: git-gui.sh:1901 +#, tcl-format +msgid "Visualize %s's History" +msgstr "Απεικόνιση Ιστορικού του %s" + +#: git-gui.sh:1906 lib/database.tcl:27 lib/database.tcl:67 +msgid "Database Statistics" +msgstr "Στατιστικά Βάσης Δεδομένων" + +#: git-gui.sh:1909 lib/database.tcl:34 +msgid "Compress Database" +msgstr "Συμπίεση Βάσης Δεδομένων" + +#: git-gui.sh:1912 +msgid "Verify Database" +msgstr "Επαλήθευση Βάσης Δεδομένων" + +#: git-gui.sh:1919 git-gui.sh:1923 git-gui.sh:1927 lib/shortcut.tcl:7 +#: lib/shortcut.tcl:39 lib/shortcut.tcl:71 +msgid "Create Desktop Icon" +msgstr "Δημιουργία Εικονιδίου Επιφάνειας Εργασίας" + +#: git-gui.sh:1932 lib/choose_repository.tcl:177 lib/choose_repository.tcl:185 +msgid "Quit" +msgstr "Έξοδος" + +#: git-gui.sh:1939 +msgid "Undo" +msgstr "Αναίρεση" + +#: git-gui.sh:1942 +msgid "Redo" +msgstr "Ξανά" + +#: git-gui.sh:1946 git-gui.sh:2443 +msgid "Cut" +msgstr "Αποκοπή" + +#: git-gui.sh:1949 git-gui.sh:2446 git-gui.sh:2520 git-gui.sh:2614 +#: lib/console.tcl:69 +msgid "Copy" +msgstr "Αντιγραφή" + +#: git-gui.sh:1952 git-gui.sh:2449 +msgid "Paste" +msgstr "Επικόλληση" + +#: git-gui.sh:1955 git-gui.sh:2452 lib/branch_delete.tcl:26 +#: lib/remote_branch_delete.tcl:38 +msgid "Delete" +msgstr "Διαγραφή" + +#: git-gui.sh:1959 git-gui.sh:2456 git-gui.sh:2618 lib/console.tcl:71 +msgid "Select All" +msgstr "Επιλογή Όλων" + +#: git-gui.sh:1968 +msgid "Create..." +msgstr "Δημιουργία..." + +#: git-gui.sh:1974 +msgid "Checkout..." +msgstr "Εξαγωγή..." + +#: git-gui.sh:1980 +msgid "Rename..." +msgstr "Μετονομασία..." + +#: git-gui.sh:1985 git-gui.sh:2085 +msgid "Delete..." +msgstr "Διαγραφή..." + +#: git-gui.sh:1990 +msgid "Reset..." +msgstr "Επαναφορά..." + +#: git-gui.sh:2002 git-gui.sh:2389 +msgid "New Commit" +msgstr "Νέα Υποβολή" + +#: git-gui.sh:2010 git-gui.sh:2396 +msgid "Amend Last Commit" +msgstr "Διόρθωση Τελευταίας Υποβολής" + +#: git-gui.sh:2019 git-gui.sh:2356 lib/remote_branch_delete.tcl:99 +msgid "Rescan" +msgstr "Επανανίχνευση" + +#: git-gui.sh:2025 +msgid "Stage To Commit" +msgstr "Σταδιοποίηση Προς Υποβολή" + +#: git-gui.sh:2031 +msgid "Stage Changed Files To Commit" +msgstr "Σταδιοποίηση Αλλαγμένων Αρχείων Προς Υποβολή" + +#: git-gui.sh:2037 +msgid "Unstage From Commit" +msgstr "Αποσταδιοποίηση Από Υποβολή" + +#: git-gui.sh:2042 lib/index.tcl:395 +msgid "Revert Changes" +msgstr "Αναίρεση Αλλαγών" + +#: git-gui.sh:2049 git-gui.sh:2368 git-gui.sh:2467 +msgid "Sign Off" +msgstr "Αποσύνδεση" + +#: git-gui.sh:2053 git-gui.sh:2372 +msgid "Commit@@verb" +msgstr "Υποβολή@@verb" + +#: git-gui.sh:2064 +msgid "Local Merge..." +msgstr "Τοπική Συγχώνευση..." + +#: git-gui.sh:2069 +msgid "Abort Merge..." +msgstr "Ακύρωση Συγχώνευσης..." + +#: git-gui.sh:2081 +msgid "Push..." +msgstr "Ώθηση..." + +#: git-gui.sh:2092 lib/choose_repository.tcl:41 +#, fuzzy +msgid "Apple" +msgstr "" + +#: git-gui.sh:2095 git-gui.sh:2117 lib/about.tcl:14 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50 +#, tcl-format +msgid "About %s" +msgstr "Περί %s" + +#: git-gui.sh:2099 +msgid "Preferences..." +msgstr "Προτιμήσεις..." + +#: git-gui.sh:2107 git-gui.sh:2639 +msgid "Options..." +msgstr "Επιλογές..." + +#: git-gui.sh:2113 lib/choose_repository.tcl:47 +msgid "Help" +msgstr "Βοήθεια" + +#: git-gui.sh:2154 +msgid "Online Documentation" +msgstr "Διαδικτυακή Τεκμηρίωση" + +#: git-gui.sh:2238 +#, tcl-format +msgid "fatal: cannot stat path %s: No such file or directory" +msgstr "κρίσιμο: δε βρέθηκε η διαδρομή: %s: Δεν υπάρχει το αρχείο ή ο φάκελος" + +#: git-gui.sh:2271 +msgid "Current Branch:" +msgstr "Τρέχων Κλάδος:" + +#: git-gui.sh:2292 +msgid "Staged Changes (Will Commit)" +msgstr "Σταδιοποιημένες Αλλαγές (Θα Υποβληθούν)" + +#: git-gui.sh:2312 +msgid "Unstaged Changes" +msgstr "Μη Σταδιοποιημένες Αλλαγές" + +#: git-gui.sh:2362 +msgid "Stage Changed" +msgstr "Σταδιοποίηση Αλλαγών" + +#: git-gui.sh:2378 lib/transport.tcl:93 lib/transport.tcl:182 +msgid "Push" +msgstr "Ώθηση" + +#: git-gui.sh:2408 +msgid "Initial Commit Message:" +msgstr "Αρχικό Μήνυμα Υποβολής:" + +#: git-gui.sh:2409 +msgid "Amended Commit Message:" +msgstr "Διορθωμένο Μήνυμα Υποβολής:" + +#: git-gui.sh:2410 +msgid "Amended Initial Commit Message:" +msgstr "Διορθωμένο Αρχικό Μήνυμα Υποβολής:" + +#: git-gui.sh:2411 +msgid "Amended Merge Commit Message:" +msgstr "Διορθωμένο Μήνυμα Υποβολής Συγχώνευσης:" + +#: git-gui.sh:2412 +msgid "Merge Commit Message:" +msgstr "Μήνυμα Υποβολής Συγχώνευσης:" + +#: git-gui.sh:2413 +msgid "Commit Message:" +msgstr "Μήνυμα Υποβολής:" + +#: git-gui.sh:2459 git-gui.sh:2622 lib/console.tcl:73 +msgid "Copy All" +msgstr "Αντιγραφή Όλων" + +#: git-gui.sh:2483 lib/blame.tcl:107 +msgid "File:" +msgstr "Αρχείο:" + +#: git-gui.sh:2589 +msgid "Apply/Reverse Hunk" +msgstr "Εφαρμογή/Αντιστροφή Κομματιού" + +#: git-gui.sh:2595 +msgid "Show Less Context" +msgstr "Προβολή Στενότερου Πλαισίου" + +#: git-gui.sh:2602 +msgid "Show More Context" +msgstr "Προβολή Ευρύτερου Πλαισίου" + +#: git-gui.sh:2610 +msgid "Refresh" +msgstr "Ανανέωση" + +#: git-gui.sh:2631 +msgid "Decrease Font Size" +msgstr "Μείωση Μεγέθους Γραμματοσειράς" + +#: git-gui.sh:2635 +msgid "Increase Font Size" +msgstr "Αύξηση Μεγέθους Γραμματοσειράς" + +#: git-gui.sh:2646 +msgid "Unstage Hunk From Commit" +msgstr "Αποσταδιοποίηση Κομματιού Από Υποβολή" + +#: git-gui.sh:2648 +msgid "Stage Hunk For Commit" +msgstr "Σταδιοποίηση Κομματιού Προς Υποβολή" + +#: git-gui.sh:2667 +msgid "Initializing..." +msgstr "Γίνεται αρχικοποίηση..." + +#: git-gui.sh:2762 +#, tcl-format +msgid "" +"Possible environment issues exist.\n" +"\n" +"The following environment variables are probably\n" +"going to be ignored by any Git subprocess run\n" +"by %s:\n" +"\n" +msgstr "" +"Υπάρχουν πιθανά θέματα με το περιβάλλον.\n" +"\n" +"Οι εξής μεταβλητές περιβάλλοντος μάλλον θα\n" +"αγνοηθούν από πιθανή εκτέλεση υποδιεργασίας Git\n" +"από το %s:\n" +"\n" + +#: git-gui.sh:2792 +msgid "" +"\n" +"This is due to a known issue with the\n" +"Tcl binary distributed by Cygwin." +msgstr "" +"\n" +"Αυτό οφείλεται σε ένα γνωστό θέμα με το\n" +"εκτελέσιμο Tcl που διανέμεται με το Cygwin." + +#: git-gui.sh:2797 +#, tcl-format +msgid "" +"\n" +"\n" +"A good replacement for %s\n" +"is placing values for the user.name and\n" +"user.email settings into your personal\n" +"~/.gitconfig file.\n" +msgstr "" +"\n" +"\n" +"Ένα καλό υποκατάστατο για το %s\n" +"είναι η τοποθέτηση τιμών για τις ρυθμίσεις\n" +"user.name και user.email στο προσωπικό σας\n" +"αρχείο ~/.gitconfig .\n" + +#: lib/about.tcl:26 +msgid "git-gui - a graphical user interface for Git." +msgstr "git-gui - ένα γραφικό περιβάλλον για το Git." + +#: lib/blame.tcl:77 +msgid "File Viewer" +msgstr "Εφαρμογή Προβολής Αρχείου" + +#: lib/blame.tcl:81 +msgid "Commit:" +msgstr "Υποβολή:" + +#: lib/blame.tcl:264 +msgid "Copy Commit" +msgstr "Αντιγραφή Υποβολής" + +#: lib/blame.tcl:384 +#, tcl-format +msgid "Reading %s..." +msgstr "Ανάγνωση %s..." + +#: lib/blame.tcl:488 +msgid "Loading copy/move tracking annotations..." +msgstr "Γίνεται φόρτωση σχολίων παρακολούθησης αντιγραφής/μετακίνησης..." + +#: lib/blame.tcl:508 +msgid "lines annotated" +msgstr "γραμμές σχολιασμένες" + +#: lib/blame.tcl:689 +msgid "Loading original location annotations..." +msgstr "Γίνεται φόρτωση σχολίων αρχικής τοποθεσίας..." + +#: lib/blame.tcl:692 +msgid "Annotation complete." +msgstr "Έγινε ολοκλήρωση του σχολιασμού." + +#: lib/blame.tcl:746 +msgid "Loading annotation..." +msgstr "Φόρτωση σχολίου..." + +#: lib/blame.tcl:802 +msgid "Author:" +msgstr "Δημιουργός:" + +#: lib/blame.tcl:806 +msgid "Committer:" +msgstr "Υποβολέας:" + +#: lib/blame.tcl:811 +msgid "Original File:" +msgstr "Αρχικό Αρχείο:" + +#: lib/blame.tcl:925 +msgid "Originally By:" +msgstr "Αρχικά Από:" + +#: lib/blame.tcl:931 +msgid "In File:" +msgstr "Στο Αρχείο:" + +#: lib/blame.tcl:936 +msgid "Copied Or Moved Here By:" +msgstr "Αντιγράφηκε ή Μετακινήθηκε Εδώ Από:" + +#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 +msgid "Checkout Branch" +msgstr "Εξαγωγή Κλάδου" + +#: lib/branch_checkout.tcl:23 +msgid "Checkout" +msgstr "Εξαγωγή" + +#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 +#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282 +#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:171 +#: lib/option.tcl:103 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97 +msgid "Cancel" +msgstr "Ακύρωση" + +#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 +msgid "Revision" +msgstr "Αναθεώρηση" + +#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:242 +msgid "Options" +msgstr "Επιλογές" + +#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92 +msgid "Fetch Tracking Branch" +msgstr "Ανάκτηση Κλάδου Παρακολούθησης" + +#: lib/branch_checkout.tcl:44 +msgid "Detach From Local Branch" +msgstr "Αποκόλληση Από Τοπικό Κλάδο" + +#: lib/branch_create.tcl:22 +msgid "Create Branch" +msgstr "Δημιουργία Κλάδου" + +#: lib/branch_create.tcl:27 +msgid "Create New Branch" +msgstr "Δημιουργία Νέου Κλάδου" + +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:371 +msgid "Create" +msgstr "Δημιουργία" + +#: lib/branch_create.tcl:40 +msgid "Branch Name" +msgstr "Όνομα Κλάδου" + +#: lib/branch_create.tcl:43 +msgid "Name:" +msgstr "Όνομα:" + +#: lib/branch_create.tcl:58 +msgid "Match Tracking Branch Name" +msgstr "Συμφωνία Ονόματος Κλάδου Παρακολούθησης" + +#: lib/branch_create.tcl:66 +msgid "Starting Revision" +msgstr "Αρχική Αναθεώρηση" + +#: lib/branch_create.tcl:72 +msgid "Update Existing Branch:" +msgstr "Ενημέρωση Υπάρχοντα Κλάδου:" + +#: lib/branch_create.tcl:75 +#, fuzzy +msgid "No" +msgstr "Όχι" + +#: lib/branch_create.tcl:80 +msgid "Fast Forward Only" +msgstr "Συγχώνευση Επιτάχυνσης Μόνο" + +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514 +msgid "Reset" +msgstr "Επαναφορά" + +#: lib/branch_create.tcl:97 +msgid "Checkout After Creation" +msgstr "Εξαγωγή Μετά τη Δημιουργία" + +#: lib/branch_create.tcl:131 +msgid "Please select a tracking branch." +msgstr "Παρακαλώ επιλέξτε έναν κλάδο παρακολούθησης." + +#: lib/branch_create.tcl:140 +#, tcl-format +msgid "Tracking branch %s is not a branch in the remote repository." +msgstr "" +"Ο κλάδος παρακολούθησης %s δεν είναι κλάδος που βρίσκεται στο απομακρυσμένο " +"αποθετήριο." + +#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86 +msgid "Please supply a branch name." +msgstr "Παρακαλώ δώστε ένα όνομα κλάδου." + +#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106 +#, tcl-format +msgid "'%s' is not an acceptable branch name." +msgstr "'%s' δεν είναι αποδεκτό όνομα κλάδου." + +#: lib/branch_delete.tcl:15 +msgid "Delete Branch" +msgstr "Διαγραφή Κλάδου" + +#: lib/branch_delete.tcl:20 +msgid "Delete Local Branch" +msgstr "Διαγραφή Τοπικού Κλάδου" + +#: lib/branch_delete.tcl:37 +msgid "Local Branches" +msgstr "Τοπικοί Κλάδοι" + +#: lib/branch_delete.tcl:52 +msgid "Delete Only If Merged Into" +msgstr "Διαγραφή Μόνο Εάν Είναι Συγχωνευμένο Με" + +#: lib/branch_delete.tcl:54 +msgid "Always (Do not perform merge test.)" +msgstr "Πάντα (Μη διενεργηθεί δοκιμή συγχώνευσης.)" + +#: lib/branch_delete.tcl:103 +#, tcl-format +msgid "The following branches are not completely merged into %s:" +msgstr "Οι εξής κλάδοι δεν είναι πλήρως συγχωνευμένοι με το %s:" + +#: lib/branch_delete.tcl:115 +msgid "" +"Recovering deleted branches is difficult. \n" +"\n" +" Delete the selected branches?" +msgstr "" +"Η ανάκτηση διεγραμμένων κλάδων είναι δύσκολη.\n" +"\n" +"Διαγραφή των επιλεγμένων κλάδων;" + +#: lib/branch_delete.tcl:141 +#, tcl-format +msgid "" +"Failed to delete branches:\n" +"%s" +msgstr "" +"Αποτυχία διαγραφής κλάδων:\n" +"%s" + +#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22 +msgid "Rename Branch" +msgstr "Μετονομασία Κλάδου" + +#: lib/branch_rename.tcl:26 +msgid "Rename" +msgstr "Μετονομασία" + +#: lib/branch_rename.tcl:36 +msgid "Branch:" +msgstr "Κλάδος:" + +#: lib/branch_rename.tcl:39 +msgid "New Name:" +msgstr "Νέο Όνομα:" + +#: lib/branch_rename.tcl:75 +msgid "Please select a branch to rename." +msgstr "Παρακαλώ επιλέξτε κλάδο προς μετονομασία:" + +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179 +#, tcl-format +msgid "Branch '%s' already exists." +msgstr "Ο Κλάδος '%s' υπάρχει ήδη." + +#: lib/branch_rename.tcl:117 +#, tcl-format +msgid "Failed to rename '%s'." +msgstr "Αποτυχία μετονομασίας '%s'." + +#: lib/browser.tcl:17 +msgid "Starting..." +msgstr "Γίνεται Εκκίνηση..." + +#: lib/browser.tcl:26 +msgid "File Browser" +msgstr "Περιηγητής Αρχείων" + +#: lib/browser.tcl:126 lib/browser.tcl:143 +#, tcl-format +msgid "Loading %s..." +msgstr "Γίνεται φόρτωση %s..." + +#: lib/browser.tcl:187 +#, fuzzy +msgid "[Up To Parent]" +msgstr "[Πάνω Προς Γονέα]" + +#: lib/browser.tcl:267 lib/browser.tcl:273 +msgid "Browse Branch Files" +msgstr "Περιήγηση Αρχείων Κλάδου" + +#: lib/browser.tcl:278 lib/choose_repository.tcl:387 +#: lib/choose_repository.tcl:474 lib/choose_repository.tcl:484 +#: lib/choose_repository.tcl:987 +msgid "Browse" +msgstr "Περιήγηση" + +#: lib/checkout_op.tcl:79 +#, tcl-format +msgid "Fetching %s from %s" +msgstr "Ανάκτηση %s από το %s" + +#: lib/checkout_op.tcl:127 +#, tcl-format +msgid "fatal: Cannot resolve %s" +msgstr "κρίσιμο: Δε μπόρεσε να επιλυθεί το %s" + +#: lib/checkout_op.tcl:140 lib/console.tcl:81 lib/database.tcl:31 +msgid "Close" +msgstr "Κλείσιμο" + +#: lib/checkout_op.tcl:169 +#, tcl-format +msgid "Branch '%s' does not exist." +msgstr "Ο Κλάδος '%s' δεν υπάρχει." + +#: lib/checkout_op.tcl:206 +#, tcl-format +msgid "" +"Branch '%s' already exists.\n" +"\n" +"It cannot fast-forward to %s.\n" +"A merge is required." +msgstr "" +"Ο Κλάδος '%s' υπάρχει ήδη.\n" +"\n" +"Δε γίνεται συγχώνευση επιτάχυνσής του στο %s.\n" +"Απαιτείται συγχώνευση." + +#: lib/checkout_op.tcl:220 +#, tcl-format +msgid "Merge strategy '%s' not supported." +msgstr "Η στρατηγική Συγχώνευσης %s δεν υποστηρίζεται." + +#: lib/checkout_op.tcl:239 +#, tcl-format +msgid "Failed to update '%s'." +msgstr "Αποτυχία ενημέρωσης '%s'." + +#: lib/checkout_op.tcl:251 +msgid "Staging area (index) is already locked." +msgstr "Η περιοχή σταδιοποίησης (το ευρετήριο) είναι ήδη κλειδωμένη." + +#: lib/checkout_op.tcl:266 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before the current branch can be changed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"Η τελευταία κατάσταση που ανιχνεύθηκε δε συμφωνεί με την κατάσταση του " +"αποθετηρίου.\n" +"\n" +"Κάποιο άλλο πρόγραμμα Git τροποποίησε το αποθετήριο από την τελευταία " +"ανίχνευση. Πρέπει να γίνει επανανίχνευση πριν να αλλαχθεί ο τρέχων κλάδος.\n" +"\n" +"Η επανανίχνευση θα ξεκινήσει αυτόματα τώρα.\n" + +#: lib/checkout_op.tcl:322 +#, tcl-format +msgid "Updating working directory to '%s'..." +msgstr "Ενημέρωση φακέλου εργασίας σε '%s'..." + +#: lib/checkout_op.tcl:323 +msgid "files checked out" +msgstr "αρχεία έχουν εξαχθεί" + +#: lib/checkout_op.tcl:353 +#, tcl-format +msgid "Aborted checkout of '%s' (file level merging is required)." +msgstr "" +"Έγινε ακύρωση εξαγωγής του '%s' (απαιτείται συγχώνευση επιπέδου αρχείου)." + +#: lib/checkout_op.tcl:354 +msgid "File level merge required." +msgstr "Απαιτείται συγχώνευση επιπέδου αρχείου." + +#: lib/checkout_op.tcl:358 +#, tcl-format +msgid "Staying on branch '%s'." +msgstr "Παραμονή στον κλάδο '%s'." + +#: lib/checkout_op.tcl:429 +msgid "" +"You are no longer on a local branch.\n" +"\n" +"If you wanted to be on a branch, create one now starting from 'This Detached " +"Checkout'." +msgstr "" +"Δε βρίσκεστε πια σε τοπικό κλάδο.\n" +"\n" +"Αν θέλατε να βρίσκεστε σε κλάδο, δημιουργήστε έναν τώρα αρχίζοντας από 'This " +"Detached Checkout'." + +#: lib/checkout_op.tcl:446 lib/checkout_op.tcl:450 +#, tcl-format +msgid "Checked out '%s'." +msgstr "Έγινε εξαγωγή του '%s'." + +#: lib/checkout_op.tcl:478 +#, tcl-format +msgid "Resetting '%s' to '%s' will lose the following commits:" +msgstr "Η επαναφορά '%s' στο '%s' θα χάσει τις εξής υποβολές:" + +#: lib/checkout_op.tcl:500 +msgid "Recovering lost commits may not be easy." +msgstr "Η ανάκτηση χαμένων υποβολών μπορεί να είναι δύσκολη." + +#: lib/checkout_op.tcl:505 +#, tcl-format +msgid "Reset '%s'?" +msgstr "Επαναφορά '%s';" + +#: lib/checkout_op.tcl:510 lib/merge.tcl:163 +msgid "Visualize" +msgstr "Απεικόνιση" + +#: lib/checkout_op.tcl:578 +#, tcl-format +msgid "" +"Failed to set current branch.\n" +"\n" +"This working directory is only partially switched. We successfully updated " +"your files, but failed to update an internal Git file.\n" +"\n" +"This should not have occurred. %s will now close and give up." +msgstr "" +"Αποτυχία ορισμού τρέχοντος κλάδου.\n" +"\n" +"Αυτός ο φάκελος εργασίας είναι μόνο εν μέρει επιλεγμένος. 'Εγινε επιτυχής " +"ενημέρωση των αρχείων σας, αλλά απέτυχε η ενημέρωση ενός εσωτερικού αρχείου " +"του Git.\n" +"\n" +"Αυτό δε θα έπρεπε να συμβεί. Το %s θα κλείσει και θα εγκαταλείψει τώρα." + +#: lib/choose_font.tcl:39 +msgid "Select" +msgstr "Επιλογή" + +#: lib/choose_font.tcl:53 +msgid "Font Family" +msgstr "Οικογένεια Γραμματοσειράς" + +#: lib/choose_font.tcl:74 +msgid "Font Size" +msgstr "Μέγεθος Γραμματοσειράς" + +#: lib/choose_font.tcl:91 +msgid "Font Example" +msgstr "Παράδειγμα Γραμματοσειράς" + +#: lib/choose_font.tcl:103 +msgid "" +"This is example text.\n" +"If you like this text, it can be your font." +msgstr "" +"Αυτό είναι ένα παράδειγμα κειμένου.\n" +"Αν σας αρέσει αυτό το κείμενο, μπορεί να γίνει δικό σας." + +#: lib/choose_repository.tcl:28 +msgid "Git Gui" +msgstr "Γραφικό Περιβάλλον Git" + +#: lib/choose_repository.tcl:81 lib/choose_repository.tcl:376 +msgid "Create New Repository" +msgstr "Δημιουργία Νέου Αποθετηρίου" + +#: lib/choose_repository.tcl:87 +msgid "New..." +msgstr "Νέο..." + +#: lib/choose_repository.tcl:94 lib/choose_repository.tcl:460 +msgid "Clone Existing Repository" +msgstr "Κλωνοποίηση Υπάρχοντος Αποθετηρίου" + +#: lib/choose_repository.tcl:100 +msgid "Clone..." +msgstr "Κλωνοποίηση..." + +#: lib/choose_repository.tcl:107 lib/choose_repository.tcl:976 +msgid "Open Existing Repository" +msgstr "Άνοιγμα Υπάρχοντος Αποθετηρίου" + +#: lib/choose_repository.tcl:113 +msgid "Open..." +msgstr "Άνοιγμα..." + +#: lib/choose_repository.tcl:126 +msgid "Recent Repositories" +msgstr "Πρόσφατα Αποθετήρια" + +#: lib/choose_repository.tcl:132 +msgid "Open Recent Repository:" +msgstr "Άνοιγμα Πρόσφατου Αποθετηρίου:" + +#: lib/choose_repository.tcl:296 lib/choose_repository.tcl:303 +#: lib/choose_repository.tcl:310 +#, tcl-format +msgid "Failed to create repository %s:" +msgstr "Αποτυχία δημιουργίας αποθετηρίου %s:" + +#: lib/choose_repository.tcl:381 lib/choose_repository.tcl:478 +msgid "Directory:" +msgstr "Φάκελος:" + +#: lib/choose_repository.tcl:412 lib/choose_repository.tcl:537 +#: lib/choose_repository.tcl:1011 +msgid "Git Repository" +msgstr "Αποθετήριο Git" + +#: lib/choose_repository.tcl:437 +#, tcl-format +msgid "Directory %s already exists." +msgstr "Ο Φάκελος '%s' υπάρχει ήδη." + +#: lib/choose_repository.tcl:441 +#, tcl-format +msgid "File %s already exists." +msgstr "Το αρχείο %s υπάρχει ήδη." + +#: lib/choose_repository.tcl:455 +msgid "Clone" +msgstr "Κλώνος" + +#: lib/choose_repository.tcl:468 +#, fuzzy +msgid "URL:" +msgstr "" + +#: lib/choose_repository.tcl:489 +msgid "Clone Type:" +msgstr "Τύπος Κλώνου:" + +#: lib/choose_repository.tcl:495 +msgid "Standard (Fast, Semi-Redundant, Hardlinks)" +msgstr "Τυπικό (Ταχύ, Ημι-Πλεονάζον, Hardlinks)" + +#: lib/choose_repository.tcl:501 +msgid "Full Copy (Slower, Redundant Backup)" +msgstr "Πλήρες Αντίγραφο (Πιο αργό, Πλεονάζον Αντίγραφο Ασφαλείας)" + +#: lib/choose_repository.tcl:507 +msgid "Shared (Fastest, Not Recommended, No Backup)" +msgstr "Κοινή Χρήση (Ταχύτατο, Δε Συνιστάται, Κανένα Αντίγραφο Ασφαλείας)" + +#: lib/choose_repository.tcl:543 lib/choose_repository.tcl:590 +#: lib/choose_repository.tcl:736 lib/choose_repository.tcl:806 +#: lib/choose_repository.tcl:1017 lib/choose_repository.tcl:1025 +#, tcl-format +msgid "Not a Git repository: %s" +msgstr "Δεν είναι αποθετήριο Git: %s" + +#: lib/choose_repository.tcl:579 +msgid "Standard only available for local repository." +msgstr "\"Τυπικό\" διαθέσιμο μόνο για τοπικό αποθετήριο." + +#: lib/choose_repository.tcl:583 +msgid "Shared only available for local repository." +msgstr "\"Κοινή Χρήση\" διαθέσιμη μόνο για τοπικό αποθετήριο." + +#: lib/choose_repository.tcl:604 +#, tcl-format +msgid "Location %s already exists." +msgstr "Η Τοποθεσία %s υπάρχει ήδη." + +#: lib/choose_repository.tcl:615 +msgid "Failed to configure origin" +msgstr "Αποτυχία ρύθμισης πηγής" + +#: lib/choose_repository.tcl:627 +msgid "Counting objects" +msgstr "Γίνεται καταμέτρηση αντικειμένων" + +#: lib/choose_repository.tcl:628 +#, fuzzy +msgid "buckets" +msgstr "" + +#: lib/choose_repository.tcl:652 +#, tcl-format +msgid "Unable to copy objects/info/alternates: %s" +msgstr "Αδυναμία αντιγραφής αντικειμένων/πληροφοριών/ενναλακτικών: %s" + +#: lib/choose_repository.tcl:688 +#, tcl-format +msgid "Nothing to clone from %s." +msgstr "Τίποτα προς κλωνοποίηση από το %s." + +#: lib/choose_repository.tcl:690 lib/choose_repository.tcl:904 +#: lib/choose_repository.tcl:916 +msgid "The 'master' branch has not been initialized." +msgstr "Ο κλάδος 'master' δεν έχει αρχικοποιηθεί." + +#: lib/choose_repository.tcl:703 +msgid "Hardlinks are unavailable. Falling back to copying." +msgstr "Hardlinks μη διαθέσιμα. Μετάπτωση σε αντιγραφή." + +#: lib/choose_repository.tcl:715 +#, tcl-format +msgid "Cloning from %s" +msgstr "Γίνεται κλωνοποίηση από το %s" + +#: lib/choose_repository.tcl:746 +msgid "Copying objects" +msgstr "Γίνεται αντιγραφή αντικειμένων" + +#: lib/choose_repository.tcl:747 +#, fuzzy +msgid "KiB" +msgstr "" + +#: lib/choose_repository.tcl:771 +#, tcl-format +msgid "Unable to copy object: %s" +msgstr "Αδυναμία αντιγραφής αντικειμένου: %s" + +#: lib/choose_repository.tcl:781 +msgid "Linking objects" +msgstr "Γίνεται σύνδεση αντικειμένων" + +#: lib/choose_repository.tcl:782 +msgid "objects" +msgstr "αντικείμενα" + +#: lib/choose_repository.tcl:790 +#, tcl-format +msgid "Unable to hardlink object: %s" +msgstr "Αδυναμία hardlink αντικειμένου: %s" + +#: lib/choose_repository.tcl:845 +msgid "Cannot fetch branches and objects. See console output for details." +msgstr "" +"Δε μπόρεσε να γίνει ανάκτηση κλάδων και αντικειμένων. Δείτε την έξοδο " +"κονσόλας για λεπτομέρειες." + +#: lib/choose_repository.tcl:856 +msgid "Cannot fetch tags. See console output for details." +msgstr "" +"Δε μπόρεσε να γίνει ανάκτηση ετικετών. Δείτε την έξοδο κονσόλας για " +"λεπτομέρειες." + +#: lib/choose_repository.tcl:880 +msgid "Cannot determine HEAD. See console output for details." +msgstr "" +"Δε μπόρεσε να γίνει καθορισμός του HEAD (παρακλαδιού). Δείτε την έξοδο " +"κονσόλας για " +"λεπτομέρειες." + +#: lib/choose_repository.tcl:889 +#, tcl-format +msgid "Unable to cleanup %s" +msgstr "Αδυναμία εκκαθάρισης %s" + +#: lib/choose_repository.tcl:895 +msgid "Clone failed." +msgstr "Αποτυχία κλωνοποίησης." + +#: lib/choose_repository.tcl:902 +msgid "No default branch obtained." +msgstr "Δεν έγινε ανάκτηση προεπιλεγμένου κλάδου." + +#: lib/choose_repository.tcl:913 +#, tcl-format +msgid "Cannot resolve %s as a commit." +msgstr "Δε μπόρεσε να επιλυθεί το %s ως υποβολή." + +#: lib/choose_repository.tcl:925 +msgid "Creating working directory" +msgstr "Δημιουργία φακέλου εργασίας" + +#: lib/choose_repository.tcl:926 lib/index.tcl:65 lib/index.tcl:127 +#: lib/index.tcl:193 +msgid "files" +msgstr "αρχεία" + +#: lib/choose_repository.tcl:955 +msgid "Initial file checkout failed." +msgstr "Η αρχική εξαγωγή αρχείου απέτυχε." + +#: lib/choose_repository.tcl:971 +msgid "Open" +msgstr "Άνοιγμα" + +#: lib/choose_repository.tcl:981 +msgid "Repository:" +msgstr "Αποθετήριο:" + +#: lib/choose_repository.tcl:1031 +#, tcl-format +msgid "Failed to open repository %s:" +msgstr "Αποτυχία ανοίγματος αποθετηρίου %s:" + +#: lib/choose_rev.tcl:53 +#, fuzzy +msgid "This Detached Checkout" +msgstr "Αποσυνδεδεμένη Εξαγωγή" + +#: lib/choose_rev.tcl:60 +msgid "Revision Expression:" +msgstr "Έκφραση Αναθεώρησης:" + +#: lib/choose_rev.tcl:74 +msgid "Local Branch" +msgstr "Τοπικός Κλάδος" + +#: lib/choose_rev.tcl:79 +msgid "Tracking Branch" +msgstr "Κλάδος Παρακολούθησης" + +#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538 +msgid "Tag" +msgstr "Ετικέτα" + +#: lib/choose_rev.tcl:317 +#, tcl-format +msgid "Invalid revision: %s" +msgstr "Μη έγκυρη αναθεώρηση: %s" + +#: lib/choose_rev.tcl:338 +msgid "No revision selected." +msgstr "Δεν έχει επιλεγεί αναθεώρηση." + +#: lib/choose_rev.tcl:346 +msgid "Revision expression is empty." +msgstr "Η έκφραση αναθεώρησης είναι κενή." + +#: lib/choose_rev.tcl:531 +msgid "Updated" +msgstr "Ενημερωμένο" + +#: lib/choose_rev.tcl:559 +#, fuzzy +msgid "URL" +msgstr "" + +#: lib/commit.tcl:9 +msgid "" +"There is nothing to amend.\n" +"\n" +"You are about to create the initial commit. There is no commit before this " +"to amend.\n" +msgstr "" +"Δεν υπάρχει κάτι προς διόρθωση.\n" +"\n" +"Πρόκειται να δημιουργήσετε την αρχική υποβολή. Δεν υπάρχει υποβολή πριν από " +"αυτή για να διορθώσετε.\n" + +#: lib/commit.tcl:18 +msgid "" +"Cannot amend while merging.\n" +"\n" +"You are currently in the middle of a merge that has not been fully " +"completed. You cannot amend the prior commit unless you first abort the " +"current merge activity.\n" +msgstr "" +"Δε γίνεται διόρθωση καθώς συγχωνεύετε.\n" +"\n" +"Βρίσκεστε στο μέσο μιας συγχώνευσης που δεν έχει ολοκληρωθεί. Δε μπορείτε να " +"διορθώσετε την προηγούμενη υποβολή εκτός εάν ακυρώσετε την τρέχουσα ενέργεια " +"συγχώνευσης.\n" + +#: lib/commit.tcl:49 +msgid "Error loading commit data for amend:" +msgstr "Σφάλμα φόρτωσης δεδομένων υποβολής προς διόρθωση:" + +#: lib/commit.tcl:76 +msgid "Unable to obtain your identity:" +msgstr "Αδυναμία ανάκτησης της ταυτότητάς σας:" + +#: lib/commit.tcl:81 +msgid "Invalid GIT_COMMITTER_IDENT:" +msgstr "Μη έγκυρο GIT_COMMITTER_IDENT:" + +#: lib/commit.tcl:133 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before another commit can be created.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"Η τελευταία κατάσταση που ανιχνεύθηκε δε συμφωνεί με την κατάσταση του " +"αποθετηρίου.\n" +"\n" +"Κάποιο άλλο πρόγραμμα Git τροποποίησε το αποθετήριο από την τελευταία " +"ανίχνευση. Πρέπει να γίνει επανανίχνευση πριν τη δημιουργία νέας υποβολής.\n" +"\n" +"Η επανανίχνευση θα ξεκινήσει αυτόματα τώρα.\n" + +#: lib/commit.tcl:154 +#, tcl-format +msgid "" +"Unmerged files cannot be committed.\n" +"\n" +"File %s has merge conflicts. You must resolve them and stage the file " +"before committing.\n" +msgstr "" +"Τα μη συγχωνευμένα αρχεία δε μπορούν να υποβληθούν.\n" +"\n" +"Το αρχείο %s έχει συγκρούσεις συγχώνευσης. Πρέπει να τις επιλύσετε και να " +"σταδιοποιήσετε το αρχείο πριν την υποβολή.\n" + +#: lib/commit.tcl:162 +#, tcl-format +msgid "" +"Unknown file state %s detected.\n" +"\n" +"File %s cannot be committed by this program.\n" +msgstr "" +"Άγνωστη κατάσταση αρχείου %s ανιχνεύθηκε.\n" +"\n" +"Το αρχείο %s δε μπορεί να υποβληθεί από αυτό το πρόγραμμα.\n" + +#: lib/commit.tcl:170 +msgid "" +"No changes to commit.\n" +"\n" +"You must stage at least 1 file before you can commit.\n" +msgstr "" +"Δεν υπάρχουν αλλαγές προς υποβολή.\n" +"\n " +"Πρέπει να σταδιοποιήσετε τουλάχιστον 1 αρχείο πριν να κάνετε υποβολή.\n" + +#: lib/commit.tcl:183 +msgid "" +"Please supply a commit message.\n" +"\n" +"A good commit message has the following format:\n" +"\n" +"- First line: Describe in one sentence what you did.\n" +"- Second line: Blank\n" +"- Remaining lines: Describe why this change is good.\n" +msgstr "" +"Παρακαλώ δώστε ένα μήνυμα υποβολής.\n" +"\n" +"Ένα σωστό μήνυμα υποβολής έχει την εξής μορφή:\n" +"\n" +"- Πρώτη γραμμή: Περιγραφή σε μία πρόταση του τι κάνατε.\n" +"- Δεύτερη γραμμή: Κενή\n" +"- Υπόλοιπες γραμμές: Περιγραφή του γιατί αυτή η αλλαγή είναι σωστή.\n" + +#: lib/commit.tcl:207 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "προειδοποίηση: H Tcl δεν υποστηρίζει την κωδικοποίηση '%s'." + +#: lib/commit.tcl:221 +msgid "Calling pre-commit hook..." +msgstr "Γίνεται κλήση του pre-commit hook..." + +#: lib/commit.tcl:236 +msgid "Commit declined by pre-commit hook." +msgstr "Η υποβολή απορρίφθηκε από το pre-commit hook." + +#: lib/commit.tcl:259 +msgid "Calling commit-msg hook..." +msgstr "Γίνεται κλήση του commit-msg hook..." + +#: lib/commit.tcl:274 +msgid "Commit declined by commit-msg hook." +msgstr "Η υποβολή απορρίφθηκε από το commit-msg hook." + +#: lib/commit.tcl:287 +msgid "Committing changes..." +msgstr "Γίνεται υποβολή των αλλαγών..." + +#: lib/commit.tcl:303 +msgid "write-tree failed:" +msgstr "το write-tree απέτυχε:" + +#: lib/commit.tcl:304 lib/commit.tcl:348 lib/commit.tcl:368 +msgid "Commit failed." +msgstr "Η υποβολή απέτυχε." + +#: lib/commit.tcl:321 +#, tcl-format +msgid "Commit %s appears to be corrupt" +msgstr "Η υποβολή %s δείχνει κατεστραμμένη" + +#: lib/commit.tcl:326 +msgid "" +"No changes to commit.\n" +"\n" +"No files were modified by this commit and it was not a merge commit.\n" +"\n" +"A rescan will be automatically started now.\n" +msgstr "" +"Δεν υπάρχουν αλλαγές προς υποβολή.\n" +"\n" +"Δεν τροποποιήθηκαν αρχεία από αυτή την υποβολή και δεν ήταν υποβολή " +"συγχώνευσης.\n" +"\n" +"Θα ξεκινήσει αυτόματα επανανίχνευση τώρα.\n" + +#: lib/commit.tcl:333 +msgid "No changes to commit." +msgstr "Δεν υπάρχουν αλλαγές προς υποβολή." + +#: lib/commit.tcl:347 +msgid "commit-tree failed:" +msgstr "το commit-tree απέτυχε:" + +#: lib/commit.tcl:367 +msgid "update-ref failed:" +msgstr "το update-ref απέτυχε:" + +#: lib/commit.tcl:454 +#, tcl-format +msgid "Created commit %s: %s" +msgstr "Δημιουργήθηκε υποβολή %s: %s" + +#: lib/console.tcl:59 +msgid "Working... please wait..." +msgstr "Γίνεται εργασία... Παρακαλώ περιμένετε..." + +#: lib/console.tcl:186 +msgid "Success" +msgstr "Επιτυχία" + +#: lib/console.tcl:200 +msgid "Error: Command Failed" +msgstr "Σφάλμα: Η Εντολή Απέτυχε" + +#: lib/database.tcl:43 +msgid "Number of loose objects" +msgstr "Αριθμός ελεύθερων αντικειμένων" + +#: lib/database.tcl:44 +msgid "Disk space used by loose objects" +msgstr "Χώρος κατειλλημένος από ελεύθερα αντικείμενα" + +#: lib/database.tcl:45 +msgid "Number of packed objects" +msgstr "Αριθμός πακεταρισμένων αντικειμένων" + +#: lib/database.tcl:46 +msgid "Number of packs" +msgstr "Αριθμός πακέτων" + +#: lib/database.tcl:47 +msgid "Disk space used by packed objects" +msgstr "Χώρος κατειλλημένος από πακεταρισμένα αντικείμενα" + +#: lib/database.tcl:48 +msgid "Packed objects waiting for pruning" +msgstr "Πακεταρισμένα αντικείμενα έτοιμα για κλάδεμα" + +#: lib/database.tcl:49 +msgid "Garbage files" +msgstr "Άχρηστα αρχεία" + +#: lib/database.tcl:72 +msgid "Compressing the object database" +msgstr "Γίνεται συμπίεση της βάσης δεδομένων αντικειμένων" + +#: lib/database.tcl:83 +msgid "Verifying the object database with fsck-objects" +msgstr "" +"Γίνεται επαλήθευση της βάσης δεδομένων αντικειμένων με αντικείμενα fsck" + +#: lib/database.tcl:108 +#, tcl-format +msgid "" +"This repository currently has approximately %i loose objects.\n" +"\n" +"To maintain optimal performance it is strongly recommended that you compress " +"the database when more than %i loose objects exist.\n" +"\n" +"Compress the database now?" +msgstr "" +"Αυτό το αποθετήριο έχει αυτή τη στιγμή περίπου %i ελεύθερα αντικείμενα.\n" +"\n" +"Για τη διατήρηση βέλτιστων επιδόσεων συνιστάται να συμπιέσετε τη βάση " +"δεδομένων όταν υπάρχουν περισσότερα από %i ελεύθερα αντικείμενα.\n" +"\n" +"Συμπίεση της βάσης δεδομένων τώρα;" + +#: lib/date.tcl:25 +#, tcl-format +msgid "Invalid date from Git: %s" +msgstr "Μη έγκυρη ημερομηνία από το Git: %s" + +#: lib/diff.tcl:42 +#, tcl-format +msgid "" +"No differences detected.\n" +"\n" +"%s has no changes.\n" +"\n" +"The modification date of this file was updated by another application, but " +"the content within the file was not changed.\n" +"\n" +"A rescan will be automatically started to find other files which may have " +"the same state." +msgstr "" +"Δεν ανιχνεύθηκαν διαφορές.\n" +"\n" +"Το %s δεν έχει αλλαγές." +"\n" +"Η ημερομηνία τροποποίησης αυτού του αρχείου ενημερώθηκε από άλλη εφαρμογή, " +"αλλά το περιεχόμενο του αρχείου δεν άλλαξε.\n" +"\n" +"Θα ξεκινήσει αυτόματα επανανίχνευση για να βρεθούν άλλα αρχεία που μπορεί να " +"βρίσκονται σε ίδια κατάσταση." + +#: lib/diff.tcl:81 +#, tcl-format +msgid "Loading diff of %s..." +msgstr "Γίνεται φόρτωση διαφοράς του %s..." + +#: lib/diff.tcl:114 lib/diff.tcl:184 +#, tcl-format +msgid "Unable to display %s" +msgstr "Δεν είναι δυνατή η προβολή του %s" + +#: lib/diff.tcl:115 +msgid "Error loading file:" +msgstr "Σφάλμα φόρτωσης αρχείου:" + +#: lib/diff.tcl:122 +msgid "Git Repository (subproject)" +msgstr "Αποθετήριο Git (θυγατρικό έργο)" + +#: lib/diff.tcl:134 +msgid "* Binary file (not showing content)." +msgstr "* Δυαδικό αρχείο (μη εμφάνιση περιεχομένου)." + +#: lib/diff.tcl:185 +msgid "Error loading diff:" +msgstr "Σφάλμα φόρτωσης διαφοράς:" + +#: lib/diff.tcl:303 +msgid "Failed to unstage selected hunk." +msgstr "Αποτυχία αποσταδιοποίησης επιλεγμένου κομματιού." + +#: lib/diff.tcl:310 +msgid "Failed to stage selected hunk." +msgstr "Αποτυχία σταδιοποίησης επιλεγμένου κομματιού." + +#: lib/error.tcl:20 lib/error.tcl:114 +msgid "error" +msgstr "σφάλμα" + +#: lib/error.tcl:36 +msgid "warning" +msgstr "προειδοποίηση" + +#: lib/error.tcl:94 +msgid "You must correct the above errors before committing." +msgstr "Πρέπει να διορθώσετε τα παραπάνω λάθη πριν την υποβολή." + +#: lib/index.tcl:6 +msgid "Unable to unlock the index." +msgstr "Αδυναμία ξεκλειδώματος του ευρετηρίου." + +#: lib/index.tcl:15 +msgid "Index Error" +msgstr "Σφάλμα Ευρετηρίου" + +#: lib/index.tcl:21 +msgid "" +"Updating the Git index failed. A rescan will be automatically started to " +"resynchronize git-gui." +msgstr "" +"Η ενημέρωση του ευρετηρίου Git απέτυχε. Θα ξεκινήσει αυτόματα επανανίχνευση " +"για επανασυγχρονισμό του git-gui." + +#: lib/index.tcl:27 +msgid "Continue" +msgstr "Συνέχεια" + +#: lib/index.tcl:31 +msgid "Unlock Index" +msgstr "Ξεκλείδωμα Ευρετηρίου" + +#: lib/index.tcl:282 +#, tcl-format +msgid "Unstaging %s from commit" +msgstr "Αποσταδιοποίηση %s από υποβολή" + +#: lib/index.tcl:313 +msgid "Ready to commit." +msgstr "Έτοιμο προς υποβολή." + +#: lib/index.tcl:326 +#, tcl-format +msgid "Adding %s" +msgstr "Προσθήκη %s" + +#: lib/index.tcl:381 +#, tcl-format +msgid "Revert changes in file %s?" +msgstr "Αναίρεση αλλαγών στο αρχείο %s;" + +#: lib/index.tcl:383 +#, tcl-format +msgid "Revert changes in these %i files?" +msgstr "Αναίρεση αλλαγών σε αυτά τα %i αρχεία;" + +#: lib/index.tcl:391 +msgid "Any unstaged changes will be permanently lost by the revert." +msgstr "" +"Όλες οι μη σταδιοποιημένες αλλαγές θα χαθούν οριστικά από την αναίρεση." + +#: lib/index.tcl:394 +msgid "Do Nothing" +msgstr "Καμία Ενέργεια" + +#: lib/merge.tcl:13 +msgid "" +"Cannot merge while amending.\n" +"\n" +"You must finish amending this commit before starting any type of merge.\n" +msgstr "" +"Δε γίνεται συγχώνευση καθώς διορθώνετε.\n" +"\n" +"Πρέπει να τελειώσετε τη διόρθωση αυτής της υποβολής πριν να ξεκινήσετε " +"οποιασδήποτε μορφής συγχώνευση.\n" + +#: lib/merge.tcl:27 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before a merge can be performed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"Η τελευταία κατάσταση που ανιχνεύθηκε δε συμφωνεί με την κατάσταση του " +"αποθετηρίου.\n" +"\n" +"Κάποιο άλλο πρόγραμμα Git τροποποίησε το αποθετήριο από την τελευταία " +"ανίχνευση. Πρέπει να γίνει επανανίχνευση πριν τη διενέργεια συγχώνευσης.\n" +"\n" +"Η επανανίχνευση θα ξεκινήσει αυτόματα τώρα.\n" + +#: lib/merge.tcl:44 +#, tcl-format +msgid "" +"You are in the middle of a conflicted merge.\n" +"\n" +"File %s has merge conflicts.\n" +"\n" +"You must resolve them, stage the file, and commit to complete the current " +"merge. Only then can you begin another merge.\n" +msgstr "" +"Βρίσκεστε στο μέσο μιας συγκρουόμενης συγχώνευσης.\n" +"\n" +"Το αρχείο %s έχει συγκρούσεις συγχώνευσης.\n" +"\n" +"Πρέπει να τις επιλύσετε, να σταδιοποιήσετε το αρχείο, και να κάνετε υποβολή " +"για να ολοκληρώσετε την τρέχουσα συγχώνευση. Μόνο τότε μπορείτε να " +"ξεκινήσετε άλλη συγχώνευση.\n" + +#: lib/merge.tcl:54 +#, tcl-format +msgid "" +"You are in the middle of a change.\n" +"\n" +"File %s is modified.\n" +"\n" +"You should complete the current commit before starting a merge. Doing so " +"will help you abort a failed merge, should the need arise.\n" +msgstr "" +"Βρίσκεστε στο μέσο μιας αλλαγής.\n" +"\n" +"Το αρχείο %s έχει τροποποιηθεί.\n" +"\n" +"Πρέπει να ολοκληρώσετε την τρέχουσα συγχώνευση πριν να ξεκινήσετε συγχώνευση." +" Αυτό βοηθά στην ακύρωση αποτυχημένης συγχώνευσης, εάν χρειαστεί.\n" + +#: lib/merge.tcl:106 +#, tcl-format +msgid "%s of %s" +msgstr "%s από %s" + +#: lib/merge.tcl:119 +#, tcl-format +msgid "Merging %s and %s..." +msgstr "Γίνεται συγχώνευση του %s με το %s..." + +#: lib/merge.tcl:130 +msgid "Merge completed successfully." +msgstr "Η συγχώνευση ολοκληρώθηκε επιτυχώς." + +#: lib/merge.tcl:132 +msgid "Merge failed. Conflict resolution is required." +msgstr "Η συγχώνευση απέτυχε. Απαιτείται επίλυση συγκρούσεων." + +#: lib/merge.tcl:157 +#, tcl-format +msgid "Merge Into %s" +msgstr "Συγχώνευση με %s" + +#: lib/merge.tcl:176 +msgid "Revision To Merge" +msgstr "Αναθεώρηση Προς Συγχώνευση" + +#: lib/merge.tcl:211 +msgid "" +"Cannot abort while amending.\n" +"\n" +"You must finish amending this commit.\n" +msgstr "" +"Δε γίνεται ακύρωση καθώς διορθώνετε.\n" +"\n" +"Πρέπει να τελειώσετε τη διόρθωση αυτής της υποβολής.\n" + +#: lib/merge.tcl:221 +msgid "" +"Abort merge?\n" +"\n" +"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with aborting the current merge?" +msgstr "" +"Ακύρωση συγχώνευσης;\n" +"\n" +"Η ακύρωση της τρέχουσας συγχώνευσης θα προκαλέσει απώλεια *ΟΛΩΝ* των μη " +"υποβεβλημένων αλλαγών.\n" +"\n" +"Να προχωρήσει η ακύρωση της τρέχουσας συγχώνευσης;" + +#: lib/merge.tcl:227 +msgid "" +"Reset changes?\n" +"\n" +"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with resetting the current changes?" +msgstr "" +"Επαναφορά αλλαγών;\n" +"\n" +"Η επαναφορά των αλλαγών θα προκαλέσει απώλεια *ΟΛΩΝ* των μη υποβεβλημένων " +"αλλαγών.\n" +"\n" +"Να συνεχίσει η επαναφορά των τρέχουσων αλλαγών;" + +#: lib/merge.tcl:238 +msgid "Aborting" +msgstr "Γίνεται ακύρωση" + +#: lib/merge.tcl:238 +msgid "files reset" +msgstr "αρχεία που επαναφέρθηκαν" + +#: lib/merge.tcl:265 +msgid "Abort failed." +msgstr "Η ακύρωση απέτυχε." + +#: lib/merge.tcl:267 +msgid "Abort completed. Ready." +msgstr "Η ακύρωση απέτυχε. Έτοιμο." + +#: lib/option.tcl:95 +msgid "Restore Defaults" +msgstr "Επαναφορά Προεπιλογών" + +#: lib/option.tcl:99 +msgid "Save" +msgstr "Αποθήκευση" + +#: lib/option.tcl:109 +#, tcl-format +msgid "%s Repository" +msgstr "%s Αποθετήριο" + +#: lib/option.tcl:110 +msgid "Global (All Repositories)" +msgstr "Ολικό (Όλα τα Αποθετήρια)" + +#: lib/option.tcl:116 +msgid "User Name" +msgstr "Όνομα Χρήστη" + +#: lib/option.tcl:117 +msgid "Email Address" +msgstr "Διεύθυνση Email" + +#: lib/option.tcl:119 +msgid "Summarize Merge Commits" +msgstr "Περίληψη Υποβολών Συγχώνευσης" + +#: lib/option.tcl:120 +msgid "Merge Verbosity" +msgstr "Λεπτομέρεια Συγχώνευσης" + +#: lib/option.tcl:121 +msgid "Show Diffstat After Merge" +msgstr "Προβολή Στατιστικών Διαφοράς Μετά από Συγχώνευση" + +#: lib/option.tcl:123 +msgid "Trust File Modification Timestamps" +msgstr "Εμπιστοσύνη Ημερομηνιών Μετατροπής Αρχείων" + +#: lib/option.tcl:124 +msgid "Prune Tracking Branches During Fetch" +msgstr "Κλάδεμα Κλάδων Παρακολούθησης Κατά Την Ανάκτηση" + +#: lib/option.tcl:125 +msgid "Match Tracking Branches" +msgstr "Συμφωνία Κλάδων Παρακολούθησης" + +#: lib/option.tcl:126 +msgid "Number of Diff Context Lines" +msgstr "Αριθμός Γραμμών Εννοιολογικού Πλαισίου Διαφοράς" + +#: lib/option.tcl:127 +msgid "Commit Message Text Width" +msgstr "Πλάτος Κειμένου Μηνύματος Υποβολής" + +#: lib/option.tcl:128 +msgid "New Branch Name Template" +msgstr "Νέο Πρότυπο Ονόματος Κλάδου" + +#: lib/option.tcl:192 +msgid "Spelling Dictionary:" +msgstr "Λεξικό Ορθογραφίας:" + +#: lib/option.tcl:216 +msgid "Change Font" +msgstr "Αλλαγή Γραμματοσειράς" + +#: lib/option.tcl:220 +#, tcl-format +msgid "Choose %s" +msgstr "Επιλογή %s" + +#: lib/option.tcl:226 +#, fuzzy +msgid "pt." +msgstr "" + +#: lib/option.tcl:240 +msgid "Preferences" +msgstr "Προτιμήσεις" + +#: lib/option.tcl:275 +msgid "Failed to completely save options:" +msgstr "Αποτυχία πλήρους αποθήκευσης επιλογών:" + +#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 +msgid "Delete Remote Branch" +msgstr "Διαγραφή Απομακρυσμένου Κλάδου" + +#: lib/remote_branch_delete.tcl:47 +msgid "From Repository" +msgstr "Από Αποθετήριο" + +#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 +msgid "Remote:" +msgstr "Απομακρυσμένο:" + +#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138 +#, fuzzy +msgid "Arbitrary URL:" +msgstr "Αυθαίρετο URL:" + +#: lib/remote_branch_delete.tcl:84 +msgid "Branches" +msgstr "Κλάδοι" + +#: lib/remote_branch_delete.tcl:109 +msgid "Delete Only If" +msgstr "Διαγραφή Μόνο Εάν" + +#: lib/remote_branch_delete.tcl:111 +msgid "Merged Into:" +msgstr "Συγχωνευμένο Με:" + +#: lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" +msgstr "Πάντα (Μη διενεργηθούν έλεγχοι συγχώνευσης)" + +#: lib/remote_branch_delete.tcl:152 +msgid "A branch is required for 'Merged Into'." +msgstr "Απαιτείται ένας κλάδος για 'Συγχωνευμένο Με'." + +#: lib/remote_branch_delete.tcl:184 +#, tcl-format +msgid "" +"The following branches are not completely merged into %s:\n" +"\n" +" - %s" +msgstr "" +"Οι εξής κλάδοι δεν είναι πλήρως συγχωνευμένοι με το %s:\n" +"\n" +" - %s" + +#: lib/remote_branch_delete.tcl:189 +#, tcl-format +msgid "" +"One or more of the merge tests failed because you have not fetched the " +"necessary commits. Try fetching from %s first." +msgstr "" +"Μία ή περισσότερες από τις δοκιμές συγχώνευσης απέτυχαν επειδή δεν έχετε " +"φέρει τις αναγκαίες υποβολές. Δοκιμάστε ανάκτηση από το %s πρώτα." + +#: lib/remote_branch_delete.tcl:207 +msgid "Please select one or more branches to delete." +msgstr "Παρακαλώ επιλέξτε έναν ή περισσότερους κλάδους προς διαγραφή." + +#: lib/remote_branch_delete.tcl:216 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"Η ανάκτηση διεγραμμένων κλάδων είναι δύσκολη.\n" +"\n" +"Διαγραφή των επιλεγμένων κλάδων;" + +#: lib/remote_branch_delete.tcl:226 +#, tcl-format +msgid "Deleting branches from %s" +msgstr "Γίνεται διαγραφή κλάδων από %s" + +#: lib/remote_branch_delete.tcl:286 +msgid "No repository selected." +msgstr "Δεν έχει επιλεγεί αποθετήριο." + +#: lib/remote_branch_delete.tcl:291 +#, tcl-format +msgid "Scanning %s..." +msgstr "Ανίχνευση %s..." + +#: lib/remote.tcl:165 +msgid "Prune from" +msgstr "Κλάδεμα από" + +#: lib/remote.tcl:170 +msgid "Fetch from" +msgstr "Ανάκτηση από" + +#: lib/remote.tcl:213 +msgid "Push to" +msgstr "Ώθηση σε" + +#: lib/shortcut.tcl:20 lib/shortcut.tcl:61 +msgid "Cannot write shortcut:" +msgstr "Δε μπόρεσε να αποθηκευτεί η συντόμευση:" + +#: lib/shortcut.tcl:136 +msgid "Cannot write icon:" +msgstr "Δε μπόρεσε να αποθηκευτεί το εικονίδιο:" + +#: lib/spellcheck.tcl:57 +msgid "Unsupported spell checker" +msgstr "Mη υποστηριζόμενος ελεγκτής ορθογραφίας" + +#: lib/spellcheck.tcl:65 +msgid "Spell checking is unavailable" +msgstr "Έλεγχος ορθογραφίας μη διαθέσιμος" + +#: lib/spellcheck.tcl:68 +msgid "Invalid spell checking configuration" +msgstr "Μη έγκυρη ρύθμιση ελέγχου ορθογραφίας" + +#: lib/spellcheck.tcl:70 +#, tcl-format +msgid "Reverting dictionary to %s." +msgstr "Γίνεται επαναφορά του λεξικού σε %s." + +#: lib/spellcheck.tcl:73 +msgid "Spell checker silently failed on startup" +msgstr "Ο ελεγκτής ορθογραφίας απέτυχε σιωπηλά κατά την εκκίνηση" + +#: lib/spellcheck.tcl:80 +msgid "Unrecognized spell checker" +msgstr "Μη αναγνωρίσιμος ελεγκτής ορθογραφίας" + +#: lib/spellcheck.tcl:180 +msgid "No Suggestions" +msgstr "Καμία Πρόταση" + +#: lib/spellcheck.tcl:381 +msgid "Unexpected EOF from spell checker" +msgstr "Μη αναμενόμενο τέλος αρχείου από τον ελεγκτή ορθογραφίας" + +#: lib/spellcheck.tcl:385 +msgid "Spell Checker Failed" +msgstr "Αποτυχία Ελεγκτή Ορθογραφίας" + +#: lib/status_bar.tcl:83 +#, tcl-format +msgid "%s ... %*i of %*i %s (%3i%%)" +msgstr "%s ... %*i από %*i %s (%3i%%)" + +#: lib/transport.tcl:6 +#, tcl-format +msgid "fetch %s" +msgstr "ανάκτηση %s" + +#: lib/transport.tcl:7 +#, tcl-format +msgid "Fetching new changes from %s" +msgstr "Ανάκτηση νέων αλλαγών από το %s" + +#: lib/transport.tcl:18 +#, tcl-format +msgid "remote prune %s" +msgstr "απομακρυσμένο κλάδεμα %s" + +#: lib/transport.tcl:19 +#, tcl-format +msgid "Pruning tracking branches deleted from %s" +msgstr "Γίνεται κλάδεμα κλάδων παρακολούθησης που διεγράφησαν από το %s" + +#: lib/transport.tcl:25 lib/transport.tcl:71 +#, tcl-format +msgid "push %s" +msgstr "ώθηση %s" + +#: lib/transport.tcl:26 +#, tcl-format +msgid "Pushing changes to %s" +msgstr "Γίνεται ώθηση αλλαγών στο %s" + +#: lib/transport.tcl:72 +#, tcl-format +msgid "Pushing %s %s to %s" +msgstr "Γίνεται ώθηση %s %s στο %s" + +#: lib/transport.tcl:89 +msgid "Push Branches" +msgstr "Ώθηση Κλάδων" + +#: lib/transport.tcl:103 +msgid "Source Branches" +msgstr "Πηγαίοι Κλάδοι" + +#: lib/transport.tcl:120 +msgid "Destination Repository" +msgstr "Αποθετήριο Προορισμού" + +#: lib/transport.tcl:158 +msgid "Transfer Options" +msgstr "Επιλογές Μεταφοράς" + +#: lib/transport.tcl:160 +msgid "Force overwrite existing branch (may discard changes)" +msgstr "" +"Εξαναγκασμός επεγγραφής υπάρχοντος κλάδου (μπορεί να απορρίψει αλλαγές)" + +#: lib/transport.tcl:164 +msgid "Use thin pack (for slow network connections)" +msgstr "Χρήση ισχνού πακέτου (για αργές συνδέσεις δικτύου)" + +#: lib/transport.tcl:168 +msgid "Include tags" +msgstr "Συμπερίληψη ετικετών" + + diff --git a/git-gui/po/git-gui.pot b/git-gui/po/git-gui.pot index 53b7d3634d..074582d979 100644 --- a/git-gui/po/git-gui.pot +++ b/git-gui/po/git-gui.pot @@ -90,6 +90,11 @@ msgstr "" msgid "Ready." msgstr "" +#: git-gui.sh:1726 +#, tcl-format +msgid "Displaying only %s of %s files." +msgstr "" + #: git-gui.sh:1819 msgid "Unmodified" msgstr "" diff --git a/git-gui/po/glossary/el.po b/git-gui/po/glossary/el.po new file mode 100644 index 0000000000..1d3cc818d5 --- /dev/null +++ b/git-gui/po/glossary/el.po @@ -0,0 +1,171 @@ +# Translation of git-gui glossary to Greek +# Copyright (C) 2009 Jimmy Angelakos +# This file is distributed under the same license as the git-gui package. +# Jimmy Angelakos <vyruss@hellug.gr>, 2009. +msgid "" +msgstr "" +"Project-Id-Version: git-gui-glossary\n" +"POT-Creation-Date: 2008-01-07 21:20+0100\n" +"PO-Revision-Date: 2009-06-23 20:41+0300\n" +"Last-Translator: Jimmy Angelakos <vyruss@hellug.gr>\n" +"Language-Team: Greek <i18n@lists.hellug.gr>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 0.3\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)" +msgid "English Term (Dear translator: This file will never be visible to the user!)" +msgstr "" + +#. "" +msgid "amend" +msgstr "διόρθωση" + +#. "" +msgid "annotate" +msgstr "σχολιασμός" + +#. "A 'branch' is an active line of development." +msgid "branch [noun]" +msgstr "κλάδος [αντικείμενο]" + +#. "" +msgid "branch [verb]" +msgstr "διακλάδωση [ενέργεια]" + +#. "" +msgid "checkout [noun]" +msgstr "εξαγωγή [αντικείμενο]" + +#. "The action of updating the working tree to a revision which was stored in the object database." +msgid "checkout [verb]" +msgstr "εξαγωγή [ενέργεια]" + +#. "" +msgid "clone [verb]" +msgstr "κλωνοποίηση [ενέργεια]" + +#. "A single point in the git history." +msgid "commit [noun]" +msgstr "υποβολή [αντικείμενο] " + +#. "The action of storing a new snapshot of the project's state in the git history." +msgid "commit [verb]" +msgstr "υποβολή [ενέργεια]" + +#. "" +msgid "diff [noun]" +msgstr "διαφορά [αντικείμενο] " + +#. "" +msgid "diff [verb]" +msgstr "διαφορά [ενέργεια]" + +#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have." +msgid "fast forward merge" +msgstr "συγχώνευση επιτάχυνσης" + +#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too." +msgid "fetch" +msgstr "ανάκτηση" + +#. "One context of consecutive lines in a whole patch, which consists of many such hunks" +msgid "hunk" +msgstr "κομμάτι" + +#. "A collection of files. The index is a stored version of your working tree." +msgid "index (in git-gui: staging area)" +msgstr "ευρετήριο (στο git-gui: περιοχή σταδιοποίησης)" + +#. "A successful merge results in the creation of a new commit representing the result of the merge." +msgid "merge [noun]" +msgstr "συγχώνευση [αντικείμενο]" + +#. "To bring the contents of another branch into the current branch." +msgid "merge [verb]" +msgstr "συγχώνευση [ενέργεια]" + +#. "" +msgid "message" +msgstr "μήνυμα" + +#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'." +msgid "prune" +msgstr "κλάδεμα" + +#. "Pulling a branch means to fetch it and merge it." +msgid "pull" +msgstr "λήψη" + +#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)" +msgid "push" +msgstr "ώθηση" + +#. "" +msgid "redo" +msgstr "ξανά" + +#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks." +msgid "remote" +msgstr "απομακρυσμένο" + +#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)" +msgid "repository" +msgstr "αποθετήριο" + +#. "" +msgid "reset" +msgstr "επαναφορά" + +#. "" +msgid "revert" +msgstr "αναίρεση" + +#. "A particular state of files and directories which was stored in the object database." +msgid "revision" +msgstr "αναθεώρηση" + +#. "" +#, fuzzy +msgid "sign off" +msgstr "αποσύνδεση" + +#. "" +msgid "staging area" +msgstr "περιοχή σταδιοποίησης" + +#. "" +msgid "status" +msgstr "κατάσταση" + +#. "A ref pointing to a tag or commit object" +msgid "tag [noun]" +msgstr "ετικέτα [αντικείμενο]" + +#. "" +msgid "tag [verb]" +msgstr "ετικέτα [ενέργεια]" + +#. "A regular git branch that is used to follow changes from another repository." +msgid "tracking branch" +msgstr "κλάδος παρακολούθησης" + +#. "" +msgid "undo" +msgstr "αναίρεση" + +#. "" +msgid "update" +msgstr "ενημέρωση" + +#. "" +msgid "verify" +msgstr "επαλήθευση" + +#. "The tree of actual checked out files." +msgid "working copy, working tree" +msgstr "αντίγραφο εργασίας" + + diff --git a/git-gui/po/ru.po b/git-gui/po/ru.po index 0ffc4a418f..364c074c50 100644 --- a/git-gui/po/ru.po +++ b/git-gui/po/ru.po @@ -90,12 +90,18 @@ msgstr "Вызов программы поддержки репозитория #: git-gui.sh:1384 msgid "Commit declined by prepare-commit-msg hook." -msgstr "Сохранение прервано программой поддержки репозитория prepare-commit-msg" +msgstr "" +"Сохранение прервано программой поддержки репозитория prepare-commit-msg" #: git-gui.sh:1542 lib/browser.tcl:246 msgid "Ready." msgstr "Готово." +#: git-gui.sh:1726 +#, tcl-format +msgid "Displaying only %s of %s files." +msgstr "Показано %s из %s файлов." + #: git-gui.sh:1819 msgid "Unmodified" msgstr "Не изменено" @@ -1297,8 +1303,8 @@ msgid "" msgstr "" "Невозможно исправить состояние во время операции слияния.\n" "\n" -"Текущее слияние не завершено. Невозможно исправить предыдущее " -"сохраненное состояние, не прерывая эту операцию.\n" +"Текущее слияние не завершено. Невозможно исправить предыдущее сохраненное " +"состояние, не прерывая эту операцию.\n" #: lib/commit.tcl:48 msgid "Error loading commit data for amend:" @@ -1723,8 +1729,7 @@ msgid "" msgstr "" "Невозможно выполнить слияние во время исправления.\n" "\n" -"Завершите исправление данного состояния перед выполнением операции " -"слияния.\n" +"Завершите исправление данного состояния перед выполнением операции слияния.\n" #: lib/merge.tcl:27 msgid "" @@ -1888,8 +1893,8 @@ msgstr "" #, tcl-format msgid "File %s seems to have unresolved conflicts, still stage?" msgstr "" -"Файл %s кажется содержит необработаные конфликты. " -"Продолжить подготовку к сохранению?" +"Файл %s кажется содержит необработаные конфликты. Продолжить подготовку к " +"сохранению?" #: lib/mergetool.tcl:60 #, tcl-format @@ -2213,8 +2218,8 @@ msgid "" "One or more of the merge tests failed because you have not fetched the " "necessary commits. Try fetching from %s first." msgstr "" -"Некоторые тесты на слияние не прошли, потому что Вы не " -"получили необходимые состояния. Попытайтесь получить их из %s." +"Некоторые тесты на слияние не прошли, потому что Вы не получили необходимые " +"состояния. Попытайтесь получить их из %s." #: lib/remote_branch_delete.tcl:207 msgid "Please select one or more branches to delete." @@ -2381,8 +2386,8 @@ msgstr "Выполнение: %s" #: lib/tools.tcl:149 #, tcl-format -msgid "Tool completed succesfully: %s" -msgstr "Программа %s успешно завершилась." +msgid "Tool completed successfully: %s" +msgstr "Программа %s завершилась успешно." #: lib/tools.tcl:151 #, tcl-format @@ -2538,4 +2543,3 @@ msgstr "Использовать thin pack (для медленных сетев #: lib/transport.tcl:179 msgid "Include tags" msgstr "Передать метки" - diff --git a/git-instaweb.sh b/git-instaweb.sh index d96eddbe56..622a5f0eb2 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -317,7 +317,21 @@ EOF resolve_full_httpd list_mods=$(echo "$full_httpd" | sed "s/-f$/-l/") $list_mods | grep 'mod_cgi\.c' >/dev/null 2>&1 || \ - echo "LoadModule cgi_module $module_path/mod_cgi.so" >> "$conf" + if test -f "$module_path/mod_cgi.so" + then + echo "LoadModule cgi_module $module_path/mod_cgi.so" >> "$conf" + else + $list_mods | grep 'mod_cgid\.c' >/dev/null 2>&1 || \ + if test -f "$module_path/mod_cgid.so" + then + echo "LoadModule cgid_module $module_path/mod_cgid.so" \ + >> "$conf" + else + echo "You have no CGI support!" + exit 2 + fi + echo "ScriptSock logs/gitweb.sock" >> "$conf" + fi cat >> "$conf" <<EOF AddHandler cgi-script .cgi <Location /gitweb.cgi> diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh index 1dadbb4966..825c52c243 100755 --- a/git-merge-octopus.sh +++ b/git-merge-octopus.sh @@ -81,7 +81,7 @@ do # tree as the intermediate result of the merge. # We still need to count this as part of the parent set. - echo "Fast forwarding to: $SHA1" + echo "Fast-forwarding to: $SHA1" git read-tree -u -m $head $SHA1 || exit MRC=$SHA1 MRT=$(git write-tree) continue diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh index 9c2c1b7202..d067894bf4 100755 --- a/git-merge-one-file.sh +++ b/git-merge-one-file.sh @@ -16,6 +16,18 @@ # been handled already by git read-tree, but that one doesn't # do any merges that might change the tree layout. +USAGE='<orig blob> <our blob> <their blob> <path>' +USAGE="$USAGE <orig mode> <our mode> <their mode>" +LONG_USAGE="Usage: git merge-one-file $USAGE + +Blob ids and modes should be empty for missing files." + +if ! test "$#" -eq 7 +then + echo "$LONG_USAGE" + exit 1 +fi + case "${1:-.}${2:-.}${3:-.}" in # # Deleted in both or deleted in one and unchanged in the other diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh index bfb01f7842..f7c571e73c 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -46,7 +46,7 @@ check_unchanged () { valid_tool () { case "$1" in kdiff3 | tkdiff | xxdiff | meld | opendiff | \ - emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis) + emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis | p4merge) ;; # happy tortoisemerge) if ! merge_mode; then @@ -130,6 +130,19 @@ run_merge_tool () { "$merge_tool_path" "$LOCAL" "$REMOTE" fi ;; + p4merge) + if merge_mode; then + touch "$BACKUP" + if $base_present; then + "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED" + else + "$merge_tool_path" "$LOCAL" "$LOCAL" "$REMOTE" "$MERGED" + fi + check_unchanged + else + "$merge_tool_path" "$LOCAL" "$REMOTE" + fi + ;; meld) if merge_mode; then touch "$BACKUP" @@ -323,7 +336,7 @@ guess_merge_tool () { else tools="opendiff kdiff3 tkdiff xxdiff meld $tools" fi - tools="$tools gvimdiff diffuse ecmerge araxis" + tools="$tools gvimdiff diffuse ecmerge p4merge araxis" fi if echo "${VISUAL:-$EDITOR}" | grep emacs > /dev/null 2>&1; then # $EDITOR is emacs so add emerge as a candidate diff --git a/git-notes.sh b/git-notes.sh new file mode 100755 index 0000000000..e642e47d9f --- /dev/null +++ b/git-notes.sh @@ -0,0 +1,121 @@ +#!/bin/sh + +USAGE="(edit [-F <file> | -m <msg>] | show) [commit]" +. git-sh-setup + +test -z "$1" && usage +ACTION="$1"; shift + +test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="$(git config core.notesref)" +test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="refs/notes/commits" + +MESSAGE= +while test $# != 0 +do + case "$1" in + -m) + test "$ACTION" = "edit" || usage + shift + if test "$#" = "0"; then + die "error: option -m needs an argument" + else + if [ -z "$MESSAGE" ]; then + MESSAGE="$1" + else + MESSAGE="$MESSAGE + +$1" + fi + shift + fi + ;; + -F) + test "$ACTION" = "edit" || usage + shift + if test "$#" = "0"; then + die "error: option -F needs an argument" + else + if [ -z "$MESSAGE" ]; then + MESSAGE="$(cat "$1")" + else + MESSAGE="$MESSAGE + +$(cat "$1")" + fi + shift + fi + ;; + -*) + usage + ;; + *) + break + ;; + esac +done + +COMMIT=$(git rev-parse --verify --default HEAD "$@") || +die "Invalid commit: $@" + +case "$ACTION" in +edit) + if [ "${GIT_NOTES_REF#refs/notes/}" = "$GIT_NOTES_REF" ]; then + die "Refusing to edit notes in $GIT_NOTES_REF (outside of refs/notes/)" + fi + + MSG_FILE="$GIT_DIR/new-notes-$COMMIT" + GIT_INDEX_FILE="$MSG_FILE.idx" + export GIT_INDEX_FILE + + trap ' + test -f "$MSG_FILE" && rm "$MSG_FILE" + test -f "$GIT_INDEX_FILE" && rm "$GIT_INDEX_FILE" + ' 0 + + CURRENT_HEAD=$(git show-ref "$GIT_NOTES_REF" | cut -f 1 -d ' ') + if [ -z "$CURRENT_HEAD" ]; then + PARENT= + else + PARENT="-p $CURRENT_HEAD" + git read-tree "$GIT_NOTES_REF" || die "Could not read index" + fi + + if [ -z "$MESSAGE" ]; then + GIT_NOTES_REF= git log -1 $COMMIT | sed "s/^/#/" > "$MSG_FILE" + if [ ! -z "$CURRENT_HEAD" ]; then + git cat-file blob :$COMMIT >> "$MSG_FILE" 2> /dev/null + fi + core_editor="$(git config core.editor)" + ${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} "$MSG_FILE" + else + echo "$MESSAGE" > "$MSG_FILE" + fi + + grep -v ^# < "$MSG_FILE" | git stripspace > "$MSG_FILE".processed + mv "$MSG_FILE".processed "$MSG_FILE" + if [ -s "$MSG_FILE" ]; then + BLOB=$(git hash-object -w "$MSG_FILE") || + die "Could not write into object database" + git update-index --add --cacheinfo 0644 $BLOB $COMMIT || + die "Could not write index" + else + test -z "$CURRENT_HEAD" && + die "Will not initialise with empty tree" + git update-index --force-remove $COMMIT || + die "Could not update index" + fi + + TREE=$(git write-tree) || die "Could not write tree" + NEW_HEAD=$(echo Annotate $COMMIT | git commit-tree $TREE $PARENT) || + die "Could not annotate" + git update-ref -m "Annotate $COMMIT" \ + "$GIT_NOTES_REF" $NEW_HEAD $CURRENT_HEAD +;; +show) + git rev-parse -q --verify "$GIT_NOTES_REF":$COMMIT > /dev/null || + die "No note for commit $COMMIT." + git show "$GIT_NOTES_REF":$COMMIT +;; +*) + usage +esac diff --git a/git-pull.sh b/git-pull.sh index 0bbd5bf7df..bfeb4a0ff6 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -16,7 +16,8 @@ cd_to_toplevel test -z "$(git ls-files -u)" || die "You are in the middle of a conflicted merge." -strategy_args= diffstat= no_commit= squash= no_ff= log_arg= verbosity= +strategy_args= diffstat= no_commit= squash= no_ff= ff_only= +log_arg= verbosity= 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) @@ -45,6 +46,8 @@ do no_ff=--ff ;; --no-ff) no_ff=--no-ff ;; + --ff-only) + ff_only=--ff-only ;; -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\ --strateg=*|--strategy=*|\ -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) @@ -89,14 +92,24 @@ error_on_no_merge_candidates () { done curr_branch=${curr_branch#refs/heads/} + upstream=$(git config "branch.$curr_branch.merge") + remote=$(git config "branch.$curr_branch.remote") - if [ -z "$curr_branch" ]; then + if [ $# -gt 1 ]; then + echo "There are no candidates for merging in the refs that you just fetched." + echo "Generally this means that you provided a wildcard refspec which had no" + echo "matches on the remote end." + elif [ $# -gt 0 ] && [ "$1" != "$remote" ]; then + echo "You asked to pull from the remote '$1', but did not specify" + echo "a branch to merge. Because this is not the default configured remote" + echo "for your current branch, you must specify a branch on the command line." + elif [ -z "$curr_branch" ]; then echo "You are not currently on a branch, so I cannot use any" echo "'branch.<branchname>.merge' in your configuration file." echo "Please specify which branch you want to merge on the command" echo "line and try again (e.g. 'git pull <repository> <refspec>')." echo "See git-pull(1) for details." - else + elif [ -z "$upstream" ]; then echo "You asked me to pull without telling me which branch you" echo "want to merge with, and 'branch.${curr_branch}.merge' in" echo "your configuration file does not tell me either. Please" @@ -114,6 +127,9 @@ error_on_no_merge_candidates () { echo " remote.<nickname>.fetch = <refspec>" echo echo "See git-config(1) for details." + else + echo "Your configuration specifies to merge the ref '${upstream#refs/heads/}' from the" + echo "remote, but no such ref was fetched." fi exit 1 } @@ -158,7 +174,7 @@ then # First update the working tree to match $curr_head. echo >&2 "Warning: fetch updated the current branch head." - echo >&2 "Warning: fast forwarding your working tree from" + echo >&2 "Warning: fast-forwarding your working tree from" echo >&2 "Warning: commit $orig_head." git update-index -q --refresh git read-tree -u -m "$orig_head" "$curr_head" || @@ -177,14 +193,7 @@ merge_head=$(sed -e '/ not-for-merge /d' \ case "$merge_head" in '') - case $? in - 0) error_on_no_merge_candidates "$@";; - 1) echo >&2 "You are not currently on a branch; you must explicitly" - echo >&2 "specify which branch you wish to merge:" - echo >&2 " git pull <remote> <branch>" - exit 1;; - *) exit $?;; - esac + error_on_no_merge_candidates "$@" ;; ?*' '?*) if test -z "$orig_head" @@ -209,5 +218,5 @@ merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit test true = "$rebase" && exec git-rebase $diffstat $strategy_args --onto $merge_head \ ${oldremoteref:-$merge_head} -exec git-merge $diffstat $no_commit $squash $no_ff $log_arg $strategy_args \ +exec git-merge $diffstat $no_commit $squash $no_ff $ff_only $log_arg $strategy_args \ "$merge_name" HEAD $merge_head $verbosity diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 23ded48322..27daaa9ded 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -168,7 +168,7 @@ pick_one () { output git reset --hard $sha1 test "a$1" = a-n && output git reset --soft $current_sha1 sha1=$(git rev-parse --short $sha1) - output warn Fast forward to $sha1 + output warn Fast-forward to $sha1 else output git cherry-pick "$@" fi @@ -248,9 +248,9 @@ pick_one_preserving_merges () { done case $fast_forward in t) - output warn "Fast forward to $sha1" + output warn "Fast-forward to $sha1" output git reset --hard $sha1 || - die "Cannot fast forward to $sha1" + die "Cannot fast-forward to $sha1" ;; f) first_parent=$(expr "$new_parents" : ' \([^ ]*\)') @@ -340,6 +340,14 @@ do_next () { pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" ;; + reword|r) + comment_for_reflog reword + + mark_action_done + pick_one $sha1 || + die_with_patch $sha1 "Could not apply $sha1... $rest" + git commit --amend + ;; edit|e) comment_for_reflog edit @@ -408,7 +416,12 @@ do_next () { ;; *) warn "Unknown command: $command $sha1 $rest" - die_with_patch $sha1 "Please fix this in the file $TODO." + if git rev-parse --verify -q "$sha1" >/dev/null + then + die_with_patch $sha1 "Please fix this in the file $TODO." + else + die "Please fix this in the file $TODO." + fi ;; esac test -s "$TODO" && return @@ -752,6 +765,7 @@ first and then run 'git rebase --continue' again." # # Commands: # p, pick = use commit +# r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # diff --git a/git-rebase.sh b/git-rebase.sh index 2315d95a9f..6830e1627d 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -387,7 +387,7 @@ fi # The tree must be really really clean. if ! git update-index --ignore-submodules --refresh > /dev/null; then echo >&2 "cannot rebase: you have unstaged changes" - git diff --name-status -r --ignore-submodules -- >&2 + git diff-files --name-status -r --ignore-submodules -- >&2 exit 1 fi diff=$(git diff-index --cached --name-status -r --ignore-submodules HEAD --) @@ -496,7 +496,7 @@ then fi # If the $onto is a proper descendant of the tip of the branch, then -# we just fast forwarded. +# we just fast-forwarded. if test "$mb" = "$branch" then say "Fast-forwarded $branch_name to $onto_name." diff --git a/git-send-email.perl b/git-send-email.perl index 0700d80afc..4f5da4ecf2 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -162,7 +162,8 @@ my $compose_filename; # Handle interactive edition of files. my $multiedit; -my $editor = $ENV{GIT_EDITOR} || Git::config(@repo, "core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi"; +my $editor = Git::command_oneline('var', 'GIT_EDITOR'); + sub do_edit { if (defined($multiedit) && !$multiedit) { map { @@ -401,7 +402,7 @@ my %aliases; my %parse_alias = ( # multiline formats can be supported in the future mutt => sub { my $fh = shift; while (<$fh>) { - if (/^\s*alias\s+(\S+)\s+(.*)$/) { + if (/^\s*alias\s+(?:-group\s+\S+\s+)*(\S+)\s+(.*)$/) { my ($alias, $addr) = ($1, $2); $addr =~ s/#.*$//; # mutt allows # comments # commas delimit multiple addresses @@ -835,7 +836,7 @@ sub send_message $gitversion = Git::version(); } - my $cc = join(", ", unique_email_list(@cc)); + my $cc = join(",\n\t", unique_email_list(@cc)); my $ccline = ""; if ($cc ne '') { $ccline = "\nCc: $cc"; @@ -921,7 +922,7 @@ X-Mailer: git-send-email $gitversion $smtp ||= Net::SMTP->new((defined $smtp_server_port) ? "$smtp_server:$smtp_server_port" : $smtp_server); - if ($smtp_encryption eq 'tls') { + if ($smtp_encryption eq 'tls' && $smtp) { require Net::SMTP::SSL; $smtp->command('STARTTLS'); $smtp->response(); @@ -976,7 +977,9 @@ X-Mailer: git-send-email $gitversion if ($smtp_server !~ m#^/#) { print "Server: $smtp_server\n"; print "MAIL FROM:<$raw_from>\n"; - print "RCPT TO:".join(',',(map { "<$_>" } @recipients))."\n"; + foreach my $entry (@recipients) { + print "RCPT TO:<$entry>\n"; + } } else { print "Sendmail: $smtp_server ".join(' ',@sendmail_parameters)."\n"; } diff --git a/git-sh-setup.sh b/git-sh-setup.sh index c41c2f7439..99cceeb858 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -99,19 +99,12 @@ set_reflog_action() { } git_editor() { - : "${GIT_EDITOR:=$(git config core.editor)}" - : "${GIT_EDITOR:=${VISUAL:-${EDITOR}}}" - case "$GIT_EDITOR,$TERM" in - ,dumb) - echo >&2 "No editor specified in GIT_EDITOR, core.editor, VISUAL," - echo >&2 "or EDITOR. Tried to fall back to vi but terminal is dumb." - echo >&2 "Please set one of these variables to an appropriate" - echo >&2 "editor or run $0 with options that will not cause an" - echo >&2 "editor to be invoked (e.g., -m or -F for git-commit)." - exit 1 - ;; - esac - eval "${GIT_EDITOR:=vi}" '"$@"' + if test -z "${GIT_EDITOR:+set}" + then + GIT_EDITOR="$(git var GIT_EDITOR)" || return $? + fi + + eval "$GIT_EDITOR" '"$@"' } is_bare_repository () { diff --git a/git-stash.sh b/git-stash.sh index d61c9d03bc..f796c2fe24 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -7,7 +7,7 @@ USAGE="list [<options>] or: $dashless drop [-q|--quiet] [<stash>] or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>] or: $dashless branch <branchname> [<stash>] - or: $dashless [save [--keep-index] [-q|--quiet] [<message>]] + or: $dashless [save [-k|--keep-index] [-q|--quiet] [<message>]] or: $dashless clear" SUBDIRECTORY_OK=Yes @@ -21,6 +21,14 @@ trap 'rm -f "$TMP-*"' 0 ref_stash=refs/stash +if git config --get-colorbool color.interactive; then + help_color="$(git config --get-color color.interactive.help 'red bold')" + reset_color="$(git config --get-color '' reset)" +else + help_color= + reset_color= +fi + no_changes () { git diff-index --quiet --cached HEAD --ignore-submodules -- && git diff-files --quiet --ignore-submodules @@ -68,19 +76,44 @@ create_stash () { git commit-tree $i_tree -p $b_commit) || die "Cannot save the current index state" - # state of the working tree - w_tree=$( ( + if test -z "$patch_mode" + then + + # state of the working tree + w_tree=$( ( + rm -f "$TMP-index" && + cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" && + GIT_INDEX_FILE="$TMP-index" && + export GIT_INDEX_FILE && + git read-tree -m $i_tree && + git add -u && + git write-tree && + rm -f "$TMP-index" + ) ) || + die "Cannot save the current worktree state" + + else + rm -f "$TMP-index" && - cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" && - GIT_INDEX_FILE="$TMP-index" && - export GIT_INDEX_FILE && - git read-tree -m $i_tree && - git add -u && - git write-tree && - rm -f "$TMP-index" - ) ) || + GIT_INDEX_FILE="$TMP-index" git read-tree HEAD && + + # find out what the user wants + GIT_INDEX_FILE="$TMP-index" \ + git add--interactive --patch=stash -- && + + # state of the working tree + w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) || die "Cannot save the current worktree state" + git diff-tree -p HEAD $w_tree > "$TMP-patch" && + test -s "$TMP-patch" || + die "No changes selected" + + rm -f "$TMP-index" || + die "Cannot remove temporary index (can't happen)" + + fi + # create the stash if test -z "$stash_msg" then @@ -95,15 +128,31 @@ create_stash () { save_stash () { keep_index= + patch_mode= while test $# != 0 do case "$1" in - --keep-index) + -k|--keep-index) + keep_index=t + ;; + --no-keep-index) + keep_index= + ;; + -p|--patch) + patch_mode=t keep_index=t ;; -q|--quiet) GIT_QUIET=t ;; + --) + shift + break + ;; + -*) + echo "error: unknown option for 'stash save': $1" + usage + ;; *) break ;; @@ -131,11 +180,22 @@ save_stash () { die "Cannot save the current status" say Saved working directory and index state "$stash_msg" - git reset --hard ${GIT_QUIET:+-q} - - if test -n "$keep_index" && test -n $i_tree + if test -z "$patch_mode" then - git read-tree --reset -u $i_tree + git reset --hard ${GIT_QUIET:+-q} + + if test -n "$keep_index" && test -n $i_tree + then + git read-tree --reset -u $i_tree + fi + else + git apply -R < "$TMP-patch" || + die "Cannot remove worktree changes" + + if test -z "$keep_index" + then + git reset + fi fi } @@ -145,8 +205,7 @@ have_stash () { list_stash () { have_stash || return 0 - git log --no-color --pretty=oneline -g "$@" $ref_stash -- | - sed -n -e 's/^[.0-9a-f]* refs\///p' + git log --format="%gd: %gs" -g "$@" $ref_stash -- } show_stash () { @@ -307,15 +366,22 @@ apply_to_branch () { drop_stash $stash } +# The default command is "save" if nothing but options are given +seen_non_option= +for opt +do + case "$opt" in + -*) ;; + *) seen_non_option=t; break ;; + esac +done + +test -n "$seen_non_option" || set "save" "$@" + # Main command set case "$1" in list) shift - if test $# = 0 - then - set x -n 10 - shift - fi list_stash "$@" ;; show) @@ -358,12 +424,13 @@ branch) apply_to_branch "$@" ;; *) - if test $# -eq 0 - then + case $# in + 0) save_stash && say '(To restore them type "git stash apply")' - else + ;; + *) usage - fi + esac ;; esac diff --git a/git-submodule.sh b/git-submodule.sh index bfbd36b6f4..850d4235a0 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -5,7 +5,7 @@ # Copyright (c) 2007 Lars Hjemli dashless=$(basename "$0" | sed -e 's/-/ /') -USAGE="[--quiet] add [-b branch] [--reference <repository>] [--] <repository> <path> +USAGE="[--quiet] add [-b branch] [--reference <repository>] [--] <repository> [<path>] or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...] or: $dashless [--quiet] init [--] [<path>...] or: $dashless [--quiet] update [--init] [-N|--no-fetch] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...] @@ -98,7 +98,7 @@ module_clone() if test -d "$path" then rmdir "$path" 2>/dev/null || - die "Directory '$path' exist, but is neither empty nor a git repository" + die "Directory '$path' exists, but is neither empty nor a git repository" fi test -e "$path" && @@ -160,6 +160,11 @@ cmd_add() repo=$1 path=$2 + if test -z "$path"; then + path=$(echo "$repo" | + sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g') + fi + if test -z "$repo" -o -z "$path"; then usage fi diff --git a/git-svn.perl b/git-svn.perl index ce4fef9d34..2746895ae6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -168,6 +168,9 @@ my %cmd = ( 'Create a .gitignore per svn:ignore', { 'revision|r=i' => \$_revision } ], + 'mkdirs' => [ \&cmd_mkdirs , + "recreate empty directories after a checkout", + { 'revision|r=i' => \$_revision } ], 'propget' => [ \&cmd_propget, 'Print the value of a property on a file or directory', { 'revision|r=i' => \$_revision } ], @@ -274,7 +277,7 @@ unless ($cmd && $cmd =~ /(?:clone|init|multi-init)$/) { my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd); -read_repo_config(\%opts); +read_git_config(\%opts); if ($cmd && ($cmd eq 'log' || $cmd eq 'blame')) { Getopt::Long::Configure('pass_through'); } @@ -603,8 +606,15 @@ sub cmd_dcommit { "\nBefore dcommitting"; } if ($url_ ne $expect_url) { - fatal "URL mismatch after rebase: ", - "$url_ != $expect_url"; + if ($url_ eq $gs->metadata_url) { + print + "Accepting rewritten URL:", + " $url_\n"; + } else { + fatal + "URL mismatch after rebase:", + " $url_ != $expect_url"; + } } if ($uuid_ ne $uuid) { fatal "uuid mismatch after rebase: ", @@ -762,6 +772,7 @@ sub cmd_rebase { $_fetch_all ? $gs->fetch_all : $gs->fetch; } command_noisy(rebase_cmd(), $gs->refname); + $gs->mkemptydirs; } sub cmd_show_ignore { @@ -823,6 +834,12 @@ sub cmd_create_ignore { }); } +sub cmd_mkdirs { + my ($url, $rev, $uuid, $gs) = working_head_info('HEAD'); + $gs ||= Git::SVN->new; + $gs->mkemptydirs($_revision); +} + sub canonicalize_path { my ($path) = @_; my $dot_slash_added = 0; @@ -1189,6 +1206,7 @@ sub post_fetch_checkout { command_noisy(qw/read-tree -m -u -v HEAD HEAD/); print STDERR "Checked out HEAD:\n ", $gs->full_url, " r", $gs->last_rev, "\n"; + $gs->mkemptydirs($gs->last_rev); } sub complete_svn_url { @@ -1314,9 +1332,8 @@ sub get_commit_entry { close $log_fh or croak $!; if ($_edit || ($type eq 'tree')) { - my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi'; - # TODO: strip out spaces, comments, like git-commit.sh - system($editor, $commit_editmsg); + chomp(my $editor = command_oneline(qw(var GIT_EDITOR))); + system('sh', '-c', $editor.' "$@"', $editor, $commit_editmsg); } rename $commit_editmsg, $commit_msg or croak $!; { @@ -1383,8 +1400,7 @@ sub load_authors { } # convert GetOpt::Long specs for use by git-config -sub read_repo_config { - return unless -d $ENV{GIT_DIR}; +sub read_git_config { my $opts = shift; my @config_only; foreach my $o (keys %$opts) { @@ -1758,7 +1774,7 @@ sub read_all_remotes { my $use_svm_props = eval { command_oneline(qw/config --bool svn.useSvmProps/) }; $use_svm_props = $use_svm_props eq 'true' if $use_svm_props; - my $svn_refspec = qr{\s*/?(.*?)\s*:\s*(.+?)\s*}; + my $svn_refspec = qr{\s*(.*?)\s*:\s*(.+?)\s*}; foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) { if (m!^(.+)\.fetch=$svn_refspec$!) { my ($remote, $local_ref, $remote_ref) = ($1, $2, $3); @@ -1972,7 +1988,7 @@ sub find_ref { my ($ref_id) = @_; foreach (command(qw/config -l/)) { next unless m!^svn-remote\.(.+)\.fetch= - \s*/?(.*?)\s*:\s*(.+?)\s*$!x; + \s*(.*?)\s*:\s*(.+?)\s*$!x; my ($repo_id, $path, $ref) = ($1, $2, $3); if ($ref eq $ref_id) { $path = '' if ($path =~ m#^\./?#); @@ -2626,7 +2642,8 @@ sub find_parent_branch { my $url = $self->ra->{url}; my $new_url = $url . $branch_from; print STDERR "Found possible branch point: ", - "$new_url => ", $self->full_url, ", $r\n"; + "$new_url => ", $self->full_url, ", $r\n" + unless $::_q > 1; $branch_from =~ s#^/##; my $gs = $self->other_gs($new_url, $url, $branch_from, $r, $self->{ref_id}); @@ -2647,11 +2664,13 @@ sub find_parent_branch { ($r0, $parent) = $gs->find_rev_before($r, 1); } if (defined $r0 && defined $parent) { - print STDERR "Found branch parent: ($self->{ref_id}) $parent\n"; + print STDERR "Found branch parent: ($self->{ref_id}) $parent\n" + unless $::_q > 1; my $ed; if ($self->ra->can_do_switch) { $self->assert_index_clean($parent); - print STDERR "Following parent with do_switch\n"; + print STDERR "Following parent with do_switch\n" + unless $::_q > 1; # do_switch works with svn/trunk >= r22312, but that # is not included with SVN 1.4.3 (the latest version # at the moment), so we can't rely on it @@ -2666,18 +2685,20 @@ sub find_parent_branch { print STDERR "Trees match:\n", " $new_url\@$r0\n", " ${\$self->full_url}\@$rev\n", - "Following parent with no changes\n"; + "Following parent with no changes\n" + unless $::_q > 1; $self->tmp_index_do(sub { command_noisy('read-tree', $parent); }); $self->{last_commit} = $parent; } else { - print STDERR "Following parent with do_update\n"; + print STDERR "Following parent with do_update\n" + unless $::_q > 1; $ed = SVN::Git::Fetcher->new($self); $self->ra->gs_do_update($rev, $rev, $self, $ed) or die "SVN connection failed somewhere...\n"; } - print STDERR "Successfully followed parent\n"; + print STDERR "Successfully followed parent\n" unless $::_q > 1; return $self->make_log_entry($rev, [$parent], $ed); } return undef; @@ -2713,6 +2734,34 @@ sub do_fetch { $self->make_log_entry($rev, \@parents, $ed); } +sub mkemptydirs { + my ($self, $r) = @_; + my %empty_dirs = (); + + open my $fh, '<', "$self->{dir}/unhandled.log" or return; + binmode $fh or croak "binmode: $!"; + while (<$fh>) { + if (defined $r && /^r(\d+)$/) { + last if $1 > $r; + } elsif (/^ \+empty_dir: (.+)$/) { + $empty_dirs{$1} = 1; + } elsif (/^ \-empty_dir: (.+)$/) { + delete $empty_dirs{$1}; + } + } + close $fh; + foreach my $d (sort keys %empty_dirs) { + $d = uri_decode($d); + next if -d $d; + if (-e _) { + warn "$d exists but is not a directory\n"; + } else { + print "creating empty directory: $d\n"; + mkpath([$d]); + } + } +} + sub get_untracked { my ($self, $ed) = @_; my @out; @@ -2822,7 +2871,7 @@ sub other_gs { $ref_id .= "\@$r"; # just grow a tail if we're not unique enough :x $ref_id .= '-' while find_ref($ref_id); - print STDERR "Initializing parent: $ref_id\n"; + print STDERR "Initializing parent: $ref_id\n" unless $::_q > 1; my ($u, $p, $repo_id) = ($new_url, '', $ref_id); if ($u =~ s#^\Q$url\E(/|$)##) { $p = $u; @@ -2836,6 +2885,7 @@ sub other_gs { sub call_authors_prog { my ($orig_author) = @_; + $orig_author = command_oneline('rev-parse', '--sq-quote', $orig_author); my $author = `$::_authors_prog $orig_author`; if ($? != 0) { die "$::_authors_prog failed with exit code $?\n" @@ -2865,14 +2915,160 @@ sub check_author { $author; } +sub find_extra_svk_parents { + my ($self, $ed, $tickets, $parents) = @_; + # aha! svk:merge property changed... + my @tickets = split "\n", $tickets; + my @known_parents; + for my $ticket ( @tickets ) { + my ($uuid, $path, $rev) = split /:/, $ticket; + if ( $uuid eq $self->ra_uuid ) { + my $url = $self->rewrite_root || $self->{url}; + my $repos_root = $url; + my $branch_from = $path; + $branch_from =~ s{^/}{}; + my $gs = $self->other_gs($repos_root."/".$branch_from, + $url, + $branch_from, + $rev, + $self->{ref_id}); + if ( my $commit = $gs->rev_map_get($rev, $uuid) ) { + # wahey! we found it, but it might be + # an old one (!) + push @known_parents, $commit; + } + } + } + for my $parent ( @known_parents ) { + my @cmd = ('rev-list', $parent, map { "^$_" } @$parents ); + my ($msg_fh, $ctx) = command_output_pipe(@cmd); + my $new; + while ( <$msg_fh> ) { + $new=1;last; + } + command_close_pipe($msg_fh, $ctx); + if ( $new ) { + print STDERR + "Found merge parent (svk:merge ticket): $parent\n"; + push @$parents, $parent; + } + } +} + +# note: this function should only be called if the various dirprops +# have actually changed +sub find_extra_svn_parents { + my ($self, $ed, $mergeinfo, $parents) = @_; + # aha! svk:merge property changed... + + # We first search for merged tips which are not in our + # history. Then, we figure out which git revisions are in + # that tip, but not this revision. If all of those revisions + # are now marked as merge, we can add the tip as a parent. + my @merges = split "\n", $mergeinfo; + my @merge_tips; + my @merged_commit_ranges; + my $url = $self->rewrite_root || $self->{url}; + for my $merge ( @merges ) { + my ($source, $revs) = split ":", $merge; + my $path = $source; + $path =~ s{^/}{}; + my $gs = Git::SVN->find_by_url($url.$source, $url, $path); + if ( !$gs ) { + warn "Couldn't find revmap for $url$source\n"; + next; + } + my @ranges = split ",", $revs; + my ($tip, $tip_commit); + # find the tip + for my $range ( @ranges ) { + my ($bottom, $top) = split "-", $range; + $top ||= $bottom; + my $bottom_commit = + $gs->rev_map_get($bottom, $self->ra_uuid) || + $gs->rev_map_get($bottom+1, $self->ra_uuid); + my $top_commit; + for (; !$top_commit && $top >= $bottom; --$top) { + $top_commit = + $gs->rev_map_get($top, $self->ra_uuid); + } + + unless ($top_commit and $bottom_commit) { + warn "W:unknown path/rev in svn:mergeinfo " + ."dirprop: $source:$range\n"; + next; + } + + push @merged_commit_ranges, + "$bottom_commit..$top_commit"; + + if ( !defined $tip or $top > $tip ) { + $tip = $top; + $tip_commit = $top_commit; + } + } + unless (!$tip_commit or + grep { $_ eq $tip_commit } @$parents ) { + push @merge_tips, $tip_commit; + } else { + push @merge_tips, undef; + } + } + for my $merge_tip ( @merge_tips ) { + my $spec = shift @merges; + next unless $merge_tip; + my @cmd = ('rev-list', "-1", $merge_tip, + "--not", @$parents ); + my ($msg_fh, $ctx) = command_output_pipe(@cmd); + my $new; + while ( <$msg_fh> ) { + $new=1;last; + } + command_close_pipe($msg_fh, $ctx); + if ( $new ) { + push @cmd, @merged_commit_ranges; + my ($msg_fh, $ctx) = command_output_pipe(@cmd); + my $unmerged; + while ( <$msg_fh> ) { + $unmerged=1;last; + } + command_close_pipe($msg_fh, $ctx); + if ( $unmerged ) { + warn "W:svn cherry-pick ignored ($spec)\n"; + } else { + warn + "Found merge parent (svn:mergeinfo prop): ", + $merge_tip, "\n"; + push @$parents, $merge_tip; + } + } + } +} + sub make_log_entry { my ($self, $rev, $parents, $ed) = @_; my $untracked = $self->get_untracked($ed); + my @parents = @$parents; + my $ps = $ed->{path_strip} || ""; + for my $path ( grep { m/$ps/ } %{$ed->{dir_prop}} ) { + my $props = $ed->{dir_prop}{$path}; + if ( $props->{"svk:merge"} ) { + $self->find_extra_svk_parents + ($ed, $props->{"svk:merge"}, \@parents); + } + if ( $props->{"svn:mergeinfo"} ) { + $self->find_extra_svn_parents + ($ed, + $props->{"svn:mergeinfo"}, + \@parents); + } + } + open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; print $un "r$rev\n" or croak $!; print $un $_, "\n" foreach @$untracked; - my %log_entry = ( parents => $parents || [], revision => $rev, + my %log_entry = ( parents => \@parents, revision => $rev, log => ''); my $headrev; @@ -3398,6 +3594,12 @@ sub uri_encode { $f } +sub uri_decode { + my ($f) = @_; + $f =~ s#%([0-9a-fA-F]{2})#chr(hex($1))#eg; + $f +} + sub remove_username { $_[0] =~ s{^([^:]*://)[^@]+@}{$1}; } @@ -5016,10 +5218,8 @@ sub git_svn_log_cmd { # adapted from pager.c sub config_pager { - $pager ||= $ENV{GIT_PAGER} || $ENV{PAGER}; - if (!defined $pager) { - $pager = 'less'; - } elsif (length $pager == 0 || $pager eq 'cat') { + chomp(my $pager = command_oneline(qw(var GIT_PAGER))); + if ($pager eq 'cat') { $pager = undef; } $ENV{GIT_PAGER_IN_USE} = defined($pager); diff --git a/git-web--browse.sh b/git-web--browse.sh index 4f5c740df5..a578c3a732 100755 --- a/git-web--browse.sh +++ b/git-web--browse.sh @@ -111,7 +111,8 @@ if test -z "$browser" ; then browser_candidates="w3m links lynx" fi # SECURITYSESSIONID indicates an OS X GUI login session - if test -n "$SECURITYSESSIONID"; then + if test -n "$SECURITYSESSIONID" \ + -o "$TERM_PROGRAM" = "Apple_Terminal" ; then browser_candidates="open $browser_candidates" fi # /bin/start indicates MinGW @@ -5,7 +5,10 @@ #include "run-command.h" const char git_usage_string[] = - "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]"; + "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n" + " [-p|--paginate|--no-pager] [--no-replace-objects]\n" + " [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]\n" + " [--help] COMMAND [ARGS]"; const char git_more_info_string[] = "See 'git help COMMAND' for more information on a specific command."; @@ -84,6 +87,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) use_pager = 0; if (envchanged) *envchanged = 1; + } else if (!strcmp(cmd, "--no-replace-objects")) { + read_replace_refs = 0; } else if (!strcmp(cmd, "--git-dir")) { if (*argc < 2) { fprintf(stderr, "No directory given for --git-dir.\n" ); @@ -224,21 +229,24 @@ struct cmd_struct { static int run_builtin(struct cmd_struct *p, int argc, const char **argv) { - int status; + int status, help; struct stat st; const char *prefix; prefix = NULL; - if (p->option & RUN_SETUP) - prefix = setup_git_directory(); - - if (use_pager == -1 && p->option & RUN_SETUP) - use_pager = check_pager_config(p->cmd); - if (use_pager == -1 && p->option & USE_PAGER) - use_pager = 1; + help = argc == 2 && !strcmp(argv[1], "-h"); + if (!help) { + if (p->option & RUN_SETUP) + prefix = setup_git_directory(); + + if (use_pager == -1 && p->option & RUN_SETUP) + use_pager = check_pager_config(p->cmd); + if (use_pager == -1 && p->option & USE_PAGER) + use_pager = 1; + } commit_pager_choice(); - if (p->option & NEED_WORK_TREE) + if (!help && p->option & NEED_WORK_TREE) setup_work_tree(); trace_argv_printf(argv, "trace: built-in: git"); @@ -299,7 +307,6 @@ static void handle_internal_command(int argc, const char **argv) { "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 }, { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP }, { "for-each-ref", cmd_for_each_ref, RUN_SETUP }, { "format-patch", cmd_format_patch, RUN_SETUP }, @@ -309,9 +316,6 @@ static void handle_internal_command(int argc, const char **argv) { "get-tar-commit-id", cmd_get_tar_commit_id }, { "grep", cmd_grep, RUN_SETUP | USE_PAGER }, { "help", cmd_help }, -#ifndef NO_CURL - { "http-fetch", cmd_http_fetch, RUN_SETUP }, -#endif { "init", cmd_init_db }, { "init-db", cmd_init_db }, { "log", cmd_log, RUN_SETUP | USE_PAGER }, diff --git a/git.spec.in b/git.spec.in index 4be0834f0b..ab224f7eae 100644 --- a/git.spec.in +++ b/git.spec.in @@ -233,7 +233,7 @@ rm -rf $RPM_BUILD_ROOT * Tue Mar 27 2007 Eygene Ryabinkin <rea-git@codelabs.ru> - Added the git-p4 package: Perforce import stuff. -* Mon Feb 13 2007 Nicolas Pitre <nico@cam.org> +* Mon Feb 13 2007 Nicolas Pitre <nico@fluxnic.net> - Update core package description (Git isn't as stupid as it used to be) * Mon Feb 12 2007 Junio C Hamano <junkio@cox.net> diff --git a/gitk-git/gitk b/gitk-git/gitk index 8c08310e6d..a0214b7004 100644 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -2526,6 +2526,7 @@ proc savestuff {w} { if {$stuffsaved} return if {![winfo viewable .]} return catch { + if {[file exists ~/.gitk-new]} {file delete -force ~/.gitk-new} set f [open "~/.gitk-new" w] if {$::tcl_platform(platform) eq {windows}} { file attributes "~/.gitk-new" -hidden true @@ -3167,6 +3168,28 @@ proc flist_hl {only} { set gdttype [mc "touching paths:"] } +proc gitknewtmpdir {} { + global diffnum gitktmpdir gitdir + + if {![info exists gitktmpdir]} { + set gitktmpdir [file join [file dirname $gitdir] \ + [format ".gitk-tmp.%s" [pid]]] + if {[catch {file mkdir $gitktmpdir} err]} { + error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err" + unset gitktmpdir + return {} + } + set diffnum 0 + } + incr diffnum + set diffdir [file join $gitktmpdir $diffnum] + if {[catch {file mkdir $diffdir} err]} { + error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err" + return {} + } + return $diffdir +} + proc save_file_from_commit {filename output what} { global nullfile @@ -3201,11 +3224,10 @@ proc external_diff_get_one_file {diffid filename diffdir} { } proc external_diff {} { - global gitktmpdir nullid nullid2 + global nullid nullid2 global flist_menu_file global diffids - global diffnum - global gitdir extdifftool + global extdifftool if {[llength $diffids] == 1} { # no reference commit given @@ -3227,22 +3249,8 @@ proc external_diff {} { } # make sure that several diffs wont collide - if {![info exists gitktmpdir]} { - set gitktmpdir [file join [file dirname $gitdir] \ - [format ".gitk-tmp.%s" [pid]]] - if {[catch {file mkdir $gitktmpdir} err]} { - error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err" - unset gitktmpdir - return - } - set diffnum 0 - } - incr diffnum - set diffdir [file join $gitktmpdir $diffnum] - if {[catch {file mkdir $diffdir} err]} { - error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err" - return - } + set diffdir [gitknewtmpdir] + if {$diffdir eq {}} return # gather files to diff set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir] @@ -7400,7 +7408,7 @@ proc getblobdiffline {bdf ids} { $ctext conf -state normal while {[incr nr] <= 1000 && [gets $bdf line] >= 0} { if {$ids != $diffids || $bdf != $blobdifffd($ids)} { - close $bdf + catch {close $bdf} return 0 } if {![string compare -length 5 "diff " $line]} { @@ -7552,7 +7560,7 @@ proc getblobdiffline {bdf ids} { } $ctext conf -state disabled if {[eof $bdf]} { - close $bdf + catch {close $bdf} return 0 } return [expr {$nr >= 1000? 2: 1}] @@ -8273,8 +8281,11 @@ proc do_cmp_commits {a b} { appendshortlink $a [mc "Commit "] " $heada\n" appendshortlink $b [mc " differs from\n "] \ " $headb\n" - $ctext insert end [mc "- stopping\n"] - break + $ctext insert end [mc "Diff of commits:\n\n"] + $ctext conf -state disabled + update + diffcommits $a $b + return } } if {$skipa} { @@ -8300,6 +8311,31 @@ proc do_cmp_commits {a b} { $ctext conf -state disabled } +proc diffcommits {a b} { + global diffcontext diffids blobdifffd diffinhdr + + set tmpdir [gitknewtmpdir] + set fna [file join $tmpdir "commit-[string range $a 0 7]"] + set fnb [file join $tmpdir "commit-[string range $b 0 7]"] + if {[catch { + exec git diff-tree -p --pretty $a >$fna + exec git diff-tree -p --pretty $b >$fnb + } err]} { + error_popup [mc "Error writing commit to file: %s" $err] + return + } + if {[catch { + set fd [open "| diff -U$diffcontext $fna $fnb" r] + } err]} { + error_popup [mc "Error diffing commits: %s" $err] + return + } + set diffids [list commits $a $b] + set blobdifffd($diffids) $fd + set diffinhdr 0 + filerun $fd [list getblobdiffline $fd $diffids] +} + proc diffvssel {dirn} { global rowmenuid selectedline diff --git a/gitk-git/po/sv.po b/gitk-git/po/sv.po index 947b53f6b0..624eb2281e 100644 --- a/gitk-git/po/sv.po +++ b/gitk-git/po/sv.po @@ -1,32 +1,40 @@ # Swedish translation for gitk -# Copyright (C) 2005-2008 Paul Mackerras +# Copyright (C) 2005-2009 Paul Mackerras # This file is distributed under the same license as the gitk package. # -# Peter Karlsson <peter@softwolves.pp.se>, 2008. +# Peter Krefting <peter@softwolves.pp.se>, 2008-2009. # Mikael Magnusson <mikachu@gmail.com>, 2008. msgid "" msgstr "" "Project-Id-Version: sv\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-10-18 22:03+1100\n" -"PO-Revision-Date: 2008-08-03 19:03+0200\n" -"Last-Translator: Mikael Magnusson <mikachu@gmail.com>\n" -"Language-Team: Swedish <sv@li.org>\n" +"POT-Creation-Date: 2009-08-13 13:38+0100\n" +"PO-Revision-Date: 2009-08-13 13:40+0100\n" +"Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n" +"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" +"Content-Transfer-Encoding: 8bit" #: gitk:113 msgid "Couldn't get list of unmerged files:" -msgstr "Kunde inta hämta lista över ej sammanslagna filer:" +msgstr "Kunde inte hämta lista över ej sammanslagna filer:" -#: gitk:340 +#: gitk:269 +msgid "Error parsing revisions:" +msgstr "Fel vid tolkning av revisioner:" + +#: gitk:324 +msgid "Error executing --argscmd command:" +msgstr "Fel vid körning av --argscmd-kommando:" + +#: gitk:337 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "Inga filer valdes: --merge angavs men det finns inga filer som inte har " "slagits samman." -#: gitk:343 +#: gitk:340 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." @@ -34,261 +42,290 @@ msgstr "" "Inga filer valdes: --merge angavs men det finns inga filer inom " "filbegränsningen." -#: gitk:365 gitk:503 +#: gitk:362 gitk:509 msgid "Error executing git log:" msgstr "Fel vid körning av git log:" -#: gitk:378 +#: gitk:380 gitk:525 msgid "Reading" msgstr "Läser" -#: gitk:438 gitk:3462 +#: gitk:440 gitk:4123 msgid "Reading commits..." msgstr "Läser incheckningar..." -#: gitk:441 gitk:1528 gitk:3465 +#: gitk:443 gitk:1561 gitk:4126 msgid "No commits selected" msgstr "Inga incheckningar markerade" -#: gitk:1399 +#: gitk:1437 msgid "Can't parse git log output:" msgstr "Kan inte tolka utdata från git log:" -#: gitk:1605 +#: gitk:1657 msgid "No commit information available" msgstr "Ingen incheckningsinformation är tillgänglig" -#: gitk:1709 gitk:1731 gitk:3259 gitk:7764 gitk:9293 gitk:9466 +#: gitk:1793 gitk:1817 gitk:3916 gitk:8786 gitk:10322 gitk:10498 msgid "OK" msgstr "OK" -#: gitk:1733 gitk:3260 gitk:7439 gitk:7510 gitk:7613 gitk:7660 gitk:7766 -#: gitk:9294 gitk:9467 +#: gitk:1819 gitk:3918 gitk:8383 gitk:8457 gitk:8567 gitk:8616 gitk:8788 +#: gitk:10323 gitk:10499 msgid "Cancel" msgstr "Avbryt" -#: gitk:1811 +#: gitk:1919 msgid "Update" msgstr "Uppdatera" -#: gitk:1812 +#: gitk:1920 msgid "Reload" msgstr "Ladda om" -#: gitk:1813 +#: gitk:1921 msgid "Reread references" msgstr "Läs om referenser" -#: gitk:1814 +#: gitk:1922 msgid "List references" msgstr "Visa referenser" -#: gitk:1815 +#: gitk:1924 +msgid "Start git gui" +msgstr "Starta git gui" + +#: gitk:1926 msgid "Quit" msgstr "Avsluta" -#: gitk:1810 +#: gitk:1918 msgid "File" msgstr "Arkiv" -#: gitk:1818 +#: gitk:1930 msgid "Preferences" msgstr "Inställningar" -#: gitk:1817 +#: gitk:1929 msgid "Edit" msgstr "Redigera" -#: gitk:1821 +#: gitk:1934 msgid "New view..." msgstr "Ny vy..." -#: gitk:1822 +#: gitk:1935 msgid "Edit view..." msgstr "Ändra vy..." -#: gitk:1823 +#: gitk:1936 msgid "Delete view" msgstr "Ta bort vy" -#: gitk:1825 +#: gitk:1938 msgid "All files" msgstr "Alla filer" -#: gitk:1820 gitk:3196 +#: gitk:1933 gitk:3670 msgid "View" msgstr "Visa" -#: gitk:1828 gitk:2487 +#: gitk:1943 gitk:1953 gitk:2654 msgid "About gitk" msgstr "Om gitk" -#: gitk:1829 +#: gitk:1944 gitk:1958 msgid "Key bindings" msgstr "Tangentbordsbindningar" -#: gitk:1827 +#: gitk:1942 gitk:1957 msgid "Help" msgstr "Hjälp" -#: gitk:1887 +#: gitk:2018 msgid "SHA1 ID: " msgstr "SHA1-id: " -#: gitk:1918 +#: gitk:2049 msgid "Row" msgstr "Rad" -#: gitk:1949 +#: gitk:2080 msgid "Find" msgstr "Sök" -#: gitk:1950 +#: gitk:2081 msgid "next" msgstr "nästa" -#: gitk:1951 +#: gitk:2082 msgid "prev" msgstr "föreg" -#: gitk:1952 +#: gitk:2083 msgid "commit" msgstr "incheckning" -#: gitk:1955 gitk:1957 gitk:3617 gitk:3640 gitk:3664 gitk:5550 gitk:5621 +#: gitk:2086 gitk:2088 gitk:4284 gitk:4307 gitk:4331 gitk:6272 gitk:6344 +#: gitk:6428 msgid "containing:" msgstr "som innehåller:" -#: gitk:1958 gitk:2954 gitk:2959 gitk:3692 +#: gitk:2089 gitk:3162 gitk:3167 gitk:4359 msgid "touching paths:" msgstr "som rör sökväg:" -#: gitk:1959 gitk:3697 +#: gitk:2090 gitk:4364 msgid "adding/removing string:" msgstr "som lägger/till tar bort sträng:" -#: gitk:1968 gitk:1970 +#: gitk:2099 gitk:2101 msgid "Exact" msgstr "Exakt" -#: gitk:1970 gitk:3773 gitk:5518 +#: gitk:2101 gitk:4439 gitk:6240 msgid "IgnCase" msgstr "IgnVersaler" -#: gitk:1970 gitk:3666 gitk:3771 gitk:5514 +#: gitk:2101 gitk:4333 gitk:4437 gitk:6236 msgid "Regexp" msgstr "Reg.uttr." -#: gitk:1972 gitk:1973 gitk:3792 gitk:3822 gitk:3829 gitk:5641 gitk:5708 +#: gitk:2103 gitk:2104 gitk:4458 gitk:4488 gitk:4495 gitk:6364 gitk:6432 msgid "All fields" msgstr "Alla fält" -#: gitk:1973 gitk:3790 gitk:3822 gitk:5580 +#: gitk:2104 gitk:4456 gitk:4488 gitk:6303 msgid "Headline" msgstr "Rubrik" -#: gitk:1974 gitk:3790 gitk:5580 gitk:5708 gitk:6109 +#: gitk:2105 gitk:4456 gitk:6303 gitk:6432 gitk:6866 msgid "Comments" msgstr "Kommentarer" -#: gitk:1974 gitk:3790 gitk:3794 gitk:3829 gitk:5580 gitk:6045 gitk:7285 -#: gitk:7300 +#: gitk:2105 gitk:4456 gitk:4460 gitk:4495 gitk:6303 gitk:6801 gitk:8063 +#: gitk:8078 msgid "Author" msgstr "Författare" -#: gitk:1974 gitk:3790 gitk:5580 gitk:6047 +#: gitk:2105 gitk:4456 gitk:6303 gitk:6803 msgid "Committer" msgstr "Incheckare" -#: gitk:2003 +#: gitk:2134 msgid "Search" msgstr "Sök" -#: gitk:2010 +#: gitk:2141 msgid "Diff" msgstr "Diff" -#: gitk:2012 +#: gitk:2143 msgid "Old version" msgstr "Gammal version" -#: gitk:2014 +#: gitk:2145 msgid "New version" msgstr "Ny version" -#: gitk:2016 +#: gitk:2147 msgid "Lines of context" msgstr "Rader sammanhang" -#: gitk:2026 +#: gitk:2157 msgid "Ignore space change" msgstr "Ignorera ändringar i blanksteg" -#: gitk:2084 +#: gitk:2215 msgid "Patch" msgstr "Patch" -#: gitk:2086 +#: gitk:2217 msgid "Tree" msgstr "Träd" -#: gitk:2213 gitk:2226 +#: gitk:2361 gitk:2378 msgid "Diff this -> selected" msgstr "Diff denna -> markerad" -#: gitk:2214 gitk:2227 +#: gitk:2362 gitk:2379 msgid "Diff selected -> this" msgstr "Diff markerad -> denna" -#: gitk:2215 gitk:2228 +#: gitk:2363 gitk:2380 msgid "Make patch" msgstr "Skapa patch" -#: gitk:2216 gitk:7494 +#: gitk:2364 gitk:8441 msgid "Create tag" msgstr "Skapa tagg" -#: gitk:2217 gitk:7593 +#: gitk:2365 gitk:8547 msgid "Write commit to file" msgstr "Skriv incheckning till fil" -#: gitk:2218 gitk:7647 +#: gitk:2366 gitk:8604 msgid "Create new branch" msgstr "Skapa ny gren" -#: gitk:2219 +#: gitk:2367 msgid "Cherry-pick this commit" msgstr "Plocka denna incheckning" -#: gitk:2220 +#: gitk:2368 msgid "Reset HEAD branch to here" msgstr "Återställ HEAD-grenen hit" -#: gitk:2234 +#: gitk:2369 +msgid "Mark this commit" +msgstr "Markera denna incheckning" + +#: gitk:2370 +msgid "Return to mark" +msgstr "Återgå till markering" + +#: gitk:2371 +msgid "Find descendant of this and mark" +msgstr "Hitta efterföljare till denna och markera" + +#: gitk:2372 +msgid "Compare with marked commit" +msgstr "Jämför med markerad incheckning" + +#: gitk:2386 msgid "Check out this branch" msgstr "Checka ut denna gren" -#: gitk:2235 +#: gitk:2387 msgid "Remove this branch" msgstr "Ta bort denna gren" -#: gitk:2242 +#: gitk:2394 msgid "Highlight this too" msgstr "Markera även detta" -#: gitk:2243 +#: gitk:2395 msgid "Highlight this only" msgstr "Markera bara detta" -#: gitk:2244 +#: gitk:2396 msgid "External diff" msgstr "Extern diff" -#: gitk:2245 +#: gitk:2397 msgid "Blame parent commit" -msgstr "" +msgstr "Klandra föräldraincheckning" -#: gitk:2488 +#: gitk:2404 +msgid "Show origin of this line" +msgstr "Visa ursprunget för den här raden" + +#: gitk:2405 +msgid "Run git gui blame on this line" +msgstr "Kör git gui blame på den här raden" + +#: gitk:2656 msgid "" "\n" "Gitk - a commit viewer for git\n" @@ -304,427 +341,672 @@ msgstr "" "\n" "Använd och vidareförmedla enligt villkoren i GNU General Public License" -#: gitk:2496 gitk:2557 gitk:7943 +#: gitk:2664 gitk:2726 gitk:8969 msgid "Close" msgstr "Stäng" -#: gitk:2515 +#: gitk:2683 msgid "Gitk key bindings" msgstr "Tangentbordsbindningar för Gitk" -#: gitk:2517 +#: gitk:2686 msgid "Gitk key bindings:" msgstr "Tangentbordsbindningar för Gitk:" -#: gitk:2519 +#: gitk:2688 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tAvsluta" -#: gitk:2520 +#: gitk:2689 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\tGå till första incheckning" -#: gitk:2521 +#: gitk:2690 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\tGå till sista incheckning" -#: gitk:2522 +#: gitk:2691 msgid "<Up>, p, i\tMove up one commit" msgstr "<Upp>, p, i\tGå en incheckning upp" -#: gitk:2523 +#: gitk:2692 msgid "<Down>, n, k\tMove down one commit" msgstr "<Ned>, n, k\tGå en incheckning ned" -#: gitk:2524 +#: gitk:2693 msgid "<Left>, z, j\tGo back in history list" msgstr "<Vänster>, z, j\tGå bakåt i historiken" -#: gitk:2525 +#: gitk:2694 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Höger>, x, l\tGå framåt i historiken" -#: gitk:2526 +#: gitk:2695 msgid "<PageUp>\tMove up one page in commit list" msgstr "<PageUp>\tGå upp en sida i incheckningslistan" -#: gitk:2527 +#: gitk:2696 msgid "<PageDown>\tMove down one page in commit list" msgstr "<PageDown>\tGå ned en sida i incheckningslistan" -#: gitk:2528 +#: gitk:2697 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tRulla till början av incheckningslistan" -#: gitk:2529 +#: gitk:2698 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tRulla till slutet av incheckningslistan" -#: gitk:2530 +#: gitk:2699 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Upp>\tRulla incheckningslistan upp ett steg" -#: gitk:2531 +#: gitk:2700 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Ned>\tRulla incheckningslistan ned ett steg" -#: gitk:2532 +#: gitk:2701 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PageUp>\tRulla incheckningslistan upp en sida" -#: gitk:2533 +#: gitk:2702 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PageDown>\tRulla incheckningslistan ned en sida" -#: gitk:2534 +#: gitk:2703 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Skift-Upp>\tSök bakåt (uppåt, senare incheckningar)" -#: gitk:2535 +#: gitk:2704 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Skift-Ned>\tSök framåt (nedåt, tidigare incheckningar)" -#: gitk:2536 +#: gitk:2705 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Delete>, b\tRulla diffvisningen upp en sida" -#: gitk:2537 +#: gitk:2706 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Baksteg>\tRulla diffvisningen upp en sida" -#: gitk:2538 +#: gitk:2707 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Blanksteg>\tRulla diffvisningen ned en sida" -#: gitk:2539 +#: gitk:2708 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tRulla diffvisningen upp 18 rader" -#: gitk:2540 +#: gitk:2709 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tRulla diffvisningen ned 18 rader" -#: gitk:2541 +#: gitk:2710 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tSök" -#: gitk:2542 +#: gitk:2711 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tGå till nästa sökträff" -#: gitk:2543 +#: gitk:2712 msgid "<Return>\tMove to next find hit" msgstr "<Return>\t\tGå till nästa sökträff" -#: gitk:2544 -msgid "/\t\tMove to next find hit, or redo find" -msgstr "/\t\tGå till nästa sökträff, eller sök på nytt" +#: gitk:2713 +msgid "/\t\tFocus the search box" +msgstr "/\t\tFokusera sökrutan" -#: gitk:2545 +#: gitk:2714 msgid "?\t\tMove to previous find hit" msgstr "?\t\tGå till föregående sökträff" -#: gitk:2546 +#: gitk:2715 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tRulla diffvisningen till nästa fil" -#: gitk:2547 +#: gitk:2716 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tGå till nästa sökträff i diffvisningen" -#: gitk:2548 +#: gitk:2717 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tGå till föregående sökträff i diffvisningen" -#: gitk:2549 +#: gitk:2718 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-Num+>\tÖka teckenstorlek" -#: gitk:2550 +#: gitk:2719 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\tÖka teckenstorlek" -#: gitk:2551 +#: gitk:2720 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-Num->\tMinska teckenstorlek" -#: gitk:2552 +#: gitk:2721 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\tMinska teckenstorlek" -#: gitk:2553 +#: gitk:2722 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tUppdatera" -#: gitk:3200 +#: gitk:3177 +#, tcl-format +msgid "Error getting \"%s\" from %s:" +msgstr "Fel vid hämtning av \"%s\" från %s:" + +#: gitk:3234 gitk:3243 +#, tcl-format +msgid "Error creating temporary directory %s:" +msgstr "Fel vid skapande av temporär katalog %s:" + +#: gitk:3255 +msgid "command failed:" +msgstr "kommando misslyckades:" + +#: gitk:3401 +msgid "No such commit" +msgstr "Incheckning saknas" + +#: gitk:3415 +msgid "git gui blame: command failed:" +msgstr "git gui blame: kommando misslyckades:" + +#: gitk:3446 +#, tcl-format +msgid "Couldn't read merge head: %s" +msgstr "Kunde inte läsa sammanslagningshuvud: %s" + +#: gitk:3454 +#, tcl-format +msgid "Error reading index: %s" +msgstr "Fel vid läsning av index: %s" + +#: gitk:3479 +#, tcl-format +msgid "Couldn't start git blame: %s" +msgstr "Kunde inte starta git blame: %s" + +#: gitk:3482 gitk:6271 +msgid "Searching" +msgstr "Söker" + +#: gitk:3514 +#, tcl-format +msgid "Error running git blame: %s" +msgstr "Fel vid körning av git blame: %s" + +#: gitk:3542 +#, tcl-format +msgid "That line comes from commit %s, which is not in this view" +msgstr "Raden kommer från incheckningen %s, som inte finns i denna vy" + +#: gitk:3556 +msgid "External diff viewer failed:" +msgstr "Externt diff-verktyg misslyckades:" + +#: gitk:3674 msgid "Gitk view definition" msgstr "Definition av Gitk-vy" -#: gitk:3225 -msgid "Name" -msgstr "Namn" - -#: gitk:3228 +#: gitk:3678 msgid "Remember this view" msgstr "Spara denna vy" -#: gitk:3232 -msgid "Commits to include (arguments to git log):" -msgstr "Incheckningar att ta med (argument till git log):" +#: gitk:3679 +msgid "References (space separated list):" +msgstr "Referenser (blankstegsavdelad lista):" -#: gitk:3239 -msgid "Command to generate more commits to include:" -msgstr "Kommando för att generera fler incheckningar att ta med:" +#: gitk:3680 +msgid "Branches & tags:" +msgstr "Grenar & taggar:" + +#: gitk:3681 +msgid "All refs" +msgstr "Alla referenser" + +#: gitk:3682 +msgid "All (local) branches" +msgstr "Alla (lokala) grenar" + +#: gitk:3683 +msgid "All tags" +msgstr "Alla taggar" + +#: gitk:3684 +msgid "All remote-tracking branches" +msgstr "Alla fjärrspårande grenar" + +#: gitk:3685 +msgid "Commit Info (regular expressions):" +msgstr "Incheckningsinfo (reguljära uttryck):" -#: gitk:3246 +#: gitk:3686 +msgid "Author:" +msgstr "Författare:" + +#: gitk:3687 +msgid "Committer:" +msgstr "Incheckare:" + +#: gitk:3688 +msgid "Commit Message:" +msgstr "Incheckningsmeddelande:" + +#: gitk:3689 +msgid "Matches all Commit Info criteria" +msgstr "Motsvarar alla kriterier för incheckningsinfo" + +#: gitk:3690 +msgid "Changes to Files:" +msgstr "Ändringar av filer:" + +#: gitk:3691 +msgid "Fixed String" +msgstr "Fast sträng" + +#: gitk:3692 +msgid "Regular Expression" +msgstr "Reguljärt uttryck" + +#: gitk:3693 +msgid "Search string:" +msgstr "Söksträng:" + +#: gitk:3694 +msgid "" +"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" +msgstr "" +"Incheckingsdatum (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" + +#: gitk:3695 +msgid "Since:" +msgstr "Från:" + +#: gitk:3696 +msgid "Until:" +msgstr "Till:" + +#: gitk:3697 +msgid "Limit and/or skip a number of revisions (positive integer):" +msgstr "Begränsa och/eller hoppa över ett antal revisioner (positivt heltal):" + +#: gitk:3698 +msgid "Number to show:" +msgstr "Antal att visa:" + +#: gitk:3699 +msgid "Number to skip:" +msgstr "Antal att hoppa över:" + +#: gitk:3700 +msgid "Miscellaneous options:" +msgstr "Diverse alternativ:" + +#: gitk:3701 +msgid "Strictly sort by date" +msgstr "Strikt datumsortering" + +#: gitk:3702 +msgid "Mark branch sides" +msgstr "Markera sidogrenar" + +#: gitk:3703 +msgid "Limit to first parent" +msgstr "Begränsa till första förälder" + +#: gitk:3704 +msgid "Simple history" +msgstr "Enkel historik" + +#: gitk:3705 +msgid "Additional arguments to git log:" +msgstr "Ytterligare argument till git log:" + +#: gitk:3706 msgid "Enter files and directories to include, one per line:" msgstr "Ange filer och kataloger att ta med, en per rad:" -#: gitk:3293 +#: gitk:3707 +msgid "Command to generate more commits to include:" +msgstr "Kommando för att generera fler incheckningar att ta med:" + +#: gitk:3829 +msgid "Gitk: edit view" +msgstr "Gitk: redigera vy" + +#: gitk:3837 +msgid "-- criteria for selecting revisions" +msgstr " - kriterier för val av revisioner" + +#: gitk:3842 +msgid "View Name:" +msgstr "Namn på vy:" + +#: gitk:3917 +msgid "Apply (F5)" +msgstr "Använd (F5)" + +#: gitk:3955 msgid "Error in commit selection arguments:" msgstr "Fel i argument för val av incheckningar:" -#: gitk:3347 gitk:3399 gitk:3842 gitk:3856 gitk:5060 gitk:10141 gitk:10142 +#: gitk:4008 gitk:4060 gitk:4508 gitk:4522 gitk:5783 gitk:11196 gitk:11197 msgid "None" msgstr "Inget" -#: gitk:3790 gitk:5580 gitk:7287 gitk:7302 +#: gitk:4456 gitk:6303 gitk:8065 gitk:8080 msgid "Date" msgstr "Datum" -#: gitk:3790 gitk:5580 +#: gitk:4456 gitk:6303 msgid "CDate" msgstr "Skapat datum" -#: gitk:3939 gitk:3944 +#: gitk:4605 gitk:4610 msgid "Descendant" msgstr "Avkomling" -#: gitk:3940 +#: gitk:4606 msgid "Not descendant" msgstr "Inte avkomling" -#: gitk:3947 gitk:3952 +#: gitk:4613 gitk:4618 msgid "Ancestor" msgstr "Förfader" -#: gitk:3948 +#: gitk:4614 msgid "Not ancestor" msgstr "Inte förfader" -#: gitk:4187 +#: gitk:4904 msgid "Local changes checked in to index but not committed" msgstr "Lokala ändringar sparade i indexet men inte incheckade" -#: gitk:4220 +#: gitk:4940 msgid "Local uncommitted changes, not checked in to index" msgstr "Lokala ändringar, ej sparade i indexet" -#: gitk:5549 -msgid "Searching" -msgstr "Söker" +#: gitk:6621 +msgid "many" +msgstr "många" -#: gitk:6049 +#: gitk:6805 msgid "Tags:" msgstr "Taggar:" -#: gitk:6066 gitk:6072 gitk:7280 +#: gitk:6822 gitk:6828 gitk:8058 msgid "Parent" msgstr "Förälder" -#: gitk:6077 +#: gitk:6833 msgid "Child" msgstr "Barn" -#: gitk:6086 +#: gitk:6842 msgid "Branch" msgstr "Gren" -#: gitk:6089 +#: gitk:6845 msgid "Follows" msgstr "Följer" -#: gitk:6092 +#: gitk:6848 msgid "Precedes" msgstr "Föregår" -#: gitk:6378 -msgid "Error getting merge diffs:" -msgstr "Fel vid hämtning av sammanslagningsdiff:" +#: gitk:7346 +#, tcl-format +msgid "Error getting diffs: %s" +msgstr "Fel vid hämtning av diff: %s" -#: gitk:7113 +#: gitk:7886 msgid "Goto:" msgstr "Gå till:" -#: gitk:7115 +#: gitk:7888 msgid "SHA1 ID:" msgstr "SHA1-id:" -#: gitk:7134 +#: gitk:7907 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "Förkortat SHA1-id %s är tvetydigt" -#: gitk:7146 +#: gitk:7914 +#, tcl-format +msgid "Revision %s is not known" +msgstr "Revisionen %s är inte känd" + +#: gitk:7924 #, tcl-format msgid "SHA1 id %s is not known" msgstr "SHA-id:t %s är inte känt" -#: gitk:7148 +#: gitk:7926 #, tcl-format -msgid "Tag/Head %s is not known" -msgstr "Tagg/huvud %s är okänt" +msgid "Revision %s is not in the current view" +msgstr "Revisionen %s finns inte i den nuvarande vyn" -#: gitk:7290 +#: gitk:8068 msgid "Children" msgstr "Barn" -#: gitk:7347 +#: gitk:8125 #, tcl-format msgid "Reset %s branch to here" msgstr "Återställ grenen %s hit" -#: gitk:7349 +#: gitk:8127 msgid "Detached head: can't reset" msgstr "Frånkopplad head: kan inte återställa" -#: gitk:7381 +#: gitk:8236 gitk:8242 +msgid "Skipping merge commit " +msgstr "Hoppar över sammanslagningsincheckning " + +#: gitk:8251 gitk:8256 +msgid "Error getting patch ID for " +msgstr "Fel vid hämtning av patch-id för " + +#: gitk:8252 gitk:8257 +msgid " - stopping\n" +msgstr " - stannar\n" + +#: gitk:8262 gitk:8265 gitk:8273 gitk:8283 gitk:8292 +msgid "Commit " +msgstr "Incheckning " + +#: gitk:8266 +msgid "" +" is the same patch as\n" +" " +msgstr "" +" är samma patch som\n" +" " + +#: gitk:8274 +msgid "" +" differs from\n" +" " +msgstr "" +" skiljer sig från\n" +" " + +#: gitk:8276 +msgid "- stopping\n" +msgstr "- stannar\n" + +#: gitk:8284 gitk:8293 +#, tcl-format +msgid " has %s children - stopping\n" +msgstr " har %s barn - stannar\n" + +#: gitk:8324 msgid "Top" msgstr "Topp" -#: gitk:7382 +#: gitk:8325 msgid "From" msgstr "Från" -#: gitk:7387 +#: gitk:8330 msgid "To" msgstr "Till" -#: gitk:7410 +#: gitk:8354 msgid "Generate patch" msgstr "Generera patch" -#: gitk:7412 +#: gitk:8356 msgid "From:" msgstr "Från:" -#: gitk:7421 +#: gitk:8365 msgid "To:" msgstr "Till:" -#: gitk:7430 +#: gitk:8374 msgid "Reverse" msgstr "Vänd" -#: gitk:7432 gitk:7607 +#: gitk:8376 gitk:8561 msgid "Output file:" msgstr "Utdatafil:" -#: gitk:7438 +#: gitk:8382 msgid "Generate" msgstr "Generera" -#: gitk:7474 +#: gitk:8420 msgid "Error creating patch:" msgstr "Fel vid generering av patch:" -#: gitk:7496 gitk:7595 gitk:7649 +#: gitk:8443 gitk:8549 gitk:8606 msgid "ID:" msgstr "Id:" -#: gitk:7505 +#: gitk:8452 msgid "Tag name:" msgstr "Taggnamn:" -#: gitk:7509 gitk:7659 +#: gitk:8456 gitk:8615 msgid "Create" msgstr "Skapa" -#: gitk:7524 +#: gitk:8473 msgid "No tag name specified" msgstr "Inget taggnamn angavs" -#: gitk:7528 +#: gitk:8477 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "Taggen \"%s\" finns redan" -#: gitk:7534 +#: gitk:8483 msgid "Error creating tag:" msgstr "Fel vid skapande av tagg:" -#: gitk:7604 +#: gitk:8558 msgid "Command:" msgstr "Kommando:" -#: gitk:7612 +#: gitk:8566 msgid "Write" msgstr "Skriv" -#: gitk:7628 +#: gitk:8584 msgid "Error writing commit:" msgstr "Fel vid skrivning av incheckning:" -#: gitk:7654 +#: gitk:8611 msgid "Name:" msgstr "Namn:" -#: gitk:7674 +#: gitk:8634 msgid "Please specify a name for the new branch" msgstr "Ange ett namn för den nya grenen" -#: gitk:7703 +#: gitk:8639 +#, tcl-format +msgid "Branch '%s' already exists. Overwrite?" +msgstr "Grenen \"%s\" finns redan. Skriva över?" + +#: gitk:8705 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "" "Incheckningen %s finns redan på grenen %s -- skall den verkligen appliceras " "på nytt?" -#: gitk:7708 +#: gitk:8710 msgid "Cherry-picking" msgstr "Plockar" -#: gitk:7720 +#: gitk:8719 +#, tcl-format +msgid "" +"Cherry-pick failed because of local changes to file '%s'.\n" +"Please commit, reset or stash your changes and try again." +msgstr "" +"Cherry-pick misslyckades på grund av lokala ändringar i filen \"%s\".\n" +"Checka in, återställ eller spara undan (stash) dina ändringar och försök " +"igen." + +#: gitk:8725 +msgid "" +"Cherry-pick failed because of merge conflict.\n" +"Do you wish to run git citool to resolve it?" +msgstr "" +"Cherry-pick misslyckades på grund av en sammanslagningskonflikt.\n" +"Vill du köra git citool för att lösa den?" + +#: gitk:8741 msgid "No changes committed" msgstr "Inga ändringar incheckade" -#: gitk:7745 +#: gitk:8767 msgid "Confirm reset" msgstr "Bekräfta återställning" -#: gitk:7747 +#: gitk:8769 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Återställa grenen %s till %s?" -#: gitk:7751 +#: gitk:8773 msgid "Reset type:" msgstr "Typ av återställning:" -#: gitk:7755 +#: gitk:8777 msgid "Soft: Leave working tree and index untouched" msgstr "Mjuk: Rör inte utcheckning och index" -#: gitk:7758 +#: gitk:8780 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Blandad: Rör inte utcheckning, återställ index" -#: gitk:7761 +#: gitk:8783 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -732,19 +1014,19 @@ msgstr "" "Hård: Återställ utcheckning och index\n" "(förkastar ALLA lokala ändringar)" -#: gitk:7777 +#: gitk:8800 msgid "Resetting" msgstr "Återställer" -#: gitk:7834 +#: gitk:8857 msgid "Checking out" msgstr "Checkar ut" -#: gitk:7885 +#: gitk:8910 msgid "Cannot delete the currently checked-out branch" msgstr "Kan inte ta bort den just nu utcheckade grenen" -#: gitk:7891 +#: gitk:8916 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -753,16 +1035,16 @@ msgstr "" "Incheckningarna på grenen %s existerar inte på någon annan gren.\n" "Vill du verkligen ta bort grenen %s?" -#: gitk:7922 +#: gitk:8947 #, tcl-format msgid "Tags and heads: %s" msgstr "Taggar och huvuden: %s" -#: gitk:7936 +#: gitk:8962 msgid "Filter" msgstr "Filter" -#: gitk:8230 +#: gitk:9257 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -770,129 +1052,161 @@ msgstr "" "Fel vid läsning av information om incheckningstopologi; information om " "grenar och föregående/senare taggar kommer inte vara komplett." -#: gitk:9216 +#: gitk:10243 msgid "Tag" msgstr "Tagg" -#: gitk:9216 +#: gitk:10243 msgid "Id" msgstr "Id" -#: gitk:9262 +#: gitk:10291 msgid "Gitk font chooser" msgstr "Teckensnittsväljare för Gitk" -#: gitk:9279 +#: gitk:10308 msgid "B" msgstr "F" -#: gitk:9282 +#: gitk:10311 msgid "I" msgstr "K" -#: gitk:9375 +#: gitk:10407 msgid "Gitk preferences" msgstr "Inställningar för Gitk" -#: gitk:9376 +#: gitk:10409 msgid "Commit list display options" msgstr "Alternativ för incheckningslistvy" -#: gitk:9379 +#: gitk:10412 msgid "Maximum graph width (lines)" msgstr "Maximal grafbredd (rader)" -#: gitk:9383 +#: gitk:10416 #, tcl-format msgid "Maximum graph width (% of pane)" msgstr "Maximal grafbredd (% av ruta)" -#: gitk:9388 +#: gitk:10420 msgid "Show local changes" msgstr "Visa lokala ändringar" -#: gitk:9393 +#: gitk:10423 msgid "Auto-select SHA1" msgstr "Välj SHA1 automatiskt" -#: gitk:9398 +#: gitk:10427 msgid "Diff display options" msgstr "Alternativ för diffvy" -#: gitk:9400 +#: gitk:10429 msgid "Tab spacing" msgstr "Blanksteg för tabulatortecken" -#: gitk:9404 +#: gitk:10432 msgid "Display nearby tags" msgstr "Visa närliggande taggar" -#: gitk:9409 +#: gitk:10435 +msgid "Hide remote refs" +msgstr "Dölj fjärr-referenser" + +#: gitk:10438 msgid "Limit diffs to listed paths" msgstr "Begränsa diff till listade sökvägar" -#: gitk:9414 +#: gitk:10441 msgid "Support per-file encodings" -msgstr "" +msgstr "Stöd för filspecifika teckenkodningar" -#: gitk:9421 +#: gitk:10447 gitk:10512 msgid "External diff tool" msgstr "Externt diff-verktyg" -#: gitk:9423 +#: gitk:10449 msgid "Choose..." msgstr "Välj..." -#: gitk:9428 +#: gitk:10454 msgid "Colors: press to choose" msgstr "Färger: tryck för att välja" -#: gitk:9431 +#: gitk:10457 msgid "Background" msgstr "Bakgrund" -#: gitk:9435 +#: gitk:10458 gitk:10488 +msgid "background" +msgstr "bakgrund" + +#: gitk:10461 msgid "Foreground" msgstr "Förgrund" -#: gitk:9439 +#: gitk:10462 +msgid "foreground" +msgstr "förgrund" + +#: gitk:10465 msgid "Diff: old lines" msgstr "Diff: gamla rader" -#: gitk:9444 +#: gitk:10466 +msgid "diff old lines" +msgstr "diff gamla rader" + +#: gitk:10470 msgid "Diff: new lines" msgstr "Diff: nya rader" -#: gitk:9449 +#: gitk:10471 +msgid "diff new lines" +msgstr "diff nya rader" + +#: gitk:10475 msgid "Diff: hunk header" msgstr "Diff: delhuvud" -#: gitk:9455 +#: gitk:10477 +msgid "diff hunk header" +msgstr "diff delhuvud" + +#: gitk:10481 +msgid "Marked line bg" +msgstr "Markerad rad bakgrund" + +#: gitk:10483 +msgid "marked line background" +msgstr "markerad rad bakgrund" + +#: gitk:10487 msgid "Select bg" msgstr "Markerad bakgrund" -#: gitk:9459 +#: gitk:10491 msgid "Fonts: press to choose" msgstr "Teckensnitt: tryck för att välja" -#: gitk:9461 +#: gitk:10493 msgid "Main font" msgstr "Huvudteckensnitt" -#: gitk:9462 +#: gitk:10494 msgid "Diff display font" msgstr "Teckensnitt för diffvisning" -#: gitk:9463 +#: gitk:10495 msgid "User interface font" msgstr "Teckensnitt för användargränssnitt" -#: gitk:9488 +#: gitk:10522 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: välj färg för %s" -#: gitk:9934 +#: gitk:10973 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." @@ -900,24 +1214,33 @@ msgstr "" "Gitk kan tyvärr inte köra med denna version av Tcl/Tk.\n" " Gitk kräver åtminstone Tcl/Tk 8.4." -#: gitk:10047 +#: gitk:11101 msgid "Cannot find a git repository here." msgstr "Hittar inget gitk-arkiv här." -#: gitk:10051 +#: gitk:11105 #, tcl-format msgid "Cannot find the git directory \"%s\"." msgstr "Hittar inte git-katalogen \"%s\"." -#: gitk:10098 +#: gitk:11152 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Tvetydigt argument \"%s\": både revision och filnamn" -#: gitk:10110 +#: gitk:11164 msgid "Bad arguments to gitk:" msgstr "Felaktiga argument till gitk:" -#: gitk:10170 +#: gitk:11249 msgid "Command line" msgstr "Kommandorad" + +#~ msgid "Tag/Head %s is not known" +#~ msgstr "Tagg/huvud %s är okänt" + +#~ msgid "/\t\tMove to next find hit, or redo find" +#~ msgstr "/\t\tGå till nästa sökträff, eller sök på nytt" + +#~ msgid "Name" +#~ msgstr "Namn" diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 8f68fe3091..cb3f0baf05 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -32,6 +32,10 @@ img.avatar { vertical-align: middle; } +a.list img.avatar { + border-style: none; +} + div.page_header { height: 25px; padding: 8px; @@ -341,6 +345,12 @@ td.mode { font-family: monospace; } +/* format of (optional) objects size in 'tree' view */ +td.size { + font-family: monospace; + text-align: right; +} + /* styling of diffs (patchsets): commitdiff and blobdiff views */ div.diff.header, div.diff.extended_header { diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 24b219310a..681e635090 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -297,6 +297,19 @@ our %feature = ( 'override' => 0, 'default' => [1]}, + # Enable showing size of blobs in a 'tree' view, in a separate + # column, similar to what 'ls -l' does. This cost a bit of IO. + + # To disable system wide have in $GITWEB_CONFIG + # $feature{'show-sizes'}{'default'} = [0]; + # To have project specific config enable override in $GITWEB_CONFIG + # $feature{'show-sizes'}{'override'} = 1; + # and in project config gitweb.showsizes = 0|1; + 'show-sizes' => { + 'sub' => sub { feature_bool('showsizes', @_) }, + 'override' => 0, + 'default' => [1]}, + # Make gitweb use an alternative format of the URLs which can be # more readable and natural-looking: project name is embedded # directly in the path and the query string contains other @@ -1083,8 +1096,7 @@ sub to_utf8 { # correct, but quoted slashes look too horrible in bookmarks sub esc_param { my $str = shift; - $str =~ s/([^A-Za-z0-9\-_.~()\/:@])/sprintf("%%%02X", ord($1))/eg; - $str =~ s/\+/%2B/g; + $str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg; $str =~ s/ /\+/g; return $str; } @@ -1595,6 +1607,29 @@ sub git_get_avatar { } } +sub format_search_author { + my ($author, $searchtype, $displaytext) = @_; + my $have_search = gitweb_check_feature('search'); + + if ($have_search) { + my $performed = ""; + if ($searchtype eq 'author') { + $performed = "authored"; + } elsif ($searchtype eq 'committer') { + $performed = "committed"; + } + + return $cgi->a({-href => href(action=>"search", hash=>$hash, + searchtext=>$author, + searchtype=>$searchtype), class=>"list", + title=>"Search for commits $performed by $author"}, + $displaytext); + + } else { + return $displaytext; + } +} + # format the author name of the given commit with the given tag # the author name is chopped and escaped according to the other # optional parameters (see chop_str). @@ -1603,8 +1638,10 @@ sub format_author_html { my $co = shift; my $author = chop_and_escape_str($co->{'author_name'}, @_); return "<$tag class=\"author\">" . - git_get_avatar($co->{'author_email'}, -pad_after => 1) . - $author . "</$tag>"; + format_search_author($co->{'author_name'}, "author", + git_get_avatar($co->{'author_email'}, -pad_after => 1) . + $author) . + "</$tag>"; } # format git diff header line, i.e. "diff --(git|combined|cc) ..." @@ -2764,16 +2801,31 @@ sub parse_ls_tree_line { my %opts = @_; my %res; - #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s; + if ($opts{'-l'}) { + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa 16717 panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s; - $res{'mode'} = $1; - $res{'type'} = $2; - $res{'hash'} = $3; - if ($opts{'-z'}) { - $res{'name'} = $4; + $res{'mode'} = $1; + $res{'type'} = $2; + $res{'hash'} = $3; + $res{'size'} = $4; + if ($opts{'-z'}) { + $res{'name'} = $5; + } else { + $res{'name'} = unquote($5); + } } else { - $res{'name'} = unquote($4); + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s; + + $res{'mode'} = $1; + $res{'type'} = $2; + $res{'hash'} = $3; + if ($opts{'-z'}) { + $res{'name'} = $4; + } else { + $res{'name'} = unquote($4); + } } return wantarray ? %res : \%res; @@ -3311,22 +3363,18 @@ sub git_print_page_nav { } sub format_paging_nav { - my ($action, $hash, $head, $page, $has_next_link) = @_; + my ($action, $page, $has_next_link) = @_; my $paging_nav; - if ($hash ne $head || $page) { - $paging_nav .= $cgi->a({-href => href(action=>$action)}, "HEAD"); - } else { - $paging_nav .= "HEAD"; - } - if ($page > 0) { - $paging_nav .= " ⋅ " . + $paging_nav .= + $cgi->a({-href => href(-replay=>1, page=>undef)}, "first") . + " ⋅ " . $cgi->a({-href => href(-replay=>1, page=>$page-1), -accesskey => "p", -title => "Alt-p"}, "prev"); } else { - $paging_nav .= " ⋅ prev"; + $paging_nav .= "first ⋅ prev"; } if ($has_next_link) { @@ -3373,10 +3421,11 @@ sub git_print_authorship { my $co = shift; my %opts = @_; my $tag = $opts{-tag} || 'div'; + my $author = $co->{'author_name'}; my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'}); print "<$tag class=\"author_date\">" . - esc_html($co->{'author_name'}) . + format_search_author($author, "author", esc_html($author)) . " [$ad{'rfc2822'}"; print_local_time(%ad) if ($opts{-localtime}); print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1) @@ -3395,8 +3444,12 @@ sub git_print_authorship_rows { @people = ('author', 'committer') unless @people; foreach my $who (@people) { my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"}); - print "<tr><td>$who</td><td>" . esc_html($co->{$who}) . "</td>" . - "<td rowspan=\"2\">" . + print "<tr><td>$who</td><td>" . + format_search_author($co->{"${who}_name"}, $who, + esc_html($co->{"${who}_name"})) . " " . + format_search_author($co->{"${who}_email"}, $who, + esc_html("<" . $co->{"${who}_email"} . ">")) . + "</td><td rowspan=\"2\">" . git_get_avatar($co->{"${who}_email"}, -size => 'double') . "</td></tr>\n" . "<tr>" . @@ -3564,6 +3617,9 @@ sub git_print_tree_entry { # and link is the action links of the entry. print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n"; + if (exists $t->{'size'}) { + print "<td class=\"size\">$t->{'size'}</td>\n"; + } if ($t->{'type'} eq "blob") { print "<td class=\"list\">" . $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, @@ -3609,12 +3665,14 @@ sub git_print_tree_entry { } elsif ($t->{'type'} eq "tree") { print "<td class=\"list\">"; print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, + file_name=>"$basedir$t->{'name'}", + %base_key)}, esc_path($t->{'name'})); print "</td>\n"; print "<td class=\"link\">"; print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, + file_name=>"$basedir$t->{'name'}", + %base_key)}, "tree"); if (defined $hash_base) { print " | " . @@ -4299,6 +4357,46 @@ sub git_project_list_body { print "</table>\n"; } +sub git_log_body { + # uses global variable $project + my ($commitlist, $from, $to, $refs, $extra) = @_; + + $from = 0 unless defined $from; + $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to); + + for (my $i = 0; $i <= $to; $i++) { + my %co = %{$commitlist->[$i]}; + next if !%co; + my $commit = $co{'id'}; + my $ref = format_ref_marker($refs, $commit); + my %ad = parse_date($co{'author_epoch'}); + git_print_header_div('commit', + "<span class=\"age\">$co{'age_string'}</span>" . + esc_html($co{'title'}) . $ref, + $commit); + print "<div class=\"title_text\">\n" . + "<div class=\"log_link\">\n" . + $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . + " | " . + $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . + " | " . + $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") . + "<br/>\n" . + "</div>\n"; + git_print_authorship(\%co, -tag => 'span'); + print "<br/>\n</div>\n"; + + print "<div class=\"log_body\">\n"; + git_print_log($co{'comment'}, -final_empty_line=> 1); + print "</div>\n"; + } + if ($extra) { + print "<div class=\"page_nav\">\n"; + print "$extra\n"; + print "</div>\n"; + } +} + sub git_shortlog_body { # uses global variable $project my ($commitlist, $from, $to, $refs, $extra) = @_; @@ -4345,7 +4443,8 @@ sub git_shortlog_body { sub git_history_body { # Warning: assumes constant type (blob or tree) during history - my ($commitlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_; + my ($commitlist, $from, $to, $refs, $extra, + $file_name, $file_hash, $ftype) = @_; $from = 0 unless defined $from; $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist}); @@ -4379,7 +4478,7 @@ sub git_history_body { $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff"); if ($ftype eq 'blob') { - my $blob_current = git_get_hash_by_path($hash_base, $file_name); + my $blob_current = $file_hash; my $blob_parent = git_get_hash_by_path($commit, $file_name); if (defined $blob_current && defined $blob_parent && $blob_current ne $blob_parent) { @@ -5065,7 +5164,8 @@ sub git_blob { chomp $line; $nr++; $line = untabify($line); - printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n", + printf "<div class=\"pre\"><a id=\"l%i\" href=\"" . href(-replay => 1) + . "#l%i\" class=\"linenr\">%4i</a> %s</div>\n", $nr, $nr, $nr, esc_html($line, -nbsp=>1); } } @@ -5088,10 +5188,14 @@ sub git_tree { } die_error(404, "No such tree") unless defined($hash); + my $show_sizes = gitweb_check_feature('show-sizes'); + my $have_blame = gitweb_check_feature('blame'); + my @entries = (); { local $/ = "\0"; - open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash + open my $fd, "-|", git_cmd(), "ls-tree", '-z', + ($show_sizes ? '-l' : ()), @extra_options, $hash or die_error(500, "Open git-ls-tree failed"); @entries = map { chomp; $_ } <$fd>; close $fd @@ -5102,7 +5206,6 @@ sub git_tree { my $ref = format_ref_marker($refs, $hash_base); git_header_html(); my $basedir = ''; - my $have_blame = gitweb_check_feature('blame'); if (defined $hash_base && (my %co = parse_commit($hash_base))) { my @views_nav = (); if (defined $file_name) { @@ -5118,7 +5221,8 @@ sub git_tree { # FIXME: Should be available when we have no hash base as well. push @views_nav, $snapshot_links; } - git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav)); + git_print_page_nav('tree','', $hash_base, undef, undef, + join(' | ', @views_nav)); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); } else { undef $hash_base; @@ -5151,8 +5255,10 @@ sub git_tree { undef $up unless $up; # based on git_print_tree_entry print '<td class="mode">' . mode_str('040000') . "</td>\n"; + print '<td class="size"> </td>'."\n" if $show_sizes; print '<td class="list">'; - print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base, + print $cgi->a({-href => href(action=>"tree", + hash_base=>$hash_base, file_name=>$up)}, ".."); print "</td>\n"; @@ -5161,7 +5267,7 @@ sub git_tree { print "</tr>\n"; } foreach my $line (@entries) { - my %t = parse_ls_tree_line($line, -z => 1); + my %t = parse_ls_tree_line($line, -z => 1, -l => $show_sizes); if ($alternate) { print "<tr class=\"dark\">\n"; @@ -5228,22 +5334,57 @@ sub git_snapshot { close $fd; } -sub git_log { +sub git_log_generic { + my ($fmt_name, $body_subr, $base, $parent, $file_name, $file_hash) = @_; + my $head = git_get_head_hash($project); - if (!defined $hash) { - $hash = $head; + if (!defined $base) { + $base = $head; } if (!defined $page) { $page = 0; } my $refs = git_get_references(); - my @commitlist = parse_commits($hash, 101, (100 * $page)); + my $commit_hash = $base; + if (defined $parent) { + $commit_hash = "$parent..$base"; + } + my @commitlist = + parse_commits($commit_hash, 101, (100 * $page), + defined $file_name ? ($file_name, "--full-history") : ()); - my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#commitlist >= 100); + my $ftype; + if (!defined $file_hash && defined $file_name) { + # some commits could have deleted file in question, + # and not have it in tree, but one of them has to have it + for (my $i = 0; $i < @commitlist; $i++) { + $file_hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name); + last if defined $file_hash; + } + } + if (defined $file_hash) { + $ftype = git_get_type($file_hash); + } + if (defined $file_name && !defined $ftype) { + die_error(500, "Unknown type of object"); + } + my %co; + if (defined $file_name) { + %co = parse_commit($base) + or die_error(404, "Unknown commit object"); + } - my ($patch_max) = gitweb_get_feature('patches'); - if ($patch_max) { + + my $paging_nav = format_paging_nav($fmt_name, $page, $#commitlist >= 100); + my $next_link = ''; + if ($#commitlist >= 100) { + $next_link = + $cgi->a({-href => href(-replay=>1, page=>$page+1), + -accesskey => "n", -title => "Alt-n"}, "next"); + } + my $patch_max = gitweb_get_feature('patches'); + if ($patch_max && !defined $file_name) { if ($patch_max < 0 || @commitlist <= $patch_max) { $paging_nav .= " ⋅ " . $cgi->a({-href => href(action=>"patches", -replay=>1)}, @@ -5252,50 +5393,26 @@ sub git_log { } git_header_html(); - git_print_page_nav('log','', $hash,undef,undef, $paging_nav); - - if (!@commitlist) { - my %co = parse_commit($hash); - - git_print_header_div('summary', $project); - print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n"; + git_print_page_nav($fmt_name,'', $hash,$hash,$hash, $paging_nav); + if (defined $file_name) { + git_print_header_div('commit', esc_html($co{'title'}), $base); + } else { + git_print_header_div('summary', $project) } - my $to = ($#commitlist >= 99) ? (99) : ($#commitlist); - for (my $i = 0; $i <= $to; $i++) { - my %co = %{$commitlist[$i]}; - next if !%co; - my $commit = $co{'id'}; - my $ref = format_ref_marker($refs, $commit); - my %ad = parse_date($co{'author_epoch'}); - git_print_header_div('commit', - "<span class=\"age\">$co{'age_string'}</span>" . - esc_html($co{'title'}) . $ref, - $commit); - print "<div class=\"title_text\">\n" . - "<div class=\"log_link\">\n" . - $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . - " | " . - $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . - " | " . - $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") . - "<br/>\n" . - "</div>\n"; - git_print_authorship(\%co, -tag => 'span'); - print "<br/>\n</div>\n"; + git_print_page_path($file_name, $ftype, $hash_base) + if (defined $file_name); + + $body_subr->(\@commitlist, 0, 99, $refs, $next_link, + $file_name, $file_hash, $ftype); - print "<div class=\"log_body\">\n"; - git_print_log($co{'comment'}, -final_empty_line=> 1); - print "</div>\n"; - } - if ($#commitlist >= 100) { - print "<div class=\"page_nav\">\n"; - print $cgi->a({-href => href(-replay=>1, page=>$page+1), - -accesskey => "n", -title => "Alt-n"}, "next"); - print "</div>\n"; - } git_footer_html(); } +sub git_log { + git_log_generic('log', \&git_log_body, + $hash, $hash_parent); +} + sub git_commit { $hash ||= $hash_base || "HEAD"; my %co = parse_commit($hash) @@ -5328,7 +5445,7 @@ sub git_commit { } @$parents ) . ')'; } - if (gitweb_check_feature('patches')) { + if (gitweb_check_feature('patches') && @$parents <= 1) { $formats_nav .= " | " . $cgi->a({-href => href(action=>"patch", -replay=>1)}, "patch"); @@ -5616,7 +5733,7 @@ sub git_commitdiff { $formats_nav = $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)}, "raw"); - if ($patch_max) { + if ($patch_max && @{$co{'parents'}} <= 1) { $formats_nav .= " | " . $cgi->a({-href => href(action=>"patch", -replay=>1)}, "patch"); @@ -5824,7 +5941,7 @@ sub git_commitdiff_plain { # format-patch-style patches sub git_patch { - git_commitdiff(-format => 'patch', -single=> 1); + git_commitdiff(-format => 'patch', -single => 1); } sub git_patches { @@ -5832,70 +5949,9 @@ sub git_patches { } sub git_history { - if (!defined $hash_base) { - $hash_base = git_get_head_hash($project); - } - if (!defined $page) { - $page = 0; - } - my $ftype; - my %co = parse_commit($hash_base) - or die_error(404, "Unknown commit object"); - - my $refs = git_get_references(); - my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - - my @commitlist = parse_commits($hash_base, 101, (100 * $page), - $file_name, "--full-history") - or die_error(404, "No such file or directory on given branch"); - - if (!defined $hash && defined $file_name) { - # some commits could have deleted file in question, - # and not have it in tree, but one of them has to have it - for (my $i = 0; $i <= @commitlist; $i++) { - $hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name); - last if defined $hash; - } - } - if (defined $hash) { - $ftype = git_get_type($hash); - } - if (!defined $ftype) { - die_error(500, "Unknown type of object"); - } - - my $paging_nav = ''; - if ($page > 0) { - $paging_nav .= - $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, - file_name=>$file_name)}, - "first"); - $paging_nav .= " ⋅ " . - $cgi->a({-href => href(-replay=>1, page=>$page-1), - -accesskey => "p", -title => "Alt-p"}, "prev"); - } else { - $paging_nav .= "first"; - $paging_nav .= " ⋅ prev"; - } - my $next_link = ''; - if ($#commitlist >= 100) { - $next_link = - $cgi->a({-href => href(-replay=>1, page=>$page+1), - -accesskey => "n", -title => "Alt-n"}, "next"); - $paging_nav .= " ⋅ $next_link"; - } else { - $paging_nav .= " ⋅ next"; - } - - git_header_html(); - git_print_page_nav('history','', $hash_base,$co{'tree'},$hash_base, $paging_nav); - git_print_header_div('commit', esc_html($co{'title'}), $hash_base); - git_print_page_path($file_name, $ftype, $hash_base); - - git_history_body(\@commitlist, 0, 99, - $refs, $hash_base, $ftype, $next_link); - - git_footer_html(); + git_log_generic('history', \&git_history_body, + $hash_base, $hash_parent_base, + $file_name, $hash); } sub git_search { @@ -6159,44 +6215,8 @@ EOT } sub git_shortlog { - my $head = git_get_head_hash($project); - if (!defined $hash) { - $hash = $head; - } - if (!defined $page) { - $page = 0; - } - my $refs = git_get_references(); - - my $commit_hash = $hash; - if (defined $hash_parent) { - $commit_hash = "$hash_parent..$hash"; - } - my @commitlist = parse_commits($commit_hash, 101, (100 * $page)); - - my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#commitlist >= 100); - my $next_link = ''; - if ($#commitlist >= 100) { - $next_link = - $cgi->a({-href => href(-replay=>1, page=>$page+1), - -accesskey => "n", -title => "Alt-n"}, "next"); - } - my $patch_max = gitweb_check_feature('patches'); - if ($patch_max) { - if ($patch_max < 0 || @commitlist <= $patch_max) { - $paging_nav .= " ⋅ " . - $cgi->a({-href => href(action=>"patches", -replay=>1)}, - "patches"); - } - } - - git_header_html(); - git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); - git_print_header_div('summary', $project); - - git_shortlog_body(\@commitlist, 0, 99, $refs, $next_link); - - git_footer_html(); + git_log_generic('shortlog', \&git_shortlog_body, + $hash, $hash_parent); } ## ...................................................................... @@ -59,6 +59,7 @@ struct grep_opt { struct grep_pat *pattern_list; struct grep_pat **pattern_tail; struct grep_expr *pattern_expression; + const char *prefix; int prefix_length; regex_t regexp; int linenum; @@ -126,8 +126,8 @@ static int is_executable(const char *name) !S_ISREG(st.st_mode)) return 0; -#ifdef __MINGW32__ - /* cannot trust the executable bit, peek into the file instead */ +#ifdef WIN32 +{ /* cannot trust the executable bit, peek into the file instead */ char buf[3] = { 0 }; int n; int fd = open(name, O_RDONLY); @@ -140,6 +140,7 @@ static int is_executable(const char *name) st.st_mode |= S_IXUSR; close(fd); } +} #endif return st.st_mode & S_IXUSR; } diff --git a/http-backend.c b/http-backend.c new file mode 100644 index 0000000000..f729488fc5 --- /dev/null +++ b/http-backend.c @@ -0,0 +1,655 @@ +#include "cache.h" +#include "refs.h" +#include "pkt-line.h" +#include "object.h" +#include "tag.h" +#include "exec_cmd.h" +#include "run-command.h" +#include "string-list.h" + +static const char content_type[] = "Content-Type"; +static const char content_length[] = "Content-Length"; +static const char last_modified[] = "Last-Modified"; +static int getanyfile = 1; + +static struct string_list *query_params; + +struct rpc_service { + const char *name; + const char *config_name; + signed enabled : 2; +}; + +static struct rpc_service rpc_service[] = { + { "upload-pack", "uploadpack", 1 }, + { "receive-pack", "receivepack", -1 }, +}; + +static int decode_char(const char *q) +{ + int i; + unsigned char val = 0; + for (i = 0; i < 2; i++) { + unsigned char c = *q++; + val <<= 4; + if (c >= '0' && c <= '9') + val += c - '0'; + else if (c >= 'a' && c <= 'f') + val += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val += c - 'A' + 10; + else + return -1; + } + return val; +} + +static char *decode_parameter(const char **query, int is_name) +{ + const char *q = *query; + struct strbuf out; + + strbuf_init(&out, 16); + do { + unsigned char c = *q; + + if (!c) + break; + if (c == '&' || (is_name && c == '=')) { + q++; + break; + } + + if (c == '%') { + int val = decode_char(q + 1); + if (0 <= val) { + strbuf_addch(&out, val); + q += 3; + continue; + } + } + + if (c == '+') + strbuf_addch(&out, ' '); + else + strbuf_addch(&out, c); + q++; + } while (1); + *query = q; + return strbuf_detach(&out, NULL); +} + +static struct string_list *get_parameters(void) +{ + if (!query_params) { + const char *query = getenv("QUERY_STRING"); + + query_params = xcalloc(1, sizeof(*query_params)); + while (query && *query) { + char *name = decode_parameter(&query, 1); + char *value = decode_parameter(&query, 0); + struct string_list_item *i; + + i = string_list_lookup(name, query_params); + if (!i) + i = string_list_insert(name, query_params); + else + free(i->util); + i->util = value; + } + } + return query_params; +} + +static const char *get_parameter(const char *name) +{ + struct string_list_item *i; + i = string_list_lookup(name, get_parameters()); + return i ? i->util : NULL; +} + +__attribute__((format (printf, 2, 3))) +static void format_write(int fd, const char *fmt, ...) +{ + static char buffer[1024]; + + va_list args; + unsigned n; + + va_start(args, fmt); + n = vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + if (n >= sizeof(buffer)) + die("protocol error: impossibly long line"); + + safe_write(fd, buffer, n); +} + +static void http_status(unsigned code, const char *msg) +{ + format_write(1, "Status: %u %s\r\n", code, msg); +} + +static void hdr_str(const char *name, const char *value) +{ + format_write(1, "%s: %s\r\n", name, value); +} + +static void hdr_int(const char *name, uintmax_t value) +{ + format_write(1, "%s: %" PRIuMAX "\r\n", name, value); +} + +static void hdr_date(const char *name, unsigned long when) +{ + const char *value = show_date(when, 0, DATE_RFC2822); + hdr_str(name, value); +} + +static void hdr_nocache(void) +{ + hdr_str("Expires", "Fri, 01 Jan 1980 00:00:00 GMT"); + hdr_str("Pragma", "no-cache"); + hdr_str("Cache-Control", "no-cache, max-age=0, must-revalidate"); +} + +static void hdr_cache_forever(void) +{ + unsigned long now = time(NULL); + hdr_date("Date", now); + hdr_date("Expires", now + 31536000); + hdr_str("Cache-Control", "public, max-age=31536000"); +} + +static void end_headers(void) +{ + safe_write(1, "\r\n", 2); +} + +__attribute__((format (printf, 1, 2))) +static NORETURN void not_found(const char *err, ...) +{ + va_list params; + + http_status(404, "Not Found"); + hdr_nocache(); + end_headers(); + + va_start(params, err); + if (err && *err) + vfprintf(stderr, err, params); + va_end(params); + exit(0); +} + +__attribute__((format (printf, 1, 2))) +static NORETURN void forbidden(const char *err, ...) +{ + va_list params; + + http_status(403, "Forbidden"); + hdr_nocache(); + end_headers(); + + va_start(params, err); + if (err && *err) + vfprintf(stderr, err, params); + va_end(params); + exit(0); +} + +static void select_getanyfile(void) +{ + if (!getanyfile) + forbidden("Unsupported service: getanyfile"); +} + +static void send_strbuf(const char *type, struct strbuf *buf) +{ + hdr_int(content_length, buf->len); + hdr_str(content_type, type); + end_headers(); + safe_write(1, buf->buf, buf->len); +} + +static void send_local_file(const char *the_type, const char *name) +{ + const char *p = git_path("%s", name); + size_t buf_alloc = 8192; + char *buf = xmalloc(buf_alloc); + int fd; + struct stat sb; + + fd = open(p, O_RDONLY); + if (fd < 0) + not_found("Cannot open '%s': %s", p, strerror(errno)); + if (fstat(fd, &sb) < 0) + die_errno("Cannot stat '%s'", p); + + hdr_int(content_length, sb.st_size); + hdr_str(content_type, the_type); + hdr_date(last_modified, sb.st_mtime); + end_headers(); + + for (;;) { + ssize_t n = xread(fd, buf, buf_alloc); + if (n < 0) + die_errno("Cannot read '%s'", p); + if (!n) + break; + safe_write(1, buf, n); + } + close(fd); + free(buf); +} + +static void get_text_file(char *name) +{ + select_getanyfile(); + hdr_nocache(); + send_local_file("text/plain", name); +} + +static void get_loose_object(char *name) +{ + select_getanyfile(); + hdr_cache_forever(); + send_local_file("application/x-git-loose-object", name); +} + +static void get_pack_file(char *name) +{ + select_getanyfile(); + hdr_cache_forever(); + send_local_file("application/x-git-packed-objects", name); +} + +static void get_idx_file(char *name) +{ + select_getanyfile(); + hdr_cache_forever(); + send_local_file("application/x-git-packed-objects-toc", name); +} + +static int http_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "http.getanyfile")) { + getanyfile = git_config_bool(var, value); + return 0; + } + + if (!prefixcmp(var, "http.")) { + int i; + + for (i = 0; i < ARRAY_SIZE(rpc_service); i++) { + struct rpc_service *svc = &rpc_service[i]; + if (!strcmp(var + 5, svc->config_name)) { + svc->enabled = git_config_bool(var, value); + return 0; + } + } + } + + /* we are not interested in parsing any other configuration here */ + return 0; +} + +static struct rpc_service *select_service(const char *name) +{ + struct rpc_service *svc = NULL; + int i; + + if (prefixcmp(name, "git-")) + forbidden("Unsupported service: '%s'", name); + + for (i = 0; i < ARRAY_SIZE(rpc_service); i++) { + struct rpc_service *s = &rpc_service[i]; + if (!strcmp(s->name, name + 4)) { + svc = s; + break; + } + } + + if (!svc) + forbidden("Unsupported service: '%s'", name); + + if (svc->enabled < 0) { + const char *user = getenv("REMOTE_USER"); + svc->enabled = (user && *user) ? 1 : 0; + } + if (!svc->enabled) + forbidden("Service not enabled: '%s'", svc->name); + return svc; +} + +static void inflate_request(const char *prog_name, int out) +{ + z_stream stream; + unsigned char in_buf[8192]; + unsigned char out_buf[8192]; + unsigned long cnt = 0; + int ret; + + memset(&stream, 0, sizeof(stream)); + ret = inflateInit2(&stream, (15 + 16)); + if (ret != Z_OK) + die("cannot start zlib inflater, zlib err %d", ret); + + while (1) { + ssize_t n = xread(0, in_buf, sizeof(in_buf)); + if (n <= 0) + die("request ended in the middle of the gzip stream"); + + stream.next_in = in_buf; + stream.avail_in = n; + + while (0 < stream.avail_in) { + int ret; + + stream.next_out = out_buf; + stream.avail_out = sizeof(out_buf); + + ret = inflate(&stream, Z_NO_FLUSH); + if (ret != Z_OK && ret != Z_STREAM_END) + die("zlib error inflating request, result %d", ret); + + n = stream.total_out - cnt; + if (write_in_full(out, out_buf, n) != n) + die("%s aborted reading request", prog_name); + cnt += n; + + if (ret == Z_STREAM_END) + goto done; + } + } + +done: + inflateEnd(&stream); + close(out); +} + +static void run_service(const char **argv) +{ + const char *encoding = getenv("HTTP_CONTENT_ENCODING"); + const char *user = getenv("REMOTE_USER"); + const char *host = getenv("REMOTE_ADDR"); + char *env[3]; + struct strbuf buf = STRBUF_INIT; + int gzipped_request = 0; + struct child_process cld; + + if (encoding && !strcmp(encoding, "gzip")) + gzipped_request = 1; + else if (encoding && !strcmp(encoding, "x-gzip")) + gzipped_request = 1; + + if (!user || !*user) + user = "anonymous"; + if (!host || !*host) + host = "(none)"; + + memset(&env, 0, sizeof(env)); + strbuf_addf(&buf, "GIT_COMMITTER_NAME=%s", user); + env[0] = strbuf_detach(&buf, NULL); + + strbuf_addf(&buf, "GIT_COMMITTER_EMAIL=%s@http.%s", user, host); + env[1] = strbuf_detach(&buf, NULL); + env[2] = NULL; + + memset(&cld, 0, sizeof(cld)); + cld.argv = argv; + cld.env = (const char *const *)env; + if (gzipped_request) + cld.in = -1; + cld.git_cmd = 1; + if (start_command(&cld)) + exit(1); + + close(1); + if (gzipped_request) + inflate_request(argv[0], cld.in); + else + close(0); + + if (finish_command(&cld)) + exit(1); + free(env[0]); + free(env[1]); + strbuf_release(&buf); +} + +static int show_text_ref(const char *name, const unsigned char *sha1, + int flag, void *cb_data) +{ + struct strbuf *buf = cb_data; + struct object *o = parse_object(sha1); + if (!o) + return 0; + + strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name); + if (o->type == OBJ_TAG) { + o = deref_tag(o, name, 0); + if (!o) + return 0; + strbuf_addf(buf, "%s\t%s^{}\n", sha1_to_hex(o->sha1), name); + } + return 0; +} + +static void get_info_refs(char *arg) +{ + const char *service_name = get_parameter("service"); + struct strbuf buf = STRBUF_INIT; + + hdr_nocache(); + + if (service_name) { + const char *argv[] = {NULL /* service name */, + "--stateless-rpc", "--advertise-refs", + ".", NULL}; + struct rpc_service *svc = select_service(service_name); + + strbuf_addf(&buf, "application/x-git-%s-advertisement", + svc->name); + hdr_str(content_type, buf.buf); + end_headers(); + + packet_write(1, "# service=git-%s\n", svc->name); + packet_flush(1); + + argv[0] = svc->name; + run_service(argv); + + } else { + select_getanyfile(); + for_each_ref(show_text_ref, &buf); + send_strbuf("text/plain", &buf); + } + strbuf_release(&buf); +} + +static void get_info_packs(char *arg) +{ + size_t objdirlen = strlen(get_object_directory()); + struct strbuf buf = STRBUF_INIT; + struct packed_git *p; + size_t cnt = 0; + + select_getanyfile(); + prepare_packed_git(); + for (p = packed_git; p; p = p->next) { + if (p->pack_local) + cnt++; + } + + strbuf_grow(&buf, cnt * 53 + 2); + for (p = packed_git; p; p = p->next) { + if (p->pack_local) + strbuf_addf(&buf, "P %s\n", p->pack_name + objdirlen + 6); + } + strbuf_addch(&buf, '\n'); + + hdr_nocache(); + send_strbuf("text/plain; charset=utf-8", &buf); + strbuf_release(&buf); +} + +static void check_content_type(const char *accepted_type) +{ + const char *actual_type = getenv("CONTENT_TYPE"); + + if (!actual_type) + actual_type = ""; + + if (strcmp(actual_type, accepted_type)) { + http_status(415, "Unsupported Media Type"); + hdr_nocache(); + end_headers(); + format_write(1, + "Expected POST with Content-Type '%s'," + " but received '%s' instead.\n", + accepted_type, actual_type); + exit(0); + } +} + +static void service_rpc(char *service_name) +{ + const char *argv[] = {NULL, "--stateless-rpc", ".", NULL}; + struct rpc_service *svc = select_service(service_name); + struct strbuf buf = STRBUF_INIT; + + strbuf_reset(&buf); + strbuf_addf(&buf, "application/x-git-%s-request", svc->name); + check_content_type(buf.buf); + + hdr_nocache(); + + strbuf_reset(&buf); + strbuf_addf(&buf, "application/x-git-%s-result", svc->name); + hdr_str(content_type, buf.buf); + + end_headers(); + + argv[0] = svc->name; + run_service(argv); + strbuf_release(&buf); +} + +static NORETURN void die_webcgi(const char *err, va_list params) +{ + char buffer[1000]; + + http_status(500, "Internal Server Error"); + hdr_nocache(); + end_headers(); + + vsnprintf(buffer, sizeof(buffer), err, params); + fprintf(stderr, "fatal: %s\n", buffer); + exit(0); +} + +static char* getdir(void) +{ + struct strbuf buf = STRBUF_INIT; + char *pathinfo = getenv("PATH_INFO"); + char *root = getenv("GIT_PROJECT_ROOT"); + char *path = getenv("PATH_TRANSLATED"); + + if (root && *root) { + if (!pathinfo || !*pathinfo) + die("GIT_PROJECT_ROOT is set but PATH_INFO is not"); + if (daemon_avoid_alias(pathinfo)) + die("'%s': aliased", pathinfo); + strbuf_addstr(&buf, root); + if (buf.buf[buf.len - 1] != '/') + strbuf_addch(&buf, '/'); + if (pathinfo[0] == '/') + pathinfo++; + strbuf_addstr(&buf, pathinfo); + return strbuf_detach(&buf, NULL); + } else if (path && *path) { + return xstrdup(path); + } else + die("No GIT_PROJECT_ROOT or PATH_TRANSLATED from server"); + return NULL; +} + +static struct service_cmd { + const char *method; + const char *pattern; + void (*imp)(char *); +} services[] = { + {"GET", "/HEAD$", get_text_file}, + {"GET", "/info/refs$", get_info_refs}, + {"GET", "/objects/info/alternates$", get_text_file}, + {"GET", "/objects/info/http-alternates$", get_text_file}, + {"GET", "/objects/info/packs$", get_info_packs}, + {"GET", "/objects/[0-9a-f]{2}/[0-9a-f]{38}$", get_loose_object}, + {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.pack$", get_pack_file}, + {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.idx$", get_idx_file}, + + {"POST", "/git-upload-pack$", service_rpc}, + {"POST", "/git-receive-pack$", service_rpc} +}; + +int main(int argc, char **argv) +{ + char *method = getenv("REQUEST_METHOD"); + char *dir; + struct service_cmd *cmd = NULL; + char *cmd_arg = NULL; + int i; + + git_extract_argv0_path(argv[0]); + set_die_routine(die_webcgi); + + if (!method) + die("No REQUEST_METHOD from server"); + if (!strcmp(method, "HEAD")) + method = "GET"; + dir = getdir(); + + for (i = 0; i < ARRAY_SIZE(services); i++) { + struct service_cmd *c = &services[i]; + regex_t re; + regmatch_t out[1]; + + if (regcomp(&re, c->pattern, REG_EXTENDED)) + die("Bogus regex in service table: %s", c->pattern); + if (!regexec(&re, dir, 1, out, 0)) { + size_t n; + + if (strcmp(method, c->method)) { + const char *proto = getenv("SERVER_PROTOCOL"); + if (proto && !strcmp(proto, "HTTP/1.1")) + http_status(405, "Method Not Allowed"); + else + http_status(400, "Bad Request"); + hdr_nocache(); + end_headers(); + return 0; + } + + cmd = c; + n = out[0].rm_eo - out[0].rm_so; + cmd_arg = xmalloc(n); + memcpy(cmd_arg, dir + out[0].rm_so + 1, n-1); + cmd_arg[n-1] = '\0'; + dir[out[0].rm_so] = 0; + break; + } + regfree(&re); + } + + if (!cmd) + not_found("Request not supported: '%s'", dir); + + setup_path(); + if (!enter_repo(dir, 0)) + not_found("Not a git repository: '%s'", dir); + + git_config(http_config, NULL); + cmd->imp(cmd_arg); + return 0; +} diff --git a/builtin-http-fetch.c b/http-fetch.c index f3e63d7206..ffd0ad7e29 100644 --- a/builtin-http-fetch.c +++ b/http-fetch.c @@ -1,8 +1,13 @@ #include "cache.h" +#include "exec_cmd.h" #include "walker.h" -int cmd_http_fetch(int argc, const char **argv, const char *prefix) +static const char http_fetch_usage[] = "git http-fetch " +"[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url"; + +int main(int argc, const char **argv) { + const char *prefix; struct walker *walker; int commits_on_stdin = 0; int commits; @@ -18,7 +23,7 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix) int get_verbosely = 0; int get_recover = 0; - git_config(git_default_config, NULL); + git_extract_argv0_path(argv[0]); while (arg < argc && argv[arg][0] == '-') { if (argv[arg][1] == 't') { @@ -34,6 +39,8 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix) } else if (argv[arg][1] == 'w') { write_ref = &argv[arg + 1]; arg++; + } else if (argv[arg][1] == 'h') { + usage(http_fetch_usage); } else if (!strcmp(argv[arg], "--recover")) { get_recover = 1; } else if (!strcmp(argv[arg], "--stdin")) { @@ -41,10 +48,8 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix) } arg++; } - if (argc < arg + 2 - commits_on_stdin) { - usage("git http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url"); - return 1; - } + if (argc != arg + 2 - commits_on_stdin) + usage(http_fetch_usage); if (commits_on_stdin) { commits = walker_targets_stdin(&commit_id, &write_ref); } else { @@ -52,6 +57,11 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix) commits = 1; } url = argv[arg]; + + prefix = setup_git_directory(); + + git_config(git_default_config, NULL); + if (url && url[strlen(url)-1] != '/') { rewritten_url = xmalloc(strlen(url)+2); strcpy(rewritten_url, url); diff --git a/http-push.c b/http-push.c index 00e83dcec1..0e040f8c9a 100644 --- a/http-push.c +++ b/http-push.c @@ -78,6 +78,7 @@ static int push_verbosely; static int push_all = MATCH_REFS_NONE; static int force_all; static int dry_run; +static int helper_status; static struct object_list *objects; @@ -604,7 +605,7 @@ static void finish_request(struct transfer_request *request) preq = (struct http_pack_request *)request->userData; if (preq) { - if (finish_http_pack_request(preq) > 0) + if (finish_http_pack_request(preq) == 0) fail = 0; release_http_pack_request(preq); } @@ -1792,8 +1793,6 @@ int main(int argc, char **argv) git_extract_argv0_path(argv[0]); - setup_git_directory(); - repo = xcalloc(sizeof(*repo), 1); argv++; @@ -1813,6 +1812,10 @@ int main(int argc, char **argv) dry_run = 1; continue; } + if (!strcmp(arg, "--helper-status")) { + helper_status = 1; + continue; + } if (!strcmp(arg, "--verbose")) { push_verbosely = 1; http_is_verbose = 1; @@ -1827,6 +1830,8 @@ int main(int argc, char **argv) force_delete = 1; continue; } + if (!strcmp(arg, "-h")) + usage(http_push_usage); } if (!repo->url) { char *path = strstr(arg, "//"); @@ -1854,6 +1859,8 @@ int main(int argc, char **argv) if (delete_branch && nr_refspec != 1) die("You must specify only one branch name when deleting a remote branch"); + setup_git_directory(); + memset(remote_dir_exists, -1, 256); /* @@ -1911,9 +1918,12 @@ int main(int argc, char **argv) /* Remove a remote branch if -d or -D was specified */ if (delete_branch) { - if (delete_remote_branch(refspec[0], force_delete) == -1) + if (delete_remote_branch(refspec[0], force_delete) == -1) { fprintf(stderr, "Unable to delete remote branch %s\n", refspec[0]); + if (helper_status) + printf("error %s cannot remove\n", refspec[0]); + } goto cleanup; } @@ -1925,6 +1935,8 @@ int main(int argc, char **argv) } if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n"); + if (helper_status) + printf("error null no match\n"); rc = 0; goto cleanup; } @@ -1942,8 +1954,12 @@ int main(int argc, char **argv) if (is_null_sha1(ref->peer_ref->new_sha1)) { if (delete_remote_branch(ref->name, 1) == -1) { error("Could not remove %s", ref->name); + if (helper_status) + printf("error %s cannot remove\n", ref->name); rc = -4; } + else if (helper_status) + printf("ok %s\n", ref->name); new_refs++; continue; } @@ -1951,6 +1967,8 @@ int main(int argc, char **argv) if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { if (push_verbosely || 1) fprintf(stderr, "'%s': up-to-date\n", ref->name); + if (helper_status) + printf("ok %s up to date\n", ref->name); continue; } @@ -1974,6 +1992,8 @@ int main(int argc, char **argv) "need to pull first?", ref->name, ref->peer_ref->name); + if (helper_status) + printf("error %s non-fast forward\n", ref->name); rc = -2; continue; } @@ -1987,14 +2007,19 @@ int main(int argc, char **argv) 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); - if (dry_run) + if (dry_run) { + if (helper_status) + printf("ok %s\n", ref->name); continue; + } /* Lock remote branch ref */ ref_lock = lock_remote(ref->name, LOCK_TIME); if (ref_lock == NULL) { fprintf(stderr, "Unable to lock remote branch %s\n", ref->name); + if (helper_status) + printf("error %s lock error\n", ref->name); rc = 1; continue; } @@ -2045,6 +2070,8 @@ int main(int argc, char **argv) if (!rc) fprintf(stderr, " done\n"); + if (helper_status) + printf("%s %s\n", !rc ? "ok" : "error", ref->name); unlock_remote(ref_lock); check_locks(); } @@ -1,9 +1,11 @@ #include "http.h" #include "pack.h" +#include "sideband.h" int data_received; int active_requests; int http_is_verbose; +size_t http_post_buffer = 16 * LARGE_PACKET_MAX; #ifdef USE_CURL_MULTI static int max_requests = -1; @@ -97,8 +99,6 @@ size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf) return eltsize * nmemb; } -static void finish_active_slot(struct active_request_slot *slot); - #ifdef USE_CURL_MULTI static void process_curl_messages(void) { @@ -174,6 +174,13 @@ static int http_options(const char *var, const char *value, void *cb) if (!strcmp("http.proxy", var)) return git_config_string(&curl_http_proxy, var, value); + if (!strcmp("http.postbuffer", var)) { + http_post_buffer = git_config_int(var, value); + if (http_post_buffer < LARGE_PACKET_MAX) + http_post_buffer = LARGE_PACKET_MAX; + return 0; + } + /* Fall back on the default ones */ return git_default_config(var, value, cb); } @@ -638,7 +645,7 @@ void release_active_slot(struct active_request_slot *slot) #endif } -static void finish_active_slot(struct active_request_slot *slot) +void finish_active_slot(struct active_request_slot *slot) { closedown_active_slot(slot); curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code); @@ -866,20 +873,9 @@ static int fetch_pack_index(unsigned char *sha1, const char *base_url) int ret = 0; char *hex = xstrdup(sha1_to_hex(sha1)); char *filename; - char *url; + char *url = NULL; struct strbuf buf = STRBUF_INIT; - /* Don't use the index if the pack isn't there */ - end_url_with_slash(&buf, base_url); - strbuf_addf(&buf, "objects/pack/pack-%s.pack", hex); - url = strbuf_detach(&buf, 0); - - if (http_get_strbuf(url, NULL, 0)) { - ret = error("Unable to verify pack %s is available", - hex); - goto cleanup; - } - if (has_pack_index(sha1)) { ret = 0; goto cleanup; @@ -79,6 +79,7 @@ extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp); extern struct active_request_slot *get_active_slot(void); extern int start_active_slot(struct active_request_slot *slot); extern void run_active_slot(struct active_request_slot *slot); +extern void finish_active_slot(struct active_request_slot *slot); extern void finish_all_active_slots(void); extern void release_active_slot(struct active_request_slot *slot); @@ -94,6 +95,7 @@ extern void http_cleanup(void); extern int data_received; extern int active_requests; extern int http_is_verbose; +extern size_t http_post_buffer; extern char curl_errorstr[CURL_ERROR_SIZE]; @@ -205,7 +205,7 @@ const char *fmt_ident(const char *name, const char *email, if ((warn_on_no_name || error_on_no_name) && name == git_default_name && env_hint) { fprintf(stderr, env_hint, au_env, co_env); - env_hint = NULL; /* warn only once, for "git var -l" */ + env_hint = NULL; /* warn only once */ } if (error_on_no_name) die("empty ident %s <%s> not allowed", name, email); diff --git a/imap-send.c b/imap-send.c index 3847fd151d..834301cf40 100644 --- a/imap-send.c +++ b/imap-send.c @@ -24,6 +24,7 @@ #include "cache.h" #include "exec_cmd.h" +#include "run-command.h" #ifdef NO_OPENSSL typedef void *SSL; #endif @@ -93,6 +94,9 @@ struct msg_data { unsigned int crlf:1; }; +static const char imap_send_usage[] = "git imap-send < <mbox>"; + +#undef DRV_OK #define DRV_OK 0 #define DRV_MSG_BAD -1 #define DRV_BOX_BAD -2 @@ -123,9 +127,6 @@ static int nfvasprintf(char **strp, const char *fmt, va_list ap) return len; } -static void arc4_init(void); -static unsigned char arc4_getbyte(void); - struct imap_server_conf { char *name; char *tunnel; @@ -154,7 +155,7 @@ struct imap_list { }; struct imap_socket { - int fd; + int fd[2]; SSL *ssl; }; @@ -273,7 +274,11 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve fprintf(stderr, "SSL requested but SSL support not compiled in\n"); return -1; #else +#if (OPENSSL_VERSION_NUMBER >= 0x10000000L) + const SSL_METHOD *meth; +#else SSL_METHOD *meth; +#endif SSL_CTX *ctx; int ret; @@ -304,8 +309,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve ssl_socket_perror("SSL_new"); return -1; } - if (!SSL_set_fd(sock->ssl, sock->fd)) { - ssl_socket_perror("SSL_set_fd"); + if (!SSL_set_rfd(sock->ssl, sock->fd[0])) { + ssl_socket_perror("SSL_set_rfd"); + return -1; + } + if (!SSL_set_wfd(sock->ssl, sock->fd[1])) { + ssl_socket_perror("SSL_set_wfd"); return -1; } @@ -327,11 +336,12 @@ static int socket_read(struct imap_socket *sock, char *buf, int len) n = SSL_read(sock->ssl, buf, len); else #endif - n = xread(sock->fd, buf, len); + n = xread(sock->fd[0], buf, len); if (n <= 0) { socket_perror("read", sock, n); - close(sock->fd); - sock->fd = -1; + close(sock->fd[0]); + close(sock->fd[1]); + sock->fd[0] = sock->fd[1] = -1; } return n; } @@ -344,11 +354,12 @@ static int socket_write(struct imap_socket *sock, const char *buf, int len) n = SSL_write(sock->ssl, buf, len); else #endif - n = write_in_full(sock->fd, buf, len); + n = write_in_full(sock->fd[1], buf, len); if (n != len) { socket_perror("write", sock, n); - close(sock->fd); - sock->fd = -1; + close(sock->fd[0]); + close(sock->fd[1]); + sock->fd[0] = sock->fd[1] = -1; } return n; } @@ -361,7 +372,8 @@ static void socket_shutdown(struct imap_socket *sock) SSL_free(sock->ssl); } #endif - close(sock->fd); + close(sock->fd[0]); + close(sock->fd[1]); } /* simple line buffering */ @@ -489,52 +501,6 @@ static int nfsnprintf(char *buf, int blen, const char *fmt, ...) return ret; } -static struct { - unsigned char i, j, s[256]; -} rs; - -static void arc4_init(void) -{ - int i, fd; - unsigned char j, si, dat[128]; - - if ((fd = open("/dev/urandom", O_RDONLY)) < 0 && (fd = open("/dev/random", O_RDONLY)) < 0) { - fprintf(stderr, "Fatal: no random number source available.\n"); - exit(3); - } - if (read_in_full(fd, dat, 128) != 128) { - fprintf(stderr, "Fatal: cannot read random number source.\n"); - exit(3); - } - close(fd); - - for (i = 0; i < 256; i++) - rs.s[i] = i; - for (i = j = 0; i < 256; i++) { - si = rs.s[i]; - j += si + dat[i & 127]; - rs.s[i] = rs.s[j]; - rs.s[j] = si; - } - rs.i = rs.j = 0; - - for (i = 0; i < 256; i++) - arc4_getbyte(); -} - -static unsigned char arc4_getbyte(void) -{ - unsigned char si, sj; - - rs.i++; - si = rs.s[rs.i]; - rs.j += si; - sj = rs.s[rs.j]; - rs.s[rs.i] = sj; - rs.s[rs.j] = si; - return rs.s[(si + sj) & 0xff]; -} - static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx, struct imap_cmd_cb *cb, const char *fmt, va_list ap) @@ -960,7 +926,7 @@ static void imap_close_server(struct imap_store *ictx) { struct imap *imap = ictx->imap; - if (imap->buf.sock.fd != -1) { + if (imap->buf.sock.fd[0] != -1) { imap_exec(ictx, NULL, "LOGOUT"); socket_shutdown(&imap->buf.sock); } @@ -982,40 +948,35 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) struct imap_store *ctx; struct imap *imap; char *arg, *rsp; - int s = -1, a[2], preauth; - pid_t pid; + int s = -1, preauth; ctx = xcalloc(sizeof(*ctx), 1); ctx->imap = imap = xcalloc(sizeof(*imap), 1); - imap->buf.sock.fd = -1; + imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1; imap->in_progress_append = &imap->in_progress; /* open connection to IMAP server */ if (srvc->tunnel) { - imap_info("Starting tunnel '%s'... ", srvc->tunnel); + const char *argv[4]; + struct child_process tunnel = {0}; - if (socketpair(PF_UNIX, SOCK_STREAM, 0, a)) { - perror("socketpair"); - exit(1); - } + imap_info("Starting tunnel '%s'... ", srvc->tunnel); - pid = fork(); - if (pid < 0) - _exit(127); - if (!pid) { - if (dup2(a[0], 0) == -1 || dup2(a[0], 1) == -1) - _exit(127); - close(a[0]); - close(a[1]); - execl("/bin/sh", "sh", "-c", srvc->tunnel, NULL); - _exit(127); - } + argv[0] = "sh"; + argv[1] = "-c"; + argv[2] = srvc->tunnel; + argv[3] = NULL; - close(a[0]); + tunnel.argv = argv; + tunnel.in = -1; + tunnel.out = -1; + if (start_command(&tunnel)) + die("cannot start proxy %s", argv[0]); - imap->buf.sock.fd = a[1]; + imap->buf.sock.fd[0] = tunnel.out; + imap->buf.sock.fd[1] = tunnel.in; imap_info("ok\n"); } else { @@ -1092,7 +1053,8 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) goto bail; } - imap->buf.sock.fd = s; + imap->buf.sock.fd[0] = s; + imap->buf.sock.fd[1] = dup(s); if (srvc->use_ssl && ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) { @@ -1198,88 +1160,20 @@ static int imap_make_flags(int flags, char *buf) return d; } -#define TUIDL 8 - -static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid) +static int imap_store_msg(struct store *gctx, struct msg_data *data) { struct imap_store *ctx = (struct imap_store *)gctx; struct imap *imap = ctx->imap; struct imap_cmd_cb cb; - char *fmap, *buf; const char *prefix, *box; - int ret, i, j, d, len, extra, nocr; - int start, sbreak = 0, ebreak = 0; - char flagstr[128], tuid[TUIDL * 2 + 1]; + int ret, d; + char flagstr[128]; memset(&cb, 0, sizeof(cb)); - fmap = data->data; - len = data->len; - nocr = !data->crlf; - extra = 0, i = 0; - if (!CAP(UIDPLUS) && uid) { - nloop: - start = i; - while (i < len) - if (fmap[i++] == '\n') { - extra += nocr; - if (i - 2 + nocr == start) { - sbreak = ebreak = i - 2 + nocr; - goto mktid; - } - if (!memcmp(fmap + start, "X-TUID: ", 8)) { - extra -= (ebreak = i) - (sbreak = start) + nocr; - goto mktid; - } - goto nloop; - } - /* invalid message */ - free(fmap); - return DRV_MSG_BAD; - mktid: - for (j = 0; j < TUIDL; j++) - sprintf(tuid + j * 2, "%02x", arc4_getbyte()); - extra += 8 + TUIDL * 2 + 2; - } - if (nocr) - for (; i < len; i++) - if (fmap[i] == '\n') - extra++; - - cb.dlen = len + extra; - buf = cb.data = xmalloc(cb.dlen); - i = 0; - if (!CAP(UIDPLUS) && uid) { - if (nocr) { - for (; i < sbreak; i++) - if (fmap[i] == '\n') { - *buf++ = '\r'; - *buf++ = '\n'; - } else - *buf++ = fmap[i]; - } else { - memcpy(buf, fmap, sbreak); - buf += sbreak; - } - memcpy(buf, "X-TUID: ", 8); - buf += 8; - memcpy(buf, tuid, TUIDL * 2); - buf += TUIDL * 2; - *buf++ = '\r'; - *buf++ = '\n'; - i = ebreak; - } - if (nocr) { - for (; i < len; i++) - if (fmap[i] == '\n') { - *buf++ = '\r'; - *buf++ = '\n'; - } else - *buf++ = fmap[i]; - } else - memcpy(buf, fmap + i, len - i); - - free(fmap); + cb.dlen = data->len; + cb.data = xmalloc(cb.dlen); + memcpy(cb.data, data->data, data->len); d = 0; if (data->flags) { @@ -1288,26 +1182,14 @@ static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid) } flagstr[d] = 0; - if (!uid) { - box = gctx->conf->trash; - prefix = ctx->prefix; - cb.create = 1; - if (ctx->trashnc) - imap->caps = imap->rcaps & ~(1 << LITERALPLUS); - } else { - box = gctx->name; - prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix; - cb.create = 0; - } - cb.ctx = uid; + box = gctx->name; + prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix; + cb.create = 0; ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr); imap->caps = imap->rcaps; if (ret != DRV_OK) return ret; - if (!uid) - ctx->trashnc = 0; - else - gctx->count++; + gctx->count++; return DRV_OK; } @@ -1483,7 +1365,6 @@ int main(int argc, char **argv) { struct msg_data all_msgs, msg; struct store *ctx = NULL; - int uid = 0; int ofs = 0; int r; int total, n = 0; @@ -1491,8 +1372,8 @@ int main(int argc, char **argv) git_extract_argv0_path(argv[0]); - /* init the random number generator */ - arc4_init(); + if (argc != 1) + usage(imap_send_usage); setup_git_directory_gently(&nongit_ok); git_config(git_imap_config, NULL); @@ -1540,7 +1421,7 @@ int main(int argc, char **argv) break; if (server.use_html) wrap_in_html(&msg); - r = imap_store_msg(ctx, &msg, &uid); + r = imap_store_msg(ctx, &msg); if (r != DRV_OK) break; n++; diff --git a/index-pack.c b/index-pack.c index 340074fc79..190f372dd8 100644 --- a/index-pack.c +++ b/index-pack.c @@ -206,8 +206,8 @@ static void parse_pack_header(void) use(sizeof(struct pack_header)); } -static void bad_object(unsigned long offset, const char *format, - ...) NORETURN __attribute__((format (printf, 2, 3))); +static NORETURN void bad_object(unsigned long offset, const char *format, + ...) __attribute__((format (printf, 2, 3))); static void bad_object(unsigned long offset, const char *format, ...) { @@ -882,6 +882,9 @@ int main(int argc, char **argv) git_extract_argv0_path(argv[0]); + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(index_pack_usage); + /* * We wish to read the repository's config file if any, and * for that it is necessary to call setup_git_directory_gently(). diff --git a/lockfile.c b/lockfile.c index eb931eded5..6851fa55a5 100644 --- a/lockfile.c +++ b/lockfile.c @@ -155,18 +155,32 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) return lk->fd; } - -NORETURN void unable_to_lock_index_die(const char *path, int err) +static char *unable_to_lock_message(const char *path, int err) { + struct strbuf buf = STRBUF_INIT; + if (err == EEXIST) { - die("Unable to create '%s.lock': %s.\n\n" + strbuf_addf(&buf, "Unable to create '%s.lock': %s.\n\n" "If no other git process is currently running, this probably means a\n" "git process crashed in this repository earlier. Make sure no other git\n" "process is running and remove the file manually to continue.", path, strerror(err)); - } else { - die("Unable to create '%s.lock': %s", path, strerror(err)); - } + } else + strbuf_addf(&buf, "Unable to create '%s.lock': %s", path, strerror(err)); + return strbuf_detach(&buf, NULL); +} + +int unable_to_lock_error(const char *path, int err) +{ + char *msg = unable_to_lock_message(path, err); + error("%s", msg); + free(msg); + return -1; +} + +NORETURN void unable_to_lock_index_die(const char *path, int err) +{ + die("%s", unable_to_lock_message(path, err)); } int hold_lock_file_for_update(struct lock_file *lk, const char *path, int flags) diff --git a/log-tree.c b/log-tree.c index 1c9eefee33..0fdf159f80 100644 --- a/log-tree.c +++ b/log-tree.c @@ -43,6 +43,7 @@ void load_ref_decorations(int flags) if (!loaded) { loaded = 1; for_each_ref(add_ref_decoration, &flags); + head_ref(add_ref_decoration, &flags); } } @@ -178,8 +179,10 @@ void get_patch_filename(struct commit *commit, int nr, const char *suffix, strbuf_addf(buf, commit ? "%04d-" : "%d", nr); if (commit) { int max_len = start_len + FORMAT_PATCH_NAME_MAX - suffix_len; + struct pretty_print_context ctx = {0}; + ctx.date_mode = DATE_NORMAL; - format_commit_message(commit, "%f", buf, DATE_NORMAL); + format_commit_message(commit, "%f", buf, &ctx); if (max_len < buf->len) strbuf_setlen(buf, max_len); strbuf_addstr(buf, suffix); @@ -276,10 +279,9 @@ void show_log(struct rev_info *opt) struct strbuf msgbuf = STRBUF_INIT; struct log_info *log = opt->loginfo; struct commit *commit = log->commit, *parent = log->parent; - int abbrev = opt->diffopt.abbrev; int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40; - const char *subject = NULL, *extra_headers = opt->extra_headers; - int need_8bit_cte = 0; + const char *extra_headers = opt->extra_headers; + struct pretty_print_context ctx = {0}; opt->loginfo = NULL; if (!opt->verbose_header) { @@ -346,8 +348,8 @@ void show_log(struct rev_info *opt) */ if (opt->commit_format == CMIT_FMT_EMAIL) { - log_write_email_headers(opt, commit, &subject, &extra_headers, - &need_8bit_cte); + log_write_email_headers(opt, commit, &ctx.subject, &extra_headers, + &ctx.need_8bit_cte); } else if (opt->commit_format != CMIT_FMT_USERFORMAT) { fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout); if (opt->commit_format != CMIT_FMT_ONELINE) @@ -390,7 +392,9 @@ void show_log(struct rev_info *opt) */ show_reflog_message(opt->reflog_info, opt->commit_format == CMIT_FMT_ONELINE, - opt->date_mode); + opt->date_mode_explicit ? + opt->date_mode : + DATE_NORMAL); if (opt->commit_format == CMIT_FMT_ONELINE) return; } @@ -402,11 +406,13 @@ void show_log(struct rev_info *opt) /* * And then the pretty-printed message itself */ - if (need_8bit_cte >= 0) - need_8bit_cte = has_non_ascii(opt->add_signoff); - pretty_print_commit(opt->commit_format, commit, &msgbuf, - abbrev, subject, extra_headers, opt->date_mode, - need_8bit_cte); + if (ctx.need_8bit_cte >= 0) + ctx.need_8bit_cte = has_non_ascii(opt->add_signoff); + ctx.date_mode = opt->date_mode; + ctx.abbrev = opt->diffopt.abbrev; + ctx.after_subject = extra_headers; + ctx.reflog_info = opt->reflog_info; + pretty_print_commit(opt->commit_format, commit, &msgbuf, &ctx); if (opt->add_signoff) append_signoff(&msgbuf, opt->add_signoff); diff --git a/merge-recursive.c b/merge-recursive.c index 10d7913b06..f55b7ebe11 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -170,6 +170,18 @@ static int git_merge_trees(int index_only, int rc; struct tree_desc t[3]; struct unpack_trees_options opts; + static const struct unpack_trees_error_msgs msgs = { + /* would_overwrite */ + "Your local changes to '%s' would be overwritten by merge. Aborting.", + /* not_uptodate_file */ + "Your local changes to '%s' would be overwritten by merge. Aborting.", + /* not_uptodate_dir */ + "Updating '%s' would lose untracked files in it. Aborting.", + /* would_lose_untracked */ + "Untracked working tree file '%s' would be %s by merge. Aborting", + /* bind_overlap -- will not happen here */ + NULL, + }; memset(&opts, 0, sizeof(opts)); if (index_only) @@ -181,6 +193,7 @@ static int git_merge_trees(int index_only, opts.fn = threeway_merge; opts.src_index = &the_index; opts.dst_index = &the_index; + opts.msgs = msgs; init_tree_desc_from_tree(t+0, common); init_tree_desc_from_tree(t+1, head); @@ -1188,10 +1201,14 @@ int merge_trees(struct merge_options *o, code = git_merge_trees(o->call_depth, common, head, merge); - if (code != 0) - die("merging of trees %s and %s failed", - sha1_to_hex(head->object.sha1), - sha1_to_hex(merge->object.sha1)); + if (code != 0) { + if (show(o, 4) || o->call_depth) + die("merging of trees %s and %s failed", + sha1_to_hex(head->object.sha1), + sha1_to_hex(merge->object.sha1)); + else + exit(128); + } if (unmerged_cache()) { struct string_list *entries, *re_head, *re_merge; diff --git a/notes.c b/notes.c new file mode 100644 index 0000000000..50a4672d7c --- /dev/null +++ b/notes.c @@ -0,0 +1,429 @@ +#include "cache.h" +#include "commit.h" +#include "notes.h" +#include "refs.h" +#include "utf8.h" +#include "strbuf.h" +#include "tree-walk.h" + +/* + * Use a non-balancing simple 16-tree structure with struct int_node as + * internal nodes, and struct leaf_node as leaf nodes. Each int_node has a + * 16-array of pointers to its children. + * The bottom 2 bits of each pointer is used to identify the pointer type + * - ptr & 3 == 0 - NULL pointer, assert(ptr == NULL) + * - ptr & 3 == 1 - pointer to next internal node - cast to struct int_node * + * - ptr & 3 == 2 - pointer to note entry - cast to struct leaf_node * + * - ptr & 3 == 3 - pointer to subtree entry - cast to struct leaf_node * + * + * The root node is a statically allocated struct int_node. + */ +struct int_node { + void *a[16]; +}; + +/* + * Leaf nodes come in two variants, note entries and subtree entries, + * distinguished by the LSb of the leaf node pointer (see above). + * As a note entry, the key is the SHA1 of the referenced commit, and the + * value is the SHA1 of the note object. + * As a subtree entry, the key is the prefix SHA1 (w/trailing NULs) of the + * referenced commit, using the last byte of the key to store the length of + * the prefix. The value is the SHA1 of the tree object containing the notes + * subtree. + */ +struct leaf_node { + unsigned char key_sha1[20]; + unsigned char val_sha1[20]; +}; + +#define PTR_TYPE_NULL 0 +#define PTR_TYPE_INTERNAL 1 +#define PTR_TYPE_NOTE 2 +#define PTR_TYPE_SUBTREE 3 + +#define GET_PTR_TYPE(ptr) ((uintptr_t) (ptr) & 3) +#define CLR_PTR_TYPE(ptr) ((void *) ((uintptr_t) (ptr) & ~3)) +#define SET_PTR_TYPE(ptr, type) ((void *) ((uintptr_t) (ptr) | (type))) + +#define GET_NIBBLE(n, sha1) (((sha1[n >> 1]) >> ((~n & 0x01) << 2)) & 0x0f) + +#define SUBTREE_SHA1_PREFIXCMP(key_sha1, subtree_sha1) \ + (memcmp(key_sha1, subtree_sha1, subtree_sha1[19])) + +static struct int_node root_node; + +static int initialized; + +static void load_subtree(struct leaf_node *subtree, struct int_node *node, + unsigned int n); + +/* + * Search the tree until the appropriate location for the given key is found: + * 1. Start at the root node, with n = 0 + * 2. If a[0] at the current level is a matching subtree entry, unpack that + * subtree entry and remove it; restart search at the current level. + * 3. Use the nth nibble of the key as an index into a: + * - If a[n] is an int_node, recurse from #2 into that node and increment n + * - If a matching subtree entry, unpack that subtree entry (and remove it); + * restart search at the current level. + * - Otherwise, we have found one of the following: + * - a subtree entry which does not match the key + * - a note entry which may or may not match the key + * - an unused leaf node (NULL) + * In any case, set *tree and *n, and return pointer to the tree location. + */ +static void **note_tree_search(struct int_node **tree, + unsigned char *n, const unsigned char *key_sha1) +{ + struct leaf_node *l; + unsigned char i; + void *p = (*tree)->a[0]; + + if (GET_PTR_TYPE(p) == PTR_TYPE_SUBTREE) { + l = (struct leaf_node *) CLR_PTR_TYPE(p); + if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { + /* unpack tree and resume search */ + (*tree)->a[0] = NULL; + load_subtree(l, *tree, *n); + free(l); + return note_tree_search(tree, n, key_sha1); + } + } + + i = GET_NIBBLE(*n, key_sha1); + p = (*tree)->a[i]; + switch(GET_PTR_TYPE(p)) { + case PTR_TYPE_INTERNAL: + *tree = CLR_PTR_TYPE(p); + (*n)++; + return note_tree_search(tree, n, key_sha1); + case PTR_TYPE_SUBTREE: + l = (struct leaf_node *) CLR_PTR_TYPE(p); + if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { + /* unpack tree and resume search */ + (*tree)->a[i] = NULL; + load_subtree(l, *tree, *n); + free(l); + return note_tree_search(tree, n, key_sha1); + } + /* fall through */ + default: + return &((*tree)->a[i]); + } +} + +/* + * To find a leaf_node: + * Search to the tree location appropriate for the given key: + * If a note entry with matching key, return the note entry, else return NULL. + */ +static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n, + const unsigned char *key_sha1) +{ + void **p = note_tree_search(&tree, &n, key_sha1); + if (GET_PTR_TYPE(*p) == PTR_TYPE_NOTE) { + struct leaf_node *l = (struct leaf_node *) CLR_PTR_TYPE(*p); + if (!hashcmp(key_sha1, l->key_sha1)) + return l; + } + return NULL; +} + +/* Create a new blob object by concatenating the two given blob objects */ +static int concatenate_notes(unsigned char *cur_sha1, + const unsigned char *new_sha1) +{ + char *cur_msg, *new_msg, *buf; + unsigned long cur_len, new_len, buf_len; + enum object_type cur_type, new_type; + int ret; + + /* read in both note blob objects */ + new_msg = read_sha1_file(new_sha1, &new_type, &new_len); + if (!new_msg || !new_len || new_type != OBJ_BLOB) { + free(new_msg); + return 0; + } + cur_msg = read_sha1_file(cur_sha1, &cur_type, &cur_len); + if (!cur_msg || !cur_len || cur_type != OBJ_BLOB) { + free(cur_msg); + free(new_msg); + hashcpy(cur_sha1, new_sha1); + return 0; + } + + /* we will separate the notes by a newline anyway */ + if (cur_msg[cur_len - 1] == '\n') + cur_len--; + + /* concatenate cur_msg and new_msg into buf */ + buf_len = cur_len + 1 + new_len; + buf = (char *) xmalloc(buf_len); + memcpy(buf, cur_msg, cur_len); + buf[cur_len] = '\n'; + memcpy(buf + cur_len + 1, new_msg, new_len); + + free(cur_msg); + free(new_msg); + + /* create a new blob object from buf */ + ret = write_sha1_file(buf, buf_len, "blob", cur_sha1); + free(buf); + return ret; +} + +/* + * To insert a leaf_node: + * Search to the tree location appropriate for the given leaf_node's key: + * - If location is unused (NULL), store the tweaked pointer directly there + * - If location holds a note entry that matches the note-to-be-inserted, then + * concatenate the two notes. + * - If location holds a note entry that matches the subtree-to-be-inserted, + * then unpack the subtree-to-be-inserted into the location. + * - If location holds a matching subtree entry, unpack the subtree at that + * location, and restart the insert operation from that level. + * - Else, create a new int_node, holding both the node-at-location and the + * node-to-be-inserted, and store the new int_node into the location. + */ +static void note_tree_insert(struct int_node *tree, unsigned char n, + struct leaf_node *entry, unsigned char type) +{ + struct int_node *new_node; + struct leaf_node *l; + void **p = note_tree_search(&tree, &n, entry->key_sha1); + + assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ + l = (struct leaf_node *) CLR_PTR_TYPE(*p); + switch(GET_PTR_TYPE(*p)) { + case PTR_TYPE_NULL: + assert(!*p); + *p = SET_PTR_TYPE(entry, type); + return; + case PTR_TYPE_NOTE: + switch (type) { + case PTR_TYPE_NOTE: + if (!hashcmp(l->key_sha1, entry->key_sha1)) { + /* skip concatenation if l == entry */ + if (!hashcmp(l->val_sha1, entry->val_sha1)) + return; + + if (concatenate_notes(l->val_sha1, + entry->val_sha1)) + die("failed to concatenate note %s " + "into note %s for commit %s", + sha1_to_hex(entry->val_sha1), + sha1_to_hex(l->val_sha1), + sha1_to_hex(l->key_sha1)); + free(entry); + return; + } + break; + case PTR_TYPE_SUBTREE: + if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1, + entry->key_sha1)) { + /* unpack 'entry' */ + load_subtree(entry, tree, n); + free(entry); + return; + } + break; + } + break; + case PTR_TYPE_SUBTREE: + if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) { + /* unpack 'l' and restart insert */ + *p = NULL; + load_subtree(l, tree, n); + free(l); + note_tree_insert(tree, n, entry, type); + return; + } + break; + } + + /* non-matching leaf_node */ + assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE || + GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE); + new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1); + note_tree_insert(new_node, n + 1, l, GET_PTR_TYPE(*p)); + *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL); + note_tree_insert(new_node, n + 1, entry, type); +} + +/* Free the entire notes data contained in the given tree */ +static void note_tree_free(struct int_node *tree) +{ + unsigned int i; + for (i = 0; i < 16; i++) { + void *p = tree->a[i]; + switch(GET_PTR_TYPE(p)) { + case PTR_TYPE_INTERNAL: + note_tree_free(CLR_PTR_TYPE(p)); + /* fall through */ + case PTR_TYPE_NOTE: + case PTR_TYPE_SUBTREE: + free(CLR_PTR_TYPE(p)); + } + } +} + +/* + * Convert a partial SHA1 hex string to the corresponding partial SHA1 value. + * - hex - Partial SHA1 segment in ASCII hex format + * - hex_len - Length of above segment. Must be multiple of 2 between 0 and 40 + * - sha1 - Partial SHA1 value is written here + * - sha1_len - Max #bytes to store in sha1, Must be >= hex_len / 2, and < 20 + * Returns -1 on error (invalid arguments or invalid SHA1 (not in hex format). + * Otherwise, returns number of bytes written to sha1 (i.e. hex_len / 2). + * Pads sha1 with NULs up to sha1_len (not included in returned length). + */ +static int get_sha1_hex_segment(const char *hex, unsigned int hex_len, + unsigned char *sha1, unsigned int sha1_len) +{ + unsigned int i, len = hex_len >> 1; + if (hex_len % 2 != 0 || len > sha1_len) + return -1; + for (i = 0; i < len; i++) { + unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]); + if (val & ~0xff) + return -1; + *sha1++ = val; + hex += 2; + } + for (; i < sha1_len; i++) + *sha1++ = 0; + return len; +} + +static void load_subtree(struct leaf_node *subtree, struct int_node *node, + unsigned int n) +{ + unsigned char commit_sha1[20]; + unsigned int prefix_len; + void *buf; + struct tree_desc desc; + struct name_entry entry; + + buf = fill_tree_descriptor(&desc, subtree->val_sha1); + if (!buf) + die("Could not read %s for notes-index", + sha1_to_hex(subtree->val_sha1)); + + prefix_len = subtree->key_sha1[19]; + assert(prefix_len * 2 >= n); + memcpy(commit_sha1, subtree->key_sha1, prefix_len); + while (tree_entry(&desc, &entry)) { + int len = get_sha1_hex_segment(entry.path, strlen(entry.path), + commit_sha1 + prefix_len, 20 - prefix_len); + if (len < 0) + continue; /* entry.path is not a SHA1 sum. Skip */ + len += prefix_len; + + /* + * If commit SHA1 is complete (len == 20), assume note object + * If commit SHA1 is incomplete (len < 20), assume note subtree + */ + if (len <= 20) { + unsigned char type = PTR_TYPE_NOTE; + struct leaf_node *l = (struct leaf_node *) + xcalloc(sizeof(struct leaf_node), 1); + hashcpy(l->key_sha1, commit_sha1); + hashcpy(l->val_sha1, entry.sha1); + if (len < 20) { + l->key_sha1[19] = (unsigned char) len; + type = PTR_TYPE_SUBTREE; + } + note_tree_insert(node, n, l, type); + } + } + free(buf); +} + +static void initialize_notes(const char *notes_ref_name) +{ + unsigned char sha1[20], commit_sha1[20]; + unsigned mode; + struct leaf_node root_tree; + + if (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) || + get_tree_entry(commit_sha1, "", sha1, &mode)) + return; + + hashclr(root_tree.key_sha1); + hashcpy(root_tree.val_sha1, sha1); + load_subtree(&root_tree, &root_node, 0); +} + +static unsigned char *lookup_notes(const unsigned char *commit_sha1) +{ + struct leaf_node *found = note_tree_find(&root_node, 0, commit_sha1); + if (found) + return found->val_sha1; + return NULL; +} + +void free_notes(void) +{ + note_tree_free(&root_node); + memset(&root_node, 0, sizeof(struct int_node)); + initialized = 0; +} + +void get_commit_notes(const struct commit *commit, struct strbuf *sb, + const char *output_encoding, int flags) +{ + static const char utf8[] = "utf-8"; + unsigned char *sha1; + char *msg, *msg_p; + unsigned long linelen, msglen; + enum object_type type; + + if (!initialized) { + const char *env = getenv(GIT_NOTES_REF_ENVIRONMENT); + if (env) + notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT); + else if (!notes_ref_name) + notes_ref_name = GIT_NOTES_DEFAULT_REF; + initialize_notes(notes_ref_name); + initialized = 1; + } + + sha1 = lookup_notes(commit->object.sha1); + if (!sha1) + return; + + if (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen || + type != OBJ_BLOB) { + free(msg); + return; + } + + if (output_encoding && *output_encoding && + strcmp(utf8, output_encoding)) { + char *reencoded = reencode_string(msg, output_encoding, utf8); + if (reencoded) { + free(msg); + msg = reencoded; + msglen = strlen(msg); + } + } + + /* we will end the annotation by a newline anyway */ + if (msglen && msg[msglen - 1] == '\n') + msglen--; + + if (flags & NOTES_SHOW_HEADER) + strbuf_addstr(sb, "\nNotes:\n"); + + for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) { + linelen = strchrnul(msg_p, '\n') - msg_p; + + if (flags & NOTES_INDENT) + strbuf_addstr(sb, " "); + strbuf_add(sb, msg_p, linelen); + strbuf_addch(sb, '\n'); + } + + free(msg); +} diff --git a/notes.h b/notes.h new file mode 100644 index 0000000000..a1421e351a --- /dev/null +++ b/notes.h @@ -0,0 +1,13 @@ +#ifndef NOTES_H +#define NOTES_H + +/* Free (and de-initialize) the internal notes tree structure */ +void free_notes(void); + +#define NOTES_SHOW_HEADER 1 +#define NOTES_INDENT 2 + +void get_commit_notes(const struct commit *commit, struct strbuf *sb, + const char *output_encoding, int flags); + +#endif diff --git a/pack-redundant.c b/pack-redundant.c index 69a7ab2e27..21c61dbbe9 100644 --- a/pack-redundant.c +++ b/pack-redundant.c @@ -603,6 +603,9 @@ int main(int argc, char **argv) git_extract_argv0_path(argv[0]); + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(pack_redundant_usage); + setup_git_directory(); for (i = 1; i < argc; i++) { @@ -2,6 +2,10 @@ #include "run-command.h" #include "sigchain.h" +#ifndef DEFAULT_PAGER +#define DEFAULT_PAGER "less" +#endif + /* * This is split up from the rest of git so that we can do * something different on Windows. @@ -9,7 +13,7 @@ static int spawned_pager; -#ifndef __MINGW32__ +#ifndef WIN32 static void pager_preexec(void) { /* @@ -21,8 +25,6 @@ static void pager_preexec(void) FD_ZERO(&in); FD_SET(0, &in); select(1, &in, NULL, &in, NULL); - - setenv("LESS", "FRSX", 0); } #endif @@ -46,12 +48,14 @@ static void wait_for_pager_signal(int signo) raise(signo); } -void setup_pager(void) +const char *git_pager(void) { - const char *pager = getenv("GIT_PAGER"); + const char *pager; if (!isatty(1)) - return; + return NULL; + + pager = getenv("GIT_PAGER"); if (!pager) { if (!pager_program) git_config(git_default_config, NULL); @@ -60,8 +64,18 @@ void setup_pager(void) if (!pager) pager = getenv("PAGER"); if (!pager) - pager = "less"; + pager = DEFAULT_PAGER; else if (!*pager || !strcmp(pager, "cat")) + pager = NULL; + + return pager; +} + +void setup_pager(void) +{ + const char *pager = git_pager(); + + if (!pager) return; spawned_pager = 1; /* means we are emitting to terminal */ @@ -70,7 +84,11 @@ void setup_pager(void) pager_argv[2] = pager; pager_process.argv = pager_argv; pager_process.in = -1; -#ifndef __MINGW32__ + if (!getenv("LESS")) { + static const char *env[] = { "LESS=FRSX", NULL }; + pager_process.env = env; + } +#ifndef WIN32 pager_process.preexec_cb = pager_preexec; #endif if (start_command(&pager_process)) diff --git a/parse-options.c b/parse-options.c index a64a4d6ee2..f5594114ed 100644 --- a/parse-options.c +++ b/parse-options.c @@ -230,6 +230,9 @@ is_abbreviated: abbrev_flags = flags; continue; } + /* negation allowed? */ + if (options->flags & PARSE_OPT_NONEG) + continue; /* negated and abbreviated very much? */ if (!prefixcmp("no-", arg)) { flags |= OPT_UNSET; diff --git a/patch-delta.c b/patch-delta.c index ef748ce96d..e02e13bd4e 100644 --- a/patch-delta.c +++ b/patch-delta.c @@ -2,7 +2,7 @@ * patch-delta.c: * recreate a buffer from a source and the delta produced by diff-delta.c * - * (C) 2005 Nicolas Pitre <nico@cam.org> + * (C) 2005 Nicolas Pitre <nico@fluxnic.net> * * This code is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -564,3 +564,50 @@ char *strip_path_suffix(const char *path, const char *suffix) return NULL; return xstrndup(path, chomp_trailing_dir_sep(path, path_len)); } + +int daemon_avoid_alias(const char *p) +{ + int sl, ndot; + + /* + * This resurrects the belts and suspenders paranoia check by HPA + * done in <435560F7.4080006@zytor.com> thread, now enter_repo() + * does not do getcwd() based path canonicalizations. + * + * sl becomes true immediately after seeing '/' and continues to + * be true as long as dots continue after that without intervening + * non-dot character. + */ + if (!p || (*p != '/' && *p != '~')) + return -1; + sl = 1; ndot = 0; + p++; + + while (1) { + char ch = *p++; + if (sl) { + if (ch == '.') + ndot++; + else if (ch == '/') { + if (ndot < 3) + /* reject //, /./ and /../ */ + return -1; + ndot = 0; + } + else if (ch == 0) { + if (0 < ndot && ndot < 3) + /* reject /.$ and /..$ */ + return -1; + return 0; + } + else + sl = ndot = 0; + } + else if (ch == 0) + return 0; + else if (ch == '/') { + sl = 1; + ndot = 0; + } + } +} diff --git a/perl/Makefile b/perl/Makefile index e3dd1a5547..4ab21d61b8 100644 --- a/perl/Makefile +++ b/perl/Makefile @@ -29,11 +29,11 @@ $(makfile): ../GIT-CFLAGS Makefile '$(PERL_PATH_SQ)' -MError -e 'exit($$Error::VERSION < 0.15009)' || \ echo ' cp private-Error.pm blib/lib/Error.pm' >> $@ echo install: >> $@ - echo ' mkdir -p "$(instdir_SQ)"' >> $@ - echo ' $(RM) "$(instdir_SQ)/Git.pm"; cp Git.pm "$(instdir_SQ)"' >> $@ - echo ' $(RM) "$(instdir_SQ)/Error.pm"' >> $@ + echo ' mkdir -p "$$(DESTDIR)$(instdir_SQ)"' >> $@ + echo ' $(RM) "$$(DESTDIR)$(instdir_SQ)/Git.pm"; cp Git.pm "$$(DESTDIR)$(instdir_SQ)"' >> $@ + echo ' $(RM) "$$(DESTDIR)$(instdir_SQ)/Error.pm"' >> $@ '$(PERL_PATH_SQ)' -MError -e 'exit($$Error::VERSION < 0.15009)' || \ - echo ' cp private-Error.pm "$(instdir_SQ)/Error.pm"' >> $@ + echo ' cp private-Error.pm "$$(DESTDIR)$(instdir_SQ)/Error.pm"' >> $@ echo instlibdir: >> $@ echo ' echo $(instdir_SQ)' >> $@ else diff --git a/perl/Makefile.PL b/perl/Makefile.PL index 320253eb8e..0b9deca2cc 100644 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -5,6 +5,14 @@ sub MY::postamble { instlibdir: @echo '$(INSTALLSITELIB)' +ifneq (,$(DESTDIR)) +ifeq (0,$(shell expr '$(MM_VERSION)' '>' 6.10)) +$(error ExtUtils::MakeMaker version "$(MM_VERSION)" is older than 6.11 and so \ + is likely incompatible with the DESTDIR mechanism. Try setting \ + NO_PERL_MAKEMAKER=1 instead) +endif +endif + MAKE_FRAG } diff --git a/pkt-line.c b/pkt-line.c index b691abebd7..295ba2b16c 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -42,17 +42,19 @@ void packet_flush(int fd) safe_write(fd, "0000", 4); } +void packet_buf_flush(struct strbuf *buf) +{ + strbuf_add(buf, "0000", 4); +} + #define hex(a) (hexchar[(a) & 15]) -void packet_write(int fd, const char *fmt, ...) +static char buffer[1000]; +static unsigned format_packet(const char *fmt, va_list args) { - static char buffer[1000]; static char hexchar[] = "0123456789abcdef"; - va_list args; unsigned n; - va_start(args, fmt); n = vsnprintf(buffer + 4, sizeof(buffer) - 4, fmt, args); - va_end(args); if (n >= sizeof(buffer)-4) die("protocol error: impossibly long line"); n += 4; @@ -60,9 +62,31 @@ void packet_write(int fd, const char *fmt, ...) buffer[1] = hex(n >> 8); buffer[2] = hex(n >> 4); buffer[3] = hex(n); + return n; +} + +void packet_write(int fd, const char *fmt, ...) +{ + va_list args; + unsigned n; + + va_start(args, fmt); + n = format_packet(fmt, args); + va_end(args); safe_write(fd, buffer, n); } +void packet_buf_write(struct strbuf *buf, const char *fmt, ...) +{ + va_list args; + unsigned n; + + va_start(args, fmt); + n = format_packet(fmt, args); + va_end(args); + strbuf_add(buf, buffer, n); +} + static void safe_read(int fd, void *buffer, unsigned size) { ssize_t ret = read_in_full(fd, buffer, size); @@ -72,15 +96,11 @@ static void safe_read(int fd, void *buffer, unsigned size) die("The remote end hung up unexpectedly"); } -int packet_read_line(int fd, char *buffer, unsigned size) +static int packet_length(const char *linelen) { int n; - unsigned len; - char linelen[4]; + int len = 0; - safe_read(fd, linelen, 4); - - len = 0; for (n = 0; n < 4; n++) { unsigned char c = linelen[n]; len <<= 4; @@ -96,8 +116,20 @@ int packet_read_line(int fd, char *buffer, unsigned size) len += c - 'A' + 10; continue; } - die("protocol error: bad line length character"); + return -1; } + return len; +} + +int packet_read_line(int fd, char *buffer, unsigned size) +{ + int len; + char linelen[4]; + + safe_read(fd, linelen, 4); + len = packet_length(linelen); + if (len < 0) + die("protocol error: bad line length character: %.4s", linelen); if (!len) return 0; len -= 4; @@ -107,3 +139,31 @@ int packet_read_line(int fd, char *buffer, unsigned size) buffer[len] = 0; return len; } + +int packet_get_line(struct strbuf *out, + char **src_buf, size_t *src_len) +{ + int len; + + if (*src_len < 4) + return -1; + len = packet_length(*src_buf); + if (len < 0) + return -1; + if (!len) { + *src_buf += 4; + *src_len -= 4; + return 0; + } + if (*src_len < len) + return -2; + + *src_buf += 4; + *src_len -= 4; + len -= 4; + + strbuf_add(out, *src_buf, len); + *src_buf += len; + *src_len -= len; + return len; +} diff --git a/pkt-line.h b/pkt-line.h index 9df653f6f5..1e5dcfe87c 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -2,14 +2,18 @@ #define PKTLINE_H #include "git-compat-util.h" +#include "strbuf.h" /* * Silly packetized line writing interface */ void packet_flush(int fd); void packet_write(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3))); +void packet_buf_flush(struct strbuf *buf); +void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3))); int packet_read_line(int fd, char *buffer, unsigned size); +int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len); ssize_t safe_write(int, const void *, ssize_t); #endif @@ -6,7 +6,9 @@ #include "string-list.h" #include "mailmap.h" #include "log-tree.h" +#include "notes.h" #include "color.h" +#include "reflog-walk.h" static char *user_format; @@ -442,9 +444,10 @@ struct chunk { struct format_commit_context { const struct commit *commit; - enum date_mode dmode; + const struct pretty_print_context *pretty_ctx; unsigned commit_header_parsed:1; unsigned commit_message_parsed:1; + size_t width, indent1, indent2; /* These offsets are relative to the start of the commit message. */ struct chunk author; @@ -458,6 +461,7 @@ struct format_commit_context { struct chunk abbrev_commit_hash; struct chunk abbrev_tree_hash; struct chunk abbrev_parent_hashes; + size_t wrap_start; }; static int add_again(struct strbuf *sb, struct chunk *chunk) @@ -595,6 +599,35 @@ static void format_decoration(struct strbuf *sb, const struct commit *commit) strbuf_addch(sb, ')'); } +static void strbuf_wrap(struct strbuf *sb, size_t pos, + size_t width, size_t indent1, size_t indent2) +{ + struct strbuf tmp = STRBUF_INIT; + + if (pos) + strbuf_add(&tmp, sb->buf, pos); + strbuf_add_wrapped_text(&tmp, sb->buf + pos, + (int) indent1, (int) indent2, (int) width); + strbuf_swap(&tmp, sb); + strbuf_release(&tmp); +} + +static void rewrap_message_tail(struct strbuf *sb, + struct format_commit_context *c, + size_t new_width, size_t new_indent1, + size_t new_indent2) +{ + if (c->width == new_width && c->indent1 == new_indent1 && + c->indent2 == new_indent2) + return; + if (c->wrap_start < sb->len) + strbuf_wrap(sb, c->wrap_start, c->width, c->indent1, c->indent2); + c->wrap_start = sb->len; + c->width = new_width; + c->indent1 = new_indent1; + c->indent2 = new_indent2; +} + static size_t format_commit_item(struct strbuf *sb, const char *placeholder, void *context) { @@ -645,6 +678,30 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, return 3; } else return 0; + case 'w': + if (placeholder[1] == '(') { + unsigned long width = 0, indent1 = 0, indent2 = 0; + char *next; + const char *start = placeholder + 2; + const char *end = strchr(start, ')'); + if (!end) + return 0; + if (end > start) { + width = strtoul(start, &next, 10); + if (*next == ',') { + indent1 = strtoul(next + 1, &next, 10); + if (*next == ',') { + indent2 = strtoul(next + 1, + &next, 10); + } + } + if (*next != ')') + return 0; + } + rewrap_message_tail(sb, c, width, indent1, indent2); + return end - placeholder + 1; + } else + return 0; } /* these depend on the commit */ @@ -701,6 +758,26 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, case 'd': format_decoration(sb, commit); return 1; + case 'g': /* reflog info */ + switch(placeholder[1]) { + case 'd': /* reflog selector */ + case 'D': + if (c->pretty_ctx->reflog_info) + get_reflog_selector(sb, + c->pretty_ctx->reflog_info, + c->pretty_ctx->date_mode, + (placeholder[1] == 'd')); + return 2; + case 's': /* reflog message */ + if (c->pretty_ctx->reflog_info) + get_reflog_message(sb, c->pretty_ctx->reflog_info); + return 2; + } + return 0; /* unknown %g placeholder */ + case 'N': + get_commit_notes(commit, sb, git_log_output_encoding ? + git_log_output_encoding : git_commit_encoding, 0); + return 1; } /* For the rest we have to parse the commit header. */ @@ -711,11 +788,11 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, case 'a': /* author ... */ return format_person_part(sb, placeholder[1], msg + c->author.off, c->author.len, - c->dmode); + c->pretty_ctx->date_mode); case 'c': /* committer ... */ return format_person_part(sb, placeholder[1], msg + c->committer.off, c->committer.len, - c->dmode); + c->pretty_ctx->date_mode); case 'e': /* encoding */ strbuf_add(sb, msg + c->encoding.off, c->encoding.len); return 1; @@ -740,15 +817,17 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, } void format_commit_message(const struct commit *commit, - const void *format, struct strbuf *sb, - enum date_mode dmode) + const char *format, struct strbuf *sb, + const struct pretty_print_context *pretty_ctx) { struct format_commit_context context; memset(&context, 0, sizeof(context)); context.commit = commit; - context.dmode = dmode; + context.pretty_ctx = pretty_ctx; + context.wrap_start = sb->len; strbuf_expand(sb, format, format_commit_item, &context); + rewrap_message_tail(sb, &context, 0, 0, 0); } static void pp_header(enum cmit_fmt fmt, @@ -900,18 +979,18 @@ char *reencode_commit_message(const struct commit *commit, const char **encoding } void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, - struct strbuf *sb, int abbrev, - const char *subject, const char *after_subject, - enum date_mode dmode, int need_8bit_cte) + struct strbuf *sb, + const struct pretty_print_context *context) { unsigned long beginning_of_body; int indent = 4; const char *msg = commit->buffer; char *reencoded; const char *encoding; + int need_8bit_cte = context->need_8bit_cte; if (fmt == CMIT_FMT_USERFORMAT) { - format_commit_message(commit, user_format, sb, dmode); + format_commit_message(commit, user_format, sb, context); return; } @@ -946,8 +1025,9 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, } } - pp_header(fmt, abbrev, dmode, encoding, commit, &msg, sb); - if (fmt != CMIT_FMT_ONELINE && !subject) { + pp_header(fmt, context->abbrev, context->date_mode, encoding, + commit, &msg, sb); + if (fmt != CMIT_FMT_ONELINE && !context->subject) { strbuf_addch(sb, '\n'); } @@ -956,8 +1036,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, /* These formats treat the title line specially. */ if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL) - pp_title_line(fmt, &msg, sb, subject, - after_subject, encoding, need_8bit_cte); + pp_title_line(fmt, &msg, sb, context->subject, + context->after_subject, encoding, need_8bit_cte); beginning_of_body = sb->len; if (fmt != CMIT_FMT_ONELINE) @@ -975,5 +1055,10 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, */ if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body) strbuf_addch(sb, '\n'); + + if (fmt != CMIT_FMT_ONELINE) + get_commit_notes(commit, sb, encoding, + NOTES_SHOW_HEADER | NOTES_INDENT); + free(reencoded); } diff --git a/progress.c b/progress.c index 621c34edc2..3971f49f4d 100644 --- a/progress.c +++ b/progress.c @@ -1,7 +1,7 @@ /* * Simple text-based progress display module for GIT * - * Copyright (c) 2007 by Nicolas Pitre <nico@cam.org> + * Copyright (c) 2007 by Nicolas Pitre <nico@fluxnic.net> * * This code is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -131,7 +131,13 @@ static void throughput_string(struct throughput *tp, off_t total, } else { l -= snprintf(tp->display, l, ", %u bytes", (int)total); } - if (rate) + + if (rate > 1 << 10) { + int x = rate + 5; /* for rounding */ + snprintf(tp->display + sizeof(tp->display) - l, l, + " | %u.%2.2u MiB/s", + x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10); + } else if (rate) snprintf(tp->display + sizeof(tp->display) - l, l, " | %u KiB/s", rate); } diff --git a/reflog-walk.c b/reflog-walk.c index 5623ea6b48..caba4f743f 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -8,6 +8,7 @@ struct complete_reflogs { char *ref; + const char *short_ref; struct reflog_info { unsigned char osha1[20], nsha1[20]; char *email; @@ -241,36 +242,74 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit) commit->object.flags &= ~(ADDED | SEEN | SHOWN); } -void show_reflog_message(struct reflog_walk_info *info, int oneline, +void get_reflog_selector(struct strbuf *sb, + struct reflog_walk_info *reflog_info, + enum date_mode dmode, + int shorten) +{ + struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; + struct reflog_info *info; + const char *printed_ref; + + if (!commit_reflog) + return; + + if (shorten) { + if (!commit_reflog->reflogs->short_ref) + commit_reflog->reflogs->short_ref + = shorten_unambiguous_ref(commit_reflog->reflogs->ref, 0); + printed_ref = commit_reflog->reflogs->short_ref; + } else { + printed_ref = commit_reflog->reflogs->ref; + } + + strbuf_addf(sb, "%s@{", printed_ref); + if (commit_reflog->flag || dmode) { + info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; + strbuf_addstr(sb, show_date(info->timestamp, info->tz, dmode)); + } else { + strbuf_addf(sb, "%d", commit_reflog->reflogs->nr + - 2 - commit_reflog->recno); + } + + strbuf_addch(sb, '}'); +} + +void get_reflog_message(struct strbuf *sb, + struct reflog_walk_info *reflog_info) +{ + struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; + struct reflog_info *info; + size_t len; + + if (!commit_reflog) + return; + + info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; + len = strlen(info->message); + if (len > 0) + len--; /* strip away trailing newline */ + strbuf_add(sb, info->message, len); +} + +void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline, enum date_mode dmode) { - if (info && info->last_commit_reflog) { - struct commit_reflog *commit_reflog = info->last_commit_reflog; + if (reflog_info && reflog_info->last_commit_reflog) { + struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; struct reflog_info *info; + struct strbuf selector = STRBUF_INIT; info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; + get_reflog_selector(&selector, reflog_info, dmode, 0); if (oneline) { - printf("%s@{", commit_reflog->reflogs->ref); - if (commit_reflog->flag || dmode) - printf("%s", show_date(info->timestamp, - info->tz, - dmode)); - else - printf("%d", commit_reflog->reflogs->nr - - 2 - commit_reflog->recno); - printf("}: %s", info->message); + printf("%s: %s", selector.buf, info->message); } else { - printf("Reflog: %s@{", commit_reflog->reflogs->ref); - if (commit_reflog->flag || dmode) - printf("%s", show_date(info->timestamp, - info->tz, - dmode)); - else - printf("%d", commit_reflog->reflogs->nr - - 2 - commit_reflog->recno); - printf("} (%s)\nReflog message: %s", - info->email, info->message); + printf("Reflog: %s (%s)\nReflog message: %s", + selector.buf, info->email, info->message); } + + strbuf_release(&selector); } } diff --git a/reflog-walk.h b/reflog-walk.h index 74c90964bd..7bd2cd4c4e 100644 --- a/reflog-walk.h +++ b/reflog-walk.h @@ -3,6 +3,8 @@ #include "cache.h" +struct reflog_walk_info; + extern void init_reflog_walk(struct reflog_walk_info** info); extern int add_reflog_for_walk(struct reflog_walk_info *info, struct commit *commit, const char *name); @@ -10,5 +12,11 @@ extern void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit); extern void show_reflog_message(struct reflog_walk_info *info, int, enum date_mode); +extern void get_reflog_message(struct strbuf *sb, + struct reflog_walk_info *reflog_info); +extern void get_reflog_selector(struct strbuf *sb, + struct reflog_walk_info *reflog_info, + enum date_mode dmode, + int shorten); #endif @@ -972,8 +972,10 @@ static int repack_without_ref(const char *refname) if (!found) return 0; fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0); - if (fd < 0) + if (fd < 0) { + unable_to_lock_error(git_path("packed-refs"), errno); return error("cannot delete '%s' from packed refs", refname); + } for (list = packed_ref_list; list; list = list->next) { char line[PATH_MAX + 100]; diff --git a/remote-curl.c b/remote-curl.c new file mode 100644 index 0000000000..4f28c222f2 --- /dev/null +++ b/remote-curl.c @@ -0,0 +1,815 @@ +#include "cache.h" +#include "remote.h" +#include "strbuf.h" +#include "walker.h" +#include "http.h" +#include "exec_cmd.h" +#include "run-command.h" +#include "pkt-line.h" +#include "sideband.h" + +static struct remote *remote; +static const char *url; +static struct walker *walker; + +struct options { + int verbosity; + unsigned long depth; + unsigned progress : 1, + followtags : 1, + dry_run : 1, + thin : 1; +}; +static struct options options; + +static void init_walker(void) +{ + if (!walker) + walker = get_http_walker(url, remote); +} + +static int set_option(const char *name, const char *value) +{ + if (!strcmp(name, "verbosity")) { + char *end; + int v = strtol(value, &end, 10); + if (value == end || *end) + return -1; + options.verbosity = v; + return 0; + } + else if (!strcmp(name, "progress")) { + if (!strcmp(value, "true")) + options.progress = 1; + else if (!strcmp(value, "false")) + options.progress = 0; + else + return -1; + return 0; + } + else if (!strcmp(name, "depth")) { + char *end; + unsigned long v = strtoul(value, &end, 10); + if (value == end || *end) + return -1; + options.depth = v; + return 0; + } + else if (!strcmp(name, "followtags")) { + if (!strcmp(value, "true")) + options.followtags = 1; + else if (!strcmp(value, "false")) + options.followtags = 0; + else + return -1; + return 0; + } + else if (!strcmp(name, "dry-run")) { + if (!strcmp(value, "true")) + options.dry_run = 1; + else if (!strcmp(value, "false")) + options.dry_run = 0; + else + return -1; + return 0; + } + else { + return 1 /* unsupported */; + } +} + +struct discovery { + const char *service; + char *buf_alloc; + char *buf; + size_t len; + unsigned proto_git : 1; +}; +static struct discovery *last_discovery; + +static void free_discovery(struct discovery *d) +{ + if (d) { + if (d == last_discovery) + last_discovery = NULL; + free(d->buf_alloc); + free(d); + } +} + +static struct discovery* discover_refs(const char *service) +{ + struct strbuf buffer = STRBUF_INIT; + struct discovery *last = last_discovery; + char *refs_url; + int http_ret, is_http = 0; + + if (last && !strcmp(service, last->service)) + return last; + free_discovery(last); + + strbuf_addf(&buffer, "%s/info/refs", url); + if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://")) { + is_http = 1; + if (!strchr(url, '?')) + strbuf_addch(&buffer, '?'); + else + strbuf_addch(&buffer, '&'); + strbuf_addf(&buffer, "service=%s", service); + } + refs_url = strbuf_detach(&buffer, NULL); + + init_walker(); + http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); + switch (http_ret) { + case HTTP_OK: + break; + case HTTP_MISSING_TARGET: + die("%s not found: did you run git update-server-info on the" + " server?", refs_url); + default: + http_error(refs_url, http_ret); + die("HTTP request failed"); + } + + last= xcalloc(1, sizeof(*last_discovery)); + last->service = service; + last->buf_alloc = strbuf_detach(&buffer, &last->len); + last->buf = last->buf_alloc; + + if (is_http && 5 <= last->len && last->buf[4] == '#') { + /* smart HTTP response; validate that the service + * pkt-line matches our request. + */ + struct strbuf exp = STRBUF_INIT; + + if (packet_get_line(&buffer, &last->buf, &last->len) <= 0) + die("%s has invalid packet header", refs_url); + if (buffer.len && buffer.buf[buffer.len - 1] == '\n') + strbuf_setlen(&buffer, buffer.len - 1); + + strbuf_addf(&exp, "# service=%s", service); + if (strbuf_cmp(&exp, &buffer)) + die("invalid server response; got '%s'", buffer.buf); + strbuf_release(&exp); + + /* The header can include additional metadata lines, up + * until a packet flush marker. Ignore these now, but + * in the future we might start to scan them. + */ + strbuf_reset(&buffer); + while (packet_get_line(&buffer, &last->buf, &last->len) > 0) + strbuf_reset(&buffer); + + last->proto_git = 1; + } + + free(refs_url); + strbuf_release(&buffer); + last_discovery = last; + return last; +} + +static int write_discovery(int fd, void *data) +{ + struct discovery *heads = data; + int err = 0; + if (write_in_full(fd, heads->buf, heads->len) != heads->len) + err = 1; + close(fd); + return err; +} + +static struct ref *parse_git_refs(struct discovery *heads) +{ + struct ref *list = NULL; + struct async async; + + memset(&async, 0, sizeof(async)); + async.proc = write_discovery; + async.data = heads; + + if (start_async(&async)) + die("cannot start thread to parse advertised refs"); + get_remote_heads(async.out, &list, 0, NULL, 0, NULL); + close(async.out); + if (finish_async(&async)) + die("ref parsing thread failed"); + return list; +} + +static struct ref *parse_info_refs(struct discovery *heads) +{ + char *data, *start, *mid; + char *ref_name; + int i = 0; + + struct ref *refs = NULL; + struct ref *ref = NULL; + struct ref *last_ref = NULL; + + data = heads->buf; + start = NULL; + mid = data; + while (i < heads->len) { + if (!start) { + start = &data[i]; + } + if (data[i] == '\t') + mid = &data[i]; + if (data[i] == '\n') { + data[i] = 0; + ref_name = mid + 1; + ref = xmalloc(sizeof(struct ref) + + strlen(ref_name) + 1); + memset(ref, 0, sizeof(struct ref)); + strcpy(ref->name, ref_name); + get_sha1_hex(start, ref->old_sha1); + if (!refs) + refs = ref; + if (last_ref) + last_ref->next = ref; + last_ref = ref; + start = NULL; + } + i++; + } + + init_walker(); + ref = alloc_ref("HEAD"); + if (!walker->fetch_ref(walker, ref) && + !resolve_remote_symref(ref, refs)) { + ref->next = refs; + refs = ref; + } else { + free(ref); + } + + return refs; +} + +static struct ref *get_refs(int for_push) +{ + struct discovery *heads; + + if (for_push) + heads = discover_refs("git-receive-pack"); + else + heads = discover_refs("git-upload-pack"); + + if (heads->proto_git) + return parse_git_refs(heads); + return parse_info_refs(heads); +} + +static void output_refs(struct ref *refs) +{ + struct ref *posn; + for (posn = refs; posn; posn = posn->next) { + if (posn->symref) + printf("@%s %s\n", posn->symref, posn->name); + else + printf("%s %s\n", sha1_to_hex(posn->old_sha1), posn->name); + } + printf("\n"); + fflush(stdout); + free_refs(refs); +} + +struct rpc_state { + const char *service_name; + const char **argv; + char *service_url; + char *hdr_content_type; + char *hdr_accept; + char *buf; + size_t alloc; + size_t len; + size_t pos; + int in; + int out; + struct strbuf result; + unsigned gzip_request : 1; +}; + +static size_t rpc_out(void *ptr, size_t eltsize, + size_t nmemb, void *buffer_) +{ + size_t max = eltsize * nmemb; + struct rpc_state *rpc = buffer_; + size_t avail = rpc->len - rpc->pos; + + if (!avail) { + avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc); + if (!avail) + return 0; + rpc->pos = 0; + rpc->len = avail; + } + + if (max < avail); + avail = max; + memcpy(ptr, rpc->buf + rpc->pos, avail); + rpc->pos += avail; + return avail; +} + +static size_t rpc_in(const void *ptr, size_t eltsize, + size_t nmemb, void *buffer_) +{ + size_t size = eltsize * nmemb; + struct rpc_state *rpc = buffer_; + write_or_die(rpc->in, ptr, size); + return size; +} + +static int post_rpc(struct rpc_state *rpc) +{ + struct active_request_slot *slot; + struct slot_results results; + struct curl_slist *headers = NULL; + int use_gzip = rpc->gzip_request; + char *gzip_body = NULL; + int err = 0, large_request = 0; + + /* Try to load the entire request, if we can fit it into the + * allocated buffer space we can use HTTP/1.0 and avoid the + * chunked encoding mess. + */ + while (1) { + size_t left = rpc->alloc - rpc->len; + char *buf = rpc->buf + rpc->len; + int n; + + if (left < LARGE_PACKET_MAX) { + large_request = 1; + use_gzip = 0; + break; + } + + n = packet_read_line(rpc->out, buf, left); + if (!n) + break; + rpc->len += n; + } + + slot = get_active_slot(); + slot->results = &results; + + curl_easy_setopt(slot->curl, CURLOPT_POST, 1); + curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); + curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url); + curl_easy_setopt(slot->curl, CURLOPT_ENCODING, ""); + + headers = curl_slist_append(headers, rpc->hdr_content_type); + headers = curl_slist_append(headers, rpc->hdr_accept); + + if (large_request) { + /* The request body is large and the size cannot be predicted. + * We must use chunked encoding to send it. + */ + headers = curl_slist_append(headers, "Expect: 100-continue"); + headers = curl_slist_append(headers, "Transfer-Encoding: chunked"); + curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out); + curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc); + if (options.verbosity > 1) { + fprintf(stderr, "POST %s (chunked)\n", rpc->service_name); + fflush(stderr); + } + + } else if (use_gzip && 1024 < rpc->len) { + /* The client backend isn't giving us compressed data so + * we can try to deflate it ourselves, this may save on. + * the transfer time. + */ + size_t size; + z_stream stream; + int ret; + + memset(&stream, 0, sizeof(stream)); + ret = deflateInit2(&stream, Z_BEST_COMPRESSION, + Z_DEFLATED, (15 + 16), + 8, Z_DEFAULT_STRATEGY); + if (ret != Z_OK) + die("cannot deflate request; zlib init error %d", ret); + size = deflateBound(&stream, rpc->len); + gzip_body = xmalloc(size); + + stream.next_in = (unsigned char *)rpc->buf; + stream.avail_in = rpc->len; + stream.next_out = (unsigned char *)gzip_body; + stream.avail_out = size; + + ret = deflate(&stream, Z_FINISH); + if (ret != Z_STREAM_END) + die("cannot deflate request; zlib deflate error %d", ret); + + ret = deflateEnd(&stream); + if (ret != Z_OK) + die("cannot deflate request; zlib end error %d", ret); + + size = stream.total_out; + + headers = curl_slist_append(headers, "Content-Encoding: gzip"); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, gzip_body); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, size); + + if (options.verbosity > 1) { + fprintf(stderr, "POST %s (gzip %lu to %lu bytes)\n", + rpc->service_name, + (unsigned long)rpc->len, (unsigned long)size); + fflush(stderr); + } + } else { + /* We know the complete request size in advance, use the + * more normal Content-Length approach. + */ + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, rpc->buf); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, rpc->len); + if (options.verbosity > 1) { + fprintf(stderr, "POST %s (%lu bytes)\n", + rpc->service_name, (unsigned long)rpc->len); + fflush(stderr); + } + } + + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in); + curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc); + + slot->curl_result = curl_easy_perform(slot->curl); + finish_active_slot(slot); + + if (results.curl_result != CURLE_OK) { + err |= error("RPC failed; result=%d, HTTP code = %ld", + results.curl_result, results.http_code); + } + + curl_slist_free_all(headers); + free(gzip_body); + return err; +} + +static int rpc_service(struct rpc_state *rpc, struct discovery *heads) +{ + const char *svc = rpc->service_name; + struct strbuf buf = STRBUF_INIT; + struct child_process client; + int err = 0; + + init_walker(); + memset(&client, 0, sizeof(client)); + client.in = -1; + client.out = -1; + client.git_cmd = 1; + client.argv = rpc->argv; + if (start_command(&client)) + exit(1); + if (heads) + write_or_die(client.in, heads->buf, heads->len); + + rpc->alloc = http_post_buffer; + rpc->buf = xmalloc(rpc->alloc); + rpc->in = client.in; + rpc->out = client.out; + strbuf_init(&rpc->result, 0); + + strbuf_addf(&buf, "%s/%s", url, svc); + rpc->service_url = strbuf_detach(&buf, NULL); + + strbuf_addf(&buf, "Content-Type: application/x-%s-request", svc); + rpc->hdr_content_type = strbuf_detach(&buf, NULL); + + strbuf_addf(&buf, "Accept: application/x-%s-response", svc); + rpc->hdr_accept = strbuf_detach(&buf, NULL); + + while (!err) { + int n = packet_read_line(rpc->out, rpc->buf, rpc->alloc); + if (!n) + break; + rpc->pos = 0; + rpc->len = n; + err |= post_rpc(rpc); + } + strbuf_read(&rpc->result, client.out, 0); + + close(client.in); + close(client.out); + client.in = -1; + client.out = -1; + + err |= finish_command(&client); + free(rpc->service_url); + free(rpc->hdr_content_type); + free(rpc->hdr_accept); + free(rpc->buf); + strbuf_release(&buf); + return err; +} + +static int fetch_dumb(int nr_heads, struct ref **to_fetch) +{ + char **targets = xmalloc(nr_heads * sizeof(char*)); + int ret, i; + + if (options.depth) + die("dumb http transport does not support --depth"); + for (i = 0; i < nr_heads; i++) + targets[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1)); + + init_walker(); + walker->get_all = 1; + walker->get_tree = 1; + walker->get_history = 1; + walker->get_verbosely = options.verbosity >= 3; + walker->get_recover = 0; + ret = walker_fetch(walker, nr_heads, targets, NULL, NULL); + + for (i = 0; i < nr_heads; i++) + free(targets[i]); + free(targets); + + return ret ? error("Fetch failed.") : 0; +} + +static int fetch_git(struct discovery *heads, + int nr_heads, struct ref **to_fetch) +{ + struct rpc_state rpc; + char *depth_arg = NULL; + const char **argv; + int argc = 0, i, err; + + argv = xmalloc((15 + nr_heads) * sizeof(char*)); + argv[argc++] = "fetch-pack"; + argv[argc++] = "--stateless-rpc"; + argv[argc++] = "--lock-pack"; + if (options.followtags) + argv[argc++] = "--include-tag"; + if (options.thin) + argv[argc++] = "--thin"; + if (options.verbosity >= 3) { + argv[argc++] = "-v"; + argv[argc++] = "-v"; + } + if (!options.progress) + argv[argc++] = "--no-progress"; + if (options.depth) { + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "--depth=%lu", options.depth); + depth_arg = strbuf_detach(&buf, NULL); + argv[argc++] = depth_arg; + } + argv[argc++] = url; + for (i = 0; i < nr_heads; i++) { + struct ref *ref = to_fetch[i]; + if (!ref->name || !*ref->name) + die("cannot fetch by sha1 over smart http"); + argv[argc++] = ref->name; + } + argv[argc++] = NULL; + + memset(&rpc, 0, sizeof(rpc)); + rpc.service_name = "git-upload-pack", + rpc.argv = argv; + rpc.gzip_request = 1; + + err = rpc_service(&rpc, heads); + if (rpc.result.len) + safe_write(1, rpc.result.buf, rpc.result.len); + strbuf_release(&rpc.result); + free(argv); + free(depth_arg); + return err; +} + +static int fetch(int nr_heads, struct ref **to_fetch) +{ + struct discovery *d = discover_refs("git-upload-pack"); + if (d->proto_git) + return fetch_git(d, nr_heads, to_fetch); + else + return fetch_dumb(nr_heads, to_fetch); +} + +static void parse_fetch(struct strbuf *buf) +{ + struct ref **to_fetch = NULL; + struct ref *list_head = NULL; + struct ref **list = &list_head; + int alloc_heads = 0, nr_heads = 0; + + do { + if (!prefixcmp(buf->buf, "fetch ")) { + char *p = buf->buf + strlen("fetch "); + char *name; + struct ref *ref; + unsigned char old_sha1[20]; + + if (strlen(p) < 40 || get_sha1_hex(p, old_sha1)) + die("protocol error: expected sha/ref, got %s'", p); + if (p[40] == ' ') + name = p + 41; + else if (!p[40]) + name = ""; + else + die("protocol error: expected sha/ref, got %s'", p); + + ref = alloc_ref(name); + hashcpy(ref->old_sha1, old_sha1); + + *list = ref; + list = &ref->next; + + ALLOC_GROW(to_fetch, nr_heads + 1, alloc_heads); + to_fetch[nr_heads++] = ref; + } + else + die("http transport does not support %s", buf->buf); + + strbuf_reset(buf); + if (strbuf_getline(buf, stdin, '\n') == EOF) + return; + if (!*buf->buf) + break; + } while (1); + + if (fetch(nr_heads, to_fetch)) + exit(128); /* error already reported */ + free_refs(list_head); + free(to_fetch); + + printf("\n"); + fflush(stdout); + strbuf_reset(buf); +} + +static int push_dav(int nr_spec, char **specs) +{ + const char **argv = xmalloc((10 + nr_spec) * sizeof(char*)); + int argc = 0, i; + + argv[argc++] = "http-push"; + argv[argc++] = "--helper-status"; + if (options.dry_run) + argv[argc++] = "--dry-run"; + if (options.verbosity > 1) + argv[argc++] = "--verbose"; + argv[argc++] = url; + for (i = 0; i < nr_spec; i++) + argv[argc++] = specs[i]; + argv[argc++] = NULL; + + if (run_command_v_opt(argv, RUN_GIT_CMD)) + die("git-%s failed", argv[0]); + free(argv); + return 0; +} + +static int push_git(struct discovery *heads, int nr_spec, char **specs) +{ + struct rpc_state rpc; + const char **argv; + int argc = 0, i, err; + + argv = xmalloc((10 + nr_spec) * sizeof(char*)); + argv[argc++] = "send-pack"; + argv[argc++] = "--stateless-rpc"; + argv[argc++] = "--helper-status"; + if (options.thin) + argv[argc++] = "--thin"; + if (options.dry_run) + argv[argc++] = "--dry-run"; + if (options.verbosity > 1) + argv[argc++] = "--verbose"; + argv[argc++] = url; + for (i = 0; i < nr_spec; i++) + argv[argc++] = specs[i]; + argv[argc++] = NULL; + + memset(&rpc, 0, sizeof(rpc)); + rpc.service_name = "git-receive-pack", + rpc.argv = argv; + + err = rpc_service(&rpc, heads); + if (rpc.result.len) + safe_write(1, rpc.result.buf, rpc.result.len); + strbuf_release(&rpc.result); + free(argv); + return err; +} + +static int push(int nr_spec, char **specs) +{ + struct discovery *heads = discover_refs("git-receive-pack"); + int ret; + + if (heads->proto_git) + ret = push_git(heads, nr_spec, specs); + else + ret = push_dav(nr_spec, specs); + free_discovery(heads); + return ret; +} + +static void parse_push(struct strbuf *buf) +{ + char **specs = NULL; + int alloc_spec = 0, nr_spec = 0, i; + + do { + if (!prefixcmp(buf->buf, "push ")) { + ALLOC_GROW(specs, nr_spec + 1, alloc_spec); + specs[nr_spec++] = xstrdup(buf->buf + 5); + } + else + die("http transport does not support %s", buf->buf); + + strbuf_reset(buf); + if (strbuf_getline(buf, stdin, '\n') == EOF) + return; + if (!*buf->buf) + break; + } while (1); + + if (push(nr_spec, specs)) + exit(128); /* error already reported */ + for (i = 0; i < nr_spec; i++) + free(specs[i]); + free(specs); + + printf("\n"); + fflush(stdout); +} + +int main(int argc, const char **argv) +{ + struct strbuf buf = STRBUF_INIT; + int nongit; + + git_extract_argv0_path(argv[0]); + setup_git_directory_gently(&nongit); + if (argc < 2) { + fprintf(stderr, "Remote needed\n"); + return 1; + } + + options.verbosity = 1; + options.progress = !!isatty(2); + options.thin = 1; + + remote = remote_get(argv[1]); + + if (argc > 2) { + url = argv[2]; + } else { + url = remote->url[0]; + } + + do { + if (strbuf_getline(&buf, stdin, '\n') == EOF) + break; + if (!prefixcmp(buf.buf, "fetch ")) { + if (nongit) + die("Fetch attempted without a local repo"); + parse_fetch(&buf); + + } else if (!strcmp(buf.buf, "list") || !prefixcmp(buf.buf, "list ")) { + int for_push = !!strstr(buf.buf + 4, "for-push"); + output_refs(get_refs(for_push)); + + } else if (!prefixcmp(buf.buf, "push ")) { + parse_push(&buf); + + } else if (!prefixcmp(buf.buf, "option ")) { + char *name = buf.buf + strlen("option "); + char *value = strchr(name, ' '); + int result; + + if (value) + *value++ = '\0'; + else + value = "true"; + + result = set_option(name, value); + if (!result) + printf("ok\n"); + else if (result < 0) + printf("error invalid value\n"); + else + printf("unsupported\n"); + fflush(stdout); + + } else if (!strcmp(buf.buf, "capabilities")) { + printf("fetch\n"); + printf("option\n"); + printf("push\n"); + printf("\n"); + fflush(stdout); + } else { + return 1; + } + strbuf_reset(&buf); + } while (1); + return 0; +} @@ -6,6 +6,7 @@ #include "revision.h" #include "dir.h" #include "tag.h" +#include "string-list.h" static struct refspec s_tag_refspec = { 0, @@ -28,6 +29,11 @@ struct rewrite { int instead_of_nr; int instead_of_alloc; }; +struct rewrites { + struct rewrite **rewrite; + int rewrite_alloc; + int rewrite_nr; +}; static struct remote **remotes; static int remotes_alloc; @@ -41,14 +47,13 @@ static struct branch *current_branch; static const char *default_remote_name; static int explicit_default_remote_name; -static struct rewrite **rewrite; -static int rewrite_alloc; -static int rewrite_nr; +static struct rewrites rewrites; +static struct rewrites rewrites_push; #define BUF_SIZE (2048) static char buffer[BUF_SIZE]; -static const char *alias_url(const char *url) +static const char *alias_url(const char *url, struct rewrites *r) { int i, j; char *ret; @@ -57,14 +62,14 @@ static const char *alias_url(const char *url) longest = NULL; longest_i = -1; - for (i = 0; i < rewrite_nr; i++) { - if (!rewrite[i]) + for (i = 0; i < r->rewrite_nr; i++) { + if (!r->rewrite[i]) continue; - for (j = 0; j < rewrite[i]->instead_of_nr; j++) { - if (!prefixcmp(url, rewrite[i]->instead_of[j].s) && + for (j = 0; j < r->rewrite[i]->instead_of_nr; j++) { + if (!prefixcmp(url, r->rewrite[i]->instead_of[j].s) && (!longest || - longest->len < rewrite[i]->instead_of[j].len)) { - longest = &(rewrite[i]->instead_of[j]); + longest->len < r->rewrite[i]->instead_of[j].len)) { + longest = &(r->rewrite[i]->instead_of[j]); longest_i = i; } } @@ -72,10 +77,10 @@ static const char *alias_url(const char *url) if (!longest) return url; - ret = xmalloc(rewrite[longest_i]->baselen + + ret = xmalloc(r->rewrite[longest_i]->baselen + (strlen(url) - longest->len) + 1); - strcpy(ret, rewrite[longest_i]->base); - strcpy(ret + rewrite[longest_i]->baselen, url + longest->len); + strcpy(ret, r->rewrite[longest_i]->base); + strcpy(ret + r->rewrite[longest_i]->baselen, url + longest->len); return ret; } @@ -101,17 +106,25 @@ static void add_url(struct remote *remote, const char *url) remote->url[remote->url_nr++] = url; } -static void add_url_alias(struct remote *remote, const char *url) -{ - add_url(remote, alias_url(url)); -} - static void add_pushurl(struct remote *remote, const char *pushurl) { ALLOC_GROW(remote->pushurl, remote->pushurl_nr + 1, remote->pushurl_alloc); remote->pushurl[remote->pushurl_nr++] = pushurl; } +static void add_pushurl_alias(struct remote *remote, const char *url) +{ + const char *pushurl = alias_url(url, &rewrites_push); + if (pushurl != url) + add_pushurl(remote, pushurl); +} + +static void add_url_alias(struct remote *remote, const char *url) +{ + add_url(remote, alias_url(url, &rewrites)); + add_pushurl_alias(remote, url); +} + static struct remote *make_remote(const char *name, int len) { struct remote *ret; @@ -169,22 +182,22 @@ static struct branch *make_branch(const char *name, int len) return ret; } -static struct rewrite *make_rewrite(const char *base, int len) +static struct rewrite *make_rewrite(struct rewrites *r, const char *base, int len) { struct rewrite *ret; int i; - for (i = 0; i < rewrite_nr; i++) { + for (i = 0; i < r->rewrite_nr; i++) { if (len - ? (len == rewrite[i]->baselen && - !strncmp(base, rewrite[i]->base, len)) - : !strcmp(base, rewrite[i]->base)) - return rewrite[i]; + ? (len == r->rewrite[i]->baselen && + !strncmp(base, r->rewrite[i]->base, len)) + : !strcmp(base, r->rewrite[i]->base)) + return r->rewrite[i]; } - ALLOC_GROW(rewrite, rewrite_nr + 1, rewrite_alloc); + ALLOC_GROW(r->rewrite, r->rewrite_nr + 1, r->rewrite_alloc); ret = xcalloc(1, sizeof(struct rewrite)); - rewrite[rewrite_nr++] = ret; + r->rewrite[r->rewrite_nr++] = ret; if (len) { ret->base = xstrndup(base, len); ret->baselen = len; @@ -355,8 +368,13 @@ static int handle_config(const char *key, const char *value, void *cb) subkey = strrchr(name, '.'); if (!subkey) return 0; - rewrite = make_rewrite(name, subkey - name); if (!strcmp(subkey, ".insteadof")) { + rewrite = make_rewrite(&rewrites, name, subkey - name); + if (!value) + return config_error_nonbool(key); + add_instead_of(rewrite, xstrdup(value)); + } else if (!strcmp(subkey, ".pushinsteadof")) { + rewrite = make_rewrite(&rewrites_push, name, subkey - name); if (!value) return config_error_nonbool(key); add_instead_of(rewrite, xstrdup(value)); @@ -430,13 +448,17 @@ static void alias_all_urls(void) { int i, j; for (i = 0; i < remotes_nr; i++) { + int add_pushurl_aliases; if (!remotes[i]) continue; - for (j = 0; j < remotes[i]->url_nr; j++) { - remotes[i]->url[j] = alias_url(remotes[i]->url[j]); - } for (j = 0; j < remotes[i]->pushurl_nr; j++) { - remotes[i]->pushurl[j] = alias_url(remotes[i]->pushurl[j]); + remotes[i]->pushurl[j] = alias_url(remotes[i]->pushurl[j], &rewrites); + } + add_pushurl_aliases = remotes[i]->pushurl_nr == 0; + for (j = 0; j < remotes[i]->url_nr; j++) { + if (add_pushurl_aliases) + add_pushurl_alias(remotes[i], remotes[i]->url[j]); + remotes[i]->url[j] = alias_url(remotes[i]->url[j], &rewrites); } } } @@ -713,29 +735,33 @@ int for_each_remote(each_remote_fn fn, void *priv) void ref_remove_duplicates(struct ref *ref_map) { - struct ref **posn; - struct ref *next; - for (; ref_map; ref_map = ref_map->next) { + struct string_list refs = { NULL, 0, 0, 0 }; + struct string_list_item *item = NULL; + struct ref *prev = NULL, *next = NULL; + for (; ref_map; prev = ref_map, ref_map = next) { + next = ref_map->next; if (!ref_map->peer_ref) continue; - posn = &ref_map->next; - while (*posn) { - if ((*posn)->peer_ref && - !strcmp((*posn)->peer_ref->name, - ref_map->peer_ref->name)) { - if (strcmp((*posn)->name, ref_map->name)) - die("%s tracks both %s and %s", - ref_map->peer_ref->name, - (*posn)->name, ref_map->name); - next = (*posn)->next; - free((*posn)->peer_ref); - free(*posn); - *posn = next; - } else { - posn = &(*posn)->next; - } + + item = string_list_lookup(ref_map->peer_ref->name, &refs); + if (item) { + if (strcmp(((struct ref *)item->util)->name, + ref_map->name)) + die("%s tracks both %s and %s", + ref_map->peer_ref->name, + ((struct ref *)item->util)->name, + ref_map->name); + prev->next = ref_map->next; + free(ref_map->peer_ref); + free(ref_map); + ref_map = prev; /* skip this; we freed it */ + continue; } + + item = string_list_insert(ref_map->peer_ref->name, &refs); + item->util = ref_map; } + string_list_clear(&refs, 0); } int remote_has_url(struct remote *remote, const char *url) @@ -61,7 +61,7 @@ static int write_rr(struct string_list *rr, int out_fd) path = rr->items[i].string; length = strlen(path) + 1; if (write_in_full(out_fd, rr->items[i].util, 40) != 40 || - write_in_full(out_fd, "\t", 1) != 1 || + write_str_in_full(out_fd, "\t") != 1 || write_in_full(out_fd, path, length) != length) die("unable to write rerere record"); } diff --git a/revision.c b/revision.c index 35eca4a361..a36c0d9bcd 100644 --- a/revision.c +++ b/revision.c @@ -994,7 +994,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg if (!strcmp(arg, "--all") || !strcmp(arg, "--branches") || !strcmp(arg, "--tags") || !strcmp(arg, "--remotes") || !strcmp(arg, "--reflog") || !strcmp(arg, "--not") || - !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk")) + !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") || + !strcmp(arg, "--bisect")) { unkv[(*unkc)++] = arg; return 1; @@ -1159,8 +1160,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->simplify_history = 0; } else if (!strcmp(arg, "--relative-date")) { revs->date_mode = DATE_RELATIVE; + revs->date_mode_explicit = 1; } else if (!strncmp(arg, "--date=", 7)) { revs->date_mode = parse_date_format(arg + 7); + revs->date_mode_explicit = 1; } else if (!strcmp(arg, "--log-size")) { revs->show_log_size = 1; } @@ -1216,6 +1219,16 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx, ctx->argc -= n; } +static int for_each_bad_bisect_ref(each_ref_fn fn, void *cb_data) +{ + return for_each_ref_in("refs/bisect/bad", fn, cb_data); +} + +static int for_each_good_bisect_ref(each_ref_fn fn, void *cb_data) +{ + return for_each_ref_in("refs/bisect/good", fn, cb_data); +} + /* * Parse revision information, filling in the "rev_info" structure, * and removing the used arguments from the argument list. @@ -1257,6 +1270,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch handle_refs(revs, flags, for_each_branch_ref); continue; } + if (!strcmp(arg, "--bisect")) { + handle_refs(revs, flags, for_each_bad_bisect_ref); + handle_refs(revs, flags ^ UNINTERESTING, for_each_good_bisect_ref); + revs->bisect = 1; + continue; + } if (!strcmp(arg, "--tags")) { handle_refs(revs, flags, for_each_tag_ref); continue; diff --git a/revision.h b/revision.h index 9d0dddbcbc..921656aaab 100644 --- a/revision.h +++ b/revision.h @@ -63,6 +63,7 @@ struct rev_info { reverse:1, reverse_output_stage:1, cherry_pick:1, + bisect:1, first_parent_only:1; /* Diff flags */ @@ -81,7 +82,8 @@ struct rev_info { show_merge:1, abbrev_commit:1, use_terminator:1, - missing_newline:1; + missing_newline:1, + date_mode_explicit:1; enum date_mode date_mode; unsigned int abbrev; diff --git a/run-command.c b/run-command.c index f3e7abb7de..cf2d8f7fae 100644 --- a/run-command.c +++ b/run-command.c @@ -75,7 +75,7 @@ fail_pipe: trace_argv_printf(cmd->argv, "trace: run_command:"); -#ifndef __MINGW32__ +#ifndef WIN32 fflush(NULL); cmd->pid = fork(); if (!cmd->pid) { @@ -134,6 +134,7 @@ fail_pipe: error("cannot fork() for %s: %s", cmd->argv[0], strerror(failed_errno = errno)); #else +{ int s0 = -1, s1 = -1, s2 = -1; /* backups of stdin, stdout, stderr */ const char **sargv = cmd->argv; char **env = environ; @@ -173,11 +174,8 @@ fail_pipe: if (cmd->dir) die("chdir in start_command() not implemented"); - if (cmd->env) { - env = copy_environ(); - for (; *cmd->env; cmd->env++) - env = env_setenv(env, *cmd->env); - } + if (cmd->env) + env = make_augmented_environ(cmd->env); if (cmd->git_cmd) { cmd->argv = prepare_git_cmd(cmd->argv); @@ -200,6 +198,7 @@ fail_pipe: dup2(s1, 1), close(s1); if (s2 >= 0) dup2(s2, 2), close(s2); +} #endif if (cmd->pid < 0) { @@ -316,8 +315,8 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const return run_command(&cmd); } -#ifdef __MINGW32__ -static __stdcall unsigned run_thread(void *data) +#ifdef WIN32 +static unsigned __stdcall run_thread(void *data) { struct async *async = data; return async->proc(async->fd_for_proc, async->data); @@ -332,7 +331,7 @@ int start_async(struct async *async) return error("cannot create pipe: %s", strerror(errno)); async->out = pipe_out[0]; -#ifndef __MINGW32__ +#ifndef WIN32 /* Flush stdio before fork() to avoid cloning buffers */ fflush(NULL); @@ -361,7 +360,7 @@ int start_async(struct async *async) int finish_async(struct async *async) { -#ifndef __MINGW32__ +#ifndef WIN32 int ret = wait_or_whine(async->pid, "child process", 0); #else DWORD ret = 0; diff --git a/run-command.h b/run-command.h index 0c00b25ff2..fb342090e3 100644 --- a/run-command.h +++ b/run-command.h @@ -70,7 +70,7 @@ struct async { int (*proc)(int fd, void *data); void *data; int out; /* caller reads from here and closes it */ -#ifndef __MINGW32__ +#ifndef WIN32 pid_t pid; #else HANDLE tid; diff --git a/send-pack.h b/send-pack.h index 8b3cf028ed..28141ac913 100644 --- a/send-pack.h +++ b/send-pack.h @@ -8,7 +8,8 @@ struct send_pack_args { force_update:1, use_thin_pack:1, use_ofs_delta:1, - dry_run:1; + dry_run:1, + stateless_rpc:1; }; int send_pack(struct send_pack_args *args, @@ -41,7 +41,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]; -#ifndef __MINGW32__ +#ifndef WIN32 if (!pfx || !*pfx || is_absolute_path(arg)) return arg; memcpy(path, pfx, pfx_len); @@ -61,6 +61,19 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) return path; } +int check_filename(const char *prefix, const char *arg) +{ + const char *name; + struct stat st; + + name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg; + if (!lstat(name, &st)) + return 1; /* file exists */ + if (errno == ENOENT || errno == ENOTDIR) + return 0; /* file does not exist */ + die_errno("failed to stat '%s'", arg); +} + /* * Verify a filename that we got as an argument for a pathspec * entry. Note that a filename that begins with "-" never verifies @@ -70,18 +83,12 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) */ void verify_filename(const char *prefix, const char *arg) { - const char *name; - struct stat st; - if (*arg == '-') die("bad flag '%s' used after filename", arg); - name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg; - if (!lstat(name, &st)) + if (check_filename(prefix, arg)) return; - if (errno == ENOENT) - die("ambiguous argument '%s': unknown revision or path not in the working tree.\n" - "Use '--' to separate paths from revisions", arg); - die_errno("failed to stat '%s'", arg); + die("ambiguous argument '%s': unknown revision or path not in the working tree.\n" + "Use '--' to separate paths from revisions", arg); } /* @@ -91,19 +98,14 @@ void verify_filename(const char *prefix, const char *arg) */ void verify_non_filename(const char *prefix, const char *arg) { - const char *name; - struct stat st; - if (!is_inside_work_tree() || is_inside_git_dir()) return; if (*arg == '-') return; /* flag */ - name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg; - if (!lstat(name, &st)) - die("ambiguous argument '%s': both revision and filename\n" - "Use '--' to separate filenames from revisions", arg); - if (errno != ENOENT && errno != ENOTDIR) - die_errno("failed to stat '%s'", arg); + if (!check_filename(prefix, arg)) + return; + die("ambiguous argument '%s': both revision and filename\n" + "Use '--' to separate filenames from revisions", arg); } const char **get_pathspec(const char *prefix, const char **pathspec) diff --git a/sha1_file.c b/sha1_file.c index 4ea0b18d0a..63981fb3fd 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1587,13 +1587,15 @@ static void *unpack_compressed_entry(struct packed_git *p, buffer[size] = 0; memset(&stream, 0, sizeof(stream)); stream.next_out = buffer; - stream.avail_out = size; + stream.avail_out = size + 1; git_inflate_init(&stream); do { in = use_pack(p, w_curs, curpos, &stream.avail_in); stream.next_in = in; st = git_inflate(&stream, Z_FINISH); + if (!stream.avail_out) + break; /* the payload is larger than it should be */ curpos += stream.next_in - in; } while (st == Z_OK || st == Z_BUF_ERROR); git_inflate_end(&stream); diff --git a/show-index.c b/show-index.c index 45bb535773..63f9da5323 100644 --- a/show-index.c +++ b/show-index.c @@ -1,6 +1,9 @@ #include "cache.h" #include "pack.h" +static const char show_index_usage[] = +"git show-index < <packed archive index>"; + int main(int argc, char **argv) { int i; @@ -8,6 +11,8 @@ int main(int argc, char **argv) unsigned int version; static unsigned int top_index[256]; + if (argc != 1) + usage(show_index_usage); if (fread(top_index, 2 * 4, 1, stdin) != 1) die("unable to read header"); if (top_index[0] == htonl(PACK_IDX_SIGNATURE)) { diff --git a/sideband.c b/sideband.c index 899b1ff366..d5ffa1c891 100644 --- a/sideband.c +++ b/sideband.c @@ -135,9 +135,14 @@ ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet n = sz; if (packet_max - 5 < n) n = packet_max - 5; - sprintf(hdr, "%04x", n + 5); - hdr[4] = band; - safe_write(fd, hdr, 5); + if (0 <= band) { + sprintf(hdr, "%04x", n + 5); + hdr[4] = band; + safe_write(fd, hdr, 5); + } else { + sprintf(hdr, "%04x", n + 4); + safe_write(fd, hdr, 4); + } safe_write(fd, p, n); p += n; sz -= n; diff --git a/submodule.c b/submodule.c new file mode 100644 index 0000000000..86aad653b7 --- /dev/null +++ b/submodule.c @@ -0,0 +1,114 @@ +#include "cache.h" +#include "submodule.h" +#include "dir.h" +#include "diff.h" +#include "commit.h" +#include "revision.h" + +int add_submodule_odb(const char *path) +{ + struct strbuf objects_directory = STRBUF_INIT; + struct alternate_object_database *alt_odb; + + strbuf_addf(&objects_directory, "%s/.git/objects/", path); + if (!is_directory(objects_directory.buf)) + return -1; + + /* avoid adding it twice */ + for (alt_odb = alt_odb_list; alt_odb; alt_odb = alt_odb->next) + if (alt_odb->name - alt_odb->base == objects_directory.len && + !strncmp(alt_odb->base, objects_directory.buf, + objects_directory.len)) + return 0; + + alt_odb = xmalloc(objects_directory.len + 42 + sizeof(*alt_odb)); + alt_odb->next = alt_odb_list; + strcpy(alt_odb->base, objects_directory.buf); + alt_odb->name = alt_odb->base + objects_directory.len; + alt_odb->name[2] = '/'; + alt_odb->name[40] = '\0'; + alt_odb->name[41] = '\0'; + alt_odb_list = alt_odb; + prepare_alt_odb(); + return 0; +} + +void show_submodule_summary(FILE *f, const char *path, + unsigned char one[20], unsigned char two[20], + const char *del, const char *add, const char *reset) +{ + struct rev_info rev; + struct commit *commit, *left = left, *right = right; + struct commit_list *merge_bases, *list; + const char *message = NULL; + struct strbuf sb = STRBUF_INIT; + static const char *format = " %m %s"; + int fast_forward = 0, fast_backward = 0; + + if (is_null_sha1(two)) + message = "(submodule deleted)"; + else if (add_submodule_odb(path)) + message = "(not checked out)"; + else if (is_null_sha1(one)) + message = "(new submodule)"; + else if (!(left = lookup_commit_reference(one)) || + !(right = lookup_commit_reference(two))) + message = "(commits not present)"; + + if (!message) { + init_revisions(&rev, NULL); + setup_revisions(0, NULL, &rev, NULL); + rev.left_right = 1; + rev.first_parent_only = 1; + left->object.flags |= SYMMETRIC_LEFT; + add_pending_object(&rev, &left->object, path); + add_pending_object(&rev, &right->object, path); + merge_bases = get_merge_bases(left, right, 1); + if (merge_bases) { + if (merge_bases->item == left) + fast_forward = 1; + else if (merge_bases->item == right) + fast_backward = 1; + } + for (list = merge_bases; list; list = list->next) { + list->item->object.flags |= UNINTERESTING; + add_pending_object(&rev, &list->item->object, + sha1_to_hex(list->item->object.sha1)); + } + if (prepare_revision_walk(&rev)) + message = "(revision walker failed)"; + } + + strbuf_addf(&sb, "Submodule %s %s..", path, + find_unique_abbrev(one, DEFAULT_ABBREV)); + if (!fast_backward && !fast_forward) + strbuf_addch(&sb, '.'); + strbuf_addf(&sb, "%s", find_unique_abbrev(two, DEFAULT_ABBREV)); + if (message) + strbuf_addf(&sb, " %s\n", message); + else + strbuf_addf(&sb, "%s:\n", fast_backward ? " (rewind)" : ""); + fwrite(sb.buf, sb.len, 1, f); + + if (!message) { + while ((commit = get_revision(&rev))) { + struct pretty_print_context ctx = {0}; + ctx.date_mode = rev.date_mode; + strbuf_setlen(&sb, 0); + if (commit->object.flags & SYMMETRIC_LEFT) { + if (del) + strbuf_addstr(&sb, del); + } + else if (add) + strbuf_addstr(&sb, add); + format_commit_message(commit, format, &sb, &ctx); + if (reset) + strbuf_addstr(&sb, reset); + strbuf_addch(&sb, '\n'); + fprintf(f, "%s", sb.buf); + } + clear_commit_marks(left, ~0); + clear_commit_marks(right, ~0); + } + strbuf_release(&sb); +} diff --git a/submodule.h b/submodule.h new file mode 100644 index 0000000000..4c0269d679 --- /dev/null +++ b/submodule.h @@ -0,0 +1,8 @@ +#ifndef SUBMODULE_H +#define SUBMODULE_H + +void show_submodule_summary(FILE *f, const char *path, + unsigned char one[20], unsigned char two[20], + const char *del, const char *add, const char *reset); + +#endif diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index fd8631f906..0f7f35ccc9 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -16,6 +16,7 @@ fi GIT_DIR=$PWD/.git GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn SVN_TREE=$GIT_SVN_DIR/svn-tree +PERL=${PERL:-perl} svn >/dev/null 2>&1 if test $? -ne 1 @@ -29,7 +30,7 @@ export svnrepo svnconf=$PWD/svnconf export svnconf -perl -w -e " +$PERL -w -e " use SVN::Core; use SVN::Repos; \$SVN::Core::VERSION gt '1.1.0' or exit(42); @@ -130,7 +131,7 @@ stop_httpd () { } convert_to_rev_db () { - perl -w -- - "$@" <<\EOF + $PERL -w -- - "$@" <<\EOF use strict; @ARGV == 2 or die "Usage: convert_to_rev_db <input> <output>"; open my $wr, '+>', $ARGV[1] or die "$!: couldn't open: $ARGV[1]"; diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 21aa42f1c6..0fe3fd0d01 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -8,6 +8,28 @@ ErrorLog error.log <IfModule !mod_log_config.c> LoadModule log_config_module modules/mod_log_config.so </IfModule> +<IfModule !mod_alias.c> + LoadModule alias_module modules/mod_alias.so +</IfModule> +<IfModule !mod_cgi.c> + LoadModule cgi_module modules/mod_cgi.so +</IfModule> +<IfModule !mod_env.c> + LoadModule env_module modules/mod_env.so +</IfModule> + +Alias /dumb/ www/ + +<Location /smart/> + SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} +</Location> +ScriptAlias /smart/ ${GIT_EXEC_PATH}/git-http-backend/ +<Directory ${GIT_EXEC_PATH}> + Options None +</Directory> +<Files ${GIT_EXEC_PATH}/git-http-backend> + Options ExecCGI +</Files> <IfDefine SSL> LoadModule ssl_module modules/mod_ssl.so @@ -26,7 +48,7 @@ SSLEngine On LoadModule dav_fs_module modules/mod_dav_fs.so DAVLockDB DAVLock - <Location /> + <Location /dumb/> Dav on </Location> </IfDefine> diff --git a/t/lib-patch-mode.sh b/t/lib-patch-mode.sh new file mode 100755 index 0000000000..75a3ee283d --- /dev/null +++ b/t/lib-patch-mode.sh @@ -0,0 +1,41 @@ +. ./test-lib.sh + +if ! test_have_prereq PERL; then + say 'skipping --patch tests, perl not available' + test_done +fi + +set_state () { + echo "$3" > "$1" && + git add "$1" && + echo "$2" > "$1" +} + +save_state () { + noslash="$(echo "$1" | tr / _)" && + cat "$1" > _worktree_"$noslash" && + git show :"$1" > _index_"$noslash" +} + +set_and_save_state () { + set_state "$@" && + save_state "$1" +} + +verify_state () { + test "$(cat "$1")" = "$2" && + test "$(git show :"$1")" = "$3" +} + +verify_saved_state () { + noslash="$(echo "$1" | tr / _)" && + verify_state "$1" "$(cat _worktree_"$noslash")" "$(cat _index_"$noslash")" +} + +save_head () { + git rev-parse HEAD > _head +} + +verify_saved_head () { + test "$(cat _head)" = "$(git rev-parse HEAD)" +} diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 260a231933..62f452c8ea 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -9,8 +9,8 @@ # # "[<lineno1>] [<lineno2>]..." # -# If a line number is prefixed with "squash" or "edit", the respective line's -# command will be replaced with the specified one. +# If a line number is prefixed with "squash", "edit", or "reword", the +# respective line's command will be replaced with the specified one. set_fake_editor () { echo "#!$SHELL_PATH" >fake-editor.sh @@ -32,7 +32,7 @@ cat "$1".tmp action=pick for line in $FAKE_LINES; do case $line in - squash|edit) + squash|edit|reword) action="$line";; *) echo sed -n "${line}s/^pick/$action/p" diff --git a/t/t0006-date.sh b/t/t0006-date.sh index a4d8fa8fa1..75b02af86d 100755 --- a/t/t0006-date.sh +++ b/t/t0006-date.sh @@ -24,6 +24,7 @@ check_show 13000000 '5 months ago' check_show 37500000 '1 year, 2 months ago' check_show 55188000 '1 year, 9 months ago' check_show 630000000 '20 years ago' +check_show 31449600 '12 months ago' check_parse() { echo "$1 -> $2" >expect diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index bbc821ef97..3d450ed379 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -33,6 +33,8 @@ Magic arguments --quux means --quux -NUM set integer to NUM + same as -b + --ambiguous positive ambiguity + --no-ambiguous negative ambiguity Standard options --abbrev[=<n>] use <n> digits to display SHA-1s @@ -315,4 +317,22 @@ test_expect_success 'OPT_NUMBER_CALLBACK() works' ' test_cmp expect output ' +cat >expect <<EOF +boolean: 0 +integer: 0 +timestamp: 0 +string: (not set) +abbrev: 7 +verbose: 0 +quiet: no +dry run: no +file: (not set) +EOF + +test_expect_success 'negation of OPT_NONEG flags is not ambiguous' ' + test-parse-options --no-ambig >output 2>output.err && + test ! -s output.err && + test_cmp expect output +' + test_done diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index 271bc4e17f..c2d408b461 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -5,7 +5,7 @@ test_description='Two way merge with read-tree -m $H $M -This test tries two-way merge (aka fast forward with carry forward). +This test tries two-way merge (aka fast-forward with carry forward). There is the head (called H) and another commit (called M), which is simply ahead of H. The index and the work tree contains a state that @@ -51,7 +51,7 @@ check_cache_at () { } cat >bozbar-old <<\EOF -This is a sample file used in two-way fast forward merge +This is a sample file used in two-way fast-forward merge tests. Its second line ends with a magic word bozbar which will be modified by the merged head to gnusto. It has some extra lines so that external tools can @@ -300,7 +300,7 @@ test_expect_success \ echo gnusto gnusto >bozbar && if read_tree_twoway $treeH $treeM; then false; else :; fi' -# This fails with straight two-way fast forward. +# This fails with straight two-way fast-forward. test_expect_success \ '22 - local change cache updated.' \ 'rm -f .git/index && diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index 67e637b781..6bf84755f3 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -7,14 +7,18 @@ test_description='A simple turial in the form of a test case' . ./test-lib.sh -echo "Hello World" > hello -echo "Silly example" > example +test_expect_success 'blob' ' + echo "Hello World" > hello && + echo "Silly example" > example && -git update-index --add hello example + git update-index --add hello example && -test_expect_success 'blob' "test blob = \"$(git cat-file -t 557db03)\"" + test blob = "$(git cat-file -t 557db03)" +' -test_expect_success 'blob 557db03' "test \"Hello World\" = \"$(git cat-file blob 557db03)\"" +test_expect_success 'blob 557db03' ' + test "Hello World" = "$(git cat-file blob 557db03)" +' echo "It's a new day for git" >>hello cat > diff.expect << EOF @@ -26,25 +30,34 @@ index 557db03..263414f 100644 Hello World +It's a new day for git EOF -git diff-files -p > diff.output -test_expect_success 'git diff-files -p' 'cmp diff.expect diff.output' -git diff > diff.output -test_expect_success 'git diff' 'cmp diff.expect diff.output' - -tree=$(git write-tree 2>/dev/null) -test_expect_success 'tree' "test 8988da15d077d4829fc51d8544c097def6644dbb = $tree" +test_expect_success 'git diff-files -p' ' + git diff-files -p > diff.output && + test_cmp diff.expect diff.output +' -output="$(echo "Initial commit" | git commit-tree $(git write-tree) 2>&1 > .git/refs/heads/master)" +test_expect_success 'git diff' ' + git diff > diff.output && + test_cmp diff.expect diff.output +' -git diff-index -p HEAD > diff.output -test_expect_success 'git diff-index -p HEAD' 'cmp diff.expect diff.output' +test_expect_success 'tree' ' + tree=$(git write-tree 2>/dev/null) + test 8988da15d077d4829fc51d8544c097def6644dbb = $tree +' -git diff HEAD > diff.output -test_expect_success 'git diff HEAD' 'cmp diff.expect diff.output' +test_expect_success 'git diff-index -p HEAD' ' + tree=$(git write-tree) + commit=$(echo "Initial commit" | git commit-tree $tree) && + git update-ref HEAD $commit && + git diff-index -p HEAD > diff.output && + test_cmp diff.expect diff.output +' -#rm hello -#test_expect_success 'git read-tree --reset HEAD' "git read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git update-index --refresh)\"" +test_expect_success 'git diff HEAD' ' + git diff HEAD > diff.output && + test_cmp diff.expect diff.output +' cat > whatchanged.expect << EOF commit VARIABLE @@ -69,39 +82,45 @@ index 0000000..557db03 +Hello World EOF -git whatchanged -p --root | \ - sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \ +test_expect_success 'git whatchanged -p --root' ' + git whatchanged -p --root | + sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \ -e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \ -> whatchanged.output -test_expect_success 'git whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output' - -git tag my-first-tag -test_expect_success 'git tag my-first-tag' 'cmp .git/refs/heads/master .git/refs/tags/my-first-tag' + > whatchanged.output && + test_cmp whatchanged.expect whatchanged.output +' -# TODO: test git clone +test_expect_success 'git tag my-first-tag' ' + git tag my-first-tag && + test_cmp .git/refs/heads/master .git/refs/tags/my-first-tag +' -git checkout -b mybranch -test_expect_success 'git checkout -b mybranch' 'cmp .git/refs/heads/master .git/refs/heads/mybranch' +test_expect_success 'git checkout -b mybranch' ' + git checkout -b mybranch && + test_cmp .git/refs/heads/master .git/refs/heads/mybranch +' cat > branch.expect <<EOF master * mybranch EOF -git branch > branch.output -test_expect_success 'git branch' 'cmp branch.expect branch.output' +test_expect_success 'git branch' ' + git branch > branch.output && + test_cmp branch.expect branch.output +' -git checkout mybranch -echo "Work, work, work" >>hello -git commit -m 'Some work.' -i hello +test_expect_success 'git resolve now fails' ' + git checkout mybranch && + echo "Work, work, work" >>hello && + git commit -m "Some work." -i hello && -git checkout master + git checkout master && -echo "Play, play, play" >>hello -echo "Lots of fun" >>example -git commit -m 'Some fun.' -i hello example + echo "Play, play, play" >>hello && + echo "Lots of fun" >>example && + git commit -m "Some fun." -i hello example && -test_expect_success 'git resolve now fails' ' test_must_fail git merge -m "Merge work in mybranch" mybranch ' @@ -112,52 +131,131 @@ Play, play, play Work, work, work EOF -git commit -m 'Merged "mybranch" changes.' -i hello - -test_done - cat > show-branch.expect << EOF -* [master] Merged "mybranch" changes. +* [master] Merge work in mybranch ! [mybranch] Some work. -- -- [master] Merged "mybranch" changes. +- [master] Merge work in mybranch *+ [mybranch] Some work. +* [master^] Some fun. EOF -git show-branch --topo-order master mybranch > show-branch.output -test_expect_success 'git show-branch' 'cmp show-branch.expect show-branch.output' - -git checkout mybranch +test_expect_success 'git show-branch' ' + git commit -m "Merge work in mybranch" -i hello && + git show-branch --topo-order --more=1 master mybranch \ + > show-branch.output && + test_cmp show-branch.expect show-branch.output +' cat > resolve.expect << EOF -Updating from VARIABLE to VARIABLE +Updating VARIABLE..VARIABLE +FASTFORWARD (no commit created; -m option ignored) example | 1 + hello | 1 + 2 files changed, 2 insertions(+), 0 deletions(-) EOF -git merge -s "Merge upstream changes." master | \ - sed -e "1s/[0-9a-f]\{40\}/VARIABLE/g" >resolve.output -test_expect_success 'git resolve' 'cmp resolve.expect resolve.output' +test_expect_success 'git resolve' ' + git checkout mybranch && + git merge -m "Merge upstream changes." master | + sed -e "1s/[0-9a-f]\{7\}/VARIABLE/g" \ + -e "s/^Fast[- ]forward /FASTFORWARD /" >resolve.output && + test_cmp resolve.expect resolve.output +' cat > show-branch2.expect << EOF -! [master] Merged "mybranch" changes. - * [mybranch] Merged "mybranch" changes. +! [master] Merge work in mybranch + * [mybranch] Merge work in mybranch +-- +-- [master] Merge work in mybranch +EOF + +test_expect_success 'git show-branch (part 2)' ' + git show-branch --topo-order master mybranch > show-branch2.output && + test_cmp show-branch2.expect show-branch2.output +' + +cat > show-branch3.expect << EOF +! [master] Merge work in mybranch + * [mybranch] Merge work in mybranch +-- +-- [master] Merge work in mybranch ++* [master^2] Some work. ++* [master^] Some fun. +EOF + +test_expect_success 'git show-branch (part 3)' ' + git show-branch --topo-order --more=2 master mybranch \ + > show-branch3.output && + test_cmp show-branch3.expect show-branch3.output +' + +test_expect_success 'rewind to "Some fun." and "Some work."' ' + git checkout mybranch && + git reset --hard master^2 && + git checkout master && + git reset --hard master^ +' + +cat > show-branch4.expect << EOF +* [master] Some fun. + ! [mybranch] Some work. -- --- [master] Merged "mybranch" changes. + + [mybranch] Some work. +* [master] Some fun. +*+ [mybranch^] Initial commit +EOF + +test_expect_success 'git show-branch (part 4)' ' + git show-branch --topo-order > show-branch4.output && + test_cmp show-branch4.expect show-branch4.output +' + +test_expect_success 'manual merge' ' + mb=$(git merge-base HEAD mybranch) && + git name-rev --name-only --tags $mb > name-rev.output && + test "my-first-tag" = $(cat name-rev.output) && + + git read-tree -m -u $mb HEAD mybranch +' + +cat > ls-files.expect << EOF +100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example +100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello +100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello +100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello +EOF + +test_expect_success 'git ls-files --stage' ' + git ls-files --stage > ls-files.output && + test_cmp ls-files.expect ls-files.output +' + +cat > ls-files-unmerged.expect << EOF +100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello +100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello +100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello EOF -git show-branch --topo-order master mybranch > show-branch2.output -test_expect_success 'git show-branch' 'cmp show-branch2.expect show-branch2.output' +test_expect_success 'git ls-files --unmerged' ' + git ls-files --unmerged > ls-files-unmerged.output && + test_cmp ls-files-unmerged.expect ls-files-unmerged.output +' -# TODO: test git fetch +test_expect_success 'git-merge-index' ' + test_must_fail git merge-index git-merge-one-file hello +' -# TODO: test git push +test_expect_success 'git ls-files --stage (part 2)' ' + git ls-files --stage > ls-files.output2 && + test_cmp ls-files.expect ls-files.output2 +' test_expect_success 'git repack' 'git repack' test_expect_success 'git prune-packed' 'git prune-packed' test_expect_success '-> only packed objects' ' - ! find -type f .git/objects/[0-9a-f][0-9a-f] + git prune && # Remove conflict marked blobs + ! find .git/objects/[0-9a-f][0-9a-f] -type f ' test_done diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh new file mode 100755 index 0000000000..eb45afb018 --- /dev/null +++ b/t/t1402-check-ref-format.sh @@ -0,0 +1,61 @@ +#!/bin/sh + +test_description='Test git check-ref-format' + +. ./test-lib.sh + +valid_ref() { + test_expect_success "ref name '$1' is valid" \ + "git check-ref-format '$1'" +} +invalid_ref() { + test_expect_success "ref name '$1' is not valid" \ + "test_must_fail git check-ref-format '$1'" +} + +valid_ref 'heads/foo' +invalid_ref 'foo' +valid_ref 'foo/bar/baz' +valid_ref 'refs///heads/foo' +invalid_ref 'heads/foo/' +invalid_ref './foo' +invalid_ref '.refs/foo' +invalid_ref 'heads/foo..bar' +invalid_ref 'heads/foo?bar' +valid_ref 'foo./bar' +invalid_ref 'heads/foo.lock' +valid_ref 'heads/foo@bar' +invalid_ref 'heads/v@{ation' +invalid_ref 'heads/foo\bar' + +test_expect_success "check-ref-format --branch @{-1}" ' + T=$(git write-tree) && + sha1=$(echo A | git commit-tree $T) && + git update-ref refs/heads/master $sha1 && + git update-ref refs/remotes/origin/master $sha1 + git checkout master && + git checkout origin/master && + git checkout master && + refname=$(git check-ref-format --branch @{-1}) && + test "$refname" = "$sha1" && + refname2=$(git check-ref-format --branch @{-2}) && + test "$refname2" = master' + +valid_ref_normalized() { + test_expect_success "ref name '$1' simplifies to '$2'" " + refname=\$(git check-ref-format --print '$1') && + test \"\$refname\" = '$2'" +} +invalid_ref_normalized() { + test_expect_success "check-ref-format --print rejects '$1'" " + test_must_fail git check-ref-format --print '$1'" +} + +valid_ref_normalized 'heads/foo' 'heads/foo' +valid_ref_normalized 'refs///heads/foo' 'refs/heads/foo' +invalid_ref_normalized 'foo' +invalid_ref_normalized 'heads/foo/../bar' +invalid_ref_normalized 'heads/./foo' +invalid_ref_normalized 'heads\foo' + +test_done diff --git a/t/t2016-checkout-patch.sh b/t/t2016-checkout-patch.sh new file mode 100755 index 0000000000..4d1c2e9e09 --- /dev/null +++ b/t/t2016-checkout-patch.sh @@ -0,0 +1,107 @@ +#!/bin/sh + +test_description='git checkout --patch' + +. ./lib-patch-mode.sh + +test_expect_success 'setup' ' + mkdir dir && + echo parent > dir/foo && + echo dummy > bar && + git add bar dir/foo && + git commit -m initial && + test_tick && + test_commit second dir/foo head && + set_and_save_state bar bar_work bar_index && + save_head +' + +# note: bar sorts before dir/foo, so the first 'n' is always to skip 'bar' + +test_expect_success 'saying "n" does nothing' ' + set_and_save_state dir/foo work head && + (echo n; echo n) | git checkout -p && + verify_saved_state bar && + verify_saved_state dir/foo +' + +test_expect_success 'git checkout -p' ' + (echo n; echo y) | git checkout -p && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'git checkout -p with staged changes' ' + set_state dir/foo work index + (echo n; echo y) | git checkout -p && + verify_saved_state bar && + verify_state dir/foo index index +' + +test_expect_success 'git checkout -p HEAD with NO staged changes: abort' ' + set_and_save_state dir/foo work head && + (echo n; echo y; echo n) | git checkout -p HEAD && + verify_saved_state bar && + verify_saved_state dir/foo +' + +test_expect_success 'git checkout -p HEAD with NO staged changes: apply' ' + (echo n; echo y; echo y) | git checkout -p HEAD && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'git checkout -p HEAD with change already staged' ' + set_state dir/foo index index + # the third n is to get out in case it mistakenly does not apply + (echo n; echo y; echo n) | git checkout -p HEAD && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'git checkout -p HEAD^' ' + # the third n is to get out in case it mistakenly does not apply + (echo n; echo y; echo n) | git checkout -p HEAD^ && + verify_saved_state bar && + verify_state dir/foo parent parent +' + +# The idea in the rest is that bar sorts first, so we always say 'y' +# first and if the path limiter fails it'll apply to bar instead of +# dir/foo. There's always an extra 'n' to reject edits to dir/foo in +# the failure case (and thus get out of the loop). + +test_expect_success 'path limiting works: dir' ' + set_state dir/foo work head && + (echo y; echo n) | git checkout -p dir && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'path limiting works: -- dir' ' + set_state dir/foo work head && + (echo y; echo n) | git checkout -p -- dir && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'path limiting works: HEAD^ -- dir' ' + # the third n is to get out in case it mistakenly does not apply + (echo y; echo n; echo n) | git checkout -p HEAD^ -- dir && + verify_saved_state bar && + verify_state dir/foo parent parent +' + +test_expect_success 'path limiting works: foo inside dir' ' + set_state dir/foo work head && + # the third n is to get out in case it mistakenly does not apply + (echo y; echo n; echo n) | (cd dir && git checkout -p foo) && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'none of this moved HEAD' ' + verify_saved_head +' + +test_done diff --git a/t/t3003-ls-files-exclude.sh b/t/t3003-ls-files-exclude.sh new file mode 100755 index 0000000000..d5ec333131 --- /dev/null +++ b/t/t3003-ls-files-exclude.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +test_description='ls-files --exclude does not affect index files' +. ./test-lib.sh + +test_expect_success 'create repo with file' ' + echo content >file && + git add file && + git commit -m file && + echo modification >file +' + +check_output() { +test_expect_success "ls-files output contains file ($1)" " + echo '$2' >expect && + git ls-files --exclude-standard --$1 >output && + test_cmp expect output +" +} + +check_all_output() { + check_output 'cached' 'file' + check_output 'modified' 'file' +} + +check_all_output +test_expect_success 'add file to gitignore' ' + echo file >.gitignore +' +check_all_output + +test_expect_success 'ls-files -i lists only tracked-but-ignored files' ' + echo content >other-file && + git add other-file && + echo file >expect && + git ls-files -i --exclude-standard >output && + test_cmp expect output +' + +test_done diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh index 51cb4a30f5..8be9fb4112 100755 --- a/t/t3101-ls-tree-dirname.sh +++ b/t/t3101-ls-tree-dirname.sh @@ -39,8 +39,8 @@ test_expect_success \ tree=`git write-tree` && echo $tree' -_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' -_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" +_x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' +_x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05" test_output () { sed -e "s/ $_x40 / X /" <current >check test_cmp expected check @@ -141,4 +141,89 @@ test_expect_success 'ls-tree filter is leading path match' ' test_output ' +test_expect_success 'ls-tree --full-name' ' + ( + cd path0 && + git ls-tree --full-name $tree a + ) >current && + cat >expected <<\EOF && +040000 tree X path0/a +EOF + test_output +' + +test_expect_success 'ls-tree --full-tree' ' + ( + cd path1/b/c && + git ls-tree --full-tree $tree + ) >current && + cat >expected <<\EOF && +100644 blob X 1.txt +100644 blob X 2.txt +040000 tree X path0 +040000 tree X path1 +040000 tree X path2 +040000 tree X path3 +EOF + test_output +' + +test_expect_success 'ls-tree --full-tree -r' ' + ( + cd path3/ && + git ls-tree --full-tree -r $tree + ) >current && + cat >expected <<\EOF && +100644 blob X 1.txt +100644 blob X 2.txt +100644 blob X path0/a/b/c/1.txt +100644 blob X path1/b/c/1.txt +100644 blob X path2/1.txt +100644 blob X path3/1.txt +100644 blob X path3/2.txt +EOF + test_output +' + +test_expect_success 'ls-tree --abbrev=5' ' + git ls-tree --abbrev=5 $tree >current && + sed -e "s/ $_x05[0-9a-f]* / X /" <current >check && + cat >expected <<\EOF && +100644 blob X 1.txt +100644 blob X 2.txt +040000 tree X path0 +040000 tree X path1 +040000 tree X path2 +040000 tree X path3 +EOF + test_cmp expected check +' + +test_expect_success 'ls-tree --name-only' ' + git ls-tree --name-only $tree >current + cat >expected <<\EOF && +1.txt +2.txt +path0 +path1 +path2 +path3 +EOF + test_output +' + +test_expect_success 'ls-tree --name-only -r' ' + git ls-tree --name-only -r $tree >current + cat >expected <<\EOF && +1.txt +2.txt +path0/a/b/c/1.txt +path1/b/c/1.txt +path2/1.txt +path3/1.txt +path3/2.txt +EOF + test_output +' + test_done diff --git a/t/t3202-show-branch-octopus.sh b/t/t3202-show-branch-octopus.sh index 7fe4a6ecb0..0a5d5e669f 100755 --- a/t/t3202-show-branch-octopus.sh +++ b/t/t3202-show-branch-octopus.sh @@ -56,4 +56,12 @@ test_expect_success 'show-branch with more than 8 branches' ' ' +test_expect_success 'show-branch with showbranch.default' ' + for i in $numbers; do + git config --add showbranch.default branch$i + done && + git show-branch >out && + test_cmp expect out +' + test_done diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh new file mode 100755 index 0000000000..1e34f4836f --- /dev/null +++ b/t/t3301-notes.sh @@ -0,0 +1,150 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes E. Schindelin +# + +test_description='Test commit notes' + +. ./test-lib.sh + +cat > fake_editor.sh << \EOF +echo "$MSG" > "$1" +echo "$MSG" >& 2 +EOF +chmod a+x fake_editor.sh +VISUAL=./fake_editor.sh +export VISUAL + +test_expect_success 'cannot annotate non-existing HEAD' ' + (MSG=3 && export MSG && test_must_fail git notes edit) +' + +test_expect_success setup ' + : > a1 && + git add a1 && + test_tick && + git commit -m 1st && + : > a2 && + git add a2 && + test_tick && + git commit -m 2nd +' + +test_expect_success 'need valid notes ref' ' + (MSG=1 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF && + test_must_fail git notes edit) && + (MSG=2 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF && + test_must_fail git notes show) +' + +test_expect_success 'refusing to edit in refs/heads/' ' + (MSG=1 GIT_NOTES_REF=refs/heads/bogus && + export MSG GIT_NOTES_REF && + test_must_fail git notes edit) +' + +test_expect_success 'refusing to edit in refs/remotes/' ' + (MSG=1 GIT_NOTES_REF=refs/remotes/bogus && + export MSG GIT_NOTES_REF && + test_must_fail git notes edit) +' + +# 1 indicates caught gracefully by die, 128 means git-show barked +test_expect_success 'handle empty notes gracefully' ' + git notes show ; test 1 = $? +' + +test_expect_success 'create notes' ' + git config core.notesRef refs/notes/commits && + MSG=b1 git notes edit && + test ! -f .git/new-notes && + test 1 = $(git ls-tree refs/notes/commits | wc -l) && + test b1 = $(git notes show) && + git show HEAD^ && + test_must_fail git notes show HEAD^ +' + +cat > expect << EOF +commit 268048bfb8a1fb38e703baceb8ab235421bf80c5 +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:14:13 2005 -0700 + + 2nd + +Notes: + b1 +EOF + +test_expect_success 'show notes' ' + ! (git cat-file commit HEAD | grep b1) && + git log -1 > output && + test_cmp expect output +' +test_expect_success 'create multi-line notes (setup)' ' + : > a3 && + git add a3 && + test_tick && + git commit -m 3rd && + MSG="b3 +c3c3c3c3 +d3d3d3" git notes edit +' + +cat > expect-multiline << EOF +commit 1584215f1d29c65e99c6c6848626553fdd07fd75 +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:15:13 2005 -0700 + + 3rd + +Notes: + b3 + c3c3c3c3 + d3d3d3 +EOF + +printf "\n" >> expect-multiline +cat expect >> expect-multiline + +test_expect_success 'show multi-line notes' ' + git log -2 > output && + test_cmp expect-multiline output +' +test_expect_success 'create -m and -F notes (setup)' ' + : > a4 && + git add a4 && + test_tick && + git commit -m 4th && + echo "xyzzy" > note5 && + git notes edit -m spam -F note5 -m "foo +bar +baz" +' + +whitespace=" " +cat > expect-m-and-F << EOF +commit 15023535574ded8b1a89052b32673f84cf9582b8 +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:16:13 2005 -0700 + + 4th + +Notes: + spam +$whitespace + xyzzy +$whitespace + foo + bar + baz +EOF + +printf "\n" >> expect-m-and-F +cat expect-multiline >> expect-m-and-F + +test_expect_success 'show -m and -F notes' ' + git log -3 > output && + test_cmp expect-m-and-F output +' + +test_done diff --git a/t/t3302-notes-index-expensive.sh b/t/t3302-notes-index-expensive.sh new file mode 100755 index 0000000000..ee84fc4884 --- /dev/null +++ b/t/t3302-notes-index-expensive.sh @@ -0,0 +1,118 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes E. Schindelin +# + +test_description='Test commit notes index (expensive!)' + +. ./test-lib.sh + +test -z "$GIT_NOTES_TIMING_TESTS" && { + say Skipping timing tests + test_done + exit +} + +create_repo () { + number_of_commits=$1 + nr=0 + test -d .git || { + git init && + ( + while [ $nr -lt $number_of_commits ]; do + nr=$(($nr+1)) + mark=$(($nr+$nr)) + notemark=$(($mark+1)) + test_tick && + cat <<INPUT_END && +commit refs/heads/master +mark :$mark +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +commit #$nr +COMMIT + +M 644 inline file +data <<EOF +file in commit #$nr +EOF + +blob +mark :$notemark +data <<EOF +note for commit #$nr +EOF + +INPUT_END + + echo "N :$notemark :$mark" >> note_commit + done && + test_tick && + cat <<INPUT_END && +commit refs/notes/commits +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +notes +COMMIT + +INPUT_END + + cat note_commit + ) | + git fast-import --quiet && + git config core.notesRef refs/notes/commits + } +} + +test_notes () { + count=$1 && + git config core.notesRef refs/notes/commits && + git log | grep "^ " > output && + i=$count && + while [ $i -gt 0 ]; do + echo " commit #$i" && + echo " note for commit #$i" && + i=$(($i-1)); + done > expect && + test_cmp expect output +} + +cat > time_notes << \EOF + mode=$1 + i=1 + while [ $i -lt $2 ]; do + case $1 in + no-notes) + GIT_NOTES_REF=non-existing; export GIT_NOTES_REF + ;; + notes) + unset GIT_NOTES_REF + ;; + esac + git log >/dev/null + i=$(($i+1)) + done +EOF + +time_notes () { + for mode in no-notes notes + do + echo $mode + /usr/bin/time sh ../time_notes $mode $1 + done +} + +for count in 10 100 1000 10000; do + + mkdir $count + (cd $count; + + test_expect_success "setup $count" "create_repo $count" + + test_expect_success 'notes work' "test_notes $count" + + test_expect_success 'notes timing' "time_notes 100" + ) +done + +test_done diff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh new file mode 100755 index 0000000000..edc4bc8841 --- /dev/null +++ b/t/t3303-notes-subtrees.sh @@ -0,0 +1,188 @@ +#!/bin/sh + +test_description='Test commit notes organized in subtrees' + +. ./test-lib.sh + +number_of_commits=100 + +start_note_commit () { + test_tick && + cat <<INPUT_END +commit refs/notes/commits +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +notes +COMMIT + +from refs/notes/commits^0 +deleteall +INPUT_END + +} + +verify_notes () { + git log | grep "^ " > output && + i=$number_of_commits && + while [ $i -gt 0 ]; do + echo " commit #$i" && + echo " note for commit #$i" && + i=$(($i-1)); + done > expect && + test_cmp expect output +} + +test_expect_success "setup: create $number_of_commits commits" ' + + ( + nr=0 && + while [ $nr -lt $number_of_commits ]; do + nr=$(($nr+1)) && + test_tick && + cat <<INPUT_END +commit refs/heads/master +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +commit #$nr +COMMIT + +M 644 inline file +data <<EOF +file in commit #$nr +EOF + +INPUT_END + + done && + test_tick && + cat <<INPUT_END +commit refs/notes/commits +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +no notes +COMMIT + +deleteall + +INPUT_END + + ) | + git fast-import --quiet && + git config core.notesRef refs/notes/commits +' + +test_sha1_based () { + ( + start_note_commit && + nr=$number_of_commits && + git rev-list refs/heads/master | + while read sha1; do + note_path=$(echo "$sha1" | sed "$1") + cat <<INPUT_END && +M 100644 inline $note_path +data <<EOF +note for commit #$nr +EOF + +INPUT_END + + nr=$(($nr-1)) + done + ) | + git fast-import --quiet +} + +test_expect_success 'test notes in 2/38-fanout' 'test_sha1_based "s|^..|&/|"' +test_expect_success 'verify notes in 2/38-fanout' 'verify_notes' + +test_expect_success 'test notes in 4/36-fanout' 'test_sha1_based "s|^....|&/|"' +test_expect_success 'verify notes in 4/36-fanout' 'verify_notes' + +test_expect_success 'test notes in 2/2/36-fanout' 'test_sha1_based "s|^\(..\)\(..\)|\1/\2/|"' +test_expect_success 'verify notes in 2/2/36-fanout' 'verify_notes' + +test_same_notes () { + ( + start_note_commit && + nr=$number_of_commits && + git rev-list refs/heads/master | + while read sha1; do + first_note_path=$(echo "$sha1" | sed "$1") + second_note_path=$(echo "$sha1" | sed "$2") + cat <<INPUT_END && +M 100644 inline $second_note_path +data <<EOF +note for commit #$nr +EOF + +M 100644 inline $first_note_path +data <<EOF +note for commit #$nr +EOF + +INPUT_END + + nr=$(($nr-1)) + done + ) | + git fast-import --quiet +} + +test_expect_success 'test same notes in 4/36-fanout and 2/38-fanout' 'test_same_notes "s|^..|&/|" "s|^....|&/|"' +test_expect_success 'verify same notes in 4/36-fanout and 2/38-fanout' 'verify_notes' + +test_expect_success 'test same notes in 2/38-fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^..|&/|"' +test_expect_success 'verify same notes in 2/38-fanout and 2/2/36-fanout' 'verify_notes' + +test_expect_success 'test same notes in 4/36-fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^....|&/|"' +test_expect_success 'verify same notes in 4/36-fanout and 2/2/36-fanout' 'verify_notes' + +test_concatenated_notes () { + ( + start_note_commit && + nr=$number_of_commits && + git rev-list refs/heads/master | + while read sha1; do + first_note_path=$(echo "$sha1" | sed "$1") + second_note_path=$(echo "$sha1" | sed "$2") + cat <<INPUT_END && +M 100644 inline $second_note_path +data <<EOF +second note for commit #$nr +EOF + +M 100644 inline $first_note_path +data <<EOF +first note for commit #$nr +EOF + +INPUT_END + + nr=$(($nr-1)) + done + ) | + git fast-import --quiet +} + +verify_concatenated_notes () { + git log | grep "^ " > output && + i=$number_of_commits && + while [ $i -gt 0 ]; do + echo " commit #$i" && + echo " first note for commit #$i" && + echo " second note for commit #$i" && + i=$(($i-1)); + done > expect && + test_cmp expect output +} + +test_expect_success 'test notes in 4/36-fanout concatenated with 2/38-fanout' 'test_concatenated_notes "s|^..|&/|" "s|^....|&/|"' +test_expect_success 'verify notes in 4/36-fanout concatenated with 2/38-fanout' 'verify_concatenated_notes' + +test_expect_success 'test notes in 2/38-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^..|&/|"' +test_expect_success 'verify notes in 2/38-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes' + +test_expect_success 'test notes in 4/36-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^....|&/|"' +test_expect_success 'verify notes in 4/36-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes' + +test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 4cae019521..3a37793c0d 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -470,4 +470,18 @@ test_expect_success 'avoid unnecessary reset' ' test 123456789 = $MTIME ' +test_expect_success 'reword' ' + git checkout -b reword-branch master && + FAKE_LINES="1 2 3 reword 4" FAKE_COMMIT_MESSAGE="E changed" git rebase -i A && + git show HEAD | grep "E changed" && + test $(git rev-parse master) != $(git rev-parse HEAD) && + test $(git rev-parse master^) = $(git rev-parse HEAD^) && + FAKE_LINES="1 2 reword 3 4" FAKE_COMMIT_MESSAGE="D changed" git rebase -i A && + git show HEAD^ | grep "D changed" && + FAKE_LINES="reword 1 2 3 4" FAKE_COMMIT_MESSAGE="B changed" git rebase -i A && + git show HEAD~3 | grep "B changed" && + FAKE_LINES="1 reword 2 3 4" FAKE_COMMIT_MESSAGE="C changed" git rebase -i A && + git show HEAD~2 | grep "C changed" +' + test_done diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 62fd65e18d..d86bc81abf 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -138,6 +138,20 @@ test_expect_success 'real edit works' ' test_cmp expected output ' +test_expect_success 'skip files similarly as commit -a' ' + git reset && + echo file >.gitignore && + echo changed >file && + echo y | git add -p file && + git diff >output && + git reset && + git commit -am commit && + git diff >expected && + test_cmp expected output && + git reset --hard HEAD^ +' +rm -f .gitignore + if test "$(git config --bool core.filemode)" = false then say 'skipping filemode tests (filesystem does not properly support modes)' @@ -214,4 +228,21 @@ test_expect_success 'add first line works' ' test_cmp expected diff ' +cat >expected <<EOF +diff --git a/empty b/empty +deleted file mode 100644 +index e69de29..0000000 +EOF + +test_expect_success 'deleting an empty file' ' + git reset --hard && + > empty && + git add empty && + git commit -m empty && + rm empty && + echo y | git add -p empty && + git diff --cached >diff && + test_cmp expected diff +' + test_done diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 7a3fb67957..5514f74b30 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -200,4 +200,23 @@ test_expect_success 'drop -q is quiet' ' test ! -s output.out ' +test_expect_success 'stash -k' ' + echo bar3 > file && + echo bar4 > file2 && + git add file2 && + git stash -k && + test bar,bar4 = $(cat file),$(cat file2) +' + +test_expect_success 'stash --invalid-option' ' + echo bar5 > file && + echo bar6 > file2 && + git add file2 && + test_must_fail git stash --invalid-option && + test_must_fail git stash save --invalid-option && + test bar5,bar6 = $(cat file),$(cat file2) && + git stash -- -message-starting-with-dash && + test bar,bar2 = $(cat file),$(cat file2) +' + test_done diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh new file mode 100755 index 0000000000..f37e3bc6ec --- /dev/null +++ b/t/t3904-stash-patch.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +test_description='git checkout --patch' +. ./lib-patch-mode.sh + +test_expect_success 'setup' ' + mkdir dir && + echo parent > dir/foo && + echo dummy > bar && + git add bar dir/foo && + git commit -m initial && + test_tick && + test_commit second dir/foo head && + echo index > dir/foo && + git add dir/foo && + set_and_save_state bar bar_work bar_index && + save_head +' + +# note: bar sorts before dir, so the first 'n' is always to skip 'bar' + +test_expect_success 'saying "n" does nothing' ' + set_state dir/foo work index + (echo n; echo n) | test_must_fail git stash save -p && + verify_state dir/foo work index && + verify_saved_state bar +' + +test_expect_success 'git stash -p' ' + (echo n; echo y) | git stash save -p && + verify_state dir/foo head index && + verify_saved_state bar && + git reset --hard && + git stash apply && + verify_state dir/foo work head && + verify_state bar dummy dummy +' + +test_expect_success 'git stash -p --no-keep-index' ' + set_state dir/foo work index && + set_state bar bar_work bar_index && + (echo n; echo y) | git stash save -p --no-keep-index && + verify_state dir/foo head head && + verify_state bar bar_work dummy && + git reset --hard && + git stash apply --index && + verify_state dir/foo work index && + verify_state bar dummy bar_index +' + +test_expect_success 'none of this moved HEAD' ' + verify_saved_head +' + +test_done diff --git a/t/t4013/diff.log_--decorate=full_--all b/t/t4013/diff.log_--decorate=full_--all index 903d9d9659..d155e0bab2 100644 --- a/t/t4013/diff.log_--decorate=full_--all +++ b/t/t4013/diff.log_--decorate=full_--all @@ -1,5 +1,5 @@ $ git log --decorate=full --all -commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (refs/heads/master) +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD, refs/heads/master) Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--decorate_--all b/t/t4013/diff.log_--decorate_--all index 954210ea90..fd7c3e6439 100644 --- a/t/t4013/diff.log_--decorate_--all +++ b/t/t4013/diff.log_--decorate_--all @@ -1,5 +1,5 @@ $ git log --decorate --all -commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (master) +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD, master) Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 531f5b795c..5689d590fd 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -455,6 +455,27 @@ test_expect_success 'format-patch respects -U' ' ' +cat > expect << EOF + +diff --git a/file b/file +index 40f36c6..2dc5c23 100644 +--- a/file ++++ b/file +@@ -14,3 +14,19 @@ C + D + E + F ++5 +EOF + +test_expect_success 'format-patch -p suppresses stat' ' + + git format-patch -p -2 && + sed -e "1,/^$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output && + test_cmp expect output + +' + test_expect_success 'format-patch from a subdirectory (1)' ' filename=$( rm -rf sub && @@ -515,4 +536,22 @@ test_expect_success 'format-patch --signoff' ' grep "^Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" ' +echo "fatal: --name-only does not make sense" > expect.name-only +echo "fatal: --name-status does not make sense" > expect.name-status +echo "fatal: --check does not make sense" > expect.check + +test_expect_success 'options no longer allowed for format-patch' ' + test_must_fail git format-patch --name-only 2> output && + test_cmp expect.name-only output && + test_must_fail git format-patch --name-status 2> output && + test_cmp expect.name-status output && + test_must_fail git format-patch --check 2> output && + test_cmp expect.check output' + +test_expect_success 'format-patch --numstat should produce a patch' ' + git format-patch --numstat --stdout master..side | + grep "^diff --git a/" | + wc -l | + xargs test 6 = ' + test_done diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 6d13da30da..8dd147d78f 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -362,10 +362,17 @@ test_expect_success 'line numbers in --check output are correct' ' ' -test_expect_success 'checkdiff detects trailing blank lines' ' +test_expect_success 'checkdiff detects new trailing blank lines (1)' ' echo "foo();" >x && echo "" >>x && - git diff --check | grep "ends with blank" + git diff --check | grep "new blank line" +' + +test_expect_success 'checkdiff detects new trailing blank lines (2)' ' + { echo a; echo b; echo; echo; } >x && + git add x && + { echo a; echo; echo; echo; echo; } >x && + git diff --check | grep "new blank line" ' test_expect_success 'checkdiff allows new blank lines' ' diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index 84a1fe3115..3a3663fbcb 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -165,7 +165,7 @@ test_expect_success 'trailing empty lines (1)' ' rm -f .gitattributes && test_must_fail git diff --check >output && - grep "ends with blank lines." output && + grep "new blank line at" output && grep "trailing whitespace" output ' @@ -190,4 +190,13 @@ test_expect_success 'do not color trailing cr in context' ' ' +test_expect_success 'color new trailing blank lines' ' + { echo a; echo b; echo; echo; } >x && + git add x && + { echo a; echo; echo; echo; echo c; echo; echo; echo; echo; } >x && + git diff --color x >output && + cnt=$(grep "${blue_grep}" output | wc -l) && + test $cnt = 2 +' + test_done diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index 4508effcaa..21db6e95c4 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -68,6 +68,26 @@ cat > expect <<\EOF <WHITE>index 330b04f..5ed8eff 100644<RESET> <WHITE>--- a/pre<RESET> <WHITE>+++ b/post<RESET> +<BROWN>@@ -1 +1 @@<RESET> +<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET> +<BROWN>@@ -3,0 +4,4 @@ a = b + c<RESET> + +<GREEN>aa = a<RESET> + +<GREEN>aeff = aeff * ( aaa )<RESET> +EOF + +test_expect_success 'word diff without context' ' + + word_diff --color-words --unified=0 + +' + +cat > expect <<\EOF +<WHITE>diff --git a/pre b/post<RESET> +<WHITE>index 330b04f..5ed8eff 100644<RESET> +<WHITE>--- a/pre<RESET> +<WHITE>+++ b/post<RESET> <BROWN>@@ -1,3 +1,7 @@<RESET> h(4),<GREEN>hh<RESET>[44] <RESET> diff --git a/t/t4041-diff-submodule.sh b/t/t4041-diff-submodule.sh new file mode 100755 index 0000000000..5bb4fed3f5 --- /dev/null +++ b/t/t4041-diff-submodule.sh @@ -0,0 +1,260 @@ +#!/bin/sh +# +# Copyright (c) 2009 Jens Lehmann, based on t7401 by Ping Yin +# + +test_description='Support for verbose submodule differences in git diff + +This test tries to verify the sanity of the --submodule option of git diff. +' + +. ./test-lib.sh + +add_file () { + sm=$1 + shift + owd=$(pwd) + cd "$sm" + for name; do + echo "$name" > "$name" && + git add "$name" && + test_tick && + git commit -m "Add $name" + done >/dev/null + git rev-parse --verify HEAD | cut -c1-7 + cd "$owd" +} +commit_file () { + test_tick && + git commit "$@" -m "Commit $*" >/dev/null +} + +test_create_repo sm1 && +add_file . foo >/dev/null + +head1=$(add_file sm1 foo1 foo2) + +test_expect_success 'added submodule' " + git add sm1 && + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 0000000...$head1 (new submodule) +EOF +" + +commit_file sm1 && +head2=$(add_file sm1 foo3) + +test_expect_success 'modified submodule(forward)' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head1..$head2: + > Add foo3 +EOF +" + +test_expect_success 'modified submodule(forward)' " + git diff --submodule=log >actual && + diff actual - <<-EOF +Submodule sm1 $head1..$head2: + > Add foo3 +EOF +" + +test_expect_success 'modified submodule(forward) --submodule' " + git diff --submodule >actual && + diff actual - <<-EOF +Submodule sm1 $head1..$head2: + > Add foo3 +EOF +" + +fullhead1=$(cd sm1; git rev-list --max-count=1 $head1) +fullhead2=$(cd sm1; git rev-list --max-count=1 $head2) +test_expect_success 'modified submodule(forward) --submodule=short' " + git diff --submodule=short >actual && + diff actual - <<-EOF +diff --git a/sm1 b/sm1 +index $head1..$head2 160000 +--- a/sm1 ++++ b/sm1 +@@ -1 +1 @@ +-Subproject commit $fullhead1 ++Subproject commit $fullhead2 +EOF +" + +commit_file sm1 && +cd sm1 && +git reset --hard HEAD~2 >/dev/null && +head3=$(git rev-parse --verify HEAD | cut -c1-7) && +cd .. + +test_expect_success 'modified submodule(backward)' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head2..$head3 (rewind): + < Add foo3 + < Add foo2 +EOF +" + +head4=$(add_file sm1 foo4 foo5) && +head4_full=$(GIT_DIR=sm1/.git git rev-parse --verify HEAD) +test_expect_success 'modified submodule(backward and forward)' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head2...$head4: + > Add foo5 + > Add foo4 + < Add foo3 + < Add foo2 +EOF +" + +commit_file sm1 && +mv sm1 sm1-bak && +echo sm1 >sm1 && +head5=$(git hash-object sm1 | cut -c1-7) && +git add sm1 && +rm -f sm1 && +mv sm1-bak sm1 + +test_expect_success 'typechanged submodule(submodule->blob), --cached' " + git diff --submodule=log --cached >actual && + diff actual - <<-EOF +Submodule sm1 41fbea9...0000000 (submodule deleted) +diff --git a/sm1 b/sm1 +new file mode 100644 +index 0000000..9da5fb8 +--- /dev/null ++++ b/sm1 +@@ -0,0 +1 @@ ++sm1 +EOF +" + +test_expect_success 'typechanged submodule(submodule->blob)' " + git diff --submodule=log >actual && + diff actual - <<-EOF +diff --git a/sm1 b/sm1 +deleted file mode 100644 +index 9da5fb8..0000000 +--- a/sm1 ++++ /dev/null +@@ -1 +0,0 @@ +-sm1 +Submodule sm1 0000000...$head4 (new submodule) +EOF +" + +rm -rf sm1 && +git checkout-index sm1 +test_expect_success 'typechanged submodule(submodule->blob)' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head4...0000000 (submodule deleted) +diff --git a/sm1 b/sm1 +new file mode 100644 +index 0000000..$head5 +--- /dev/null ++++ b/sm1 +@@ -0,0 +1 @@ ++sm1 +EOF +" + +rm -f sm1 && +test_create_repo sm1 && +head6=$(add_file sm1 foo6 foo7) +fullhead6=$(cd sm1; git rev-list --max-count=1 $head6) +test_expect_success 'nonexistent commit' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head4...$head6 (commits not present) +EOF +" + +commit_file +test_expect_success 'typechanged submodule(blob->submodule)' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +diff --git a/sm1 b/sm1 +deleted file mode 100644 +index $head5..0000000 +--- a/sm1 ++++ /dev/null +@@ -1 +0,0 @@ +-sm1 +Submodule sm1 0000000...$head6 (new submodule) +EOF +" + +commit_file sm1 && +rm -rf sm1 +test_expect_success 'deleted submodule' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6...0000000 (submodule deleted) +EOF +" + +test_create_repo sm2 && +head7=$(add_file sm2 foo8 foo9) && +git add sm2 + +test_expect_success 'multiple submodules' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6...0000000 (submodule deleted) +Submodule sm2 0000000...$head7 (new submodule) +EOF +" + +test_expect_success 'path filter' " + git diff-index -p --submodule=log HEAD sm2 >actual && + diff actual - <<-EOF +Submodule sm2 0000000...$head7 (new submodule) +EOF +" + +commit_file sm2 +test_expect_success 'given commit' " + git diff-index -p --submodule=log HEAD^ >actual && + diff actual - <<-EOF +Submodule sm1 $head6...0000000 (submodule deleted) +Submodule sm2 0000000...$head7 (new submodule) +EOF +" + +test_expect_success 'given commit --submodule' " + git diff-index -p --submodule HEAD^ >actual && + diff actual - <<-EOF +Submodule sm1 $head6...0000000 (submodule deleted) +Submodule sm2 0000000...$head7 (new submodule) +EOF +" + +fullhead7=$(cd sm2; git rev-list --max-count=1 $head7) + +test_expect_success 'given commit --submodule=short' " + git diff-index -p --submodule=short HEAD^ >actual && + diff actual - <<-EOF +diff --git a/sm1 b/sm1 +deleted file mode 160000 +index $head6..0000000 +--- a/sm1 ++++ /dev/null +@@ -1 +0,0 @@ +-Subproject commit $fullhead6 +diff --git a/sm2 b/sm2 +new file mode 160000 +index 0000000..$head7 +--- /dev/null ++++ b/sm2 +@@ -0,0 +1 @@ ++Subproject commit $fullhead7 +EOF +" + +test_done diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index fac2093d7f..ca26397590 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -170,4 +170,95 @@ test_expect_success 'trailing whitespace & no newline at the end of file' ' grep "^$" target ' +test_expect_success 'blank at EOF with --whitespace=fix (1)' ' + : these can fail depending on what we did before + git config --unset core.whitespace + rm -f .gitattributes + + { echo a; echo b; echo c; } >one && + git add one && + { echo a; echo b; echo c; } >expect && + { cat expect; echo; } >one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + +test_expect_success 'blank at EOF with --whitespace=fix (2)' ' + { echo a; echo b; echo c; } >one && + git add one && + { echo a; echo c; } >expect && + { cat expect; echo; echo; } >one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + +test_expect_success 'blank at EOF with --whitespace=fix (3)' ' + { echo a; echo b; echo; } >one && + git add one && + { echo a; echo c; echo; } >expect && + { cat expect; echo; echo; } >one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + +test_expect_success 'blank at end of hunk, not at EOF with --whitespace=fix' ' + { echo a; echo b; echo; echo; echo; echo; echo; echo d; } >one && + git add one && + { echo a; echo c; echo; echo; echo; echo; echo; echo; echo d; } >expect && + cp expect one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + +test_expect_success 'blank at EOF with --whitespace=warn' ' + { echo a; echo b; echo c; } >one && + git add one && + echo >>one && + cat one >expect && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=warn patch 2>error && + test_cmp expect one && + grep "new blank line at EOF" error +' + +test_expect_success 'blank at EOF with --whitespace=error' ' + { echo a; echo b; echo c; } >one && + git add one && + cat one >expect && + echo >>one && + git diff -- one >patch && + + git checkout one && + test_must_fail git apply --whitespace=error patch 2>error && + test_cmp expect one && + grep "new blank line at EOF" error +' + +test_expect_success 'blank but not empty at EOF' ' + { echo a; echo b; echo c; } >one && + git add one && + echo " " >>one && + cat one >expect && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=warn patch 2>error && + test_cmp expect one && + grep "new blank line at EOF" error +' + test_done diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 5f84b18fa5..0037f63d91 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -230,4 +230,16 @@ test_expect_success \ 'git archive --list outside of a git repo' \ 'GIT_DIR=some/non-existing/directory git archive --list' +test_expect_success 'git-archive --prefix=olde-' ' + git archive --prefix=olde- >h.tar HEAD && + ( + mkdir h && + cd h && + "$TAR" xf - <../h.tar + ) && + test -d h/olde-a && + test -d h/olde-a/bin && + test -f h/olde-a/bin/sh +' + test_done diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh index e70ea94a13..0279d07c83 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -11,18 +11,26 @@ test_expect_success 'split sample box' \ 'git mailsplit -o. "$TEST_DIRECTORY"/t5100/sample.mbox >last && last=`cat last` && echo total is $last && - test `cat last` = 13' + test `cat last` = 14' + +check_mailinfo () { + mail=$1 opt=$2 + mo="$mail$opt" + git mailinfo -u $opt msg$mo patch$mo <$mail >info$mo && + test_cmp "$TEST_DIRECTORY"/t5100/msg$mo msg$mo && + test_cmp "$TEST_DIRECTORY"/t5100/patch$mo patch$mo && + test_cmp "$TEST_DIRECTORY"/t5100/info$mo info$mo +} + for mail in `echo 00*` do test_expect_success "mailinfo $mail" ' - git mailinfo -u msg$mail patch$mail <$mail >info$mail && - echo msg && - test_cmp "$TEST_DIRECTORY"/t5100/msg$mail msg$mail && - echo patch && - test_cmp "$TEST_DIRECTORY"/t5100/patch$mail patch$mail && - echo info && - test_cmp "$TEST_DIRECTORY"/t5100/info$mail info$mail + check_mailinfo $mail "" && + if test -f "$TEST_DIRECTORY"/t5100/msg$mail--scissors + then + check_mailinfo $mail --scissors + fi ' done diff --git a/t/t5100/.gitattributes b/t/t5100/.gitattributes new file mode 100644 index 0000000000..c93f5142fa --- /dev/null +++ b/t/t5100/.gitattributes @@ -0,0 +1,4 @@ +msg* encoding=UTF-8 +info* encoding=UTF-8 +rfc2047-info-* encoding=UTF-8 +sample.mbox encoding=UTF-8 diff --git a/t/t5100/0010 b/t/t5100/0010 deleted file mode 100644 index f5892c9da7..0000000000 --- a/t/t5100/0010 +++ /dev/null @@ -1,35 +0,0 @@ -From b9704a518e21158433baa2cc2d591fea687967f6 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Lukas=20Sandstr=C3=B6m?= <lukass@etek.chalmers.se> -Date: Thu, 10 Jul 2008 23:41:33 +0200 -Subject: Re: discussion that lead to this patch -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -[PATCH] git-mailinfo: Fix getting the subject from the body - -"Subject: " isn't in the static array "header", and thus -memcmp("Subject: ", header[i], 7) will never match. - -Signed-off-by: Lukas Sandström <lukass@etek.chalmers.se> -Signed-off-by: Junio C Hamano <gitster@pobox.com> ---- - builtin-mailinfo.c | 2 +- - 1 files changed, 1 insertions(+), 1 deletions(-) - -diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c -index 962aa34..2d1520f 100644 ---- a/builtin-mailinfo.c -+++ b/builtin-mailinfo.c -@@ -334,7 +334,7 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over - return 1; - if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) { - for (i = 0; header[i]; i++) { -- if (!memcmp("Subject: ", header[i], 9)) { -+ if (!memcmp("Subject", header[i], 7)) { - if (! handle_header(line, hdr_data[i], 0)) { - return 1; - } --- -1.5.6.2.455.g1efb2 - diff --git a/t/t5100/info0014 b/t/t5100/info0014 new file mode 100644 index 0000000000..08566b34b9 --- /dev/null +++ b/t/t5100/info0014 @@ -0,0 +1,5 @@ +Author: Junio Hamano +Email: junkio@cox.net +Subject: BLAH ONE +Date: Thu, 20 Aug 2009 17:18:22 -0700 + diff --git a/t/t5100/info0014--scissors b/t/t5100/info0014--scissors new file mode 100644 index 0000000000..ab9c8d0905 --- /dev/null +++ b/t/t5100/info0014--scissors @@ -0,0 +1,5 @@ +Author: Junio C Hamano +Email: gitster@pobox.com +Subject: Teach mailinfo to ignore everything before -- >8 -- mark +Date: Thu, 20 Aug 2009 17:18:22 -0700 + diff --git a/t/t5100/msg0014 b/t/t5100/msg0014 new file mode 100644 index 0000000000..62e5cd2ecd --- /dev/null +++ b/t/t5100/msg0014 @@ -0,0 +1,18 @@ +In real life, we will see a discussion that inspired this patch +discussing related and unrelated things around >8 scissors mark +in this part of the message. + +Subject: [PATCH] BLAH TWO + +And then we will see the scissors. + + This line is not a scissors mark -- >8 -- but talks about it. + - - >8 - - please remove everything above this line - - >8 - - + +Subject: [PATCH] Teach mailinfo to ignore everything before -- >8 -- mark +From: Junio C Hamano <gitster@pobox.com> + +This teaches mailinfo the scissors -- >8 -- mark; the command ignores +everything before it in the message body. + +Signed-off-by: Junio C Hamano <gitster@pobox.com> diff --git a/t/t5100/msg0014--scissors b/t/t5100/msg0014--scissors new file mode 100644 index 0000000000..259c6a46d2 --- /dev/null +++ b/t/t5100/msg0014--scissors @@ -0,0 +1,4 @@ +This teaches mailinfo the scissors -- >8 -- mark; the command ignores +everything before it in the message body. + +Signed-off-by: Junio C Hamano <gitster@pobox.com> diff --git a/t/t5100/patch0014 b/t/t5100/patch0014 new file mode 100644 index 0000000000..124efd234f --- /dev/null +++ b/t/t5100/patch0014 @@ -0,0 +1,64 @@ +--- + builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++- + 1 files changed, 36 insertions(+), 1 deletions(-) + +diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c +index b0b5d8f..461c47e 100644 +--- a/builtin-mailinfo.c ++++ b/builtin-mailinfo.c +@@ -712,6 +712,34 @@ static inline int patchbreak(const struct strbuf *line) + return 0; + } + ++static int scissors(const struct strbuf *line) ++{ ++ size_t i, len = line->len; ++ int scissors_dashes_seen = 0; ++ const char *buf = line->buf; ++ ++ for (i = 0; i < len; i++) { ++ if (isspace(buf[i])) ++ continue; ++ if (buf[i] == '-') { ++ scissors_dashes_seen |= 02; ++ continue; ++ } ++ if (i + 1 < len && !memcmp(buf + i, ">8", 2)) { ++ scissors_dashes_seen |= 01; ++ i++; ++ continue; ++ } ++ if (i + 7 < len && !memcmp(buf + i, "cut here", 8)) { ++ i += 7; ++ continue; ++ } ++ /* everything else --- not scissors */ ++ break; ++ } ++ return scissors_dashes_seen == 03; ++} ++ + static int handle_commit_msg(struct strbuf *line) + { + static int still_looking = 1; +@@ -723,10 +751,17 @@ static int handle_commit_msg(struct strbuf *line) + strbuf_ltrim(line); + if (!line->len) + return 0; +- if ((still_looking = check_header(line, s_hdr_data, 0)) != 0) ++ still_looking = check_header(line, s_hdr_data, 0); ++ if (still_looking) + return 0; + } + ++ if (scissors(line)) { ++ fseek(cmitmsg, 0L, SEEK_SET); ++ still_looking = 1; ++ return 0; ++ } ++ + /* normalize the log message to UTF-8. */ + if (metainfo_charset) + convert_to_utf8(line, charset.buf); +-- +1.6.4.1 diff --git a/t/t5100/patch0014--scissors b/t/t5100/patch0014--scissors new file mode 100644 index 0000000000..124efd234f --- /dev/null +++ b/t/t5100/patch0014--scissors @@ -0,0 +1,64 @@ +--- + builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++- + 1 files changed, 36 insertions(+), 1 deletions(-) + +diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c +index b0b5d8f..461c47e 100644 +--- a/builtin-mailinfo.c ++++ b/builtin-mailinfo.c +@@ -712,6 +712,34 @@ static inline int patchbreak(const struct strbuf *line) + return 0; + } + ++static int scissors(const struct strbuf *line) ++{ ++ size_t i, len = line->len; ++ int scissors_dashes_seen = 0; ++ const char *buf = line->buf; ++ ++ for (i = 0; i < len; i++) { ++ if (isspace(buf[i])) ++ continue; ++ if (buf[i] == '-') { ++ scissors_dashes_seen |= 02; ++ continue; ++ } ++ if (i + 1 < len && !memcmp(buf + i, ">8", 2)) { ++ scissors_dashes_seen |= 01; ++ i++; ++ continue; ++ } ++ if (i + 7 < len && !memcmp(buf + i, "cut here", 8)) { ++ i += 7; ++ continue; ++ } ++ /* everything else --- not scissors */ ++ break; ++ } ++ return scissors_dashes_seen == 03; ++} ++ + static int handle_commit_msg(struct strbuf *line) + { + static int still_looking = 1; +@@ -723,10 +751,17 @@ static int handle_commit_msg(struct strbuf *line) + strbuf_ltrim(line); + if (!line->len) + return 0; +- if ((still_looking = check_header(line, s_hdr_data, 0)) != 0) ++ still_looking = check_header(line, s_hdr_data, 0); ++ if (still_looking) + return 0; + } + ++ if (scissors(line)) { ++ fseek(cmitmsg, 0L, SEEK_SET); ++ still_looking = 1; ++ return 0; ++ } ++ + /* normalize the log message to UTF-8. */ + if (metainfo_charset) + convert_to_utf8(line, charset.buf); +-- +1.6.4.1 diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox index c3074ac573..13fa4ae03b 100644 --- a/t/t5100/sample.mbox +++ b/t/t5100/sample.mbox @@ -561,3 +561,92 @@ From: <a.u.thor@example.com> (A U Thor) Date: Fri, 9 Jun 2006 00:44:16 -0700 Subject: [PATCH] a patch +From nobody Mon Sep 17 00:00:00 2001 +From: Junio Hamano <junkio@cox.net> +Date: Thu, 20 Aug 2009 17:18:22 -0700 +Subject: Why doesn't git-am does not like >8 scissors mark? + +Subject: [PATCH] BLAH ONE + +In real life, we will see a discussion that inspired this patch +discussing related and unrelated things around >8 scissors mark +in this part of the message. + +Subject: [PATCH] BLAH TWO + +And then we will see the scissors. + + This line is not a scissors mark -- >8 -- but talks about it. + - - >8 - - please remove everything above this line - - >8 - - + +Subject: [PATCH] Teach mailinfo to ignore everything before -- >8 -- mark +From: Junio C Hamano <gitster@pobox.com> + +This teaches mailinfo the scissors -- >8 -- mark; the command ignores +everything before it in the message body. + +Signed-off-by: Junio C Hamano <gitster@pobox.com> +--- + builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++- + 1 files changed, 36 insertions(+), 1 deletions(-) + +diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c +index b0b5d8f..461c47e 100644 +--- a/builtin-mailinfo.c ++++ b/builtin-mailinfo.c +@@ -712,6 +712,34 @@ static inline int patchbreak(const struct strbuf *line) + return 0; + } + ++static int scissors(const struct strbuf *line) ++{ ++ size_t i, len = line->len; ++ int scissors_dashes_seen = 0; ++ const char *buf = line->buf; ++ ++ for (i = 0; i < len; i++) { ++ if (isspace(buf[i])) ++ continue; ++ if (buf[i] == '-') { ++ scissors_dashes_seen |= 02; ++ continue; ++ } ++ if (i + 1 < len && !memcmp(buf + i, ">8", 2)) { ++ scissors_dashes_seen |= 01; ++ i++; ++ continue; ++ } ++ if (i + 7 < len && !memcmp(buf + i, "cut here", 8)) { ++ i += 7; ++ continue; ++ } ++ /* everything else --- not scissors */ ++ break; ++ } ++ return scissors_dashes_seen == 03; ++} ++ + static int handle_commit_msg(struct strbuf *line) + { + static int still_looking = 1; +@@ -723,10 +751,17 @@ static int handle_commit_msg(struct strbuf *line) + strbuf_ltrim(line); + if (!line->len) + return 0; +- if ((still_looking = check_header(line, s_hdr_data, 0)) != 0) ++ still_looking = check_header(line, s_hdr_data, 0); ++ if (still_looking) + return 0; + } + ++ if (scissors(line)) { ++ fseek(cmitmsg, 0L, SEEK_SET); ++ still_looking = 1; ++ return 0; ++ } ++ + /* normalize the log message to UTF-8. */ + if (metainfo_charset) + convert_to_utf8(line, charset.buf); +-- +1.6.4.1 diff --git a/t/t5303-pack-corruption-resilience.sh b/t/t5303-pack-corruption-resilience.sh index 5132d41309..5f6cd4f333 100755 --- a/t/t5303-pack-corruption-resilience.sh +++ b/t/t5303-pack-corruption-resilience.sh @@ -275,4 +275,13 @@ test_expect_success \ git cat-file blob $blob_2 > /dev/null && git cat-file blob $blob_3 > /dev/null' +test_expect_success \ + 'corrupting header to have too small output buffer fails unpack' \ + 'create_new_pack && + git prune-packed && + printf "\262\001" | do_corrupt_object $blob_1 0 && + test_must_fail git cat-file blob $blob_1 > /dev/null && + test_must_fail git cat-file blob $blob_2 > /dev/null && + test_must_fail git cat-file blob $blob_3 > /dev/null' + test_done diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index 55ed7c7935..3c6687abec 100755 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -6,6 +6,17 @@ test_description='prune' . ./test-lib.sh +day=$((60*60*24)) +week=$(($day*7)) + +add_blob() { + before=$(git count-objects | sed "s/ .*//") && + BLOB=$(echo aleph_0 | 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 +} + test_expect_success setup ' : > file && @@ -31,11 +42,7 @@ test_expect_success 'prune stale packs' ' 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 && + add_blob && git prune --expire=1.hour.ago && test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && test -f $BLOB_FILE && @@ -48,16 +55,12 @@ test_expect_success 'prune --expire' ' test_expect_success 'gc: implicit prune --expire' ' - before=$(git count-objects | sed "s/ .*//") && - BLOB=$(echo aleph_0 | 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 && - test-chmtime =-$((86400*14-30)) $BLOB_FILE && + add_blob && + test-chmtime =-$((2*$week-30)) $BLOB_FILE && git gc && test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && test -f $BLOB_FILE && - test-chmtime =-$((86400*14+1)) $BLOB_FILE && + test-chmtime =-$((2*$week+1)) $BLOB_FILE && git gc && test $before = $(git count-objects | sed "s/ .*//") && ! test -f $BLOB_FILE @@ -114,12 +117,8 @@ test_expect_success 'prune: do not prune heads listed as an argument' ' test_expect_success 'gc --no-prune' ' - before=$(git count-objects | sed "s/ .*//") && - BLOB=$(echo aleph_0 | 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 && - test-chmtime =-$((86400*5001)) $BLOB_FILE && + add_blob && + test-chmtime =-$((5001*$day)) $BLOB_FILE && git config gc.pruneExpire 2.days.ago && git gc --no-prune && test 1 = $(git count-objects | sed "s/ .*//") && @@ -140,9 +139,8 @@ test_expect_success 'gc respects gc.pruneExpire' ' test_expect_success 'gc --prune=<date>' ' - BLOB=$(echo aleph_0 | git hash-object -w --stdin) && - BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && - test-chmtime =-$((86400*5001)) $BLOB_FILE && + add_blob && + test-chmtime =-$((5001*$day)) $BLOB_FILE && git gc --prune=5002.days.ago && test -f $BLOB_FILE && git gc --prune=5000.days.ago && @@ -150,4 +148,18 @@ test_expect_success 'gc --prune=<date>' ' ' +test_expect_success 'gc: prune old objects after local clone' ' + add_blob && + test-chmtime =-$((2*$week+1)) $BLOB_FILE && + git clone --no-hardlinks . aclone && + ( + cd aclone && + test 1 = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE && + git gc --prune && + test 0 = $(git count-objects | sed "s/ .*//") && + ! test -f $BLOB_FILE + ) +' + test_done diff --git a/t/t5501-post-upload-pack.sh b/t/t5501-post-upload-pack.sh new file mode 100755 index 0000000000..d89fb51bad --- /dev/null +++ b/t/t5501-post-upload-pack.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='post upload-hook' + +. ./test-lib.sh + +LOGFILE=".git/post-upload-pack-log" + +test_expect_success setup ' + test_commit A && + test_commit B && + git reset --hard A && + test_commit C && + git branch prev B && + mkdir -p .git/hooks && + { + echo "#!$SHELL_PATH" && + echo "cat >post-upload-pack-log" + } >".git/hooks/post-upload-pack" && + chmod +x .git/hooks/post-upload-pack +' + +test_expect_success initial ' + rm -fr sub && + git init sub && + ( + cd sub && + git fetch --no-tags .. prev + ) && + want=$(sed -n "s/^want //p" "$LOGFILE") && + test "$want" = "$(git rev-parse --verify B)" && + ! grep "^have " "$LOGFILE" && + kind=$(sed -n "s/^kind //p" "$LOGFILE") && + test "$kind" = fetch +' + +test_expect_success second ' + rm -fr sub && + git init sub && + ( + cd sub && + git fetch --no-tags .. prev:refs/remotes/prev && + git fetch --no-tags .. master + ) && + want=$(sed -n "s/^want //p" "$LOGFILE") && + test "$want" = "$(git rev-parse --verify C)" && + have=$(sed -n "s/^have //p" "$LOGFILE") && + test "$have" = "$(git rev-parse --verify B)" && + kind=$(sed -n "s/^kind //p" "$LOGFILE") && + test "$kind" = fetch +' + +test_expect_success all ' + rm -fr sub && + HERE=$(pwd) && + git init sub && + ( + cd sub && + git clone "file://$HERE/.git" new + ) && + sed -n "s/^want //p" "$LOGFILE" | sort >actual && + git rev-parse A B C | sort >expect && + test_cmp expect actual && + ! grep "^have " "$LOGFILE" && + kind=$(sed -n "s/^kind //p" "$LOGFILE") && + test "$kind" = clone +' + +test_done diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 852ccb5d7d..220b6a3413 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -158,7 +158,7 @@ cat > test/expect << EOF another master Local refs configured for 'git push': - ahead forces to master (fast forwardable) + ahead forces to master (fast-forwardable) master pushes to another (up to date) EOF diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index d13c806624..169af1edde 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -341,4 +341,15 @@ test_expect_success 'fetch into the current branch with --update-head-ok' ' ' +test_expect_success "should be able to fetch with duplicate refspecs" ' + mkdir dups && + cd dups && + git init && + git config branch.master.remote three && + git config remote.three.url ../three/.git && + git config remote.three.fetch +refs/heads/*:refs/remotes/origin/* && + git config --add remote.three.fetch +refs/heads/*:refs/remotes/origin/* && + git fetch three +' + test_done diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 2d2633f3f8..6889a53cf9 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -122,6 +122,23 @@ test_expect_success 'fetch with insteadOf' ' ) ' +test_expect_success 'fetch with pushInsteadOf (should not rewrite)' ' + mk_empty && + ( + TRASH=$(pwd)/ && + cd testrepo && + git config "url.trash/.pushInsteadOf" "$TRASH" && + git config remote.up.url "$TRASH." && + git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" && + git fetch up && + + r=$(git show-ref -s --verify refs/remotes/origin/master) && + test "z$r" = "z$the_commit" && + + test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + ) +' + test_expect_success 'push without wildcard' ' mk_empty && @@ -162,6 +179,36 @@ test_expect_success 'push with insteadOf' ' ) ' +test_expect_success 'push with pushInsteadOf' ' + mk_empty && + TRASH="$(pwd)/" && + git config "url.$TRASH.pushInsteadOf" trash/ && + git push trash/testrepo refs/heads/master:refs/remotes/origin/master && + ( + cd testrepo && + r=$(git show-ref -s --verify refs/remotes/origin/master) && + test "z$r" = "z$the_commit" && + + test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + ) +' + +test_expect_success 'push with pushInsteadOf and explicit pushurl (pushInsteadOf should not rewrite)' ' + mk_empty && + TRASH="$(pwd)/" && + git config "url.trash2/.pushInsteadOf" trash/ && + git config remote.r.url trash/wrong && + git config remote.r.pushurl "$TRASH/testrepo" && + git push r refs/heads/master:refs/remotes/origin/master && + ( + cd testrepo && + r=$(git show-ref -s --verify refs/remotes/origin/master) && + test "z$r" = "z$the_commit" && + + test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + ) +' + test_expect_success 'push with matching heads' ' mk_test heads/master && diff --git a/t/t5518-fetch-exit-status.sh b/t/t5518-fetch-exit-status.sh index c6bc65faa0..c2060bb870 100755 --- a/t/t5518-fetch-exit-status.sh +++ b/t/t5518-fetch-exit-status.sh @@ -22,7 +22,7 @@ test_expect_success setup ' git commit -a -m next ' -test_expect_success 'non fast forward fetch' ' +test_expect_success 'non-fast-forward fetch' ' test_must_fail git fetch . master:side diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index 65d8d474bc..65d8d474bc 100644..100755 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index f4a2cf6c17..bb18f8bfc4 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -3,23 +3,22 @@ # Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at> # -test_description='test http-push +test_description='test WebDAV http-push This test runs various sanity checks on http-push.' . ./test-lib.sh -ROOT_PATH="$PWD" -LIB_HTTPD_DAV=t -LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'} - if git http-push > /dev/null 2>&1 || [ $? -eq 128 ] then say "skipping test, USE_CURL_MULTI is not defined" test_done fi +LIB_HTTPD_DAV=t +LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'} . "$TEST_DIRECTORY"/lib-httpd.sh +ROOT_PATH="$PWD" start_httpd test_expect_success 'setup remote repository' ' @@ -36,16 +35,17 @@ test_expect_success 'setup remote repository' ' cd test_repo.git && git --bare update-server-info && mv hooks/post-update.sample hooks/post-update && + ORIG_HEAD=$(git rev-parse --verify HEAD) && cd - && mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH" ' test_expect_success 'clone remote repository' ' cd "$ROOT_PATH" && - git clone $HTTPD_URL/test_repo.git test_repo_clone + git clone $HTTPD_URL/dumb/test_repo.git test_repo_clone ' -test_expect_failure 'push to remote repository with packed refs' ' +test_expect_success 'push to remote repository with packed refs' ' cd "$ROOT_PATH"/test_repo_clone && : >path2 && git add path2 && @@ -57,11 +57,15 @@ test_expect_failure 'push to remote repository with packed refs' ' test $HEAD = $(git rev-parse --verify HEAD)) ' -test_expect_success ' push to remote repository with unpacked refs' ' +test_expect_success 'push already up-to-date' ' + git push +' + +test_expect_success 'push to remote repository with unpacked refs' ' (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && rm packed-refs && - git update-ref refs/heads/master \ - 0c973ae9bd51902a28466f3850b543fa66a6aaf4) && + git update-ref refs/heads/master $ORIG_HEAD && + git --bare update-server-info) && git push && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && test $HEAD = $(git rev-parse --verify HEAD)) @@ -71,7 +75,7 @@ test_expect_success 'http-push fetches unpacked objects' ' cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \ "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_unpacked.git && - git clone $HTTPD_URL/test_repo_unpacked.git \ + git clone $HTTPD_URL/dumb/test_repo_unpacked.git \ "$ROOT_PATH"/fetch_unpacked && # By reset, we force git to retrieve the object @@ -80,14 +84,14 @@ test_expect_success 'http-push fetches unpacked objects' ' git remote rm origin && git reflog expire --expire=0 --all && git prune && - git push -f -v $HTTPD_URL/test_repo_unpacked.git master) + git push -f -v $HTTPD_URL/dumb/test_repo_unpacked.git master) ' test_expect_success 'http-push fetches packed objects' ' cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \ "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_packed.git && - git clone $HTTPD_URL/test_repo_packed.git \ + git clone $HTTPD_URL/dumb/test_repo_packed.git \ "$ROOT_PATH"/test_repo_clone_packed && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_packed.git && @@ -100,7 +104,7 @@ test_expect_success 'http-push fetches packed objects' ' git remote rm origin && git reflog expire --expire=0 --all && git prune && - git push -f -v $HTTPD_URL/test_repo_packed.git master) + git push -f -v $HTTPD_URL/dumb/test_repo_packed.git master) ' test_expect_success 'create and delete remote branch' ' @@ -111,10 +115,7 @@ test_expect_success 'create and delete remote branch' ' test_tick && git commit -m dev && git push origin dev && - git fetch && git push origin :dev && - git branch -d -r origin/dev && - git fetch && test_must_fail git show-ref --verify refs/remotes/origin/dev ' diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh new file mode 100755 index 0000000000..2a58d0cc9c --- /dev/null +++ b/t/t5541-http-push.sh @@ -0,0 +1,92 @@ +#!/bin/sh +# +# Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at> +# + +test_description='test smart pushing over http via http-backend' +. ./test-lib.sh + +if test -n "$NO_CURL"; then + say 'skipping test, git built without http support' + test_done +fi + +ROOT_PATH="$PWD" +LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5541'} +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'setup remote repository' ' + cd "$ROOT_PATH" && + mkdir test_repo && + cd test_repo && + git init && + : >path1 && + git add path1 && + test_tick && + git commit -m initial && + cd - && + git clone --bare test_repo test_repo.git && + cd test_repo.git && + git config http.receivepack true && + ORIG_HEAD=$(git rev-parse --verify HEAD) && + cd - && + mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH" +' + +test_expect_success 'clone remote repository' ' + cd "$ROOT_PATH" && + git clone $HTTPD_URL/smart/test_repo.git test_repo_clone +' + +test_expect_success 'push to remote repository' ' + cd "$ROOT_PATH"/test_repo_clone && + : >path2 && + git add path2 && + test_tick && + git commit -m path2 && + HEAD=$(git rev-parse --verify HEAD) && + git push && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && + test $HEAD = $(git rev-parse --verify HEAD)) +' + +test_expect_success 'push already up-to-date' ' + git push +' + +test_expect_success 'create and delete remote branch' ' + cd "$ROOT_PATH"/test_repo_clone && + git checkout -b dev && + : >path3 && + git add path3 && + test_tick && + git commit -m dev && + git push origin dev && + git push origin :dev && + test_must_fail git show-ref --verify refs/remotes/origin/dev +' + +cat >exp <<EOF +GET /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200 +POST /smart/test_repo.git/git-upload-pack HTTP/1.1 200 +GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200 +POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200 +GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200 +GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200 +POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200 +GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200 +POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200 +EOF +test_expect_success 'used receive-pack service' ' + sed -e " + s/^.* \"// + s/\"// + s/ [1-9][0-9]*\$// + s/^GET /GET / + " >act <"$HTTPD_ROOT_PATH"/access.log && + test_cmp exp act +' + +stop_httpd +test_done diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh index 0e69324652..8cfce969bc 100755 --- a/t/t5550-http-fetch.sh +++ b/t/t5550-http-fetch.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='test fetching over http' +test_description='test dumb fetching over http via static file' . ./test-lib.sh if test -n "$NO_CURL"; then @@ -30,7 +30,7 @@ test_expect_success 'create http-accessible bare repository' ' ' test_expect_success 'clone http repository' ' - git clone $HTTPD_URL/repo.git clone && + git clone $HTTPD_URL/dumb/repo.git clone && test_cmp file clone/file ' @@ -58,7 +58,13 @@ test_expect_success 'fetch packed objects' ' cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && git --bare repack && git --bare prune-packed && - git clone $HTTPD_URL/repo_pack.git + git clone $HTTPD_URL/dumb/repo_pack.git +' + +test_expect_success 'did not use upload-pack service' ' + grep '/git-upload-pack' <"$HTTPD_ROOT_PATH"/access.log >act + : >exp + test_cmp exp act ' stop_httpd diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh new file mode 100755 index 0000000000..c0505ecd7b --- /dev/null +++ b/t/t5551-http-fetch.sh @@ -0,0 +1,105 @@ +#!/bin/sh + +test_description='test smart fetching over http via http-backend' +. ./test-lib.sh + +if test -n "$NO_CURL"; then + say 'skipping test, git built without http support' + test_done +fi + +LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5551'} +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'setup repository' ' + echo content >file && + git add file && + git commit -m one +' + +test_expect_success 'create http-accessible bare repository' ' + mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git --bare init + ) && + git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git push public master:master +' + +cat >exp <<EOF +> GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 +> Accept: */* +> Pragma: no-cache +< HTTP/1.1 200 OK +< Pragma: no-cache +< Cache-Control: no-cache, max-age=0, must-revalidate +< Content-Type: application/x-git-upload-pack-advertisement +> POST /smart/repo.git/git-upload-pack HTTP/1.1 +> Accept-Encoding: deflate, gzip +> Content-Type: application/x-git-upload-pack-request +> Accept: application/x-git-upload-pack-response +> Content-Length: xxx +< HTTP/1.1 200 OK +< Pragma: no-cache +< Cache-Control: no-cache, max-age=0, must-revalidate +< Content-Type: application/x-git-upload-pack-result +EOF +test_expect_success 'clone http repository' ' + GIT_CURL_VERBOSE=1 git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err && + test_cmp file clone/file && + tr '\''\015'\'' Q <err | + sed -e " + s/Q\$// + /^[*] /d + /^$/d + /^< $/d + + /^[^><]/{ + s/^/> / + } + + /^> User-Agent: /d + /^> Host: /d + /^> POST /,$ { + /^> Accept: [*]\\/[*]/d + } + s/^> Content-Length: .*/> Content-Length: xxx/ + /^> 00..want /d + /^> 00.*done/d + + /^< Server: /d + /^< Expires: /d + /^< Date: /d + /^< Content-Length: /d + /^< Transfer-Encoding: /d + " >act && + test_cmp exp act +' + +test_expect_success 'fetch changes via http' ' + echo content >>file && + git commit -a -m two && + git push public + (cd clone && git pull) && + test_cmp file clone/file +' + +cat >exp <<EOF +GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200 +POST /smart/repo.git/git-upload-pack HTTP/1.1 200 +GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200 +POST /smart/repo.git/git-upload-pack HTTP/1.1 200 +EOF +test_expect_success 'used upload-pack service' ' + sed -e " + s/^.* \"// + s/\"// + s/ [1-9][0-9]*\$// + s/^GET /GET / + " >act <"$HTTPD_ROOT_PATH"/access.log && + test_cmp exp act +' + +stop_httpd +test_done diff --git a/t/t5560-http-backend.sh b/t/t5560-http-backend.sh new file mode 100755 index 0000000000..ed034bc980 --- /dev/null +++ b/t/t5560-http-backend.sh @@ -0,0 +1,260 @@ +#!/bin/sh + +test_description='test git-http-backend' +. ./test-lib.sh + +if test -n "$NO_CURL"; then + say 'skipping test, git built without http support' + test_done +fi + +LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5560'} +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +find_file() { + cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + find $1 -type f | + sed -e 1q +} + +config() { + git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" config $1 $2 +} + +GET() { + curl --include "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null && + tr '\015' Q <out | + sed ' + s/Q$// + 1q + ' >act && + echo "HTTP/1.1 $2" >exp && + test_cmp exp act +} + +POST() { + curl --include --data "$2" \ + --header "Content-Type: application/x-$1-request" \ + "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null && + tr '\015' Q <out | + sed ' + s/Q$// + 1q + ' >act && + echo "HTTP/1.1 $3" >exp && + test_cmp exp act +} + +log_div() { + echo >>"$HTTPD_ROOT_PATH"/access.log + echo "### $1" >>"$HTTPD_ROOT_PATH"/access.log + echo "###" >>"$HTTPD_ROOT_PATH"/access.log +} + +test_expect_success 'setup repository' ' + echo content >file && + git add file && + git commit -m one && + + mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git --bare init && + : >objects/info/alternates && + : >objects/info/http-alternates + ) && + git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git push public master:master && + + (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git repack -a -d + ) && + + echo other >file && + git add file && + git commit -m two && + git push public master:master && + + LOOSE_URL=$(find_file objects/??) && + PACK_URL=$(find_file objects/pack/*.pack) && + IDX_URL=$(find_file objects/pack/*.idx) +' + +get_static_files() { + GET HEAD "$1" && + GET info/refs "$1" && + GET objects/info/packs "$1" && + GET objects/info/alternates "$1" && + GET objects/info/http-alternates "$1" && + GET $LOOSE_URL "$1" && + GET $PACK_URL "$1" && + GET $IDX_URL "$1" +} + +test_expect_success 'direct refs/heads/master not found' ' + log_div "refs/heads/master" + GET refs/heads/master "404 Not Found" +' +test_expect_success 'static file is ok' ' + log_div "getanyfile default" + get_static_files "200 OK" +' +test_expect_success 'static file if http.getanyfile true is ok' ' + log_div "getanyfile true" + config http.getanyfile true && + get_static_files "200 OK" +' +test_expect_success 'static file if http.getanyfile false fails' ' + log_div "getanyfile false" + config http.getanyfile false && + get_static_files "403 Forbidden" +' + +test_expect_success 'http.uploadpack default enabled' ' + log_div "uploadpack default" + GET info/refs?service=git-upload-pack "200 OK" && + POST git-upload-pack 0000 "200 OK" +' +test_expect_success 'http.uploadpack true' ' + log_div "uploadpack true" + config http.uploadpack true && + GET info/refs?service=git-upload-pack "200 OK" && + POST git-upload-pack 0000 "200 OK" +' +test_expect_success 'http.uploadpack false' ' + log_div "uploadpack false" + config http.uploadpack false && + GET info/refs?service=git-upload-pack "403 Forbidden" && + POST git-upload-pack 0000 "403 Forbidden" +' + +test_expect_success 'http.receivepack default disabled' ' + log_div "receivepack default" + GET info/refs?service=git-receive-pack "403 Forbidden" && + POST git-receive-pack 0000 "403 Forbidden" +' +test_expect_success 'http.receivepack true' ' + log_div "receivepack true" + config http.receivepack true && + GET info/refs?service=git-receive-pack "200 OK" && + POST git-receive-pack 0000 "200 OK" +' +test_expect_success 'http.receivepack false' ' + log_div "receivepack false" + config http.receivepack false && + GET info/refs?service=git-receive-pack "403 Forbidden" && + POST git-receive-pack 0000 "403 Forbidden" +' + +run_backend() { + REQUEST_METHOD=GET \ + GIT_PROJECT_ROOT="$HTTPD_DOCUMENT_ROOT_PATH" \ + PATH_INFO="$2" \ + git http-backend >act.out 2>act.err +} + +path_info() { + if test $1 = 0; then + run_backend "$2" + else + test_must_fail run_backend "$2" && + echo "fatal: '$2': aliased" >exp.err && + test_cmp exp.err act.err + fi +} + +test_expect_success 'http-backend blocks bad PATH_INFO' ' + config http.getanyfile true && + + run_backend 0 /repo.git/HEAD && + + run_backend 1 /repo.git/../HEAD && + run_backend 1 /../etc/passwd && + run_backend 1 ../etc/passwd && + run_backend 1 /etc//passwd && + run_backend 1 /etc/./passwd && + run_backend 1 /etc/.../passwd && + run_backend 1 //domain/data.txt +' + +cat >exp <<EOF + +### refs/heads/master +### +GET /smart/repo.git/refs/heads/master HTTP/1.1 404 - + +### getanyfile default +### +GET /smart/repo.git/HEAD HTTP/1.1 200 +GET /smart/repo.git/info/refs HTTP/1.1 200 +GET /smart/repo.git/objects/info/packs HTTP/1.1 200 +GET /smart/repo.git/objects/info/alternates HTTP/1.1 200 - +GET /smart/repo.git/objects/info/http-alternates HTTP/1.1 200 - +GET /smart/repo.git/$LOOSE_URL HTTP/1.1 200 +GET /smart/repo.git/$PACK_URL HTTP/1.1 200 +GET /smart/repo.git/$IDX_URL HTTP/1.1 200 + +### getanyfile true +### +GET /smart/repo.git/HEAD HTTP/1.1 200 +GET /smart/repo.git/info/refs HTTP/1.1 200 +GET /smart/repo.git/objects/info/packs HTTP/1.1 200 +GET /smart/repo.git/objects/info/alternates HTTP/1.1 200 - +GET /smart/repo.git/objects/info/http-alternates HTTP/1.1 200 - +GET /smart/repo.git/$LOOSE_URL HTTP/1.1 200 +GET /smart/repo.git/$PACK_URL HTTP/1.1 200 +GET /smart/repo.git/$IDX_URL HTTP/1.1 200 + +### getanyfile false +### +GET /smart/repo.git/HEAD HTTP/1.1 403 - +GET /smart/repo.git/info/refs HTTP/1.1 403 - +GET /smart/repo.git/objects/info/packs HTTP/1.1 403 - +GET /smart/repo.git/objects/info/alternates HTTP/1.1 403 - +GET /smart/repo.git/objects/info/http-alternates HTTP/1.1 403 - +GET /smart/repo.git/$LOOSE_URL HTTP/1.1 403 - +GET /smart/repo.git/$PACK_URL HTTP/1.1 403 - +GET /smart/repo.git/$IDX_URL HTTP/1.1 403 - + +### uploadpack default +### +GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200 +POST /smart/repo.git/git-upload-pack HTTP/1.1 200 - + +### uploadpack true +### +GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200 +POST /smart/repo.git/git-upload-pack HTTP/1.1 200 - + +### uploadpack false +### +GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 403 - +POST /smart/repo.git/git-upload-pack HTTP/1.1 403 - + +### receivepack default +### +GET /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 403 - +POST /smart/repo.git/git-receive-pack HTTP/1.1 403 - + +### receivepack true +### +GET /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 200 +POST /smart/repo.git/git-receive-pack HTTP/1.1 200 - + +### receivepack false +### +GET /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 403 - +POST /smart/repo.git/git-receive-pack HTTP/1.1 403 - +EOF +test_expect_success 'server request log matches test results' ' + sed -e " + s/^.* \"// + s/\"// + s/ [1-9][0-9]*\$// + s/^GET /GET / + " >act <"$HTTPD_ROOT_PATH"/access.log && + test_cmp exp act +' + +stop_httpd +test_done diff --git a/t/t5706-clone-branch.sh b/t/t5706-clone-branch.sh new file mode 100755 index 0000000000..f3f9a76056 --- /dev/null +++ b/t/t5706-clone-branch.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +test_description='clone --branch option' +. ./test-lib.sh + +check_HEAD() { + echo refs/heads/"$1" >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual +} + +check_file() { + echo "$1" >expect && + test_cmp expect file +} + +test_expect_success 'setup' ' + mkdir parent && + (cd parent && git init && + echo one >file && git add file && git commit -m one && + git checkout -b two && + echo two >file && git add file && git commit -m two && + git checkout master) +' + +test_expect_success 'vanilla clone chooses HEAD' ' + git clone parent clone && + (cd clone && + check_HEAD master && + check_file one + ) +' + +test_expect_success 'clone -b chooses specified branch' ' + git clone -b two parent clone-two && + (cd clone-two && + check_HEAD two && + check_file two + ) +' + +test_expect_success 'clone -b sets up tracking' ' + (cd clone-two && + echo origin >expect && + git config branch.two.remote >actual && + echo refs/heads/two >>expect && + git config branch.two.merge >>actual && + test_cmp expect actual + ) +' + +test_expect_success 'clone -b does not munge remotes/origin/HEAD' ' + (cd clone-two && + echo refs/remotes/origin/master >expect && + git symbolic-ref refs/remotes/origin/HEAD >actual && + test_cmp expect actual + ) +' + +test_expect_success 'clone -b with bogus branch chooses HEAD' ' + git clone -b bogus parent clone-bogus && + (cd clone-bogus && + check_HEAD master && + check_file one + ) +' + +test_done diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 59d1f6283b..7f61ab0e52 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -162,4 +162,22 @@ test_expect_success 'empty email' ' } ' +test_expect_success '"%h %gD: %gs" is same as git-reflog' ' + git reflog >expect && + git log -g --format="%h %gD: %gs" >actual && + test_cmp expect actual +' + +test_expect_success '"%h %gD: %gs" is same as git-reflog (with date)' ' + git reflog --date=raw >expect && + git log -g --format="%h %gD: %gs" --date=raw >actual && + test_cmp expect actual +' + +test_expect_success '%gd shortens ref name' ' + echo "master@{0}" >expect.gd-short && + git log -g -1 --format=%gd refs/heads/master >actual.gd-short && + test_cmp expect.gd-short actual.gd-short +' + test_done diff --git a/t/t6028-merge-up-to-date.sh b/t/t6028-merge-up-to-date.sh index f8f3e3ff2c..a91644e3b2 100755 --- a/t/t6028-merge-up-to-date.sh +++ b/t/t6028-merge-up-to-date.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='merge fast forward and up to date' +test_description='merge fast-forward and up to date' . ./test-lib.sh diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index 8b8bd81c09..d4818b430a 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -70,6 +70,13 @@ test_expect_success 'replace the author' ' git show $HASH2 | grep "O Thor" ' +test_expect_success 'test --no-replace-objects option' ' + git cat-file commit $HASH2 | grep "author O Thor" && + git --no-replace-objects cat-file commit $HASH2 | grep "author A U Thor" && + git show $HASH2 | grep "O Thor" && + git --no-replace-objects show $HASH2 | grep "A U Thor" +' + cat >tag.sig <<EOF object $HASH2 type commit diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 8c7e081c53..065deadc29 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -34,6 +34,8 @@ test_expect_success setup ' echo one >file && git add file && git commit -m initial && one=$(git rev-parse HEAD) && + git describe --always HEAD && + test_tick && echo two >file && git add file && git commit -m second && two=$(git rev-parse HEAD) && @@ -90,12 +92,18 @@ check_describe A-* HEAD^ check_describe D-* HEAD^^ check_describe A-* HEAD^^2 check_describe B HEAD^^2^ +check_describe D-* HEAD^^^ check_describe c-* --tags HEAD check_describe c-* --tags HEAD^ check_describe e-* --tags HEAD^^ check_describe c-* --tags HEAD^^2 check_describe B --tags HEAD^^2^ +check_describe e --tags HEAD^^^ + +check_describe heads/master --all HEAD +check_describe tags/c-* --all HEAD^ +check_describe tags/e --all HEAD^^^ check_describe B-0-* --long HEAD^^2^ check_describe A-3-* --long HEAD^^2 @@ -123,6 +131,20 @@ test_expect_success 'rename tag Q back to A' ' test_expect_success 'pack tag refs' 'git pack-refs' check_describe A-* HEAD +check_describe "A-*[0-9a-f]" --dirty + +test_expect_success 'set-up dirty work tree' ' + echo >>file +' + +check_describe "A-*[0-9a-f]-dirty" --dirty + +check_describe "A-*[0-9a-f].mod" --dirty=.mod + +test_expect_success 'describe --dirty HEAD' ' + test_must_fail git describe --dirty HEAD +' + test_expect_success 'set-up matching pattern tests' ' git tag -a -m test-annotated test-annotated && echo >>file && diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index b4709e28b5..ae5290ab43 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -213,6 +213,72 @@ test_expect_success 'grep -e A --and --not -e B' ' test_cmp expected actual ' +test_expect_success 'grep -f, non-existent file' ' + test_must_fail git grep -f patterns +' + +cat >expected <<EOF +file:foo mmap bar +file:foo_mmap bar +file:foo_mmap bar mmap +file:foo mmap bar_mmap +file:foo_mmap bar mmap baz +EOF + +cat >pattern <<EOF +mmap +EOF + +test_expect_success 'grep -f, one pattern' ' + git grep -f pattern >actual && + test_cmp expected actual +' + +cat >expected <<EOF +file:foo mmap bar +file:foo_mmap bar +file:foo_mmap bar mmap +file:foo mmap bar_mmap +file:foo_mmap bar mmap baz +t/a/v:vvv +t/v:vvv +v:vvv +EOF + +cat >patterns <<EOF +mmap +vvv +EOF + +test_expect_success 'grep -f, multiple patterns' ' + git grep -f patterns >actual && + test_cmp expected actual +' + +cat >expected <<EOF +file:foo mmap bar +file:foo_mmap bar +file:foo_mmap bar mmap +file:foo mmap bar_mmap +file:foo_mmap bar mmap baz +t/a/v:vvv +t/v:vvv +v:vvv +EOF + +cat >patterns <<EOF + +mmap + +vvv + +EOF + +test_expect_success 'grep -f, ignore empty lines' ' + git grep -f patterns >actual && + test_cmp expected actual +' + cat >expected <<EOF y:y yy -- @@ -328,4 +394,21 @@ test_expect_success 'grep -p -B5' ' test_cmp expected actual ' +test_expect_success 'grep from a subdirectory to search wider area (1)' ' + mkdir -p s && + ( + cd s && git grep "x x x" .. + ) +' + +test_expect_success 'grep from a subdirectory to search wider area (2)' ' + mkdir -p s && + ( + cd s || exit 1 + ( git grep xxyyzz .. >out ; echo $? >status ) + ! test -s out && + test 1 = $(cat status) + ) +' + test_done diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index 329c851685..9503875e97 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -288,4 +288,22 @@ test_expect_success 'Prune empty commits' ' test_cmp expect actual ' +test_expect_success '--remap-to-ancestor with filename filters' ' + git checkout master && + git reset --hard A && + test_commit add-foo foo 1 && + git branch moved-foo && + test_commit add-bar bar a && + git branch invariant && + orig_invariant=$(git rev-parse invariant) && + git branch moved-bar && + test_commit change-foo foo 2 && + git filter-branch -f --remap-to-ancestor \ + moved-foo moved-bar A..master \ + -- -- foo && + test $(git rev-parse moved-foo) = $(git rev-parse moved-bar) && + test $(git rev-parse moved-foo) = $(git rev-parse master^) && + test $orig_invariant = $(git rev-parse invariant) +' + test_done diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh index b647957d75..5257f4d261 100755 --- a/t/t7005-editor.sh +++ b/t/t7005-editor.sh @@ -4,7 +4,21 @@ test_description='GIT_EDITOR, core.editor, and stuff' . ./test-lib.sh -for i in GIT_EDITOR core_editor EDITOR VISUAL vi +unset EDITOR VISUAL GIT_EDITOR + +test_expect_success 'determine default editor' ' + + vi=$(TERM=vt100 git var GIT_EDITOR) && + test -n "$vi" + +' + +if ! expr "$vi" : '^[a-z]*$' >/dev/null +then + vi= +fi + +for i in GIT_EDITOR core_editor EDITOR VISUAL $vi do cat >e-$i.sh <<-EOF #!$SHELL_PATH @@ -12,19 +26,18 @@ do EOF chmod +x e-$i.sh done -unset vi -mv e-vi.sh vi -unset EDITOR VISUAL GIT_EDITOR + +if ! test -z "$vi" +then + mv e-$vi.sh $vi +fi test_expect_success setup ' - msg="Hand edited" && + msg="Hand-edited" && + test_commit "$msg" && echo "$msg" >expect && - git add vi && - test_tick && - git commit -m "$msg" && - git show -s --pretty=oneline | - sed -e "s/^[0-9a-f]* //" >actual && + git show -s --format=%s > actual && diff actual expect ' @@ -42,9 +55,19 @@ test_expect_success 'dumb should error out when falling back on vi' ' fi ' +test_expect_success 'dumb should prefer EDITOR to VISUAL' ' + + EDITOR=./e-EDITOR.sh && + VISUAL=./e-VISUAL.sh && + export EDITOR VISUAL && + git commit --amend && + test "$(git show -s --format=%s)" = "Edited by EDITOR" + +' + TERM=vt100 export TERM -for i in vi EDITOR VISUAL core_editor GIT_EDITOR +for i in $vi EDITOR VISUAL core_editor GIT_EDITOR do echo "Edited by $i" >expect unset EDITOR VISUAL GIT_EDITOR @@ -68,7 +91,7 @@ done unset EDITOR VISUAL GIT_EDITOR git config --unset-all core.editor -for i in vi EDITOR VISUAL core_editor GIT_EDITOR +for i in $vi EDITOR VISUAL core_editor GIT_EDITOR do echo "Edited by $i" >expect case "$i" in diff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh new file mode 100755 index 0000000000..c1f4fc3c65 --- /dev/null +++ b/t/t7105-reset-patch.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='git reset --patch' +. ./lib-patch-mode.sh + +test_expect_success 'setup' ' + mkdir dir && + echo parent > dir/foo && + echo dummy > bar && + git add dir && + git commit -m initial && + test_tick && + test_commit second dir/foo head && + set_and_save_state bar bar_work bar_index && + save_head +' + +# note: bar sorts before foo, so the first 'n' is always to skip 'bar' + +test_expect_success 'saying "n" does nothing' ' + set_and_save_state dir/foo work work + (echo n; echo n) | git reset -p && + verify_saved_state dir/foo && + verify_saved_state bar +' + +test_expect_success 'git reset -p' ' + (echo n; echo y) | git reset -p && + verify_state dir/foo work head && + verify_saved_state bar +' + +test_expect_success 'git reset -p HEAD^' ' + (echo n; echo y) | git reset -p HEAD^ && + verify_state dir/foo work parent && + verify_saved_state bar +' + +# The idea in the rest is that bar sorts first, so we always say 'y' +# first and if the path limiter fails it'll apply to bar instead of +# dir/foo. There's always an extra 'n' to reject edits to dir/foo in +# the failure case (and thus get out of the loop). + +test_expect_success 'git reset -p dir' ' + set_state dir/foo work work + (echo y; echo n) | git reset -p dir && + verify_state dir/foo work head && + verify_saved_state bar +' + +test_expect_success 'git reset -p -- foo (inside dir)' ' + set_state dir/foo work work + (echo y; echo n) | (cd dir && git reset -p -- foo) && + verify_state dir/foo work head && + verify_saved_state bar +' + +test_expect_success 'git reset -p HEAD^ -- dir' ' + (echo y; echo n) | git reset -p HEAD^ -- dir && + verify_state dir/foo work parent && + verify_saved_state bar +' + +test_expect_success 'none of this moved HEAD' ' + verify_saved_head +' + + +test_done diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 0f2ccc6cf0..a0cc99ab9f 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -306,4 +306,20 @@ test_expect_success 'submodule <invalid-path> warns' ' ' +test_expect_success 'add submodules without specifying an explicit path' ' + mkdir repo && + cd repo && + git init && + echo r >r && + git add r && + git commit -m "repo commit 1" && + cd .. && + git clone --bare repo/ bare.git && + cd addtest && + git submodule add "$submodurl/repo" && + git config -f .gitmodules submodule.repo.path repo && + git submodule add "$submodurl/bare.git" && + git config -f .gitmodules submodule.bare.path bare +' + test_done diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index e2ef53228e..a603f6d21a 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -86,7 +86,7 @@ chmod 755 editor test_expect_success \ "amend commit" \ - "VISUAL=./editor git commit --amend" + "EDITOR=./editor git commit --amend" test_expect_success \ "passing -m and -F" \ @@ -107,7 +107,7 @@ chmod 755 editor test_expect_success \ "editing message from other commit" \ "echo 'hula hula' >file && \ - VISUAL=./editor git commit -c HEAD^ -a" + EDITOR=./editor git commit -c HEAD^ -a" test_expect_success \ "message from stdin" \ @@ -141,10 +141,10 @@ EOF test_expect_success \ 'editor not invoked if -F is given' ' echo "moo" >file && - VISUAL=./editor git commit -a -F msg && + EDITOR=./editor git commit -a -F msg && git show -s --pretty=format:"%s" | grep -q good && echo "quack" >file && - echo "Another good message." | VISUAL=./editor git commit -a -F - && + echo "Another good message." | EDITOR=./editor git commit -a -F - && git show -s --pretty=format:"%s" | grep -q good ' # We could just check the head sha1, but checking each commit makes it @@ -247,6 +247,47 @@ $existing" && ' +test_expect_success 'signoff gap' ' + + echo 3 >positive && + git add positive && + alt="Alt-RFC-822-Header: Value" && + git commit -s -m "welcome + +$alt" && + git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + ( + echo welcome + echo + echo $alt + git var GIT_COMMITTER_IDENT | + sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'signoff gap 2' ' + + echo 4 >positive && + git add positive && + alt="fixed: 34" && + git commit -s -m "welcome + +We have now +$alt" && + git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + ( + echo welcome + echo + echo We have now + echo $alt + echo + git var GIT_COMMITTER_IDENT | + sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" + ) >expected && + test_cmp expected actual +' + test_expect_success 'multiple -m' ' >negative && diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index 56cd866019..fe94552296 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -258,4 +258,13 @@ test_expect_success 'Hand committing of a redundant merge removes dups' ' ' +test_expect_success 'A single-liner subject with a token plus colon is not a footer' ' + + git reset --hard && + git commit -s -m "hello: kitty" --allow-empty && + git cat-file commit HEAD | sed -e "1,/^$/d" >actual && + test $(wc -l <actual) = 3 + +' + test_done diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index e5b210bc96..57f6d2bae7 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -243,6 +243,16 @@ test_expect_success 'merge c0 with c1' ' test_debug 'gitk --all' +test_expect_success 'merge c0 with c1 with --ff-only' ' + git reset --hard c0 && + git merge --ff-only c1 && + git merge --ff-only HEAD c0 c1 && + verify_merge file result.1 && + verify_head "$c1" +' + +test_debug 'gitk --all' + test_expect_success 'merge c1 with c2' ' git reset --hard c1 && test_tick && @@ -263,6 +273,14 @@ test_expect_success 'merge c1 with c2 and c3' ' test_debug 'gitk --all' +test_expect_success 'failing merges with --ff-only' ' + git reset --hard c1 && + test_tick && + test_must_fail git merge --ff-only c2 && + test_must_fail git merge --ff-only c3 && + test_must_fail git merge --ff-only c2 c3 +' + test_expect_success 'merge c0 with c1 (no-commit)' ' git reset --hard c0 && git merge --no-commit c1 && @@ -303,6 +321,17 @@ test_expect_success 'merge c0 with c1 (squash)' ' test_debug 'gitk --all' +test_expect_success 'merge c0 with c1 (squash, ff-only)' ' + git reset --hard c0 && + git merge --squash --ff-only c1 && + verify_merge file result.1 && + verify_head $c0 && + verify_no_mergehead && + verify_diff squash.1 .git/SQUASH_MSG "[OOPS] bad squash message" +' + +test_debug 'gitk --all' + test_expect_success 'merge c1 with c2 (squash)' ' git reset --hard c1 && git merge --squash c2 && @@ -314,6 +343,13 @@ test_expect_success 'merge c1 with c2 (squash)' ' test_debug 'gitk --all' +test_expect_success 'unsuccesful merge of c1 with c2 (squash, ff-only)' ' + git reset --hard c1 && + test_must_fail git merge --squash --ff-only c2 +' + +test_debug 'gitk --all' + test_expect_success 'merge c1 with c2 and c3 (squash)' ' git reset --hard c1 && git merge --squash c2 c3 && @@ -432,6 +468,11 @@ test_expect_success 'combining --squash and --no-ff is refused' ' test_must_fail git merge --no-ff --squash c1 ' +test_expect_success 'combining --ff-only and --no-ff is refused' ' + test_must_fail git merge --ff-only --no-ff c1 && + test_must_fail git merge --no-ff --ff-only c1 +' + test_expect_success 'merge c0 with c1 (ff overrides no-ff)' ' git reset --hard c0 && git config branch.master.mergeoptions "--no-ff" && diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index ebdccf9a1e..fff6a6d0ea 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -136,7 +136,7 @@ test_expect_success 'GIT_DIFFTOOL_PROMPT variable' ' GIT_DIFFTOOL_PROMPT=true && export GIT_DIFFTOOL_PROMPT && - prompt=$(echo | git difftool --prompt branch | tail -1) && + prompt=$(echo | git difftool branch | tail -1) && prompt_given "$prompt" && restore_test_defaults diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index fb606a9f05..84a7f03d46 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -103,10 +103,18 @@ cat >expected-show-all-headers <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM:<from@example.com> -RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<one@example.com>,<two@example.com>,<bcc@example.com> +RCPT TO:<to@example.com> +RCPT TO:<cc@example.com> +RCPT TO:<author@example.com> +RCPT TO:<one@example.com> +RCPT TO:<two@example.com> +RCPT TO:<bcc@example.com> From: Example <from@example.com> To: to@example.com -Cc: cc@example.com, A <author@example.com>, One <one@example.com>, two@example.com +Cc: cc@example.com, + A <author@example.com>, + One <one@example.com>, + two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -164,7 +172,7 @@ test_expect_success 'cccmd works' ' --smtp-server="$(pwd)/fake.sendmail" \ cccmd.patch \ && - grep ^Cc:.*cccmd@example.com msgtxt1 + grep "^ cccmd@example.com" msgtxt1 ' z8=zzzzzzzz @@ -278,10 +286,17 @@ cat >expected-suppress-sob <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM:<from@example.com> -RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<one@example.com>,<two@example.com> +RCPT TO:<to@example.com> +RCPT TO:<cc@example.com> +RCPT TO:<author@example.com> +RCPT TO:<one@example.com> +RCPT TO:<two@example.com> From: Example <from@example.com> To: to@example.com -Cc: cc@example.com, A <author@example.com>, One <one@example.com>, two@example.com +Cc: cc@example.com, + A <author@example.com>, + One <one@example.com>, + two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -318,10 +333,15 @@ cat >expected-suppress-sob <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM:<from@example.com> -RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com> +RCPT TO:<to@example.com> +RCPT TO:<author@example.com> +RCPT TO:<one@example.com> +RCPT TO:<two@example.com> From: Example <from@example.com> To: to@example.com -Cc: A <author@example.com>, One <one@example.com>, two@example.com +Cc: A <author@example.com>, + One <one@example.com>, + two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -344,10 +364,17 @@ cat >expected-suppress-cccmd <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM:<from@example.com> -RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>,<committer@example.com> +RCPT TO:<to@example.com> +RCPT TO:<author@example.com> +RCPT TO:<one@example.com> +RCPT TO:<two@example.com> +RCPT TO:<committer@example.com> From: Example <from@example.com> To: to@example.com -Cc: A <author@example.com>, One <one@example.com>, two@example.com, C O Mitter <committer@example.com> +Cc: A <author@example.com>, + One <one@example.com>, + two@example.com, + C O Mitter <committer@example.com> Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -392,10 +419,17 @@ cat >expected-suppress-body <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM:<from@example.com> -RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>,<cc-cmd@example.com> +RCPT TO:<to@example.com> +RCPT TO:<author@example.com> +RCPT TO:<one@example.com> +RCPT TO:<two@example.com> +RCPT TO:<cc-cmd@example.com> From: Example <from@example.com> To: to@example.com -Cc: A <author@example.com>, One <one@example.com>, two@example.com, cc-cmd@example.com +Cc: A <author@example.com>, + One <one@example.com>, + two@example.com, + cc-cmd@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -416,10 +450,15 @@ cat >expected-suppress-body-cccmd <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM:<from@example.com> -RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com> +RCPT TO:<to@example.com> +RCPT TO:<author@example.com> +RCPT TO:<one@example.com> +RCPT TO:<two@example.com> From: Example <from@example.com> To: to@example.com -Cc: A <author@example.com>, One <one@example.com>, two@example.com +Cc: A <author@example.com>, + One <one@example.com>, + two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -440,10 +479,15 @@ cat >expected-suppress-sob <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM:<from@example.com> -RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com> +RCPT TO:<to@example.com> +RCPT TO:<author@example.com> +RCPT TO:<one@example.com> +RCPT TO:<two@example.com> From: Example <from@example.com> To: to@example.com -Cc: A <author@example.com>, One <one@example.com>, two@example.com +Cc: A <author@example.com>, + One <one@example.com>, + two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -466,10 +510,17 @@ cat >expected-suppress-bodycc <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM:<from@example.com> -RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>,<committer@example.com> +RCPT TO:<to@example.com> +RCPT TO:<author@example.com> +RCPT TO:<one@example.com> +RCPT TO:<two@example.com> +RCPT TO:<committer@example.com> From: Example <from@example.com> To: to@example.com -Cc: A <author@example.com>, One <one@example.com>, two@example.com, C O Mitter <committer@example.com> +Cc: A <author@example.com>, + One <one@example.com>, + two@example.com, + C O Mitter <committer@example.com> Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -489,10 +540,13 @@ cat >expected-suppress-cc <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM:<from@example.com> -RCPT TO:<to@example.com>,<author@example.com>,<committer@example.com> +RCPT TO:<to@example.com> +RCPT TO:<author@example.com> +RCPT TO:<committer@example.com> From: Example <from@example.com> To: to@example.com -Cc: A <author@example.com>, C O Mitter <committer@example.com> +Cc: A <author@example.com>, + C O Mitter <committer@example.com> Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -605,7 +659,7 @@ test_expect_success 'utf8 Cc is rfc2047 encoded' ' --to=nobody@example.com \ --smtp-server="$(pwd)/fake.sendmail" \ outdir/*.patch && - grep "^Cc:" msgtxt1 | + grep "^ " msgtxt1 | grep "=?UTF-8?q?=C3=A0=C3=A9=C3=AC=C3=B6=C3=BA?= <utf8@example.com>" ' diff --git a/t/t9115-git-svn-dcommit-funky-renames.sh b/t/t9115-git-svn-dcommit-funky-renames.sh index 9be7aefaee..767799e7a7 100755 --- a/t/t9115-git-svn-dcommit-funky-renames.sh +++ b/t/t9115-git-svn-dcommit-funky-renames.sh @@ -19,7 +19,7 @@ test_expect_success 'init and fetch repository' ' ' test_expect_success 'create file in existing ugly and empty dir' ' - mkdir "#{bad_directory_name}" && + mkdir -p "#{bad_directory_name}" && echo hi > "#{bad_directory_name}/ foo" && git update-index --add "#{bad_directory_name}/ foo" && git commit -m "new file in ugly parent" && @@ -37,7 +37,7 @@ test_expect_success 'rename pretty file' ' git update-index --add pretty && git commit -m "pretty :x" && git svn dcommit && - mkdir regular_dir_name && + mkdir -p regular_dir_name && git mv pretty regular_dir_name/pretty && git commit -m "moved pretty file" && git svn dcommit diff --git a/t/t9130-git-svn-authors-file.sh b/t/t9130-git-svn-authors-file.sh index f5abdb3c7f..134411e0a5 100755 --- a/t/t9130-git-svn-authors-file.sh +++ b/t/t9130-git-svn-authors-file.sh @@ -91,4 +91,27 @@ test_expect_success 'fetch continues after authors-file is fixed' ' ) ' +test_expect_success 'fresh clone with svn.authors-file in config' ' + ( + rm -r "$GIT_DIR" && + test x = x"$(git config svn.authorsfile)" && + HOME="`pwd`" && + export HOME && + test_config="$HOME"/.gitconfig && + unset GIT_CONFIG_NOGLOBAL && + unset GIT_DIR && + unset GIT_CONFIG && + git config --global \ + svn.authorsfile "$HOME"/svn-authors && + test x"$HOME"/svn-authors = x"$(git config svn.authorsfile)" && + git svn clone "$svnrepo" gitconfig.clone && + cd gitconfig.clone && + nr_ex=$(git log | grep "^Author:.*example.com" | wc -l) && + nr_rev=$(git rev-list HEAD | wc -l) && + test $nr_rev -eq $nr_ex + ) +' + +test_debug 'GIT_DIR=gitconfig.clone/.git git log' + test_done diff --git a/t/t9138-git-svn-authors-prog.sh b/t/t9138-git-svn-authors-prog.sh index a4b00f2a3f..83cc5fc9d1 100755 --- a/t/t9138-git-svn-authors-prog.sh +++ b/t/t9138-git-svn-authors-prog.sh @@ -66,4 +66,18 @@ test_expect_success 'authors-file overrode authors-prog' ' ) ' +git --git-dir=x/.git config --unset svn.authorsfile +git --git-dir=x/.git config --unset svn.authorsprog + +test_expect_success 'authors-prog handled special characters in username' ' + svn mkdir -m bad --username "xyz; touch evil" "$svnrepo"/bad && + ( + cd x && + git svn --authors-prog=../svn-authors-prog fetch && + git rev-list -1 --pretty=raw refs/remotes/git-svn | + grep "^author xyz; touch evil <xyz; touch evil@example\.com> " && + ! test -f evil + ) +' + test_done diff --git a/t/t9146-git-svn-empty-dirs.sh b/t/t9146-git-svn-empty-dirs.sh new file mode 100755 index 0000000000..5948544ec5 --- /dev/null +++ b/t/t9146-git-svn-empty-dirs.sh @@ -0,0 +1,85 @@ +#!/bin/sh +# +# Copyright (c) 2009 Eric Wong + +test_description='git svn creates empty directories' +. ./lib-git-svn.sh + +test_expect_success 'initialize repo' ' + for i in a b c d d/e d/e/f "weird file name" + do + svn_cmd mkdir -m "mkdir $i" "$svnrepo"/"$i" + done +' + +test_expect_success 'clone' 'git svn clone "$svnrepo" cloned' + +test_expect_success 'empty directories exist' ' + ( + cd cloned && + for i in a b c d d/e d/e/f "weird file name" + do + if ! test -d "$i" + then + echo >&2 "$i does not exist" + exit 1 + fi + done + ) +' + +test_expect_success 'more emptiness' ' + svn_cmd mkdir -m "bang bang" "$svnrepo"/"! !" +' + +test_expect_success 'git svn rebase creates empty directory' ' + ( cd cloned && git svn rebase ) + test -d cloned/"! !" +' + +test_expect_success 'git svn mkdirs recreates empty directories' ' + ( + cd cloned && + rm -r * && + git svn mkdirs && + for i in a b c d d/e d/e/f "weird file name" "! !" + do + if ! test -d "$i" + then + echo >&2 "$i does not exist" + exit 1 + fi + done + ) +' + +test_expect_success 'git svn mkdirs -r works' ' + ( + cd cloned && + rm -r * && + git svn mkdirs -r7 && + for i in a b c d d/e d/e/f "weird file name" + do + if ! test -d "$i" + then + echo >&2 "$i does not exist" + exit 1 + fi + done + + if test -d "! !" + then + echo >&2 "$i should not exist" + exit 1 + fi + + git svn mkdirs -r8 && + if ! test -d "! !" + then + echo >&2 "$i not exist" + exit 1 + fi + ) +' + +test_done diff --git a/t/t9150-svk-mergetickets.sh b/t/t9150-svk-mergetickets.sh new file mode 100755 index 0000000000..dd0c2bad24 --- /dev/null +++ b/t/t9150-svk-mergetickets.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# +# Copyright (c) 2007 Sam Vilain +# + +test_description='git-svn svk merge tickets' + +. ./lib-git-svn.sh + +test_expect_success 'load svk depot' " + svnadmin load -q '$rawsvnrepo' \ + < '$TEST_DIRECTORY/t9150/svk-merge.dump' && + git svn init --minimize-url -R svkmerge \ + -T trunk -b branches '$svnrepo' && + git svn fetch --all + " + +uuid=b48289b2-9c08-4d72-af37-0358a40b9c15 + +test_expect_success 'svk merges were represented coming in' " + [ `git-cat-file commit HEAD | grep parent | wc -l` -eq 2 ] + " + +test_done diff --git a/t/t9150/make-svk-dump b/t/t9150/make-svk-dump new file mode 100644 index 0000000000..2242f14ebe --- /dev/null +++ b/t/t9150/make-svk-dump @@ -0,0 +1,57 @@ +#!/bin/sh +# +# this script sets up a Subversion repository for Makefile in the +# first ever git merge, as if it were done with svk. +# + +set -e + +svk depotmap foo ~/.svk/foo +svk co /foo/ foo +cd foo +mkdir trunk +mkdir branches +svk add trunk branches +svk commit -m "Setup trunk and branches" +cd trunk + +git cat-file blob 6683463e:Makefile > Makefile +svk add Makefile + +svk commit -m "ancestor" +cd .. +svk cp trunk branches/left + +svk commit -m "make left branch" +cd branches/left/ + +git cat-file blob 5873b67e:Makefile > Makefile +svk commit -m "left update 1" + +cd ../../trunk +git cat-file blob 75118b13:Makefile > Makefile +svk commit -m "trunk update" + +cd ../branches/left +git cat-file blob b5039db6:Makefile > Makefile +svk commit -m "left update 2" + +cd ../../trunk +svk sm /foo/branches/left +# in theory we could delete the "left" branch here, but it's not +# required so don't do it, in case people start getting ideas ;) +svk commit -m "merge branch 'left' into 'trunk'" + +git cat-file blob b51ad431:Makefile > Makefile + +svk diff Makefile && echo "Hey! No differences, magic" + +cd ../.. + +svnadmin dump ~/.svk/foo > svk-merge.dump + +svk co -d foo +rm -rf foo +svk depotmap -d /foo/ +rm -rf ~/.svk/foo + diff --git a/t/t9150/svk-merge.dump b/t/t9150/svk-merge.dump new file mode 100644 index 0000000000..42f70dbec7 --- /dev/null +++ b/t/t9150/svk-merge.dump @@ -0,0 +1,616 @@ +SVN-fs-dump-format-version: 2 + +UUID: b48289b2-9c08-4d72-af37-0358a40b9c15 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2009-10-19T23:44:03.722969Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 123 +Content-length: 123 + +K 7 +svn:log +V 24 +Setup trunk and branches +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:04.927533Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Revision-number: 2 +Prop-content-length: 106 +Content-length: 106 + +K 7 +svn:log +V 8 +ancestor +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:05.835585Z +PROPS-END + +Node-path: trunk/Makefile +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2401 +Text-content-md5: bfd8ff778d1492dc6758567373176a89 +Content-length: 2411 + +PROPS-END +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 3 +Prop-content-length: 115 +Content-length: 115 + +K 7 +svn:log +V 16 +make left branch +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:06.719737Z +PROPS-END + +Node-path: branches/left +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk + + +Revision-number: 4 +Prop-content-length: 112 +Content-length: 112 + +K 7 +svn:log +V 13 +left update 1 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:07.167666Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2465 +Text-content-md5: 16e38d9753b061731650561ce01b1195 +Content-length: 2465 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 5 +Prop-content-length: 111 +Content-length: 111 + +K 7 +svn:log +V 12 +trunk update +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:07.619633Z +PROPS-END + +Node-path: trunk/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2521 +Text-content-md5: 0668418a621333f4aa8b6632cd63e2a0 +Content-length: 2521 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base merge-cache + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +merge-cache: merge-cache.o read-cache.o + $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 6 +Prop-content-length: 112 +Content-length: 112 + +K 7 +svn:log +V 13 +left update 2 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:08.067554Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2593 +Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba +Content-length: 2593 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 7 +Prop-content-length: 131 +Content-length: 131 + +K 7 +svn:log +V 32 +merge branch 'left' into 'trunk' +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:08.971801Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 83 +Content-length: 83 + +K 9 +svk:merge +V 53 +b48289b2-9c08-4d72-af37-0358a40b9c15:/branches/left:6 +PROPS-END + + +Node-path: trunk/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2713 +Text-content-md5: 0afbe34f244cd662b1f97d708c687f90 +Content-length: 2713 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base merge-cache + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +merge-cache: merge-cache.o read-cache.o + $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + diff --git a/t/t9151-svn-mergeinfo.sh b/t/t9151-svn-mergeinfo.sh new file mode 100755 index 0000000000..f57daf401a --- /dev/null +++ b/t/t9151-svn-mergeinfo.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# +# Copyright (c) 2007, 2009 Sam Vilain +# + +test_description='git-svn svn mergeinfo properties' + +. ./lib-git-svn.sh + +test_expect_success 'load svn dump' " + svnadmin load -q '$rawsvnrepo' \ + < '$TEST_DIRECTORY/t9151/svn-mergeinfo.dump' && + git svn init --minimize-url -R svnmerge \ + -T trunk -b branches '$svnrepo' && + git svn fetch --all + " + +test_expect_success 'represent svn merges without intervening commits' " + [ `git cat-file commit HEAD^1 | grep parent | wc -l` -eq 2 ] + " + +test_expect_success 'represent svn merges with intervening commits' " + [ `git cat-file commit HEAD | grep parent | wc -l` -eq 2 ] + " + +test_done diff --git a/t/t9151/.gitignore b/t/t9151/.gitignore new file mode 100644 index 0000000000..587c37dba3 --- /dev/null +++ b/t/t9151/.gitignore @@ -0,0 +1,2 @@ +foo +foo.svn diff --git a/t/t9151/make-svnmerge-dump b/t/t9151/make-svnmerge-dump new file mode 100644 index 0000000000..7e3da75f86 --- /dev/null +++ b/t/t9151/make-svnmerge-dump @@ -0,0 +1,103 @@ +#!/bin/sh +# +# this script sets up a Subversion repository for Makefile in the +# first ever git merge, as if it were done with svnmerge (SVN 1.5+) +# + +rm -rf foo.svn foo +set -e + +mkdir foo.svn +svnadmin create foo.svn +svn co file://`pwd`/foo.svn foo + +cd foo +mkdir trunk +mkdir branches +svn add trunk branches +svn commit -m "Setup trunk and branches" +cd trunk + +git cat-file blob 6683463e:Makefile > Makefile +svn add Makefile + +echo "Committing ANCESTOR" +svn commit -m "ancestor" +cd .. +svn cp trunk branches/left + +echo "Committing BRANCH POINT" +svn commit -m "make left branch" +svn cp trunk branches/right + +echo "Committing other BRANCH POINT" +svn commit -m "make right branch" +cd branches/left/ + +#$sm init +#svn commit -m "init svnmerge" + +git cat-file blob 5873b67e:Makefile > Makefile +echo "Committing BRANCH UPDATE 1" +svn commit -m "left update 1" +cd ../.. + +cd trunk +git cat-file blob 75118b13:Makefile > Makefile +echo "Committing TRUNK UPDATE" +svn commit -m "trunk update" + +cd ../branches/left +git cat-file blob ff5ebe39:Makefile > Makefile +echo "Committing BRANCH UPDATE 2" +svn commit -m "left update 2" + +git cat-file blob b5039db6:Makefile > Makefile +echo "Committing BRANCH UPDATE 3" +svn commit -m "left update 3" + +# merge to trunk + +cd ../.. +svn update +cd trunk + +svn merge ../branches/left --accept postpone + +git cat-file blob b51ad431:Makefile > Makefile + +svn resolved Makefile + +svn commit -m "Merge trunk 1" + +# create commits on both branches + +cd ../branches/left +git cat-file blob ff5ebe39:Makefile > Makefile +echo "Committing BRANCH UPDATE 4" +svn commit -m "left update 4" + +cd ../right +git cat-file blob b5039db6:Makefile > Makefile +echo "Committing other BRANCH UPDATE 1" +svn commit -m "right update 1" + +# merge to trun again + +cd ../.. +svn update +cd trunk + +svn merge ../branches/left --accept postpone + +git cat-file blob b51ad431:Makefile > Makefile + +svn resolved Makefile + +svn commit -m "Merge trunk 2" + +cd ../.. + +svnadmin dump foo.svn > svn-mergeinfo.dump + +rm -rf foo foo.svn diff --git a/t/t9151/svn-mergeinfo.dump b/t/t9151/svn-mergeinfo.dump new file mode 100644 index 0000000000..11a883fda9 --- /dev/null +++ b/t/t9151/svn-mergeinfo.dump @@ -0,0 +1,1010 @@ +SVN-fs-dump-format-version: 2 + +UUID: 1530d5a2-a1dc-4438-8ad5-d95e96db8945 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2009-11-12T20:29:38.812226Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 127 +Content-length: 127 + +K 7 +svn:log +V 24 +Setup trunk and branches +K 10 +svn:author +V 8 +tallsopp +K 8 +svn:date +V 27 +2009-11-12T20:29:39.045856Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Revision-number: 2 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 8 +ancestor +K 10 +svn:author +V 8 +tallsopp +K 8 +svn:date +V 27 +2009-11-12T20:29:40.079587Z +PROPS-END + +Node-path: trunk/Makefile +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2401 +Text-content-md5: bfd8ff778d1492dc6758567373176a89 +Text-content-sha1: 103205ce331f7d64086dba497574734f78439590 +Content-length: 2411 + +PROPS-END +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 3 +Prop-content-length: 119 +Content-length: 119 + +K 7 +svn:log +V 16 +make left branch +K 10 +svn:author +V 8 +tallsopp +K 8 +svn:date +V 27 +2009-11-12T20:29:42.084439Z +PROPS-END + +Node-path: branches/left +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk + + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/Makefile +Text-copy-source-md5: bfd8ff778d1492dc6758567373176a89 +Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590 + + +Revision-number: 4 +Prop-content-length: 120 +Content-length: 120 + +K 7 +svn:log +V 17 +make right branch +K 10 +svn:author +V 8 +tallsopp +K 8 +svn:date +V 27 +2009-11-12T20:29:44.065452Z +PROPS-END + +Node-path: branches/right +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk + + +Node-path: branches/right/Makefile +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/Makefile +Text-copy-source-md5: bfd8ff778d1492dc6758567373176a89 +Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590 + + +Revision-number: 5 +Prop-content-length: 116 +Content-length: 116 + +K 7 +svn:log +V 13 +left update 1 +K 10 +svn:author +V 8 +tallsopp +K 8 +svn:date +V 27 +2009-11-12T20:29:45.066262Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2465 +Text-content-md5: 16e38d9753b061731650561ce01b1195 +Text-content-sha1: 36da4b84ea9b64218ab48171dfc5c48ae025f38b +Content-length: 2465 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 6 +Prop-content-length: 115 +Content-length: 115 + +K 7 +svn:log +V 12 +trunk update +K 10 +svn:author +V 8 +tallsopp +K 8 +svn:date +V 27 +2009-11-12T20:29:46.278498Z +PROPS-END + +Node-path: trunk/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2521 +Text-content-md5: 0668418a621333f4aa8b6632cd63e2a0 +Text-content-sha1: 4f29afd038e52f45acb5ef8c41acfc70062a741a +Content-length: 2521 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base merge-cache + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +merge-cache: merge-cache.o read-cache.o + $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 7 +Prop-content-length: 116 +Content-length: 116 + +K 7 +svn:log +V 13 +left update 2 +K 10 +svn:author +V 8 +tallsopp +K 8 +svn:date +V 27 +2009-11-12T20:29:47.069090Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2529 +Text-content-md5: f6b197cc3f2e89a83e545d4bb003de73 +Text-content-sha1: 2f656677cfec0bceec85e53036ffb63e25126f8e +Content-length: 2529 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 8 +Prop-content-length: 116 +Content-length: 116 + +K 7 +svn:log +V 13 +left update 3 +K 10 +svn:author +V 8 +tallsopp +K 8 +svn:date +V 27 +2009-11-12T20:29:48.053835Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2593 +Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba +Text-content-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10 +Content-length: 2593 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 9 +Prop-content-length: 116 +Content-length: 116 + +K 7 +svn:log +V 13 +Merge trunk 1 +K 10 +svn:author +V 8 +tallsopp +K 8 +svn:date +V 27 +2009-11-12T20:29:51.098306Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 53 +Content-length: 53 + +K 13 +svn:mergeinfo +V 18 +/branches/left:2-8 +PROPS-END + + +Node-path: trunk/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2713 +Text-content-md5: 0afbe34f244cd662b1f97d708c687f90 +Text-content-sha1: 46d9377d783e67a9b581da110352e799517c8a14 +Content-length: 2713 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base merge-cache + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +merge-cache: merge-cache.o read-cache.o + $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 10 +Prop-content-length: 116 +Content-length: 116 + +K 7 +svn:log +V 13 +left update 4 +K 10 +svn:author +V 8 +tallsopp +K 8 +svn:date +V 27 +2009-11-12T20:29:52.081644Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2529 +Text-content-md5: f6b197cc3f2e89a83e545d4bb003de73 +Text-content-sha1: 2f656677cfec0bceec85e53036ffb63e25126f8e +Content-length: 2529 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 11 +Prop-content-length: 117 +Content-length: 117 + +K 7 +svn:log +V 14 +right update 1 +K 10 +svn:author +V 8 +tallsopp +K 8 +svn:date +V 27 +2009-11-12T20:29:53.059636Z +PROPS-END + +Node-path: branches/right/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2593 +Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba +Text-content-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10 +Content-length: 2593 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 12 +Prop-content-length: 116 +Content-length: 116 + +K 7 +svn:log +V 13 +Merge trunk 2 +K 10 +svn:author +V 8 +tallsopp +K 8 +svn:date +V 27 +2009-11-12T20:29:56.083003Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 54 +Content-length: 54 + +K 13 +svn:mergeinfo +V 19 +/branches/left:2-11 +PROPS-END + + diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 821be7ce8d..b49815d108 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -1088,4 +1088,170 @@ INPUT_END test_expect_success 'P: fail on blob mark in gitlink' ' test_must_fail git fast-import <input' +### +### series Q (notes) +### + +note1_data="Note for the first commit" +note2_data="Note for the second commit" +note3_data="Note for the third commit" + +test_tick +cat >input <<INPUT_END +blob +mark :2 +data <<EOF +$file2_data +EOF + +commit refs/heads/notes-test +mark :3 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +first (:3) +COMMIT + +M 644 :2 file2 + +blob +mark :4 +data $file4_len +$file4_data +commit refs/heads/notes-test +mark :5 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +second (:5) +COMMIT + +M 644 :4 file4 + +commit refs/heads/notes-test +mark :6 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +third (:6) +COMMIT + +M 644 inline file5 +data <<EOF +$file5_data +EOF + +M 755 inline file6 +data <<EOF +$file6_data +EOF + +blob +mark :7 +data <<EOF +$note1_data +EOF + +blob +mark :8 +data <<EOF +$note2_data +EOF + +commit refs/notes/foobar +mark :9 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +notes (:9) +COMMIT + +N :7 :3 +N :8 :5 +N inline :6 +data <<EOF +$note3_data +EOF + +INPUT_END +test_expect_success \ + 'Q: commit notes' \ + 'git fast-import <input && + git whatchanged notes-test' +test_expect_success \ + 'Q: verify pack' \ + 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done' + +commit1=$(git rev-parse notes-test~2) +commit2=$(git rev-parse notes-test^) +commit3=$(git rev-parse notes-test) + +cat >expect <<EOF +author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +first (:3) +EOF +test_expect_success \ + 'Q: verify first commit' \ + 'git cat-file commit notes-test~2 | sed 1d >actual && + test_cmp expect actual' + +cat >expect <<EOF +parent $commit1 +author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +second (:5) +EOF +test_expect_success \ + 'Q: verify second commit' \ + 'git cat-file commit notes-test^ | sed 1d >actual && + test_cmp expect actual' + +cat >expect <<EOF +parent $commit2 +author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +third (:6) +EOF +test_expect_success \ + 'Q: verify third commit' \ + 'git cat-file commit notes-test | sed 1d >actual && + test_cmp expect actual' + +cat >expect <<EOF +author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +notes (:9) +EOF +test_expect_success \ + 'Q: verify notes commit' \ + 'git cat-file commit refs/notes/foobar | sed 1d >actual && + test_cmp expect actual' + +cat >expect.unsorted <<EOF +100644 blob $commit1 +100644 blob $commit2 +100644 blob $commit3 +EOF +cat expect.unsorted | sort >expect +test_expect_success \ + 'Q: verify notes tree' \ + 'git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual' + +echo "$note1_data" >expect +test_expect_success \ + 'Q: verify note for first commit' \ + 'git cat-file blob refs/notes/foobar:$commit1 >actual && test_cmp expect actual' + +echo "$note2_data" >expect +test_expect_success \ + 'Q: verify note for second commit' \ + 'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual' + +echo "$note3_data" >expect +test_expect_success \ + 'Q: verify note for third commit' \ + 'git cat-file blob refs/notes/foobar:$commit3 >actual && test_cmp expect actual' + test_done diff --git a/t/t9501-gitweb-standalone-http-status.sh b/t/t9501-gitweb-standalone-http-status.sh index d0ff21d426..d0ff21d426 100644..100755 --- a/t/t9501-gitweb-standalone-http-status.sh +++ b/t/t9501-gitweb-standalone-http-status.sh diff --git a/t/test-lib.sh b/t/test-lib.sh index f2ca536472..ec3336aba5 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -30,7 +30,7 @@ TZ=UTC TERM=dumb export LANG LC_ALL PAGER TERM TZ EDITOR=: -VISUAL=: +unset VISUAL unset GIT_EDITOR unset AUTHOR_DATE unset AUTHOR_EMAIL @@ -58,7 +58,7 @@ GIT_MERGE_VERBOSITY=5 export GIT_MERGE_VERBOSITY export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME -export EDITOR VISUAL +export EDITOR GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u} # Protect ourselves from common misconfiguration to export @@ -207,8 +207,8 @@ trap 'die' EXIT test_set_editor () { FAKE_EDITOR="$1" export FAKE_EDITOR - VISUAL='"$FAKE_EDITOR"' - export VISUAL + EDITOR='"$FAKE_EDITOR"' + export EDITOR } test_tick () { diff --git a/templates/Makefile b/templates/Makefile index a12c6e214e..408f0137a8 100644 --- a/templates/Makefile +++ b/templates/Makefile @@ -50,4 +50,4 @@ clean: install: all $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(template_instdir_SQ)' (cd blt && $(TAR) cf - .) | \ - (cd '$(DESTDIR_SQ)$(template_instdir_SQ)' && umask 022 && $(TAR) xfo -) + (cd '$(DESTDIR_SQ)$(template_instdir_SQ)' && umask 022 && $(TAR) xof -) diff --git a/templates/hooks--post-receive.sample b/templates/hooks--post-receive.sample index 18d2e0f727..7a83e17ab5 100755 --- a/templates/hooks--post-receive.sample +++ b/templates/hooks--post-receive.sample @@ -9,7 +9,7 @@ # For example: # aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master # -# see contrib/hooks/ for an sample, or uncomment the next line and +# see contrib/hooks/ for a sample, or uncomment the next line and # rename the file to "post-receive". #. /usr/share/doc/git-core/contrib/hooks/post-receive-email diff --git a/templates/hooks--pre-commit.sample b/templates/hooks--pre-commit.sample index b11ad6a6fb..439eefda51 100755 --- a/templates/hooks--pre-commit.sample +++ b/templates/hooks--pre-commit.sample @@ -7,6 +7,14 @@ # # To enable this hook, rename this file to "pre-commit". +if git-rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + # If you want to allow non-ascii filenames set this variable to true. allownonascii=$(git config hooks.allownonascii) @@ -14,15 +22,18 @@ allownonascii=$(git config hooks.allownonascii) # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && - test "$(git diff --cached --name-only --diff-filter=A -z | + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test "$(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0')" then - echo "Error: Attempt to add a non-ascii filename." + echo "Error: Attempt to add a non-ascii file name." echo - echo "This can cause problems if you want to work together" - echo "with people on other platforms than you." + echo "This can cause problems if you want to work" + echo "with people on other platforms." echo - echo "To be portable it is adviseable to rename the file ..." + echo "To be portable it is advisable to rename the file ..." echo echo "If you know what you are doing you can disable this" echo "check using:" @@ -32,12 +43,4 @@ then exit 1 fi -if git-rev-parse --verify HEAD >/dev/null 2>&1 -then - against=HEAD -else - # Initial commit: diff against an empty tree object - against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 -fi - exec git diff-index --check --cached $against -- diff --git a/test-delta.c b/test-delta.c index 3d885ff37e..af40a3c49e 100644 --- a/test-delta.c +++ b/test-delta.c @@ -1,7 +1,7 @@ /* * test-delta.c: test code to exercise diff-delta.c and patch-delta.c * - * (C) 2005 Nicolas Pitre <nico@cam.org> + * (C) 2005 Nicolas Pitre <nico@fluxnic.net> * * This code is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as diff --git a/test-genrandom.c b/test-genrandom.c index 8ad276d062..b3c28d9a1c 100644 --- a/test-genrandom.c +++ b/test-genrandom.c @@ -4,8 +4,7 @@ * Copyright (C) 2007 by Nicolas Pitre, licensed under the GPL version 2. */ -#include <stdio.h> -#include <stdlib.h> +#include "git-compat-util.h" int main(int argc, char *argv[]) { diff --git a/test-parse-options.c b/test-parse-options.c index efa734b42e..acd1a2ba70 100644 --- a/test-parse-options.c +++ b/test-parse-options.c @@ -8,6 +8,7 @@ static int abbrev = 7; static int verbose = 0, dry_run = 0, quiet = 0; static char *string = NULL; static char *file = NULL; +static int ambiguous; static int length_callback(const struct option *opt, const char *arg, int unset) { @@ -59,6 +60,10 @@ int main(int argc, const char **argv) number_callback), { OPTION_BOOLEAN, '+', NULL, &boolean, NULL, "same as -b", PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH }, + { OPTION_BOOLEAN, 0, "ambiguous", &ambiguous, NULL, + "positive ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG }, + { OPTION_BOOLEAN, 0, "no-ambiguous", &ambiguous, NULL, + "negative ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG }, OPT_GROUP("Standard options"), OPT__ABBREV(&abbrev), OPT__VERBOSE(&verbose), diff --git a/thread-utils.c b/thread-utils.c index 55e7e2904e..4f9c829c2d 100644 --- a/thread-utils.c +++ b/thread-utils.c @@ -1,9 +1,6 @@ #include "cache.h" -#ifdef _WIN32 -# define WIN32_LEAN_AND_MEAN -# include <windows.h> -#elif defined(hpux) || defined(__hpux) || defined(_hpux) +#if defined(hpux) || defined(__hpux) || defined(_hpux) # include <sys/pstat.h> #endif diff --git a/transport-helper.c b/transport-helper.c new file mode 100644 index 0000000000..5078c7100f --- /dev/null +++ b/transport-helper.c @@ -0,0 +1,404 @@ +#include "cache.h" +#include "transport.h" +#include "quote.h" +#include "run-command.h" +#include "commit.h" +#include "diff.h" +#include "revision.h" +#include "quote.h" + +struct helper_data +{ + const char *name; + struct child_process *helper; + FILE *out; + unsigned fetch : 1, + option : 1, + push : 1; +}; + +static struct child_process *get_helper(struct transport *transport) +{ + struct helper_data *data = transport->data; + struct strbuf buf = STRBUF_INIT; + struct child_process *helper; + + if (data->helper) + return data->helper; + + helper = xcalloc(1, sizeof(*helper)); + helper->in = -1; + helper->out = -1; + helper->err = 0; + helper->argv = xcalloc(4, sizeof(*helper->argv)); + strbuf_addf(&buf, "remote-%s", data->name); + helper->argv[0] = strbuf_detach(&buf, NULL); + helper->argv[1] = transport->remote->name; + helper->argv[2] = transport->url; + helper->git_cmd = 1; + if (start_command(helper)) + die("Unable to run helper: git %s", helper->argv[0]); + data->helper = helper; + + write_str_in_full(helper->in, "capabilities\n"); + + data->out = xfdopen(helper->out, "r"); + while (1) { + if (strbuf_getline(&buf, data->out, '\n') == EOF) + exit(128); /* child died, message supplied already */ + + if (!*buf.buf) + break; + if (!strcmp(buf.buf, "fetch")) + data->fetch = 1; + if (!strcmp(buf.buf, "option")) + data->option = 1; + if (!strcmp(buf.buf, "push")) + data->push = 1; + } + return data->helper; +} + +static int disconnect_helper(struct transport *transport) +{ + struct helper_data *data = transport->data; + if (data->helper) { + write_str_in_full(data->helper->in, "\n"); + close(data->helper->in); + fclose(data->out); + finish_command(data->helper); + free((char *)data->helper->argv[0]); + free(data->helper->argv); + free(data->helper); + data->helper = NULL; + } + free(data); + return 0; +} + +static const char *unsupported_options[] = { + TRANS_OPT_UPLOADPACK, + TRANS_OPT_RECEIVEPACK, + TRANS_OPT_THIN, + TRANS_OPT_KEEP + }; +static const char *boolean_options[] = { + TRANS_OPT_THIN, + TRANS_OPT_KEEP, + TRANS_OPT_FOLLOWTAGS + }; + +static int set_helper_option(struct transport *transport, + const char *name, const char *value) +{ + struct helper_data *data = transport->data; + struct child_process *helper = get_helper(transport); + struct strbuf buf = STRBUF_INIT; + int i, ret, is_bool = 0; + + if (!data->option) + return 1; + + for (i = 0; i < ARRAY_SIZE(unsupported_options); i++) { + if (!strcmp(name, unsupported_options[i])) + return 1; + } + + for (i = 0; i < ARRAY_SIZE(boolean_options); i++) { + if (!strcmp(name, boolean_options[i])) { + is_bool = 1; + break; + } + } + + strbuf_addf(&buf, "option %s ", name); + if (is_bool) + strbuf_addstr(&buf, value ? "true" : "false"); + else + quote_c_style(value, &buf, NULL, 0); + strbuf_addch(&buf, '\n'); + + if (write_in_full(helper->in, buf.buf, buf.len) != buf.len) + die_errno("cannot send option to %s", data->name); + + strbuf_reset(&buf); + if (strbuf_getline(&buf, data->out, '\n') == EOF) + exit(128); /* child died, message supplied already */ + + if (!strcmp(buf.buf, "ok")) + ret = 0; + else if (!prefixcmp(buf.buf, "error")) { + ret = -1; + } else if (!strcmp(buf.buf, "unsupported")) + ret = 1; + else { + warning("%s unexpectedly said: '%s'", data->name, buf.buf); + ret = 1; + } + strbuf_release(&buf); + return ret; +} + +static void standard_options(struct transport *t) +{ + char buf[16]; + int n; + int v = t->verbose; + int no_progress = v < 0 || (!t->progress && !isatty(1)); + + set_helper_option(t, "progress", !no_progress ? "true" : "false"); + + n = snprintf(buf, sizeof(buf), "%d", v + 1); + if (n >= sizeof(buf)) + die("impossibly large verbosity value"); + set_helper_option(t, "verbosity", buf); +} + +static int fetch_with_fetch(struct transport *transport, + int nr_heads, const struct ref **to_fetch) +{ + struct helper_data *data = transport->data; + int i; + struct strbuf buf = STRBUF_INIT; + + standard_options(transport); + + for (i = 0; i < nr_heads; i++) { + const struct ref *posn = to_fetch[i]; + if (posn->status & REF_STATUS_UPTODATE) + continue; + + strbuf_addf(&buf, "fetch %s %s\n", + sha1_to_hex(posn->old_sha1), posn->name); + } + + strbuf_addch(&buf, '\n'); + if (write_in_full(data->helper->in, buf.buf, buf.len) != buf.len) + die_errno("cannot send fetch to %s", data->name); + + while (1) { + strbuf_reset(&buf); + if (strbuf_getline(&buf, data->out, '\n') == EOF) + exit(128); /* child died, message supplied already */ + + if (!prefixcmp(buf.buf, "lock ")) { + const char *name = buf.buf + 5; + if (transport->pack_lockfile) + warning("%s also locked %s", data->name, name); + else + transport->pack_lockfile = xstrdup(name); + } + else if (!buf.len) + break; + else + warning("%s unexpectedly said: '%s'", data->name, buf.buf); + } + strbuf_release(&buf); + return 0; +} + +static int fetch(struct transport *transport, + int nr_heads, const struct ref **to_fetch) +{ + struct helper_data *data = transport->data; + int i, count; + + count = 0; + for (i = 0; i < nr_heads; i++) + if (!(to_fetch[i]->status & REF_STATUS_UPTODATE)) + count++; + + if (!count) + return 0; + + if (data->fetch) + return fetch_with_fetch(transport, nr_heads, to_fetch); + + return -1; +} + +static int push_refs(struct transport *transport, + struct ref *remote_refs, int flags) +{ + int force_all = flags & TRANSPORT_PUSH_FORCE; + int mirror = flags & TRANSPORT_PUSH_MIRROR; + struct helper_data *data = transport->data; + struct strbuf buf = STRBUF_INIT; + struct child_process *helper; + struct ref *ref; + + if (!remote_refs) + return 0; + + helper = get_helper(transport); + if (!data->push) + return 1; + + for (ref = remote_refs; ref; ref = ref->next) { + if (ref->peer_ref) + hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); + else if (!mirror) + continue; + + ref->deletion = is_null_sha1(ref->new_sha1); + if (!ref->deletion && + !hashcmp(ref->old_sha1, ref->new_sha1)) { + ref->status = REF_STATUS_UPTODATE; + continue; + } + + if (force_all) + ref->force = 1; + + strbuf_addstr(&buf, "push "); + if (!ref->deletion) { + if (ref->force) + strbuf_addch(&buf, '+'); + if (ref->peer_ref) + strbuf_addstr(&buf, ref->peer_ref->name); + else + strbuf_addstr(&buf, sha1_to_hex(ref->new_sha1)); + } + strbuf_addch(&buf, ':'); + strbuf_addstr(&buf, ref->name); + strbuf_addch(&buf, '\n'); + } + if (buf.len == 0) + return 0; + + transport->verbose = flags & TRANSPORT_PUSH_VERBOSE ? 1 : 0; + standard_options(transport); + + if (flags & TRANSPORT_PUSH_DRY_RUN) { + if (set_helper_option(transport, "dry-run", "true") != 0) + die("helper %s does not support dry-run", data->name); + } + + strbuf_addch(&buf, '\n'); + if (write_in_full(helper->in, buf.buf, buf.len) != buf.len) + exit(128); + + ref = remote_refs; + while (1) { + char *refname, *msg; + int status; + + strbuf_reset(&buf); + if (strbuf_getline(&buf, data->out, '\n') == EOF) + exit(128); /* child died, message supplied already */ + if (!buf.len) + break; + + if (!prefixcmp(buf.buf, "ok ")) { + status = REF_STATUS_OK; + refname = buf.buf + 3; + } else if (!prefixcmp(buf.buf, "error ")) { + status = REF_STATUS_REMOTE_REJECT; + refname = buf.buf + 6; + } else + die("expected ok/error, helper said '%s'\n", buf.buf); + + msg = strchr(refname, ' '); + if (msg) { + struct strbuf msg_buf = STRBUF_INIT; + const char *end; + + *msg++ = '\0'; + if (!unquote_c_style(&msg_buf, msg, &end)) + msg = strbuf_detach(&msg_buf, NULL); + else + msg = xstrdup(msg); + strbuf_release(&msg_buf); + + if (!strcmp(msg, "no match")) { + status = REF_STATUS_NONE; + free(msg); + msg = NULL; + } + else if (!strcmp(msg, "up to date")) { + status = REF_STATUS_UPTODATE; + free(msg); + msg = NULL; + } + else if (!strcmp(msg, "non-fast forward")) { + status = REF_STATUS_REJECT_NONFASTFORWARD; + free(msg); + msg = NULL; + } + } + + if (ref) + ref = find_ref_by_name(ref, refname); + if (!ref) + ref = find_ref_by_name(remote_refs, refname); + if (!ref) { + warning("helper reported unexpected status of %s", refname); + continue; + } + + ref->status = status; + ref->remote_status = msg; + } + strbuf_release(&buf); + return 0; +} + +static struct ref *get_refs_list(struct transport *transport, int for_push) +{ + struct helper_data *data = transport->data; + struct child_process *helper; + struct ref *ret = NULL; + struct ref **tail = &ret; + struct ref *posn; + struct strbuf buf = STRBUF_INIT; + + helper = get_helper(transport); + + if (data->push && for_push) + write_str_in_full(helper->in, "list for-push\n"); + else + write_str_in_full(helper->in, "list\n"); + + while (1) { + char *eov, *eon; + if (strbuf_getline(&buf, data->out, '\n') == EOF) + exit(128); /* child died, message supplied already */ + + if (!*buf.buf) + break; + + eov = strchr(buf.buf, ' '); + if (!eov) + die("Malformed response in ref list: %s", buf.buf); + eon = strchr(eov + 1, ' '); + *eov = '\0'; + if (eon) + *eon = '\0'; + *tail = alloc_ref(eov + 1); + if (buf.buf[0] == '@') + (*tail)->symref = xstrdup(buf.buf + 1); + else if (buf.buf[0] != '?') + get_sha1_hex(buf.buf, (*tail)->old_sha1); + tail = &((*tail)->next); + } + strbuf_release(&buf); + + for (posn = ret; posn; posn = posn->next) + resolve_remote_symref(posn, ret); + + return ret; +} + +int transport_helper_init(struct transport *transport, const char *name) +{ + struct helper_data *data = xcalloc(sizeof(*data), 1); + data->name = name; + + transport->data = data; + transport->set_option = set_helper_option; + transport->get_refs_list = get_refs_list; + transport->fetch = fetch; + transport->push_refs = push_refs; + transport->disconnect = disconnect_helper; + return 0; +} diff --git a/transport.c b/transport.c index d6c35d91ce..7362ec09b2 100644 --- a/transport.c +++ b/transport.c @@ -1,9 +1,6 @@ #include "cache.h" #include "transport.h" #include "run-command.h" -#ifndef NO_CURL -#include "http.h" -#endif #include "pkt-line.h" #include "fetch-pack.h" #include "send-pack.h" @@ -352,164 +349,6 @@ static int rsync_transport_push(struct transport *transport, return result; } -/* Generic functions for using commit walkers */ - -#ifndef NO_CURL /* http fetch is the only user */ -static int fetch_objs_via_walker(struct transport *transport, - int nr_objs, const struct ref **to_fetch) -{ - char *dest = xstrdup(transport->url); - struct walker *walker = transport->data; - char **objs = xmalloc(nr_objs * sizeof(*objs)); - int i; - - walker->get_all = 1; - walker->get_tree = 1; - walker->get_history = 1; - walker->get_verbosely = transport->verbose >= 0; - walker->get_recover = 0; - - for (i = 0; i < nr_objs; i++) - objs[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1)); - - if (walker_fetch(walker, nr_objs, objs, NULL, NULL)) - die("Fetch failed."); - - for (i = 0; i < nr_objs; i++) - free(objs[i]); - free(objs); - free(dest); - return 0; -} -#endif /* NO_CURL */ - -static int disconnect_walker(struct transport *transport) -{ - struct walker *walker = transport->data; - if (walker) - walker_free(walker); - return 0; -} - -#ifndef NO_CURL -static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) -{ - const char **argv; - int argc; - - 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; - 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"; - argv[argc++] = transport->url; - while (refspec_nr--) - argv[argc++] = *refspec++; - argv[argc] = NULL; - return !!run_command_v_opt(argv, RUN_GIT_CMD); -} - -static struct ref *get_refs_via_curl(struct transport *transport, int for_push) -{ - struct strbuf buffer = STRBUF_INIT; - char *data, *start, *mid; - char *ref_name; - char *refs_url; - int i = 0; - int http_ret; - - struct ref *refs = NULL; - struct ref *ref = NULL; - struct ref *last_ref = NULL; - - struct walker *walker; - - if (for_push) - return NULL; - - if (!transport->data) - transport->data = get_http_walker(transport->url, - transport->remote); - - walker = transport->data; - - refs_url = xmalloc(strlen(transport->url) + 11); - sprintf(refs_url, "%s/info/refs", transport->url); - - http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); - switch (http_ret) { - case HTTP_OK: - break; - case HTTP_MISSING_TARGET: - die("%s not found: did you run git update-server-info on the" - " server?", refs_url); - default: - http_error(refs_url, http_ret); - die("HTTP request failed"); - } - - data = buffer.buf; - start = NULL; - mid = data; - while (i < buffer.len) { - if (!start) - start = &data[i]; - if (data[i] == '\t') - mid = &data[i]; - if (data[i] == '\n') { - data[i] = 0; - ref_name = mid + 1; - ref = xmalloc(sizeof(struct ref) + - strlen(ref_name) + 1); - memset(ref, 0, sizeof(struct ref)); - strcpy(ref->name, ref_name); - get_sha1_hex(start, ref->old_sha1); - if (!refs) - refs = ref; - if (last_ref) - last_ref->next = ref; - last_ref = ref; - start = NULL; - } - i++; - } - - strbuf_release(&buffer); - - ref = alloc_ref("HEAD"); - if (!walker->fetch_ref(walker, ref) && - !resolve_remote_symref(ref, refs)) { - ref->next = refs; - refs = ref; - } else { - free(ref); - } - - strbuf_release(&buffer); - free(refs_url); - return refs; -} - -static int fetch_objs_via_curl(struct transport *transport, - int nr_objs, const struct ref **to_fetch) -{ - if (!transport->data) - transport->data = get_http_walker(transport->url, - transport->remote); - return fetch_objs_via_walker(transport, nr_objs, to_fetch); -} - -#endif - struct bundle_transport_data { int fd; struct bundle_header header; @@ -800,7 +639,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i break; case REF_STATUS_REJECT_NONFASTFORWARD: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "non-fast forward", porcelain); + "non-fast-forward", porcelain); break; case REF_STATUS_REMOTE_REJECT: print_ref_status('!', "[remote rejected]", ref, @@ -892,6 +731,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re NULL); } + memset(&args, 0, sizeof(args)); args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR); args.force_update = !!(flags & TRANSPORT_PUSH_FORCE); args.use_thin_pack = data->thin; @@ -944,6 +784,9 @@ struct transport *transport_get(struct remote *remote, const char *url) { struct transport *ret = xcalloc(1, sizeof(*ret)); + if (!remote) + die("No remote provided to transport_get()"); + ret->remote = remote; ret->url = url; @@ -955,14 +798,10 @@ struct transport *transport_get(struct remote *remote, const char *url) } else if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://") || !prefixcmp(url, "ftp://")) { + transport_helper_init(ret, "curl"); #ifdef NO_CURL error("git was compiled without libcurl support."); -#else - ret->get_refs_list = get_refs_via_curl; - ret->fetch = fetch_objs_via_curl; - ret->push = curl_transport_push; #endif - ret->disconnect = disconnect_walker; } else if (is_local(url) && is_file(url)) { struct bundle_transport_data *data = xcalloc(1, sizeof(*data)); @@ -983,10 +822,10 @@ struct transport *transport_get(struct remote *remote, const char *url) data->thin = 1; data->conn = NULL; data->uploadpack = "git-upload-pack"; - if (remote && remote->uploadpack) + if (remote->uploadpack) data->uploadpack = remote->uploadpack; data->receivepack = "git-receive-pack"; - if (remote && remote->receivepack) + if (remote->receivepack) data->receivepack = remote->receivepack; } @@ -1003,8 +842,9 @@ int transport_set_option(struct transport *transport, int transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags, - int * nonfastforward) + int *nonfastforward) { + *nonfastforward = 0; verify_remote_names(refspec_nr, refspec); if (transport->push) diff --git a/transport.h b/transport.h index 171a01c7a3..e4e6177e26 100644 --- a/transport.h +++ b/transport.h @@ -25,7 +25,7 @@ struct transport { int (*disconnect)(struct transport *connection); char *pack_lockfile; - signed verbose : 2; + signed verbose : 3; /* Force progress even if the output is not a tty */ unsigned progress : 1; }; @@ -79,4 +79,7 @@ void transport_unlock_pack(struct transport *transport); int transport_disconnect(struct transport *transport); char *transport_anonymize_url(const char *url); +/* Transport methods defined outside transport.c */ +int transport_helper_init(struct transport *transport, const char *name); + #endif diff --git a/unpack-file.c b/unpack-file.c index ac9cbf7cd8..e9d8934691 100644 --- a/unpack-file.c +++ b/unpack-file.c @@ -28,7 +28,7 @@ int main(int argc, char **argv) git_extract_argv0_path(argv[0]); - if (argc != 2) + if (argc != 2 || !strcmp(argv[1], "-h")) usage("git unpack-file <sha1>"); if (get_sha1(argv[1], sha1)) die("Not a valid object name %s", argv[1]); diff --git a/unpack-trees.c b/unpack-trees.c index c424bab409..dd5999c356 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -895,7 +895,7 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o) * Two-way merge. * * The rule is to "carry forward" what is in the index without losing - * information across a "fast forward", favoring a successful merge + * information across a "fast-forward", favoring a successful merge * over a merge failure when it makes sense. For details of the * "carry forward" rule, please see <Documentation/git-read-tree.txt>. * diff --git a/upload-pack.c b/upload-pack.c index dacbc7614b..6bfb500eb4 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -32,12 +32,15 @@ static int no_progress, daemon_mode; static int shallow_nr; static struct object_array have_obj; static struct object_array want_obj; +static struct object_array extra_edge_obj; static unsigned int timeout; /* 0 for no sideband, * otherwise maximum packet size (up to 65520 bytes). */ static int use_sideband; static int debug_fd; +static int advertise_refs; +static int stateless_rpc; static void reset_timeout(void) { @@ -107,7 +110,7 @@ static int do_rev_list(int fd, void *create_full_pack) int i; struct rev_info revs; - pack_pipe = fdopen(fd, "w"); + pack_pipe = xfdopen(fd, "w"); init_revisions(&revs, NULL); revs.tag_objects = 1; revs.tree_objects = 1; @@ -135,14 +138,76 @@ static int do_rev_list(int fd, void *create_full_pack) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); mark_edges_uninteresting(revs.commits, &revs, show_edge); + if (use_thin_pack) + for (i = 0; i < extra_edge_obj.nr; i++) + fprintf(pack_pipe, "-%s\n", sha1_to_hex( + extra_edge_obj.objects[i].item->sha1)); traverse_commit_list(&revs, show_commit, show_object, NULL); fflush(pack_pipe); fclose(pack_pipe); return 0; } +static int feed_msg_to_hook(int fd, const char *fmt, ...) +{ + int cnt; + char buf[1024]; + va_list params; + + va_start(params, fmt); + cnt = vsprintf(buf, fmt, params); + va_end(params); + return write_in_full(fd, buf, cnt) != cnt; +} + +static int feed_obj_to_hook(const char *label, struct object_array *oa, int i, int fd) +{ + return feed_msg_to_hook(fd, "%s %s\n", label, + sha1_to_hex(oa->objects[i].item->sha1)); +} + +static int run_post_upload_pack_hook(size_t total, struct timeval *tv) +{ + const char *argv[2]; + struct child_process proc; + int err, i; + + argv[0] = "hooks/post-upload-pack"; + argv[1] = NULL; + + if (access(argv[0], X_OK) < 0) + return 0; + + memset(&proc, 0, sizeof(proc)); + proc.argv = argv; + proc.in = -1; + proc.stdout_to_stderr = 1; + err = start_command(&proc); + if (err) + return err; + for (i = 0; !err && i < want_obj.nr; i++) + err |= feed_obj_to_hook("want", &want_obj, i, proc.in); + for (i = 0; !err && i < have_obj.nr; i++) + err |= feed_obj_to_hook("have", &have_obj, i, proc.in); + if (!err) + err |= feed_msg_to_hook(proc.in, "time %ld.%06ld\n", + (long)tv->tv_sec, (long)tv->tv_usec); + if (!err) + err |= feed_msg_to_hook(proc.in, "size %ld\n", (long)total); + if (!err) + err |= feed_msg_to_hook(proc.in, "kind %s\n", + (nr_our_refs == want_obj.nr && !have_obj.nr) + ? "clone" : "fetch"); + if (close(proc.in)) + err = 1; + if (finish_command(&proc)) + err = 1; + return err; +} + static void create_pack_file(void) { + struct timeval start_tv, tv; struct async rev_list; struct child_process pack_objects; int create_full_pack = (nr_our_refs == want_obj.nr && !have_obj.nr); @@ -150,10 +215,12 @@ static void create_pack_file(void) char abort_msg[] = "aborting due to possible repository " "corruption on the remote side."; int buffered = -1; - ssize_t sz; + ssize_t sz, total_sz; const char *argv[10]; int arg = 0; + gettimeofday(&start_tv, NULL); + total_sz = 0; if (shallow_nr) { rev_list.proc = do_rev_list; rev_list.data = 0; @@ -190,7 +257,7 @@ static void create_pack_file(void) /* pass on revisions we (don't) want */ if (!shallow_nr) { - FILE *pipe_fd = fdopen(pack_objects.in, "w"); + FILE *pipe_fd = xfdopen(pack_objects.in, "w"); if (!create_full_pack) { int i; for (i = 0; i < want_obj.nr; i++) @@ -243,6 +310,23 @@ static void create_pack_file(void) } continue; } + if (0 <= pe && (pfd[pe].revents & (POLLIN|POLLHUP))) { + /* Status ready; we ship that in the side-band + * or dump to the standard error. + */ + sz = xread(pack_objects.err, progress, + sizeof(progress)); + if (0 < sz) + send_client_data(2, progress, sz); + else if (sz == 0) { + close(pack_objects.err); + pack_objects.err = -1; + } + else + goto fail; + /* give priority to status messages */ + continue; + } if (0 <= pu && (pfd[pu].revents & (POLLIN|POLLHUP))) { /* Data ready; we keep the last byte to ourselves * in case we detect broken rev-list, so that we @@ -262,7 +346,7 @@ static void create_pack_file(void) sz = xread(pack_objects.out, cp, sizeof(data) - outsz); if (0 < sz) - ; + total_sz += sz; else if (sz == 0) { close(pack_objects.out); pack_objects.out = -1; @@ -280,21 +364,6 @@ static void create_pack_file(void) if (sz < 0) goto fail; } - if (0 <= pe && (pfd[pe].revents & (POLLIN|POLLHUP))) { - /* Status ready; we ship that in the side-band - * or dump to the standard error. - */ - sz = xread(pack_objects.err, progress, - sizeof(progress)); - if (0 < sz) - send_client_data(2, progress, sz); - else if (sz == 0) { - close(pack_objects.err); - pack_objects.err = -1; - } - else - goto fail; - } } if (finish_command(&pack_objects)) { @@ -314,6 +383,16 @@ static void create_pack_file(void) } if (use_sideband) packet_flush(1); + + gettimeofday(&tv, NULL); + tv.tv_sec -= start_tv.tv_sec; + if (tv.tv_usec < start_tv.tv_usec) { + tv.tv_sec--; + tv.tv_usec += 1000000; + } + tv.tv_usec -= start_tv.tv_usec; + if (run_post_upload_pack_hook(total_sz, &tv)) + warning("post-upload-hook failed"); return; fail: @@ -423,7 +502,7 @@ static int get_common_commits(void) { static char line[1000]; unsigned char sha1[20]; - char hex[41], last_hex[41]; + char last_hex[41]; save_commit_buffer = 0; @@ -434,25 +513,30 @@ static int get_common_commits(void) if (!len) { if (have_obj.nr == 0 || multi_ack) packet_write(1, "NAK\n"); + if (stateless_rpc) + exit(0); continue; } strip(line, len); if (!prefixcmp(line, "have ")) { switch (got_sha1(line+5, sha1)) { case -1: /* they have what we do not */ - if (multi_ack && ok_to_give_up()) - packet_write(1, "ACK %s continue\n", - sha1_to_hex(sha1)); + if (multi_ack && ok_to_give_up()) { + const char *hex = sha1_to_hex(sha1); + if (multi_ack == 2) + packet_write(1, "ACK %s ready\n", hex); + else + packet_write(1, "ACK %s continue\n", hex); + } break; default: - memcpy(hex, sha1_to_hex(sha1), 41); - if (multi_ack) { - const char *msg = "ACK %s continue\n"; - packet_write(1, msg, hex); - memcpy(last_hex, hex, 41); - } + memcpy(last_hex, sha1_to_hex(sha1), 41); + if (multi_ack == 2) + packet_write(1, "ACK %s common\n", last_hex); + else if (multi_ack) + packet_write(1, "ACK %s continue\n", last_hex); else if (have_obj.nr == 1) - packet_write(1, "ACK %s\n", hex); + packet_write(1, "ACK %s\n", last_hex); break; } continue; @@ -478,7 +562,7 @@ static void receive_needs(void) shallow_nr = 0; if (debug_fd) - write_in_full(debug_fd, "#S\n", 3); + write_str_in_full(debug_fd, "#S\n"); for (;;) { struct object *o; unsigned char sha1_buf[20]; @@ -492,7 +576,6 @@ static void receive_needs(void) if (!prefixcmp(line, "shallow ")) { unsigned char sha1[20]; struct object *object; - use_thin_pack = 0; if (get_sha1(line + 8, sha1)) die("invalid shallow line: %s", line); object = parse_object(sha1); @@ -504,7 +587,6 @@ static void receive_needs(void) } if (!prefixcmp(line, "deepen ")) { char *end; - use_thin_pack = 0; depth = strtol(line + 7, &end, 0); if (end == line + 7 || depth <= 0) die("Invalid deepen: %s", line); @@ -514,7 +596,9 @@ static void receive_needs(void) get_sha1_hex(line+5, sha1_buf)) die("git upload-pack: protocol error, " "expected to get sha, not '%s'", line); - if (strstr(line+45, "multi_ack")) + if (strstr(line+45, "multi_ack_detailed")) + multi_ack = 2; + else if (strstr(line+45, "multi_ack")) multi_ack = 1; if (strstr(line+45, "thin-pack")) use_thin_pack = 1; @@ -546,7 +630,7 @@ static void receive_needs(void) } } if (debug_fd) - write_in_full(debug_fd, "#E\n", 3); + write_str_in_full(debug_fd, "#E\n"); if (!use_sideband && daemon_mode) no_progress = 1; @@ -587,6 +671,7 @@ static void receive_needs(void) NULL, &want_obj); parents = parents->next; } + add_object_array(object, NULL, &extra_edge_obj); } /* make sure commit traversal conforms to client */ register_shallow(object->sha1); @@ -607,7 +692,7 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo { static const char *capabilities = "multi_ack thin-pack side-band" " side-band-64k ofs-delta shallow no-progress" - " include-tag"; + " include-tag multi_ack_detailed"; struct object *o = parse_object(sha1); if (!o) @@ -631,12 +716,32 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo return 0; } +static int mark_our_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +{ + struct object *o = parse_object(sha1); + if (!o) + die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1)); + if (!(o->flags & OUR_REF)) { + o->flags |= OUR_REF; + nr_our_refs++; + } + return 0; +} + static void upload_pack(void) { - reset_timeout(); - head_ref(send_ref, NULL); - for_each_ref(send_ref, NULL); - packet_flush(1); + if (advertise_refs || !stateless_rpc) { + reset_timeout(); + head_ref(send_ref, NULL); + for_each_ref(send_ref, NULL); + packet_flush(1); + } else { + head_ref(mark_our_ref, NULL); + for_each_ref(mark_our_ref, NULL); + } + if (advertise_refs) + return; + receive_needs(); if (want_obj.nr) { get_common_commits(); @@ -658,6 +763,14 @@ int main(int argc, char **argv) if (arg[0] != '-') break; + if (!strcmp(arg, "--advertise-refs")) { + advertise_refs = 1; + continue; + } + if (!strcmp(arg, "--stateless-rpc")) { + stateless_rpc = 1; + continue; + } if (!strcmp(arg, "--strict")) { strict = 1; continue; @@ -12,9 +12,9 @@ static void report(const char *prefix, const char *err, va_list params) fprintf(stderr, "%s%s\n", prefix, msg); } -static NORETURN void usage_builtin(const char *err) +static NORETURN void usage_builtin(const char *err, va_list params) { - fprintf(stderr, "usage: %s\n", err); + report("usage: ", err, params); exit(129); } @@ -36,19 +36,28 @@ static void warn_builtin(const char *warn, va_list params) /* If we are in a dlopen()ed .so write to a global variable would segfault * (ugh), so keep things static. */ -static void (*usage_routine)(const char *err) NORETURN = usage_builtin; -static void (*die_routine)(const char *err, va_list params) NORETURN = die_builtin; +static NORETURN_PTR void (*usage_routine)(const char *err, va_list params) = usage_builtin; +static NORETURN_PTR void (*die_routine)(const char *err, va_list params) = die_builtin; static void (*error_routine)(const char *err, va_list params) = error_builtin; static void (*warn_routine)(const char *err, va_list params) = warn_builtin; -void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN) +void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params)) { die_routine = routine; } +void usagef(const char *err, ...) +{ + va_list params; + + va_start(params, err); + usage_routine(err, params); + va_end(params); +} + void usage(const char *err) { - usage_routine(err); + usagef("%s", err); } void die(const char *err, ...) @@ -1,4 +1,5 @@ #include "git-compat-util.h" +#include "strbuf.h" #include "utf8.h" /* This code is originally from http://www.cl.cam.ac.uk/~mgk25/ucs/ */ @@ -279,14 +280,22 @@ int is_utf8(const char *text) return 1; } -static void print_spaces(int count) +static inline void strbuf_write(struct strbuf *sb, const char *buf, int len) +{ + if (sb) + strbuf_insert(sb, sb->len, buf, len); + else + fwrite(buf, len, 1, stdout); +} + +static void print_spaces(struct strbuf *buf, int count) { static const char s[] = " "; while (count >= sizeof(s)) { - fwrite(s, sizeof(s) - 1, 1, stdout); + strbuf_write(buf, s, sizeof(s) - 1); count -= sizeof(s) - 1; } - fwrite(s, count, 1, stdout); + strbuf_write(buf, s, count); } /* @@ -295,11 +304,25 @@ static void print_spaces(int count) * If indent is negative, assume that already -indent columns have been * consumed (and no extra indent is necessary for the first line). */ -int print_wrapped_text(const char *text, int indent, int indent2, int width) +int strbuf_add_wrapped_text(struct strbuf *buf, + const char *text, int indent, int indent2, int width) { int w = indent, assume_utf8 = is_utf8(text); const char *bol = text, *space = NULL; + if (width <= 0) { + /* just indent */ + while (*text) { + const char *eol = strchrnul(text, '\n'); + if (*eol == '\n') + eol++; + print_spaces(buf, indent); + strbuf_write(buf, text, eol-text); + text = eol; + } + return 1; + } + if (indent < 0) { w = -indent; space = text; @@ -310,21 +333,35 @@ int print_wrapped_text(const char *text, int indent, int indent2, int width) if (!c || isspace(c)) { if (w < width || !space) { const char *start = bol; + if (!c && text == start) + return w; if (space) start = space; else - print_spaces(indent); - fwrite(start, text - start, 1, stdout); + print_spaces(buf, indent); + strbuf_write(buf, start, text - start); if (!c) return w; - else if (c == '\t') - w |= 0x07; space = text; + if (c == '\t') + w |= 0x07; + else if (c == '\n') { + space++; + if (*space == '\n') { + strbuf_write(buf, "\n", 1); + goto new_line; + } + else if (!isalnum(*space)) + goto new_line; + else + strbuf_write(buf, " ", 1); + } w++; text++; } else { - putchar('\n'); +new_line: + strbuf_write(buf, "\n", 1); text = bol = space + isspace(*space); space = NULL; w = indent = indent2; @@ -340,6 +377,11 @@ int print_wrapped_text(const char *text, int indent, int indent2, int width) } } +int print_wrapped_text(const char *text, int indent, int indent2, int width) +{ + return strbuf_add_wrapped_text(NULL, text, indent, indent2, width); +} + int is_encoding_utf8(const char *name) { if (!name) @@ -10,6 +10,8 @@ int is_utf8(const char *text); int is_encoding_utf8(const char *name); int print_wrapped_text(const char *text, int indent, int indent2, int len); +int strbuf_add_wrapped_text(struct strbuf *buf, + const char *text, int indent, int indent2, int width); #ifndef NO_ICONV char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding); @@ -8,6 +8,25 @@ static const char var_usage[] = "git var [-l | <variable>]"; +static const char *editor(int flag) +{ + const char *pgm = git_editor(); + + if (!pgm && flag & IDENT_ERROR_ON_NO_NAME) + die("Terminal is dumb, but EDITOR unset"); + + return pgm; +} + +static const char *pager(int flag) +{ + const char *pgm = git_pager(); + + if (!pgm) + pgm = "cat"; + return pgm; +} + struct git_var { const char *name; const char *(*read)(int); @@ -15,14 +34,19 @@ struct git_var { static struct git_var git_vars[] = { { "GIT_COMMITTER_IDENT", git_committer_info }, { "GIT_AUTHOR_IDENT", git_author_info }, + { "GIT_EDITOR", editor }, + { "GIT_PAGER", pager }, { "", NULL }, }; static void list_vars(void) { struct git_var *ptr; + const char *val; + for (ptr = git_vars; ptr->read; ptr++) - printf("%s=%s\n", ptr->name, ptr->read(IDENT_WARN_ON_NO_NAME)); + if ((val = ptr->read(0))) + printf("%s=%s\n", ptr->name, val); } static const char *read_var(const char *var) @@ -16,6 +16,8 @@ static struct whitespace_rule { { "space-before-tab", WS_SPACE_BEFORE_TAB, 0 }, { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB, 0 }, { "cr-at-eol", WS_CR_AT_EOL, 1 }, + { "blank-at-eol", WS_BLANK_AT_EOL, 0 }, + { "blank-at-eof", WS_BLANK_AT_EOF, 0 }, }; unsigned parse_whitespace_rule(const char *string) @@ -102,8 +104,17 @@ unsigned whitespace_rule(const char *pathname) char *whitespace_error_string(unsigned ws) { struct strbuf err = STRBUF_INIT; - if (ws & WS_TRAILING_SPACE) + if ((ws & WS_TRAILING_SPACE) == WS_TRAILING_SPACE) strbuf_addstr(&err, "trailing whitespace"); + else { + if (ws & WS_BLANK_AT_EOL) + strbuf_addstr(&err, "trailing whitespace"); + if (ws & WS_BLANK_AT_EOF) { + if (err.len) + strbuf_addstr(&err, ", "); + strbuf_addstr(&err, "new blank line at EOF"); + } + } if (ws & WS_SPACE_BEFORE_TAB) { if (err.len) strbuf_addstr(&err, ", "); @@ -141,11 +152,11 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule, } /* Check for trailing whitespace. */ - if (ws_rule & WS_TRAILING_SPACE) { + if (ws_rule & WS_BLANK_AT_EOL) { for (i = len - 1; i >= 0; i--) { if (isspace(line[i])) { trailing_whitespace = i; - result |= WS_TRAILING_SPACE; + result |= WS_BLANK_AT_EOL; } else break; @@ -261,7 +272,7 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro /* * Strip trailing whitespace */ - if (ws_rule & WS_TRAILING_SPACE) { + if (ws_rule & WS_BLANK_AT_EOL) { if (0 < len && src[len - 1] == '\n') { add_nl_to_tail = 1; len--; diff --git a/wt-status.c b/wt-status.c index 85f3fcb8a2..38eb24536b 100644 --- a/wt-status.c +++ b/wt-status.c @@ -48,6 +48,8 @@ static void wt_status_print_unmerged_header(struct wt_status *s) { const char *c = color(WT_STATUS_HEADER, s); color_fprintf_ln(s->fp, c, "# Unmerged paths:"); + if (!advice_status_hints) + return; if (!s->is_initial) color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); else @@ -60,6 +62,8 @@ static void wt_status_print_cached_header(struct wt_status *s) { const char *c = color(WT_STATUS_HEADER, s); color_fprintf_ln(s->fp, c, "# Changes to be committed:"); + if (!advice_status_hints) + return; if (!s->is_initial) { color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); } else { @@ -73,6 +77,8 @@ static void wt_status_print_dirty_header(struct wt_status *s, { const char *c = color(WT_STATUS_HEADER, s); color_fprintf_ln(s->fp, c, "# Changed but not updated:"); + if (!advice_status_hints) + return; if (!has_deleted) color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to update what will be committed)"); else @@ -85,6 +91,8 @@ static void wt_status_print_untracked_header(struct wt_status *s) { const char *c = color(WT_STATUS_HEADER, s); color_fprintf_ln(s->fp, c, "# Untracked files:"); + if (!advice_status_hints) + return; color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to include in what will be committed)"); color_fprintf_ln(s->fp, c, "#"); } |