diff options
331 files changed, 22947 insertions, 2912 deletions
diff --git a/.gitattributes b/.gitattributes index 0636deea93..5e98806c6c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ * whitespace=!indent,trail,space *.[ch] whitespace=indent,trail,space +*.sh whitespace=indent,trail,space diff --git a/.gitignore b/.gitignore index f0d2e96b88..5d32289664 100644 --- a/.gitignore +++ b/.gitignore @@ -1,185 +1,188 @@ -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-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-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 +/bin-wrappers/ +/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 @@ -189,5 +192,5 @@ cscope* *.user *.idb *.pdb -Debug/ -Release/ +/Debug/ +/Release/ diff --git a/Documentation/Makefile b/Documentation/Makefile index 037220f544..4797b2dc35 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -17,6 +17,7 @@ DOC_HTML=$(MAN_HTML) ARTICLES = howto-index ARTICLES += everyday ARTICLES += git-tools +ARTICLES += git-bisect-lk2009 # with their own formatting rules. SP_ARTICLES = howto/revert-branch-rebase howto/using-merge-subtree user-manual API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt))) diff --git a/Documentation/RelNotes-1.6.5.6.txt b/Documentation/RelNotes-1.6.5.6.txt new file mode 100644 index 0000000000..a9eaf76f62 --- /dev/null +++ b/Documentation/RelNotes-1.6.5.6.txt @@ -0,0 +1,23 @@ +Git v1.6.5.6 Release Notes +========================== + +Fixes since v1.6.5.5 +-------------------- + + * "git add -p" had a regression since v1.6.5.3 that broke deletion of + non-empty files. + + * "git archive -o o.zip -- Makefile" produced an archive in o.zip + but in POSIX tar format. + + * Error message given to "git pull --rebase" when the user didn't give + enough clue as to what branch to integrate with still talked about + "merging with" the branch. + + * Error messages given by "git merge" when the merge resulted in a + fast-forward still were in plumbing lingo, even though in v1.6.5 + we reworded messages in other cases. + + * The post-upload-hook run by upload-pack in response to "git fetch" has + been removed, due to security concerns (the hook first appeared in + 1.6.5). diff --git a/Documentation/RelNotes-1.6.5.7.txt b/Documentation/RelNotes-1.6.5.7.txt new file mode 100644 index 0000000000..5b49ea53be --- /dev/null +++ b/Documentation/RelNotes-1.6.5.7.txt @@ -0,0 +1,19 @@ +Git v1.6.5.7 Release Notes +========================== + +Fixes since v1.6.5.6 +-------------------- + +* If a user specifies a color for a <slot> (i.e. a class of things to show + in a particular color) that is known only by newer versions of git + (e.g. "color.diff.func" was recently added for upcoming 1.6.6 release), + an older version of git should just ignore them. Instead we diagnosed + it as an error. + +* With help.autocorrect set to non-zero value, the logic to guess typoes + in the subcommand name misfired and ran a random nonsense command. + +* If a command is run with an absolute path as a pathspec inside a bare + repository, e.g. "rev-list HEAD -- /home", the code tried to run + strlen() on NULL, which is the result of get_git_work_tree(), and + segfaulted. diff --git a/Documentation/RelNotes-1.6.6.1.txt b/Documentation/RelNotes-1.6.6.1.txt new file mode 100644 index 0000000000..4c88bebb90 --- /dev/null +++ b/Documentation/RelNotes-1.6.6.1.txt @@ -0,0 +1,15 @@ +Git v1.6.6.1 Release Notes +========================== + +Fixes since v1.6.6 +------------------ + + * http-backend was not listed in the command list in the documentation. + +Other minor documentation updates are included. + +-- +exec >/var/tmp/1 +O=v1.6.6-4-gd828fdb +echo O=$(git describe maint) +git shortlog --no-merges $O..maint diff --git a/Documentation/RelNotes-1.6.6.txt b/Documentation/RelNotes-1.6.6.txt new file mode 100644 index 0000000000..04e205c457 --- /dev/null +++ b/Documentation/RelNotes-1.6.6.txt @@ -0,0 +1,224 @@ +Git v1.6.6 Release Notes +======================== + +Notes on behaviour change +------------------------- + + * 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. + +Preparing yourselves for compatibility issues in 1.7.0 +------------------------------------------------------ + +In git 1.7.0, which is planned to be the release after 1.6.6, there will +be a handful of behaviour changes that will break backward compatibility. + +These changes were discussed long time ago and existing behaviours have +been identified as more problematic to the userbase than keeping them for +the sake of backward compatibility. + +When necessary, a transition strategy for existing users has been designed +not to force them running around setting configuration variables and +updating their scripts in order to either keep the traditional behaviour +or adjust to the new behaviour, on the day their sysadmin decides to install +the new version of git. When we switched from "git-foo" to "git foo" in +1.6.0, even though the change had been advertised and the transition +guide had been provided for a very long time, the users procrastinated +during the entire transtion period, and ended up panicking on the day +their sysadmins updated their git installation. We are trying to avoid +repeating that unpleasantness in the 1.7.0 release. + +For changes decided to be in 1.7.0, commands that will be affected +have been much louder to strongly discourage such procrastination, and +they continue to be in this release. If you have been using recent +versions of git, you would have seen warnings issued when you used +features whose behaviour will change, with a clear instruction on how +to keep the existing behaviour if you want to. You hopefully are +already well prepared. + +Of course, we have also been giving "this and that will change in +1.7.0; prepare yourselves" warnings in the release notes and +announcement messages for the past few releases. Let's see how well +users will fare this time. + + * "git push" into a branch that is currently checked out (i.e. pointed by + HEAD in a repository that is not bare) will be refused by default. + + Similarly, "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. + + Setting the configuration variables receive.denyCurrentBranch and + receive.denyDeleteCurrent to 'ignore' in the receiving repository + can be used to override these safety features. Versions of git + since 1.6.2 have issued a loud warning when you tried to do these + operations without setting the configuration, so repositories of + people who still need to be able to perform such a push should + already have been future proofed. + + 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 process that already took place so far. + + * "git send-email" will not make deep threads by default when sending a + patch series with more than two messages. All messages will be sent + as a reply to the first message, i.e. cover letter. Git 1.6.6 (this + release) will issue a warning about the upcoming default change, when + it uses the traditional "deep threading" behaviour as the built-in + default. To squelch the warning but still use the "deep threading" + behaviour, give --chain-reply-to option or set sendemail.chainreplyto + to true. + + It has been possible to configure send-email to send "shallow thread" + by setting sendemail.chainreplyto configuration variable to false. + The only thing 1.7.0 release will do is to change the default when + you haven't configured that variable. + + * "git status" will not be "git commit --dry-run". This change does not + affect you if you run the command without pathspec. + + Nobody sane found the current behaviour of "git status Makefile" useful + nor meaningful, and it confused users. "git commit --dry-run" has been + provided as a way to get the current behaviour of this command since + 1.6.5. + + * "git diff" traditionally treated various "ignore whitespace" options + only as a way to filter the patch output. "git diff --exit-code -b" + exited with non-zero status even if all changes were about changing the + ammount of whitespace and nothing else. and "git diff -b" showed the + "diff --git" header line for such a change without patch text. + + In 1.7.0, the "ignore whitespaces" will affect the semantics of the + diff operation itself. A change that does not affect anything but + whitespaces will be reported with zero exit status when run with + --exit-code, and there will not be "diff --git" header for such a + change. + + +Updates since v1.6.5 +-------------------- + +(subsystems) + + * various gitk updates including use of themed widgets under Tk 8.5, + Japanese translation, a fix to a bug when running "gui blame" from + a subdirectory, etc. + + * various git-gui updates including new translations, wm states fixes, + Tk bug workaround after quitting, improved heuristics to trigger gc, + etc. + + * various git-svn updates. + + * "git fetch" over http learned a new mode that is different from the + traditional "dumb commit walker". + +(portability) + + * imap-send can be built on mingw port. + +(performance) + + * "git diff -B" has smaller memory footprint. + +(usability, bells and whistles) + + * The object replace mechanism can be bypassed with --no-replace-objects + global option given to the "git" program. + + * In configuration files, a few variables that name paths can begin with ~/ + and ~username/ and they are expanded as expected. + + * "git subcmd -h" now shows short usage help for many more subcommands. + + * "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 commit -c/-C/--amend" can be told with a new "--reset-author" option + to ignore authorship information in the commit it is taking the message + from. + + * "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 diff" learned to honor diff.color.func configuration to paint + function name hint printed on the hunk header "@@ -j,k +l,m @@" line + in the specified color. + + * "git fetch" learned --all and --multiple options, to run fetch from + many repositories, and --prune option to remove remote tracking + branches that went stale. These make "git remote update" and "git + remote prune" less necessary (there is no plan to remove "remote + update" nor "remote prune", though). + + * "git fsck" by default checks the packfiles (i.e. "--full" is the + default); you can turn it off with "git fsck --no-full". + + * "git grep" can use -F (fixed strings) and -i (ignore case) together. + + * 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. + + * "git log" and "git rev-list" learned to take revs and pathspecs from + the standard input with the new "--stdin" option. + + * "--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 notes" command to annotate existing commits. + + * "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 send-email" can be told with "--envelope-sender=auto" to use the + same address as "From:" address as the envelope sender address. + + * "git send-email" will issue a warning when it defaults to the + --chain-reply-to behaviour without being told by the user and + instructs to prepare for the change of the default in 1.7.0 release. + + * In "git submodule add <repository> <path>", <path> is now optional and + inferred from <repository> the same way "git clone <repository>" does. + + * "git svn" learned to read SVN 1.5+ and SVK merge tickets. + + * "git svn" learned to recreate empty directories tracked only by SVN. + + * "gitweb" can optionally render its "blame" output incrementally (this + requires JavaScript on the client side). + + * Author names shown in gitweb output are links to search commits by the + author. + +Fixes since v1.6.5 +------------------ + +All of the fixes in v1.6.5.X maintenance series are included in this +release, unless otherwise noted. diff --git a/Documentation/RelNotes-1.7.0.txt b/Documentation/RelNotes-1.7.0.txt new file mode 100644 index 0000000000..d519d0612f --- /dev/null +++ b/Documentation/RelNotes-1.7.0.txt @@ -0,0 +1,64 @@ +Git v1.7.0 Release Notes +======================== + +Notes on behaviour change +------------------------- + + * "git push" into a branch that is currently checked out (i.e. pointed by + HEAD in a repository that is not bare) is refused by default. + + Similarly, "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. + + Setting the configuration variables receive.denyCurrentBranch and + receive.denyDeleteCurrent to 'ignore' in the receiving repository + can be used to override these safety features. + + * "git send-email" does not make deep threads by default when sending a + patch series with more than two messages. All messages will be sent + as a reply to the first message, i.e. cover letter. + + It has been possible to configure send-email to send "shallow thread" + by setting sendemail.chainreplyto configuration variable to false. The + only thing this release does is to change the default when you haven't + configured that variable. + + * "git status" is not "git commit --dry-run" anymore. This change does + not affect you if you run the command without pathspec. + + * "git diff" traditionally treated various "ignore whitespace" options + only as a way to filter the patch output. "git diff --exit-code -b" + exited with non-zero status even if all changes were about changing the + ammount of whitespace and nothing else. and "git diff -b" showed the + "diff --git" header line for such a change without patch text. + + In this release, the "ignore whitespaces" options affect the semantics + of the diff operation. A change that does not affect anything but + whitespaces is reported with zero exit status when run with + --exit-code, and there is no "diff --git" header for such a change. + + +Updates since v1.6.6 +-------------------- + +(subsystems) + +(portability) + +(performance) + +(usability, bells and whistles) + + +Fixes since v1.6.6 +------------------ + +All of the fixes in v1.6.6.X maintenance series are included in this +release, unless otherwise noted. + +-- +exec >/var/tmp/1 +O=v1.6.6-27-g648f407 +echo O=$(git describe master) +git shortlog --no-merges $O..master ^maint diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 76fc84d878..c686f8646b 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -280,6 +280,20 @@ people play with it without having to pick up and apply the patch to their trees themselves. ------------------------------------------------ +Know the status of your patch after submission + +* You can use Git itself to find out when your patch is merged in + master. 'git pull --rebase' will automatically skip already-applied + patches, and will let you know. This works only if you rebase on top + of the branch in which your patch has been merged (i.e. it will not + tell you if your patch is merged in pu if you rebase on top of + master). + +* Read the git mailing list, the maintainer regularly posts messages + entitled "What's cooking in git.git" and "What's in git.git" giving + the status of various proposed changes. + +------------------------------------------------ MUA specific hints Some of patches I receive or pick up from the list share common diff --git a/Documentation/config.txt b/Documentation/config.txt index 35e26972e9..23a965eed7 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -135,7 +135,11 @@ advice.*:: core.fileMode:: If false, the executable bit differences between the index and the working copy are ignored; useful on broken filesystems like FAT. - See linkgit:git-update-index[1]. True by default. + See linkgit:git-update-index[1]. ++ +The default is true, except linkgit:git-clone[1] or linkgit:git-init[1] +will probe and set core.fileMode false if appropriate when the +repository is created. core.ignoreCygwinFSTricks:: This option is only used by Cygwin implementation of Git. If false, @@ -148,6 +152,18 @@ core.ignoreCygwinFSTricks:: is true, in which case ignoreCygwinFSTricks is ignored as Cygwin's POSIX emulation is required to support core.filemode. +core.ignorecase:: + If true, this option enables various workarounds to enable + git to work better on filesystems that are not case sensitive, + like FAT. For example, if a directory listing finds + "makefile" when git expects "Makefile", git will assume + it is really the same file, and continue to remember it as + "Makefile". ++ +The default is false, except linkgit:git-clone[1] or linkgit:git-init[1] +will probe and set core.ignorecase true if appropriate when the repository +is created. + core.trustctime:: If false, the ctime differences between the index and the working copy are ignored; useful when the inode change time @@ -228,7 +244,11 @@ core.symlinks:: contain the link text. linkgit:git-update-index[1] and linkgit:git-add[1] will not change the recorded type to regular file. Useful on filesystems like FAT that do not support - symbolic links. True by default. + symbolic links. ++ +The default is true, except linkgit:git-clone[1] or linkgit:git-init[1] +will probe and set core.symlinks false if appropriate when the repository +is created. core.gitProxy:: A "proxy command" to execute (as 'command host port') instead @@ -277,17 +297,24 @@ false), while all other repositories are assumed to be bare (bare = true). core.worktree:: - Set the path to the working tree. The value will not be - used in combination with repositories found automatically in - a .git directory (i.e. $GIT_DIR is not set). + Set the path to the root of the work tree. This can be overridden by the GIT_WORK_TREE environment variable and the '--work-tree' command line option. It can be - a absolute path or relative path to the directory specified by - --git-dir or GIT_DIR. - Note: If --git-dir or GIT_DIR are specified but none of + an absolute path or a relative path to the .git directory, + either specified by --git-dir or GIT_DIR, or automatically + discovered. + If --git-dir or GIT_DIR are specified but none of --work-tree, GIT_WORK_TREE and core.worktree is specified, - the current working directory is regarded as the top directory - of your working tree. + the current working directory is regarded as the root of the + work tree. ++ +Note that this variable is honored even when set in a configuration +file in a ".git" subdirectory of a directory, and its value differs +from the latter directory (e.g. "/path/to/.git/config" has +core.worktree set to "/different/path"), which is most likely a +misconfiguration. Running git commands in "/path/to" directory will +still use "/different/path" as the root of the work tree and can cause +great confusion to the users. core.logAllRefUpdates:: Enable the reflog. Updates to a ref <ref> is logged to the file @@ -393,9 +420,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 @@ -464,6 +489,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' @@ -604,10 +642,10 @@ color.diff.<slot>:: Use customized color for diff colorization. `<slot>` specifies which part of the patch to use the specified color, and is one of `plain` (context text), `meta` (metainformation), `frag` - (hunk header), `old` (removed lines), `new` (added lines), - `commit` (commit headers), or `whitespace` (highlighting - whitespace errors). The values of these variables may be specified as - in color.branch.<slot>. + (hunk header), 'func' (function in hunk header), `old` (removed lines), + `new` (added lines), `commit` (commit headers), or `whitespace` + (highlighting whitespace errors). The values of these variables may be + specified as in color.branch.<slot>. color.grep:: When set to `always`, always highlight matches. When `false` (or @@ -1101,6 +1139,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. @@ -1368,7 +1414,7 @@ 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. @@ -1402,7 +1448,13 @@ remote.<name>.mirror:: remote.<name>.skipDefaultUpdate:: If true, this remote will be skipped by default when updating - using the update subcommand of linkgit:git-remote[1]. + using linkgit:git-fetch[1] or the `update` subcommand of + linkgit:git-remote[1]. + +remote.<name>.skipFetchAll:: + If true, this remote will be skipped by default when updating + using linkgit:git-fetch[1] or the `update` subcommand of + linkgit:git-remote[1]. remote.<name>.receivepack:: The default program to execute on the remote side when pushing. See @@ -1416,6 +1468,10 @@ remote.<name>.tagopt:: Setting this value to \--no-tags disables automatic tag following when fetching from remote <name> +remote.<name>.vcs:: + Setting this to a value <vcs> will cause git to interact with + the remote with the git-remote-<vcs> helper. + remotes.<group>:: The list of remotes which are fetched by "git remote update <group>". See linkgit:git-remote[1]. diff --git a/Documentation/date-formats.txt b/Documentation/date-formats.txt new file mode 100644 index 0000000000..c000f08a9d --- /dev/null +++ b/Documentation/date-formats.txt @@ -0,0 +1,26 @@ +DATE FORMATS +------------ + +The GIT_AUTHOR_DATE, GIT_COMMITTER_DATE environment variables +ifdef::git-commit[] +and the `--date` option +endif::git-commit[] +support the following date formats: + +Git internal format:: + It is `<unix timestamp> <timezone offset>`, where `<unix + timestamp>` is the number of seconds since the UNIX epoch. + `<timezone offset>` is a positive or negative offset from UTC. + For example CET (which is 2 hours ahead UTC) is `+0200`. + +RFC 2822:: + The standard email format as described by RFC 2822, for example + `Thu, 07 Apr 2005 22:13:13 +0200`. + +ISO 8601:: + Time and date specified by the ISO 8601 standard, for example + `2005-04-07T22:13:13`. The parser accepts a space instead of the + `T` character as well. ++ +NOTE: In addition, the date part is accepted in the following formats: +`YYYY.MM.DD`, `MM/DD/YYYY` and `DD.MM.YYYY`. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 9276faeb11..8707d0e740 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,39 @@ 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 - output field terminator. Also output from commands such - as "git-log" will be delimited with NUL between commits. +ifdef::git-log[] + Separate the commits with NULs instead of with new newlines. ++ +Also, when `--raw` or `--numstat` has been given, do not munge +pathnames and use NULs as output field terminators. +endif::git-log[] +ifndef::git-log[] + When `--raw` or `--numstat` has been given, do not munge + pathnames and use NULs as output field terminators. +endif::git-log[] ++ +Without this option, each pathname output will have TAB, LF, double quotes, +and backslash characters replaced with `\t`, `\n`, `\"`, and `\\`, +respectively, and the pathname will be enclosed in double quotes if +any of those replacements occurred. --name-only:: Show only names of changed files. @@ -87,6 +110,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 +140,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 +160,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 +180,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 +192,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 +204,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 +218,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 +243,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 +268,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 28868747da..ab6419fe6e 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -1,3 +1,6 @@ +--all:: + Fetch all remotes. + -a:: --append:: Append ref names and object names of fetched refs to the @@ -9,6 +12,11 @@ `git clone` with `--depth=<depth>` option (see linkgit:git-clone[1]) by the specified number of commits. +ifndef::git-pull[] +--dry-run:: + Show what would be done, without making any changes. +endif::git-pull[] + -f:: --force:: When 'git-fetch' is used with `<rbranch>:<lbranch>` @@ -21,6 +29,16 @@ --keep:: Keep downloaded pack. +ifndef::git-pull[] +--multiple:: + Allow several <repository> and <group> arguments to be + specified. No <refspec>s may be specified. + +--prune:: + After fetching, remove any remote tracking branches which + no longer exist on the remote. +endif::git-pull[] + ifdef::git-pull[] --no-tags:: endif::git-pull[] diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 5ee8c91f2d..c2528a7654 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -3,7 +3,7 @@ git-apply(1) NAME ---- -git-apply - Apply a patch on a git index file and/or a working tree +git-apply - Apply a patch to files and/or to the index SYNOPSIS @@ -20,8 +20,11 @@ SYNOPSIS DESCRIPTION ----------- -Reads supplied 'diff' output and applies it on a git index file -and a work tree. +Reads the supplied diff output (i.e. "a patch") and applies it to files. +With the `--index` option the patch is also applied to the index, and +with the `--cache` option the patch is only applied to the index. +Without these options, the command applies the patch only to files, +and does not require them to be in a git repository. OPTIONS ------- @@ -34,7 +37,7 @@ OPTIONS input. Turns off "apply". --numstat:: - Similar to \--stat, but shows the number of added and + Similar to `--stat`, but shows the number of added and deleted lines in decimal notation and the pathname without abbreviation, to make it more machine friendly. For binary files, outputs two `-` instead of saying @@ -48,22 +51,22 @@ OPTIONS --check:: Instead of applying the patch, see if the patch is - applicable to the current work tree and/or the index + applicable to the current working tree and/or the index file and detects errors. Turns off "apply". --index:: - When --check is in effect, or when applying the patch + When `--check` is in effect, or when applying the patch (which is the default when none of the options that disables it is in effect), make sure the patch is applicable to what the current index file records. If - the file to be patched in the work tree is not + the file to be patched in the working tree is not up-to-date, it is flagged as an error. This flag also causes the index file to be updated. --cached:: Apply a patch without touching the working tree. Instead take the cached data, apply the patch, and store the result in the index - without using the working tree. This implies '--index'. + without using the working tree. This implies `--index`. --build-fake-ancestor=<file>:: Newer 'git-diff' output has embedded 'index information' @@ -87,11 +90,13 @@ the information is read from the current index instead. rejected hunks in corresponding *.rej files. -z:: - When showing the index information, do not munge paths, - but use NUL terminated machine readable format. Without - this flag, the pathnames output will have TAB, LF, and - backslash characters replaced with `\t`, `\n`, and `\\`, - respectively. + When `--numstat` has been given, do not munge pathnames, + but use a NUL-terminated machine-readable format. ++ +Without this option, each pathname output will have TAB, LF, double quotes, +and backslash characters replaced with `\t`, `\n`, `\"`, and `\\`, +respectively, and the pathname will be enclosed in double quotes if +any of those replacements occurred. -p<n>:: Remove <n> leading slashes from traditional diff paths. The @@ -107,8 +112,8 @@ the information is read from the current index instead. By default, 'git-apply' expects that the patch being applied is a unified diff with at least one line of context. This provides good safety measures, but breaks down when - applying a diff generated with --unified=0. To bypass these - checks use '--unidiff-zero'. + applying a diff generated with `--unified=0`. To bypass these + checks use `--unidiff-zero`. + Note, for the reasons stated above usage of context-free patches is discouraged. @@ -144,7 +149,7 @@ discouraged. be useful when importing patchsets, where you want to include certain files or directories. + -When --exclude and --include patterns are used, they are examined in the +When `--exclude` and `--include` patterns are used, they are examined in the order they appear on the command line, and the first match determines if a patch to each path is used. A patch to a path that does not match any include/exclude pattern is used by default if there is no include pattern @@ -227,13 +232,13 @@ Submodules If the patch contains any changes to submodules then 'git-apply' treats these changes as follows. -If --index is specified (explicitly or implicitly), then the submodule +If `--index` is specified (explicitly or implicitly), then the submodule commits must match the index exactly for the patch to apply. If any of the submodules are checked-out, then these check-outs are completely ignored, i.e., they are not required to be up-to-date or clean and they are not updated. -If --index is not specified, then the submodule commits in the patch +If `--index` is not specified, then the submodule commits in the patch are ignored and only the absence or presence of the corresponding subdirectory is checked and (if possible) updated. diff --git a/Documentation/git-bisect-lk2009.txt b/Documentation/git-bisect-lk2009.txt new file mode 100644 index 0000000000..6b7b2e5497 --- /dev/null +++ b/Documentation/git-bisect-lk2009.txt @@ -0,0 +1,1358 @@ +Fighting regressions with git bisect +==================================== +:Author: Christian Couder +:Email: chriscool@tuxfamily.org +:Date: 2009/11/08 + +Abstract +-------- + +"git bisect" enables software users and developers to easily find the +commit that introduced a regression. We show why it is important to +have good tools to fight regressions. We describe how "git bisect" +works from the outside and the algorithms it uses inside. Then we +explain how to take advantage of "git bisect" to improve current +practices. And we discuss how "git bisect" could improve in the +future. + + +Introduction to "git bisect" +---------------------------- + +Git is a Distributed Version Control system (DVCS) created by Linus +Torvalds and maintained by Junio Hamano. + +In Git like in many other Version Control Systems (VCS), the different +states of the data that is managed by the system are called +commits. And, as VCS are mostly used to manage software source code, +sometimes "interesting" changes of behavior in the software are +introduced in some commits. + +In fact people are specially interested in commits that introduce a +"bad" behavior, called a bug or a regression. They are interested in +these commits because a commit (hopefully) contains a very small set +of source code changes. And it's much easier to understand and +properly fix a problem when you only need to check a very small set of +changes, than when you don't know where look in the first place. + +So to help people find commits that introduce a "bad" behavior, the +"git bisect" set of commands was invented. And it follows of course +that in "git bisect" parlance, commits where the "interesting +behavior" is present are called "bad" commits, while other commits are +called "good" commits. And a commit that introduce the behavior we are +interested in is called a "first bad commit". Note that there could be +more than one "first bad commit" in the commit space we are searching. + +So "git bisect" is designed to help find a "first bad commit". And to +be as efficient as possible, it tries to perform a binary search. + + +Fighting regressions overview +----------------------------- + +Regressions: a big problem +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Regressions are a big problem in the software industry. But it's +difficult to put some real numbers behind that claim. + +There are some numbers about bugs in general, like a NIST study in +2002 <<1>> that said: + +_____________ +Software bugs, or errors, are so prevalent and so detrimental that +they cost the U.S. economy an estimated $59.5 billion annually, or +about 0.6 percent of the gross domestic product, according to a newly +released study commissioned by the Department of Commerce's National +Institute of Standards and Technology (NIST). At the national level, +over half of the costs are borne by software users and the remainder +by software developers/vendors. The study also found that, although +all errors cannot be removed, more than a third of these costs, or an +estimated $22.2 billion, could be eliminated by an improved testing +infrastructure that enables earlier and more effective identification +and removal of software defects. These are the savings associated with +finding an increased percentage (but not 100 percent) of errors closer +to the development stages in which they are introduced. Currently, +over half of all errors are not found until "downstream" in the +development process or during post-sale software use. +_____________ + +And then: + +_____________ +Software developers already spend approximately 80 percent of +development costs on identifying and correcting defects, and yet few +products of any type other than software are shipped with such high +levels of errors. +_____________ + +Eventually the conclusion started with: + +_____________ +The path to higher software quality is significantly improved software +testing. +_____________ + +There are other estimates saying that 80% of the cost related to +software is about maintenance <<2>>. + +Though, according to Wikipedia <<3>>: + +_____________ +A common perception of maintenance is that it is merely fixing +bugs. However, studies and surveys over the years have indicated that +the majority, over 80%, of the maintenance effort is used for +non-corrective actions (Pigosky 1997). This perception is perpetuated +by users submitting problem reports that in reality are functionality +enhancements to the system. +_____________ + +But we can guess that improving on existing software is very costly +because you have to watch out for regressions. At least this would +make the above studies consistent among themselves. + +Of course some kind of software is developed, then used during some +time without being improved on much, and then finally thrown away. In +this case, of course, regressions may not be a big problem. But on the +other hand, there is a lot of big software that is continually +developed and maintained during years or even tens of years by a lot +of people. And as there are often many people who depend (sometimes +critically) on such software, regressions are a really big problem. + +One such software is the linux kernel. And if we look at the linux +kernel, we can see that a lot of time and effort is spent to fight +regressions. The release cycle start with a 2 weeks long merge +window. Then the first release candidate (rc) version is tagged. And +after that about 7 or 8 more rc versions will appear with around one +week between each of them, before the final release. + +The time between the first rc release and the final release is +supposed to be used to test rc versions and fight bugs and especially +regressions. And this time is more than 80% of the release cycle +time. But this is not the end of the fight yet, as of course it +continues after the release. + +And then this is what Ingo Molnar (a well known linux kernel +developer) says about his use of git bisect: + +_____________ +I most actively use it during the merge window (when a lot of trees +get merged upstream and when the influx of bugs is the highest) - and +yes, there have been cases that i used it multiple times a day. My +average is roughly once a day. +_____________ + +So regressions are fought all the time by developers, and indeed it is +well known that bugs should be fixed as soon as possible, so as soon +as they are found. That's why it is interesting to have good tools for +this purpose. + +Other tools to fight regressions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +So what are the tools used to fight regressions? They are nearly the +same as those used to fight regular bugs. The only specific tools are +test suites and tools similar as "git bisect". + +Test suites are very nice. But when they are used alone, they are +supposed to be used so that all the tests are checked after each +commit. This means that they are not very efficient, because many +tests are run for no interesting result, and they suffer from +combinational explosion. + +In fact the problem is that big software often has many different +configuration options and that each test case should pass for each +configuration after each commit. So if you have for each release: N +configurations, M commits and T test cases, you should perform: + +------------- +N * M * T tests +------------- + +where N, M and T are all growing with the size your software. + +So very soon it will not be possible to completely test everything. + +And if some bugs slip through your test suite, then you can add a test +to your test suite. But if you want to use your new improved test +suite to find where the bug slipped in, then you will either have to +emulate a bisection process or you will perhaps bluntly test each +commit backward starting from the "bad" commit you have which may be +very wasteful. + +"git bisect" overview +--------------------- + +Starting a bisection +~~~~~~~~~~~~~~~~~~~~ + +The first "git bisect" subcommand to use is "git bisect start" to +start the search. Then bounds must be set to limit the commit +space. This is done usually by giving one "bad" and at least one +"good" commit. They can be passed in the initial call to "git bisect +start" like this: + +------------- +$ git bisect start [BAD [GOOD...]] +------------- + +or they can be set using: + +------------- +$ git bisect bad [COMMIT] +------------- + +and: + +------------- +$ git bisect good [COMMIT...] +------------- + +where BAD, GOOD and COMMIT are all names that can be resolved to a +commit. + +Then "git bisect" will checkout a commit of its choosing and ask the +user to test it, like this: + +------------- +$ git bisect start v2.6.27 v2.6.25 +Bisecting: 10928 revisions left to test after this (roughly 14 steps) +[2ec65f8b89ea003c27ff7723525a2ee335a2b393] x86: clean up using max_low_pfn on 32-bit +------------- + +Note that the example that we will use is really a toy example, we +will be looking for the first commit that has a version like +"2.6.26-something", that is the commit that has a "SUBLEVEL = 26" line +in the top level Makefile. This is a toy example because there are +better ways to find this commit with git than using "git bisect" (for +example "git blame" or "git log -S<string>"). + +Driving a bisection manually +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +At this point there are basically 2 ways to drive the search. It can +be driven manually by the user or it can be driven automatically by a +script or a command. + +If the user is driving it, then at each step of the search, the user +will have to test the current commit and say if it is "good" or "bad" +using the "git bisect good" or "git bisect bad" commands respectively +that have been described above. For example: + +------------- +$ git bisect bad +Bisecting: 5480 revisions left to test after this (roughly 13 steps) +[66c0b394f08fd89236515c1c84485ea712a157be] KVM: kill file->f_count abuse in kvm +------------- + +And after a few more steps like that, "git bisect" will eventually +find a first bad commit: + +------------- +$ git bisect bad +2ddcca36c8bcfa251724fe342c8327451988be0d is the first bad commit +commit 2ddcca36c8bcfa251724fe342c8327451988be0d +Author: Linus Torvalds <torvalds@linux-foundation.org> +Date: Sat May 3 11:59:44 2008 -0700 + + Linux 2.6.26-rc1 + +:100644 100644 5cf8258195331a4dbdddff08b8d68642638eea57 4492984efc09ab72ff6219a7bc21fb6a957c4cd5 M Makefile +------------- + +At this point we can see what the commit does, check it out (if it's +not already checked out) or tinker with it, for example: + +------------- +$ git show HEAD +commit 2ddcca36c8bcfa251724fe342c8327451988be0d +Author: Linus Torvalds <torvalds@linux-foundation.org> +Date: Sat May 3 11:59:44 2008 -0700 + + Linux 2.6.26-rc1 + +diff --git a/Makefile b/Makefile +index 5cf8258..4492984 100644 +--- a/Makefile ++++ b/Makefile +@@ -1,7 +1,7 @@ + VERSION = 2 + PATCHLEVEL = 6 +-SUBLEVEL = 25 +-EXTRAVERSION = ++SUBLEVEL = 26 ++EXTRAVERSION = -rc1 + NAME = Funky Weasel is Jiggy wit it + + # *DOCUMENTATION* +------------- + +And when we are finished we can use "git bisect reset" to go back to +the branch we were in before we started bisecting: + +------------- +$ git bisect reset +Checking out files: 100% (21549/21549), done. +Previous HEAD position was 2ddcca3... Linux 2.6.26-rc1 +Switched to branch 'master' +------------- + +Driving a bisection automatically +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The other way to drive the bisection process is to tell "git bisect" +to launch a script or command at each bisection step to know if the +current commit is "good" or "bad". To do that, we use the "git bisect +run" command. For example: + +------------- +$ git bisect start v2.6.27 v2.6.25 +Bisecting: 10928 revisions left to test after this (roughly 14 steps) +[2ec65f8b89ea003c27ff7723525a2ee335a2b393] x86: clean up using max_low_pfn on 32-bit +$ +$ git bisect run grep '^SUBLEVEL = 25' Makefile +running grep ^SUBLEVEL = 25 Makefile +Bisecting: 5480 revisions left to test after this (roughly 13 steps) +[66c0b394f08fd89236515c1c84485ea712a157be] KVM: kill file->f_count abuse in kvm +running grep ^SUBLEVEL = 25 Makefile +SUBLEVEL = 25 +Bisecting: 2740 revisions left to test after this (roughly 12 steps) +[671294719628f1671faefd4882764886f8ad08cb] V4L/DVB(7879): Adding cx18 Support for mxl5005s +... +... +running grep ^SUBLEVEL = 25 Makefile +Bisecting: 0 revisions left to test after this (roughly 0 steps) +[2ddcca36c8bcfa251724fe342c8327451988be0d] Linux 2.6.26-rc1 +running grep ^SUBLEVEL = 25 Makefile +2ddcca36c8bcfa251724fe342c8327451988be0d is the first bad commit +commit 2ddcca36c8bcfa251724fe342c8327451988be0d +Author: Linus Torvalds <torvalds@linux-foundation.org> +Date: Sat May 3 11:59:44 2008 -0700 + + Linux 2.6.26-rc1 + +:100644 100644 5cf8258195331a4dbdddff08b8d68642638eea57 4492984efc09ab72ff6219a7bc21fb6a957c4cd5 M Makefile +bisect run success +------------- + +In this example, we passed "grep '^SUBLEVEL = 25' Makefile" as +parameter to "git bisect run". This means that at each step, the grep +command we passed will be launched. And if it exits with code 0 (that +means success) then git bisect will mark the current state as +"good". If it exits with code 1 (or any code between 1 and 127 +included, except the special code 125), then the current state will be +marked as "bad". + +Exit code between 128 and 255 are special to "git bisect run". They +make it stop immediately the bisection process. This is useful for +example if the command passed takes too long to complete, because you +can kill it with a signal and it will stop the bisection process. + +It can also be useful in scripts passed to "git bisect run" to "exit +255" if some very abnormal situation is detected. + +Avoiding untestable commits +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes it happens that the current state cannot be tested, for +example if it does not compile because there was a bug preventing it +at that time. This is what the special exit code 125 is for. It tells +"git bisect run" that the current commit should be marked as +untestable and that another one should be chosen and checked out. + +If the bisection process is driven manually, you can use "git bisect +skip" to do the same thing. (In fact the special exit code 125 makes +"git bisect run" use "git bisect skip" in the background.) + +Or if you want more control, you can inspect the current state using +for example "git bisect visualize". It will launch gitk (or "git log" +if the DISPLAY environment variable is not set) to help you find a +better bisection point. + +Either way, if you have a string of untestable commits, it might +happen that the regression you are looking for has been introduced by +one of these untestable commits. In this case it's not possible to +tell for sure which commit introduced the regression. + +So if you used "git bisect skip" (or the run script exited with +special code 125) you could get a result like this: + +------------- +There are only 'skip'ped commits left to test. +The first bad commit could be any of: +15722f2fa328eaba97022898a305ffc8172db6b1 +78e86cf3e850bd755bb71831f42e200626fbd1e0 +e15b73ad3db9b48d7d1ade32f8cd23a751fe0ace +070eab2303024706f2924822bfec8b9847e4ac1b +We cannot bisect more! +------------- + +Saving a log and replaying it +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to show other people your bisection process, you can get a +log using for example: + +------------- +$ git bisect log > bisect_log.txt +------------- + +And it is possible to replay it using: + +------------- +$ git bisect replay bisect_log.txt +------------- + + +"git bisect" details +-------------------- + +Bisection algorithm +~~~~~~~~~~~~~~~~~~~ + +As the Git commits form a directed acyclic graph (DAG), finding the +best bisection commit to test at each step is not so simple. Anyway +Linus found and implemented a "truly stupid" algorithm, later improved +by Junio Hamano, that works quite well. + +So the algorithm used by "git bisect" to find the best bisection +commit when there are no skipped commits is the following: + +1) keep only the commits that: + +a) are ancestor of the "bad" commit (including the "bad" commit itself), +b) are not ancestor of a "good" commit (excluding the "good" commits). + +This means that we get rid of the uninteresting commits in the DAG. + +For example if we start with a graph like this: + +------------- +G-Y-G-W-W-W-X-X-X-X + \ / + W-W-B + / +Y---G-W---W + \ / \ +Y-Y X-X-X-X + +-> time goes this way -> +------------- + +where B is the "bad" commit, "G" are "good" commits and W, X, and Y +are other commits, we will get the following graph after this first +step: + +------------- +W-W-W + \ + W-W-B + / +W---W +------------- + +So only the W and B commits will be kept. Because commits X and Y will +have been removed by rules a) and b) respectively, and because commits +G are removed by rule b) too. + +Note for git users, that it is equivalent as keeping only the commit +given by: + +------------- +git rev-list BAD --not GOOD1 GOOD2... +------------- + +Also note that we don't require the commits that are kept to be +descendants of a "good" commit. So in the following example, commits W +and Z will be kept: + +------------- +G-W-W-W-B + / +Z-Z +------------- + +2) starting from the "good" ends of the graph, associate to each +commit the number of ancestors it has plus one + +For example with the following graph where H is the "bad" commit and A +and D are some parents of some "good" commits: + +------------- +A-B-C + \ + F-G-H + / +D---E +------------- + +this will give: + +------------- +1 2 3 +A-B-C + \6 7 8 + F-G-H +1 2/ +D---E +------------- + +3) associate to each commit: min(X, N - X) + +where X is the value associated to the commit in step 2) and N is the +total number of commits in the graph. + +In the above example we have N = 8, so this will give: + +------------- +1 2 3 +A-B-C + \2 1 0 + F-G-H +1 2/ +D---E +------------- + +4) the best bisection point is the commit with the highest associated +number + +So in the above example the best bisection point is commit C. + +5) note that some shortcuts are implemented to speed up the algorithm + +As we know N from the beginning, we know that min(X, N - X) can't be +greater than N/2. So during steps 2) and 3), if we would associate N/2 +to a commit, then we know this is the best bisection point. So in this +case we can just stop processing any other commit and return the +current commit. + +Bisection algorithm debugging +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For any commit graph, you can see the number associated with each +commit using "git rev-list --bisect-all". + +For example, for the above graph, a command like: + +------------- +$ git rev-list --bisect-all BAD --not GOOD1 GOOD2 +------------- + +would output something like: + +------------- +e15b73ad3db9b48d7d1ade32f8cd23a751fe0ace (dist=3) +15722f2fa328eaba97022898a305ffc8172db6b1 (dist=2) +78e86cf3e850bd755bb71831f42e200626fbd1e0 (dist=2) +a1939d9a142de972094af4dde9a544e577ddef0e (dist=2) +070eab2303024706f2924822bfec8b9847e4ac1b (dist=1) +a3864d4f32a3bf5ed177ddef598490a08760b70d (dist=1) +a41baa717dd74f1180abf55e9341bc7a0bb9d556 (dist=1) +9e622a6dad403b71c40979743bb9d5be17b16bd6 (dist=0) +------------- + +Bisection algorithm discussed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +First let's define "best bisection point". We will say that a commit X +is a best bisection point or a best bisection commit if knowing its +state ("good" or "bad") gives as much information as possible whether +the state of the commit happens to be "good" or "bad". + +This means that the best bisection commits are the commits where the +following function is maximum: + +------------- +f(X) = min(information_if_good(X), information_if_bad(X)) +------------- + +where information_if_good(X) is the information we get if X is good +and information_if_bad(X) is the information we get if X is bad. + +Now we will suppose that there is only one "first bad commit". This +means that all its descendants are "bad" and all the other commits are +"good". And we will suppose that all commits have an equal probability +of being good or bad, or of being the first bad commit, so knowing the +state of c commits gives always the same amount of information +wherever these c commits are on the graph and whatever c is. (So we +suppose that these commits being for example on a branch or near a +good or a bad commit does not give more or less information). + +Let's also suppose that we have a cleaned up graph like one after step +1) in the bisection algorithm above. This means that we can measure +the information we get in terms of number of commit we can remove from +the graph.. + +And let's take a commit X in the graph. + +If X is found to be "good", then we know that its ancestors are all +"good", so we want to say that: + +------------- +information_if_good(X) = number_of_ancestors(X) (TRUE) +------------- + +And this is true because at step 1) b) we remove the ancestors of the +"good" commits. + +If X is found to be "bad", then we know that its descendants are all +"bad", so we want to say that: + +------------- +information_if_bad(X) = number_of_descendants(X) (WRONG) +------------- + +But this is wrong because at step 1) a) we keep only the ancestors of +the bad commit. So we get more information when a commit is marked as +"bad", because we also know that the ancestors of the previous "bad" +commit that are not ancestors of the new "bad" commit are not the +first bad commit. We don't know if they are good or bad, but we know +that they are not the first bad commit because they are not ancestor +of the new "bad" commit. + +So when a commit is marked as "bad" we know we can remove all the +commits in the graph except those that are ancestors of the new "bad" +commit. This means that: + +------------- +information_if_bad(X) = N - number_of_ancestors(X) (TRUE) +------------- + +where N is the number of commits in the (cleaned up) graph. + +So in the end this means that to find the best bisection commits we +should maximize the function: + +------------- +f(X) = min(number_of_ancestors(X), N - number_of_ancestors(X)) +------------- + +And this is nice because at step 2) we compute number_of_ancestors(X) +and so at step 3) we compute f(X). + +Let's take the following graph as an example: + +------------- + G-H-I-J + / \ +A-B-C-D-E-F O + \ / + K-L-M-N +------------- + +If we compute the following non optimal function on it: + +------------- +g(X) = min(number_of_ancestors(X), number_of_descendants(X)) +------------- + +we get: + +------------- + 4 3 2 1 + G-H-I-J +1 2 3 4 5 6/ \0 +A-B-C-D-E-F O + \ / + K-L-M-N + 4 3 2 1 +------------- + +but with the algorithm used by git bisect we get: + +------------- + 7 7 6 5 + G-H-I-J +1 2 3 4 5 6/ \0 +A-B-C-D-E-F O + \ / + K-L-M-N + 7 7 6 5 +------------- + +So we chose G, H, K or L as the best bisection point, which is better +than F. Because if for example L is bad, then we will know not only +that L, M and N are bad but also that G, H, I and J are not the first +bad commit (since we suppose that there is only one first bad commit +and it must be an ancestor of L). + +So the current algorithm seems to be the best possible given what we +initially supposed. + +Skip algorithm +~~~~~~~~~~~~~~ + +When some commits have been skipped (using "git bisect skip"), then +the bisection algorithm is the same for step 1) to 3). But then we use +roughly the following steps: + +6) sort the commit by decreasing associated value + +7) if the first commit has not been skipped, we can return it and stop +here + +8) otherwise filter out all the skipped commits in the sorted list + +9) use a pseudo random number generator (PRNG) to generate a random +number between 0 and 1 + +10) multiply this random number with its square root to bias it toward +0 + +11) multiply the result by the number of commits in the filtered list +to get an index into this list + +12) return the commit at the computed index + +Skip algorithm discussed +~~~~~~~~~~~~~~~~~~~~~~~~ + +After step 7) (in the skip algorithm), we could check if the second +commit has been skipped and return it if it is not the case. And in +fact that was the algorithm we used from when "git bisect skip" was +developed in git version 1.5.4 (released on February 1st 2008) until +git version 1.6.4 (released July 29th 2009). + +But Ingo Molnar and H. Peter Anvin (another well known linux kernel +developer) both complained that sometimes the best bisection points +all happened to be in an area where all the commits are +untestable. And in this case the user was asked to test many +untestable commits, which could be very inefficient. + +Indeed untestable commits are often untestable because a breakage was +introduced at one time, and that breakage was fixed only after many +other commits were introduced. + +This breakage is of course most of the time unrelated to the breakage +we are trying to locate in the commit graph. But it prevents us to +know if the interesting "bad behavior" is present or not. + +So it is a fact that commits near an untestable commit have a high +probability of being untestable themselves. And the best bisection +commits are often found together too (due to the bisection algorithm). + +This is why it is a bad idea to just chose the next best unskipped +bisection commit when the first one has been skipped. + +We found that most commits on the graph may give quite a lot of +information when they are tested. And the commits that will not on +average give a lot of information are the one near the good and bad +commits. + +So using a PRNG with a bias to favor commits away from the good and +bad commits looked like a good choice. + +One obvious improvement to this algorithm would be to look for a +commit that has an associated value near the one of the best bisection +commit, and that is on another branch, before using the PRNG. Because +if such a commit exists, then it is not very likely to be untestable +too, so it will probably give more information than a nearly randomly +chosen one. + +Checking merge bases +~~~~~~~~~~~~~~~~~~~~ + +There is another tweak in the bisection algorithm that has not been +described in the "bisection algorithm" above. + +We supposed in the previous examples that the "good" commits were +ancestors of the "bad" commit. But this is not a requirement of "git +bisect". + +Of course the "bad" commit cannot be an ancestor of a "good" commit, +because the ancestors of the good commits are supposed to be +"good". And all the "good" commits must be related to the bad commit. +They cannot be on a branch that has no link with the branch of the +"bad" commit. But it is possible for a good commit to be related to a +bad commit and yet not be neither one of its ancestor nor one of its +descendants. + +For example, there can be a "main" branch, and a "dev" branch that was +forked of the main branch at a commit named "D" like this: + +------------- +A-B-C-D-E-F-G <--main + \ + H-I-J <--dev +------------- + +The commit "D" is called a "merge base" for branch "main" and "dev" +because it's the best common ancestor for these branches for a merge. + +Now let's suppose that commit J is bad and commit G is good and that +we apply the bisection algorithm like it has been previously +described. + +As described in step 1) b) of the bisection algorithm, we remove all +the ancestors of the good commits because they are supposed to be good +too. + +So we would be left with only: + +------------- +H-I-J +------------- + +But what happens if the first bad commit is "B" and if it has been +fixed in the "main" branch by commit "F"? + +The result of such a bisection would be that we would find that H is +the first bad commit, when in fact it's B. So that would be wrong! + +And yes it's can happen in practice that people working on one branch +are not aware that people working on another branch fixed a bug! It +could also happen that F fixed more than one bug or that it is a +revert of some big development effort that was not ready to be +released. + +In fact development teams often maintain both a development branch and +a maintenance branch, and it would be quite easy for them if "git +bisect" just worked when they want to bisect a regression on the +development branch that is not on the maintenance branch. They should +be able to start bisecting using: + +------------- +$ git bisect start dev main +------------- + +To enable that additional nice feature, when a bisection is started +and when some good commits are not ancestors of the bad commit, we +first compute the merge bases between the bad and the good commits and +we chose these merge bases as the first commits that will be checked +out and tested. + +If it happens that one merge base is bad, then the bisection process +is stopped with a message like: + +------------- +The merge base BBBBBB is bad. +This means the bug has been fixed between BBBBBB and [GGGGGG,...]. +------------- + +where BBBBBB is the sha1 hash of the bad merge base and [GGGGGG,...] +is a comma separated list of the sha1 of the good commits. + +If some of the merge bases are skipped, then the bisection process +continues, but the following message is printed for each skipped merge +base: + +------------- +Warning: the merge base between BBBBBB and [GGGGGG,...] must be skipped. +So we cannot be sure the first bad commit is between MMMMMM and BBBBBB. +We continue anyway. +------------- + +where BBBBBB is the sha1 hash of the bad commit, MMMMMM is the sha1 +hash of the merge base that is skipped and [GGGGGG,...] is a comma +separated list of the sha1 of the good commits. + +So if there is no bad merge base, the bisection process continues as +usual after this step. + +Best bisecting practices +------------------------ + +Using test suites and git bisect together +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you both have a test suite and use git bisect, then it becomes less +important to check that all tests pass after each commit. Though of +course it is probably a good idea to have some checks to avoid +breaking too many things because it could make bisecting other bugs +more difficult. + +You can focus your efforts to check at a few points (for example rc +and beta releases) that all the T test cases pass for all the N +configurations. And when some tests don't pass you can use "git +bisect" (or better "git bisect run"). So you should perform roughly: + +------------- +c * N * T + b * M * log2(M) tests +------------- + +where c is the number of rounds of test (so a small constant) and b is +the ratio of bug per commit (hopefully a small constant too). + +So of course it's much better as it's O(N \* T) vs O(N \* T \* M) if +you would test everything after each commit. + +This means that test suites are good to prevent some bugs from being +committed and they are also quite good to tell you that you have some +bugs. But they are not so good to tell you where some bugs have been +introduced. To tell you that efficiently, git bisect is needed. + +The other nice thing with test suites, is that when you have one, you +already know how to test for bad behavior. So you can use this +knowledge to create a new test case for "git bisect" when it appears +that there is a regression. So it will be easier to bisect the bug and +fix it. And then you can add the test case you just created to your +test suite. + +So if you know how to create test cases and how to bisect, you will be +subject to a virtuous circle: + +more tests => easier to create tests => easier to bisect => more tests + +So test suites and "git bisect" are complementary tools that are very +powerful and efficient when used together. + +Bisecting build failures +~~~~~~~~~~~~~~~~~~~~~~~~ + +You can very easily automatically bisect broken builds using something +like: + +------------- +$ git bisect start BAD GOOD +$ git bisect run make +------------- + +Passing sh -c "some commands" to "git bisect run" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For example: + +------------- +$ git bisect run sh -c "make || exit 125; ./my_app | grep 'good output'" +------------- + +On the other hand if you do this often, then it can be worth having +scripts to avoid too much typing. + +Finding performance regressions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Here is an example script that comes slightly modified from a real +world script used by Junio Hamano <<4>>. + +This script can be passed to "git bisect run" to find the commit that +introduced a performance regression: + +------------- +#!/bin/sh + +# Build errors are not what I am interested in. +make my_app || exit 255 + +# We are checking if it stops in a reasonable amount of time, so +# let it run in the background... + +./my_app >log 2>&1 & + +# ... and grab its process ID. +pid=$! + +# ... and then wait for sufficiently long. +sleep $NORMAL_TIME + +# ... and then see if the process is still there. +if kill -0 $pid +then + # It is still running -- that is bad. + kill $pid; sleep 1; kill $pid; + exit 1 +else + # It has already finished (the $pid process was no more), + # and we are happy. + exit 0 +fi +------------- + +Following general best practices +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is obviously a good idea not to have commits with changes that +knowingly break things, even if some other commits later fix the +breakage. + +It is also a good idea when using any VCS to have only one small +logical change in each commit. + +The smaller the changes in your commit, the most effective "git +bisect" will be. And you will probably need "git bisect" less in the +first place, as small changes are easier to review even if they are +only reviewed by the commiter. + +Another good idea is to have good commit messages. They can be very +helpful to understand why some changes were made. + +These general best practices are very helpful if you bisect often. + +Avoiding bug prone merges +~~~~~~~~~~~~~~~~~~~~~~~~~ + +First merges by themselves can introduce some regressions even when +the merge needs no source code conflict resolution. This is because a +semantic change can happen in one branch while the other branch is not +aware of it. + +For example one branch can change the semantic of a function while the +other branch add more calls to the same function. + +This is made much worse if many files have to be fixed to resolve +conflicts. That's why such merges are called "evil merges". They can +make regressions very difficult to track down. It can even be +misleading to know the first bad commit if it happens to be such a +merge, because people might think that the bug comes from bad conflict +resolution when it comes from a semantic change in one branch. + +Anyway "git rebase" can be used to linearize history. This can be used +either to avoid merging in the first place. Or it can be used to +bisect on a linear history instead of the non linear one, as this +should give more information in case of a semantic change in one +branch. + +Merges can be also made simpler by using smaller branches or by using +many topic branches instead of only long version related branches. + +And testing can be done more often in special integration branches +like linux-next for the linux kernel. + +Adapting your work-flow +~~~~~~~~~~~~~~~~~~~~~~~ + +A special work-flow to process regressions can give great results. + +Here is an example of a work-flow used by Andreas Ericsson: + +* write, in the test suite, a test script that exposes the regression +* use "git bisect run" to find the commit that introduced it +* fix the bug that is often made obvious by the previous step +* commit both the fix and the test script (and if needed more tests) + +And here is what Andreas said about this work-flow <<5>>: + +_____________ +To give some hard figures, we used to have an average report-to-fix +cycle of 142.6 hours (according to our somewhat weird bug-tracker +which just measures wall-clock time). Since we moved to git, we've +lowered that to 16.2 hours. Primarily because we can stay on top of +the bug fixing now, and because everyone's jockeying to get to fix +bugs (we're quite proud of how lazy we are to let git find the bugs +for us). Each new release results in ~40% fewer bugs (almost certainly +due to how we now feel about writing tests). +_____________ + +Clearly this work-flow uses the virtuous circle between test suites +and "git bisect". In fact it makes it the standard procedure to deal +with regression. + +In other messages Andreas says that they also use the "best practices" +described above: small logical commits, topic branches, no evil +merge,... These practices all improve the bisectability of the commit +graph, by making it easier and more useful to bisect. + +So a good work-flow should be designed around the above points. That +is making bisecting easier, more useful and standard. + +Involving QA people and if possible end users +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +One nice about "git bisect" is that it is not only a developer +tool. It can effectively be used by QA people or even end users (if +they have access to the source code or if they can get access to all +the builds). + +There was a discussion at one point on the linux kernel mailing list +of whether it was ok to always ask end user to bisect, and very good +points were made to support the point of view that it is ok. + +For example David Miller wrote <<6>>: + +_____________ +What people don't get is that this is a situation where the "end node +principle" applies. When you have limited resources (here: developers) +you don't push the bulk of the burden upon them. Instead you push +things out to the resource you have a lot of, the end nodes (here: +users), so that the situation actually scales. +_____________ + +This means that it is often "cheaper" if QA people or end users can do +it. + +What is interesting too is that end users that are reporting bugs (or +QA people that reproduced a bug) have access to the environment where +the bug happens. So they can often more easily reproduce a +regression. And if they can bisect, then more information will be +extracted from the environment where the bug happens, which means that +it will be easier to understand and then fix the bug. + +For open source projects it can be a good way to get more useful +contributions from end users, and to introduce them to QA and +development activities. + +Using complex scripts +~~~~~~~~~~~~~~~~~~~~~ + +In some cases like for kernel development it can be worth developing +complex scripts to be able to fully automate bisecting. + +Here is what Ingo Molnar says about that <<7>>: + +_____________ +i have a fully automated bootup-hang bisection script. It is based on +"git-bisect run". I run the script, it builds and boots kernels fully +automatically, and when the bootup fails (the script notices that via +the serial log, which it continuously watches - or via a timeout, if +the system does not come up within 10 minutes it's a "bad" kernel), +the script raises my attention via a beep and i power cycle the test +box. (yeah, i should make use of a managed power outlet to 100% +automate it) +_____________ + +Combining test suites, git bisect and other systems together +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We have seen that test suites an git bisect are very powerful when +used together. It can be even more powerful if you can combine them +with other systems. + +For example some test suites could be run automatically at night with +some unusual (or even random) configurations. And if a regression is +found by a test suite, then "git bisect" can be automatically +launched, and its result can be emailed to the author of the first bad +commit found by "git bisect", and perhaps other people too. And a new +entry in the bug tracking system could be automatically created too. + + +The future of bisecting +----------------------- + +"git replace" +~~~~~~~~~~~~~ + +We saw earlier that "git bisect skip" is now using a PRNG to try to +avoid areas in the commit graph where commits are untestable. The +problem is that sometimes the first bad commit will be in an +untestable area. + +To simplify the discussion we will suppose that the untestable area is +a simple string of commits and that it was created by a breakage +introduced by one commit (let's call it BBC for bisect breaking +commit) and later fixed by another one (let's call it BFC for bisect +fixing commit). + +For example: + +------------- +...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z-... +------------- + +where we know that Y is good and BFC is bad, and where BBC and X1 to +X6 are untestable. + +In this case if you are bisecting manually, what you can do is create +a special branch that starts just before the BBC. The first commit in +this branch should be the BBC with the BFC squashed into it. And the +other commits in the branch should be the commits between BBC and BFC +rebased on the first commit of the branch and then the commit after +BFC also rebased on. + +For example: + +------------- + (BBC+BFC)-X1'-X2'-X3'-X4'-X5'-X6'-Z' + / +...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z-... +------------- + +where commits quoted with ' have been rebased. + +You can easily create such a branch with Git using interactive rebase. + +For example using: + +------------- +$ git rebase -i Y Z +------------- + +and then moving BFC after BBC and squashing it. + +After that you can start bisecting as usual in the new branch and you +should eventually find the first bad commit. + +For example: + +------------- +$ git bisect start Z' Y +------------- + +If you are using "git bisect run", you can use the same manual fix up +as above, and then start another "git bisect run" in the special +branch. Or as the "git bisect" man page says, the script passed to +"git bisect run" can apply a patch before it compiles and test the +software <<8>>. The patch should turn a current untestable commits +into a testable one. So the testing will result in "good" or "bad" and +"git bisect" will be able to find the first bad commit. And the script +should not forget to remove the patch once the testing is done before +exiting from the script. + +(Note that instead of a patch you can use "git cherry-pick BFC" to +apply the fix, and in this case you should use "git reset --hard +HEAD^" to revert the cherry-pick after testing and before returning +from the script.) + +But the above ways to work around untestable areas are a little bit +clunky. Using special branches is nice because these branches can be +shared by developers like usual branches, but the risk is that people +will get many such branches. And it disrupts the normal "git bisect" +work-flow. So, if you want to use "git bisect run" completely +automatically, you have to add special code in your script to restart +bisection in the special branches. + +Anyway one can notice in the above special branch example that the Z' +and Z commits should point to the same source code state (the same +"tree" in git parlance). That's because Z' result from applying the +same changes as Z just in a slightly different order. + +So if we could just "replace" Z by Z' when we bisect, then we would +not need to add anything to a script. It would just work for anyone in +the project sharing the special branches and the replacements. + +With the example above that would give: + +------------- + (BBC+BFC)-X1'-X2'-X3'-X4'-X5'-X6'-Z'-... + / +...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z +------------- + +That's why the "git replace" command was created. Technically it +stores replacements "refs" in the "refs/replace/" hierarchy. These +"refs" are like branches (that are stored in "refs/heads/") or tags +(that are stored in "refs/tags"), and that means that they can +automatically be shared like branches or tags among developers. + +"git replace" is a very powerful mechanism. It can be used to fix +commits in already released history, for example to change the commit +message or the author. And it can also be used instead of git "grafts" +to link a repository with another old repository. + +In fact it's this last feature that "sold" it to the git community, so +it is now in the "master" branch of git's git repository and it should +be released in git 1.6.5 in October or November 2009. + +One problem with "git replace" is that currently it stores all the +replacements refs in "refs/replace/", but it would be perhaps better +if the replacement refs that are useful only for bisecting would be in +"refs/replace/bisect/". This way the replacement refs could be used +only for bisecting, while other refs directly in "refs/replace/" would +be used nearly all the time. + +Bisecting sporadic bugs +~~~~~~~~~~~~~~~~~~~~~~~ + +Another possible improvement to "git bisect" would be to optionally +add some redundancy to the tests performed so that it would be more +reliable when tracking sporadic bugs. + +This has been requested by some kernel developers because some bugs +called sporadic bugs do not appear in all the kernel builds because +they are very dependent on the compiler output. + +The idea is that every 3 test for example, "git bisect" could ask the +user to test a commit that has already been found to be "good" or +"bad" (because one of its descendants or one of its ancestors has been +found to be "good" or "bad" respectively). If it happens that a commit +has been previously incorrectly classified then the bisection can be +aborted early, hopefully before too many mistakes have been made. Then +the user will have to look at what happened and then restart the +bisection using a fixed bisect log. + +There is already a project called BBChop created by Ealdwulf Wuffinga +on Github that does something like that using Bayesian Search Theory +<<9>>: + +_____________ +BBChop is like 'git bisect' (or equivalent), but works when your bug +is intermittent. That is, it works in the presence of false negatives +(when a version happens to work this time even though it contains the +bug). It assumes that there are no false positives (in principle, the +same approach would work, but adding it may be non-trivial). +_____________ + +But BBChop is independent of any VCS and it would be easier for Git +users to have something integrated in Git. + +Conclusion +---------- + +We have seen that regressions are an important problem, and that "git +bisect" has nice features that complement very well practices and +other tools, especially test suites, that are generally used to fight +regressions. But it might be needed to change some work-flows and +(bad) habits to get the most out of it. + +Some improvements to the algorithms inside "git bisect" are possible +and some new features could help in some cases, but overall "git +bisect" works already very well, is used a lot, and is already very +useful. To back up that last claim, let's give the final word to Ingo +Molnar when he was asked by the author how much time does he think +"git bisect" saves him when he uses it: + +_____________ +a _lot_. + +About ten years ago did i do my first 'bisection' of a Linux patch +queue. That was prior the Git (and even prior the BitKeeper) days. I +literally days spent sorting out patches, creating what in essence +were standalone commits that i guessed to be related to that bug. + +It was a tool of absolute last resort. I'd rather spend days looking +at printk output than do a manual 'patch bisection'. + +With Git bisect it's a breeze: in the best case i can get a ~15 step +kernel bisection done in 20-30 minutes, in an automated way. Even with +manual help or when bisecting multiple, overlapping bugs, it's rarely +more than an hour. + +In fact it's invaluable because there are bugs i would never even +_try_ to debug if it wasn't for git bisect. In the past there were bug +patterns that were immediately hopeless for me to debug - at best i +could send the crash/bug signature to lkml and hope that someone else +can think of something. + +And even if a bisection fails today it tells us something valuable +about the bug: that it's non-deterministic - timing or kernel image +layout dependent. + +So git bisect is unconditional goodness - and feel free to quote that +;-) +_____________ + +Acknowledgements +---------------- + +Many thanks to Junio Hamano for his help in reviewing this paper, for +reviewing the patches I sent to the git mailing list, for discussing +some ideas and helping me improve them, for improving "git bisect" a +lot and for his awesome work in maintaining and developing Git. + +Many thanks to Ingo Molnar for giving me very useful information that +appears in this paper, for commenting on this paper, for his +suggestions to improve "git bisect" and for evangelizing "git bisect" +on the linux kernel mailing lists. + +Many thanks to Linus Torvalds for inventing, developing and +evangelizing "git bisect", Git and Linux. + +Many thanks to the many other great people who helped one way or +another when I worked on git, especially to Andreas Ericsson, Johannes +Schindelin, H. Peter Anvin, Daniel Barkalow, Bill Lear, John Hawley, +Shawn O. Pierce, Jeff King, Sam Vilain, Jon Seymour. + +Many thanks to the Linux-Kongress program committee for choosing the +author to given a talk and for publishing this paper. + +References +---------- + +- [[[1]]] http://www.nist.gov/public_affairs/releases/n02-10.htm['Software Errors Cost U.S. Economy $59.5 Billion Annually'. Nist News Release.] +- [[[2]]] http://java.sun.com/docs/codeconv/html/CodeConventions.doc.html#16712['Code Conventions for the Java Programming Language'. Sun Microsystems.] +- [[[3]]] http://en.wikipedia.org/wiki/Software_maintenance['Software maintenance'. Wikipedia.] +- [[[4]]] http://article.gmane.org/gmane.comp.version-control.git/45195/[Junio C Hamano. 'Automated bisect success story'. Gmane.] +- [[[5]]] http://lwn.net/Articles/317154/[Christian Couder. 'Fully automated bisecting with "git bisect run"'. LWN.net.] +- [[[6]]] http://lwn.net/Articles/277872/[Jonathan Corbet. 'Bisection divides users and developers'. LWN.net.] +- [[[7]]] http://article.gmane.org/gmane.linux.scsi/36652/[Ingo Molnar. 'Re: BUG 2.6.23-rc3 can't see sd partitions on Alpha'. Gmane.] +- [[[8]]] http://www.kernel.org/pub/software/scm/git/docs/git-bisect.html[Junio C Hamano and the git-list. 'git-bisect(1) Manual Page'. Linux Kernel Archives.] +- [[[9]]] http://github.com/Ealdwulf/bbchop[Ealdwulf. 'bbchop'. GitHub.] diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index 63e7a42cb3..c39d957c3a 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 ~~~~~~~~~~~~~~~~ @@ -319,6 +330,11 @@ Documentation ------------- Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. +SEE ALSO +-------- +link:git-bisect-lk2009.html[Fighting regressions with git bisect], +linkgit:git-blame[1]. + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index e9b3b40af4..0aeef24780 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -9,6 +9,7 @@ SYNOPSIS -------- [verse] 'git check-ref-format' <refname> +'git check-ref-format' --print <refname> 'git check-ref-format' --branch <branchname-shorthand> DESCRIPTION @@ -63,19 +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 `--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. + 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. -EXAMPLE -------- - -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-commit-tree.txt b/Documentation/git-commit-tree.txt index b8834baced..4fec5d5e38 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -73,6 +73,7 @@ A commit comment is read from stdin. If a changelog entry is not provided via "<" redirection, 'git-commit-tree' will just wait for one to be entered and terminated with ^D. +include::date-formats.txt[] Diagnostics ----------- diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 0578a40d84..5fb43f9320 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -9,9 +9,9 @@ SYNOPSIS -------- [verse] 'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run] - [(-c | -C) <commit>] [-F <file> | -m <msg>] + [(-c | -C) <commit>] [-F <file> | -m <msg>] [--reset-author] [--allow-empty] [--no-verify] [-e] [--author=<author>] - [--cleanup=<mode>] [--] [[-i | -o ]<file>...] + [--date=<date>] [--cleanup=<mode>] [--] [[-i | -o ]<file>...] DESCRIPTION ----------- @@ -69,6 +69,25 @@ OPTIONS Like '-C', but with '-c' the editor is invoked, so that the user can further edit the commit message. +--reset-author:: + When used with -C/-c/--amend options, declare that the + authorship of the resulting commit now belongs of the committer. + This also renews the author timestamp. + +--short:: + When doing a dry-run, give the output in the short-format. See + linkgit:git-status[1] for details. Implies `--dry-run`. + +--porcelain:: + When doing a dry-run, give the output in a porcelain-ready + format. See linkgit:git-status[1] for details. Implies + `--dry-run`. + +-z:: + When showing `short` or `porcelain` status output, terminate + entries in the status output with NUL, instead of LF. If no + format is given, implies the `--porcelain` output format. + -F <file>:: --file=<file>:: Take the commit message from the given file. Use '-' to @@ -80,6 +99,9 @@ OPTIONS an existing commit that matches the given string and its author name is used. +--date=<date>:: + Override the author date used in the commit. + -m <msg>:: --message=<msg>:: Use the given <msg> as the commit message. @@ -212,6 +234,8 @@ specified. these files are also staged for the next commit on top of what have been staged before. +:git-commit: 1 +include::date-formats.txt[] EXAMPLES -------- @@ -323,7 +347,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-cvsserver.txt b/Documentation/git-cvsserver.txt index 785779e221..99a7c14700 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -182,10 +182,9 @@ Database Backend ---------------- 'git-cvsserver' uses one database per git head (i.e. CVS module) to -store information about the repository for faster access. The -database doesn't contain any persistent data and can be completely -regenerated from the git repository at any time. The database -needs to be updated (i.e. written to) after every commit. +store information about the repository to maintain consistent +CVS revision numbers. The database needs to be +updated (i.e. written to) after every commit. If the commit is done directly by using `git` (as opposed to using 'git-cvsserver') the update will need to happen on the @@ -204,6 +203,18 @@ write so it might not be enough to grant the users using 'git-cvsserver' write access to the database file without granting them write access to the directory, too. +The database can not be reliably regenerated in a +consistent form after the branch it is tracking has changed. +Example: For merged branches, 'git-cvsserver' only tracks +one branch of development, and after a 'git-merge' an +incrementally updated database may track a different branch +than a database regenerated from scratch, causing inconsistent +CVS revision numbers. `git-cvsserver` has no way of knowing which +branch it would have picked if it had been run incrementally +pre-merge. So if you have to fully or partially (from old +backup) regenerate the database, you should be suspicious +of pre-existing CVS sandboxes. + You can configure the database backend with the following configuration variables: diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index 2f97916781..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 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..e6d364f53c 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -311,12 +311,12 @@ change to the project. .... 'commit' SP <ref> LF mark? - ('author' SP <name> SP LT <email> GT SP <when> LF)? - 'committer' SP <name> SP LT <email> GT SP <when> LF + ('author' (SP <name>)? SP LT <email> GT SP <when> LF)? + 'committer' (SP <name>)? SP LT <email> GT SP <when> LF 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 @@ -624,7 +657,7 @@ lightweight (non-annotated) tags see the `reset` command below. .... 'tag' SP <name> LF 'from' SP <committish> LF - 'tagger' SP <name> SP LT <email> GT SP <when> LF + 'tagger' (SP <name>)? SP LT <email> GT SP <when> LF data .... diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index f2483d624e..9b9e5686e4 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -10,11 +10,17 @@ SYNOPSIS -------- 'git fetch' <options> <repository> <refspec>... +'git fetch' <options> <group> + +'git fetch' --multiple <options> [<repository> | <group>]... + +'git fetch' --all <options> + DESCRIPTION ----------- -Fetches named heads or tags from another repository, along with -the objects necessary to complete them. +Fetches named heads or tags from one or more other repositories, +along with the objects necessary to complete them. The ref names and their object names of fetched refs are stored in `.git/FETCH_HEAD`. This information is left for a later merge @@ -28,6 +34,10 @@ pointed by remote tags that it does not yet have, then fetch those missing tags. If the other end has tags that point at branches you are not interested in, you will not get them. +'git fetch' can fetch from either a single named repository, or +or from several repositories at once if <group> is given and +there is a remotes.<group> entry in the configuration file. +(See linkgit:git-config[1]). OPTIONS ------- diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index 2b40babb6b..394a77a35f 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -159,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-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-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-mailinfo.txt b/Documentation/git-mailinfo.txt index 996c3fcc6c..b81ac98cf0 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] [--scissors] <msg> <patch> +'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors] <msg> <patch> DESCRIPTION @@ -32,6 +32,11 @@ OPTIONS munging, and is most useful when used to read back 'git-format-patch -k' output. +-b:: + When -k is not in effect, all leading strings bracketed with '[' + and ']' pairs are stripped. This option limits the stripping to + only the pairs whose bracketed string contains the word "PATCH". + -u:: The commit log message, author name and author email are taken from the e-mail, and after minimally decoding MIME 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-push.txt b/Documentation/git-push.txt index 37c88953d1..52c0538df5 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -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 @@ -176,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 @@ -347,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-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..ca5e1e8653 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -228,13 +228,23 @@ OPTIONS Use merging strategies to rebase. When the recursive (default) merge strategy is used, this allows rebase to be aware of renames on the upstream side. ++ +Note that a rebase merge works by replaying each commit from the working +branch on top of the <upstream> branch. Because of this, when a merge +conflict happens, the side reported as 'ours' is the so-far rebased +series, starting with <upstream>, and 'theirs' is the working branch. In +other words, the sides are swapped. -s <strategy>:: --strategy=<strategy>:: Use the given merge strategy. - 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). This implies --merge. + If there is no `-s` option 'git-merge-recursive' is used + instead. This implies --merge. ++ +Because 'git-rebase' replays each commit from the working branch +on top of the <upstream> branch using the given strategy, using +the 'ours' strategy simply discards all patches from the <branch>, +which makes little sense. -q:: --quiet:: @@ -368,14 +378,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 index 173ee232f2..5cfdc0cfc5 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -34,15 +34,62 @@ Commands are given by the caller on the helper's standard input, one per line. 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. Outputs a blank line when the fetch is - complete. Only objects which were reported in the ref list - with a sha1 may be fetched this way. + 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. + +'import' <name>:: + Produces a fast-import stream which imports the current value + of the named ref. It may additionally import other refs as + needed to construct the history efficiently. The script writes + to a helper-specific private namespace. The value of the named + ref should be written to a location in this namespace derived + by applying the refspecs from the "refspec" capability to the + name of the ref. ++ +Supported if the helper has the "import" 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 @@ -57,10 +104,66 @@ CAPABILITIES 'fetch':: This helper supports the 'fetch' command. +'option':: + This helper supports the option command. + +'push':: + This helper supports the 'push' command. + +'import':: + This helper supports the 'import' command. + +'refspec' 'spec':: + When using the import command, expect the source ref to have + been written to the destination ref. The earliest applicable + refspec takes precedence. For example + "refs/heads/*:refs/svn/origin/branches/*" means that, after an + "import refs/heads/name", the script has written to + refs/svn/origin/branches/name. If this capability is used at + all, it must cover all refs reported by the list command; if + it is not used, it is effectively "*:*" + REF LIST ATTRIBUTES ------------------- -None are defined yet, but the caller must accept any which are supplied. +'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. + +'unchanged':: + This ref is unchanged since the last import or fetch, although + the helper cannot necessarily determine what value that produced. + +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 ------------- diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt index 915cb77b29..65a0da508a 100644 --- a/Documentation/git-replace.txt +++ b/Documentation/git-replace.txt @@ -17,12 +17,36 @@ DESCRIPTION Adds a 'replace' reference in `.git/refs/replace/` The name of the 'replace' reference is the SHA1 of the object that is -replaced. The content of the replace reference is the SHA1 of the +replaced. The content of the 'replace' reference is the SHA1 of the replacement object. -Unless `-f` is given, the replace reference must not yet exist in +Unless `-f` is given, the 'replace' reference must not yet exist in `.git/refs/replace/` directory. +Replacement 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 references 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-objects cat-file commit foo +------------------------------------------------ + +shows information about commit 'foo', while: + +------------------------------------------------ +$ git cat-file commit foo +------------------------------------------------ + +shows information about commit 'bar'. + +The 'GIT_NO_REPLACE_OBJECTS' environment variable can be set to +achieve the same effect as the `--no-replace-objects` option. + OPTIONS ------- -f:: @@ -54,6 +78,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 469cf6dbac..2d27e405a3 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -150,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> ------------ + @@ -161,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..ced35b2f53 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 @@ -84,7 +84,7 @@ See the CONFIGURATION section for 'sendemail.multiedit'. --in-reply-to=<identifier>:: Specify the contents of the first In-Reply-To header. Subsequent emails will refer to the previous email - instead of this if --chain-reply-to is set (the default) + instead of this if --chain-reply-to is set. Only necessary if --compose is also set. If --compose is not set, this will be prompted for. @@ -108,9 +108,10 @@ Sending --envelope-sender=<address>:: Specify the envelope sender used to send the emails. This is useful if your default address is not the address that is - subscribed to a list. If you use the sendmail binary, you must have - suitable privileges for the -f parameter. Default is the value of - the 'sendemail.envelopesender' configuration variable; if that is + subscribed to a list. In order to use the 'From' address, set the + value to "auto". If you use the sendmail binary, you must have + suitable privileges for the -f parameter. Default is the value of the + 'sendemail.envelopesender' configuration variable; if that is unspecified, choosing the envelope sender is left to your MTA. --smtp-encryption=<encryption>:: @@ -171,8 +172,8 @@ Automating email sent. If disabled with "--no-chain-reply-to", all emails after the first will be sent as replies to the first email sent. When using this, it is recommended that the first file given be an overview of the - entire patch series. Default is the value of the 'sendemail.chainreplyto' - configuration value; if that is unspecified, default to --chain-reply-to. + entire patch series. Disabled by default, but the 'sendemail.chainreplyto' + configuration variable can be used to enable it. --identity=<identity>:: A configuration identity. When given, causes values in the 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 fafe728f89..3f14b727b8 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -78,8 +78,7 @@ stash@{1}: On master: 9cc0589... Add git-stash ---------------------------------------------------------------- + The command takes options applicable to the 'git-log' -command to control what is shown and how. If no options are set, the -default is `-n 10`. See linkgit:git-log[1]. +command to control what is shown and how. See linkgit:git-log[1]. show [<stash>]:: diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 84f60f3407..b3dfa42cc0 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -8,7 +8,7 @@ git-status - Show the working tree status SYNOPSIS -------- -'git status' <options>... +'git status' [<options>...] [--] [<pathspec>...] DESCRIPTION ----------- @@ -20,25 +20,90 @@ are what you _would_ commit by running `git commit`; the second and third are what you _could_ commit by running 'git-add' before running `git commit`. -The command takes the same set of options as 'git-commit'; it -shows what would be committed if the same options are given to -'git-commit'. - -If there is no path that is different between the index file and -the current HEAD commit (i.e., there is nothing to commit by running -`git commit`), the command exits with non-zero status. +OPTIONS +------- + +-s:: +--short:: + Give the output in the short-format. + +--porcelain:: + Give the output in a stable, easy-to-parse format for scripts. + Currently this is identical to --short output, but is guaranteed + not to change in the future, making it safe for scripts. + +-u[<mode>]:: +--untracked-files[=<mode>]:: + Show untracked files (Default: 'all'). ++ +The mode parameter is optional, and is used to specify +the handling of untracked files. The possible options are: ++ +-- + - 'no' - Show no untracked files + - 'normal' - Shows untracked files and directories + - 'all' - Also shows individual files in untracked directories. +-- ++ +See linkgit:git-config[1] for configuration variable +used to change the default for when the option is not +specified. + +-z:: + Terminate entries with NUL, instead of LF. This implies + the `--porcelain` output format if no other format is given. OUTPUT ------ The output from this command is designed to be used as a commit template comment, and all the output lines are prefixed with '#'. +The default, long format, is designed to be human readable, +verbose and descriptive. They are subject to change in any time. The paths mentioned in the output, unlike many other git commands, are made relative to the current directory if you are working in a subdirectory (this is on purpose, to help cutting and pasting). See the status.relativePaths config option below. +In short-format, the status of each path is shown as + + XY PATH1 -> PATH2 + +where `PATH1` is the path in the `HEAD`, and ` -> PATH2` part is +shown only when `PATH1` corresponds to a different path in the +index/worktree (i.e. renamed). + +For unmerged entries, `X` shows the status of stage #2 (i.e. ours) and `Y` +shows the status of stage #3 (i.e. theirs). + +For entries that do not have conflicts, `X` shows the status of the index, +and `Y` shows the status of the work tree. For untracked paths, `XY` are +`??`. + + X Y Meaning + ------------------------------------------------- + [MD] not updated + M [ MD] updated in index + A [ MD] added to index + D [ MD] deleted from index + R [ MD] renamed in index + C [ MD] copied in index + [MARC] index and work tree matches + [ MARC] M work tree changed since index + [ MARC] D deleted in work tree + ------------------------------------------------- + D D unmerged, both deleted + A U unmerged, added by us + U D unmerged, deleted by them + U A unmerged, added by them + D U unmerged, deleted by us + A A unmerged, both added + U U unmerged, both modified + ------------------------------------------------- + ? ? untracked + ------------------------------------------------- + CONFIGURATION ------------- @@ -53,9 +118,9 @@ paths shown are relative to the repository root, not to the current directory. If `status.submodulesummary` is set to a non zero number or true (identical -to -1 or an unlimited number), the submodule summary will be enabled and a -summary of commits for modified submodules will be shown (see --summary-limit -option of linkgit:git-submodule[1]). +to -1 or an unlimited number), the submodule summary will be enabled for +the long format and a summary of commits for modified submodules will be +shown (see --summary-limit option of linkgit:git-submodule[1]). SEE ALSO -------- @@ -63,8 +128,7 @@ linkgit:gitignore[5] Author ------ -Written by Linus Torvalds <torvalds@osdl.org> and -Junio C Hamano <gitster@pobox.com>. +Written by Junio C Hamano <gitster@pobox.com>. Documentation -------------- 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 63f3b5c742..b8e49dce4a 100644 --- a/Documentation/git-upload-pack.txt +++ b/Documentation/git-upload-pack.txt @@ -20,8 +20,6 @@ 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 8e93d35e44..352c23019f 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,16 @@ 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.5.5/git.html[documentation for release 1.6.5.5] +* link:v1.6.6/git.html[documentation for release 1.6.6] * release notes for + link:RelNotes-1.6.6.txt[1.6.6]. + +* link:v1.6.5.7/git.html[documentation for release 1.6.5.7] + +* release notes for + link:RelNotes-1.6.5.7.txt[1.6.5.7], + link:RelNotes-1.6.5.6.txt[1.6.5.6], link:RelNotes-1.6.5.5.txt[1.6.5.5], link:RelNotes-1.6.5.4.txt[1.6.5.4], link:RelNotes-1.6.5.3.txt[1.6.5.3], @@ -242,6 +249,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 1f472cea59..5a45e51890 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -197,6 +197,25 @@ intent is that if someone unsets the filter driver definition, or does not have the appropriate filter program, the project should still be usable. +For example, in .gitattributes, you would assign the `filter` +attribute for paths. + +------------------------ +*.c filter=indent +------------------------ + +Then you would define a "filter.indent.clean" and "filter.indent.smudge" +configuration in your .git/config to specify a pair of commands to +modify the contents of C programs when the source files are checked +in ("clean" is run) and checked out (no change is made because the +command is "cat"). + +------------------------ +[filter "indent"] + clean = indent + smudge = cat +------------------------ + Interaction between checkin/checkout attributes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index 0382d2c0ad..f762dca440 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. @@ -1186,9 +1186,9 @@ $ git show-branch * [master] Some fun. ! [mybranch] Some work. -- - + [mybranch] Some work. * [master] Some fun. -*+ [mybranch^] New day. + + [mybranch] Some work. +*+ [master^] 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 06e0f315c3..29eeae77ca 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -229,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 @@ -310,35 +310,6 @@ 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 ~~~~~~~~~~~ diff --git a/Documentation/gitworkflows.txt b/Documentation/gitworkflows.txt index 91c0eea890..065441df64 100644 --- a/Documentation/gitworkflows.txt +++ b/Documentation/gitworkflows.txt @@ -229,7 +229,7 @@ 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 +`git log master..maint` ===================================== This command should not list any commits. Otherwise, check out diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 43d84d15e9..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>>. 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/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 48d04a5d88..fec3394305 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -49,6 +49,11 @@ merge. With --no-squash perform the merge and commit the result. This option can be used to override --squash. +--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>:: Use the given merge strategy; can be supplied more than diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt index 4365b7e842..42910a3d5e 100644 --- a/Documentation/merge-strategies.txt +++ b/Documentation/merge-strategies.txt @@ -29,8 +29,9 @@ octopus:: pulling or merging more than one branch. ours:: - This resolves any number of heads, but the result of the - merge is always the current branch head. It is meant to + This resolves any number of heads, but the resulting tree of the + merge is always that of the current branch head, effectively + ignoring all changes from all other branches. It is meant to be used to supersede old development history of side branches. diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 2a845b1e57..53a9168ba7 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 @@ -131,6 +135,22 @@ The placeholders are: - '%m': left, right or boundary mark - '%n': newline - '%x00': print a byte from a hex code +- '%w([<w>[,<i1>[,<i2>]]])': switch line wrapping, like the -w option of + linkgit:git-shortlog[1]. + +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. + +If you add a `{plus}` (plus sign) after '%' of a placeholder, a line-feed +is inserted immediately before the expansion if and only if the +placeholder expands to a non-empty string. + +If you add a `-` (minus sign) after '%' of a placeholder, line-feeds that +immediately precede the expansion are deleted if and only if the +placeholder expands to an empty string. * 'tformat:' + 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..0551ebdfaf 100644 --- a/Documentation/pull-fetch-param.txt +++ b/Documentation/pull-fetch-param.txt @@ -4,6 +4,13 @@ (see the section <<URLS,GIT URLS>> below) or the name of a remote (see the section <<REMOTES,REMOTES>> below). +ifndef::git-pull[] +<group>:: + A name referring to a list of repositories as the value + of remotes.<group> in the configuration file. + (See linkgit:git-config[1]). +endif::git-pull[] + <refspec>:: The format of a <refspec> parameter is an optional plus `{plus}`, followed by the source ref <src>, followed @@ -11,9 +18,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/rev-list-options.txt b/Documentation/rev-list-options.txt index bf66116d61..1f57aed337 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -243,12 +243,23 @@ endif::git-rev-list[] Pretend as if all the refs in `$GIT_DIR/refs/remotes` are listed on the command line as '<commit>'. -ifdef::git-rev-list[] +ifndef::git-rev-list[] +--bisect:: + + Pretend as if the bad bisection ref `$GIT_DIR/refs/bisect/bad` + was listed and as if it was followed by `--not` and the good + bisection refs `$GIT_DIR/refs/bisect/good-*` on the command + line. +endif::git-rev-list[] + --stdin:: In addition to the '<commit>' listed on the command - line, read them from the standard input. + line, read them from the standard input. If a '--' separator is + seen, stop reading commits and start reading paths to limit the + result. +ifdef::git-rev-list[] --quiet:: Don't print anything to standard output. This form @@ -536,7 +547,11 @@ Bisection Helpers --bisect:: Limit output to the one commit object which is roughly halfway between -the included and excluded commits. Thus, if +included and excluded commits. Note that the bad bisection ref +`$GIT_DIR/refs/bisect/bad` is added to the included commits (if it +exists) and the good bisection refs `$GIT_DIR/refs/bisect/good-*` are +added to the excluded commits (if they exist). Thus, supposing there +are no refs in `$GIT_DIR/refs/bisect/`, if ----------------------------------------------------------------------- $ git rev-list --bisect foo ^bar ^baz @@ -556,22 +571,24 @@ one. --bisect-vars:: -This calculates the same as `--bisect`, but outputs text ready -to be eval'ed by the shell. These lines will assign the name of -the midpoint revision to the variable `bisect_rev`, and the -expected number of commits to be tested after `bisect_rev` is -tested to `bisect_nr`, the expected number of commits to be -tested if `bisect_rev` turns out to be good to `bisect_good`, -the expected number of commits to be tested if `bisect_rev` -turns out to be bad to `bisect_bad`, and the number of commits -we are bisecting right now to `bisect_all`. +This calculates the same as `--bisect`, except that refs in +`$GIT_DIR/refs/bisect/` are not used, and except that this outputs +text ready to be eval'ed by the shell. These lines will assign the +name of the midpoint revision to the variable `bisect_rev`, and the +expected number of commits to be tested after `bisect_rev` is tested +to `bisect_nr`, the expected number of commits to be tested if +`bisect_rev` turns out to be good to `bisect_good`, the expected +number of commits to be tested if `bisect_rev` turns out to be bad to +`bisect_bad`, and the number of commits we are bisecting right now to +`bisect_all`. --bisect-all:: This outputs all the commit objects between the included and excluded commits, ordered by their distance to the included and excluded -commits. The farthest from them is displayed first. (This is the only -one displayed by `--bisect`.) +commits. Refs in `$GIT_DIR/refs/bisect/` are not used. The farthest +from them is displayed first. (This is the only one displayed by +`--bisect`.) + This is useful because it makes it easy to choose a good commit to test when you want to avoid to test some of them for some reason (they diff --git a/Documentation/technical/api-hash.txt b/Documentation/technical/api-hash.txt index c784d3edcb..e5061e0677 100644 --- a/Documentation/technical/api-hash.txt +++ b/Documentation/technical/api-hash.txt @@ -1,6 +1,52 @@ hash API ======== -Talk about <hash.h> +The hash API is a collection of simple hash table functions. Users are expected +to implement their own hashing. -(Linus) +Data Structures +--------------- + +`struct hash_table`:: + + The hash table structure. The `array` member points to the hash table + entries. The `size` member counts the total number of valid and invalid + entries in the table. The `nr` member keeps track of the number of + valid entries. + +`struct hash_table_entry`:: + + An opaque structure representing an entry in the hash table. The `hash` + member is the entry's hash key and the `ptr` member is the entry's + value. + +Functions +--------- + +`init_hash`:: + + Initialize the hash table. + +`free_hash`:: + + Release memory associated with the hash table. + +`insert_hash`:: + + Insert a pointer into the hash table. If an entry with that hash + already exists, a pointer to the existing entry's value is returned. + Otherwise NULL is returned. This allows callers to implement + chaining, etc. + +`lookup_hash`:: + + Lookup an entry in the hash table. If an entry with that hash exists + the entry's value is returned. Otherwise NULL is returned. + +`for_each_hash`:: + + Call a function for each entry in the hash table. The function is + expected to take the entry's value as its only argument and return an + int. If the function returns a negative int the loop is aborted + immediately. Otherwise, the return value is accumulated and the sum + returned upon completion of the loop. diff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt index 7438149249..a0e0f850f8 100644 --- a/Documentation/technical/api-strbuf.txt +++ b/Documentation/technical/api-strbuf.txt @@ -12,7 +12,7 @@ strbuf API actually relies on the string being free of NULs. strbufs has some invariants that are very important to keep in mind: -. The `buf` member is never NULL, so you it can be used in any usual C +. The `buf` member is never NULL, so it can be used in any usual C string operations safely. strbuf's _have_ to be initialized either by `strbuf_init()` or by `= STRBUF_INIT` before the invariants, though. + @@ -55,7 +55,7 @@ Data structures * `struct strbuf` -This is string buffer structure. The `len` member can be used to +This is the string buffer structure. The `len` member can be used to determine the current length of the string, and `buf` member provides access to the string itself. @@ -253,3 +253,9 @@ same behaviour as well. comments are considered contents to be removed or not. `launch_editor`:: + + Launch the user preferred editor to edit a file and fill the buffer + with the file's contents upon the user completing their editing. The + third argument can be used to set the environment which the editor is + run in. If the buffer is NULL the editor is launched as usual but the + file's contents are not read into the buffer upon completion. 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/user-manual.txt b/Documentation/user-manual.txt index c32dd87c8b..b169836684 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -1400,7 +1400,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. @@ -1735,7 +1735,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, @@ -1959,7 +1959,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 @@ -1992,7 +1992,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: ------------------------------------------------- @@ -2131,7 +2131,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 @@ -2585,7 +2585,7 @@ them again with linkgit:git-am[1]. Other tools ----------- -There are numerous other tools, such as StGIT, which exist for the +There are numerous other tools, such as StGit, which exist for the purpose of maintaining a patch series. These are outside of the scope of this manual. @@ -2745,9 +2745,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 @@ -4291,7 +4291,7 @@ You see, Git is actually the best tool to find out about the source of Git itself! [[glossary]] -GIT Glossary +Git Glossary ============ include::glossary-content.txt[] diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 0c9be2080b..a7d8c63427 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.5.5 +DEF_VER=v1.6.6.GIT LF=' ' @@ -38,13 +38,17 @@ Issues of note: Interactive Tools package still can install "git", but you can build it with --disable-transition option to avoid this. - - You can use git after building but without installing if you - wanted to. Various git commands need to find other git - commands and scripts to do their work, so you would need to - arrange a few environment variables to tell them that their - friends will be found in your built source area instead of at - their standard installation area. Something like this works - for me: + - You can use git after building but without installing if you want + to test drive it. Simply run git found in bin-wrappers directory + in the build directory, or prepend that directory to your $PATH. + This however is less efficient than running an installed git, as + you always need an extra fork+exec to run any git subcommand. + + It is still possible to use git without installing by setting a few + environment variables, which was the way this was done + traditionally. But using git found in bin-wrappers directory in + the build directory is far simpler. As a historical reference, the + old way went like this: GIT_EXEC_PATH=`pwd` PATH=`pwd`:$PATH @@ -168,6 +168,8 @@ all:: # # Define NO_PERL if you do not want Perl scripts or libraries at all. # +# Define NO_PYTHON if you do not want Python scripts or libraries at all. +# # Define NO_TCLTK if you do not want Tcl/Tk GUI. # # The TCL_PATH variable governs the location of the Tcl interpreter @@ -204,6 +206,21 @@ 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 JSMIN to point to JavaScript minifier that functions as +# a filter to have gitweb.js minified. +# +# 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 @@ -216,6 +233,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 @@ -256,6 +279,9 @@ lib = lib # DESTDIR= pathsep = : +# JavaScript minifier invocation that can function as filter +JSMIN = + # default configuration for gitweb GITWEB_CONFIG = gitweb_config.perl GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf @@ -271,6 +297,11 @@ GITWEB_HOMETEXT = indextext.html GITWEB_CSS = gitweb.css GITWEB_LOGO = git-logo.png GITWEB_FAVICON = git-favicon.png +ifdef JSMIN +GITWEB_JS = gitweb.min.js +else +GITWEB_JS = gitweb.js +endif GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = @@ -312,6 +343,7 @@ LIB_H = LIB_OBJS = PROGRAMS = SCRIPT_PERL = +SCRIPT_PYTHON = SCRIPT_SH = TEST_PROGRAMS = @@ -325,6 +357,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 @@ -349,6 +382,7 @@ SCRIPT_PERL += git-svn.perl SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ + $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ git-instaweb # Empty... @@ -358,6 +392,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 @@ -369,6 +404,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. @@ -395,6 +431,15 @@ ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) # what 'all' will build but not install in gitexecdir OTHER_PROGRAMS = git$X +# what test wrappers are needed and 'install' will install, in bindir +BINDIR_PROGRAMS_NEED_X += git +BINDIR_PROGRAMS_NEED_X += git-upload-pack +BINDIR_PROGRAMS_NEED_X += git-receive-pack +BINDIR_PROGRAMS_NEED_X += git-upload-archive +BINDIR_PROGRAMS_NEED_X += git-shell + +BINDIR_PROGRAMS_NO_X += git-cvsserver + # Set paths to tools early so that they can be used for version tests. ifndef SHELL_PATH SHELL_PATH = /bin/sh @@ -402,8 +447,12 @@ endif ifndef PERL_PATH PERL_PATH = /usr/bin/perl endif +ifndef PYTHON_PATH + PYTHON_PATH = /usr/bin/python +endif export PERL_PATH +export PYTHON_PATH LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a @@ -437,6 +486,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 @@ -457,6 +507,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 @@ -521,6 +572,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 @@ -555,6 +607,7 @@ 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 @@ -599,7 +652,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 @@ -793,6 +845,7 @@ ifeq ($(uname_O),Cygwin) endif ifeq ($(uname_S),FreeBSD) NEEDS_LIBICONV = YesPlease + OLD_ICONV = YesPlease NO_MEMMEM = YesPlease BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib @@ -899,11 +952,11 @@ ifeq ($(uname_S),HP-UX) NO_SYS_SELECT_H = YesPlease SNPRINTF_RETURNS_BOGUS = YesPlease endif -ifdef MSVC +ifeq ($(uname_S),Windows) GIT_VERSION := $(GIT_VERSION).MSVC pathsep = ; NO_PREAD = YesPlease - NO_OPENSSL = YesPlease + NEEDS_CRYPTO_WITH_SSL = YesPlease NO_LIBGEN_H = YesPlease NO_SYMLINK_HEAD = YesPlease NO_IPV6 = YesPlease @@ -933,6 +986,7 @@ ifdef MSVC NO_REGEX = YesPlease NO_CURL = YesPlease NO_PTHREADS = YesPlease + BLK_SHA1 = YesPlease CC = compat/vcbuild/scripts/clink.pl AR = compat/vcbuild/scripts/lib.pl @@ -951,14 +1005,13 @@ else BASIC_CFLAGS += -Zi -MTd endif X = .exe -else +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 NO_SETENV = YesPlease NO_UNSETENV = YesPlease NO_STRCASESTR = YesPlease @@ -982,6 +1035,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 @@ -1000,7 +1054,6 @@ else NO_PTHREADS = YesPlease endif endif -endif -include config.mak.autogen -include config.mak @@ -1079,7 +1132,6 @@ EXTLIBS += -lz ifndef NO_POSIX_ONLY_PROGRAMS PROGRAMS += git-daemon$X - PROGRAMS += git-imap-send$X endif ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl @@ -1312,6 +1364,10 @@ ifeq ($(PERL_PATH),) NO_PERL=NoThanks endif +ifeq ($(PYTHON_PATH),) +NO_PYTHON=NoThanks +endif + QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir QUIET_SUBDIR1 = @@ -1359,6 +1415,7 @@ prefix_SQ = $(subst ','\'',$(prefix)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) +PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH)) TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) LIBS = $(GITLIBS) $(EXTLIBS) @@ -1367,6 +1424,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) @@ -1390,6 +1463,9 @@ endif ifndef NO_PERL $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all endif +ifndef NO_PYTHON + $(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all +endif $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) please_set_SHELL_PATH_to_a_more_modern_shell: @@ -1459,8 +1535,13 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl chmod +x $@+ && \ mv $@+ $@ +ifdef JSMIN +OTHER_PROGRAMS += gitweb/gitweb.cgi gitweb/gitweb.min.js +gitweb/gitweb.cgi: gitweb/gitweb.perl gitweb/gitweb.min.js +else OTHER_PROGRAMS += gitweb/gitweb.cgi gitweb/gitweb.cgi: gitweb/gitweb.perl +endif $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ @@ -1479,13 +1560,14 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \ -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \ + -e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \ -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \ -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \ $< >$@+ && \ chmod +x $@+ && \ mv $@+ $@ -git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css +git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css gitweb/gitweb.js $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ @@ -1494,6 +1576,8 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css -e '/@@GITWEB_CGI@@/d' \ -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \ -e '/@@GITWEB_CSS@@/d' \ + -e '/@@GITWEB_JS@@/r gitweb/gitweb.js' \ + -e '/@@GITWEB_JS@@/d' \ -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \ $@.sh > $@+ && \ chmod +x $@+ && \ @@ -1508,6 +1592,41 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh mv $@+ $@ endif # NO_PERL + +ifdef JSMIN +gitweb/gitweb.min.js: gitweb/gitweb.js + $(QUIET_GEN)$(JSMIN) <$< >$@ +endif # JSMIN + +ifndef NO_PYTHON +$(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS +$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py + $(QUIET_GEN)$(RM) $@ $@+ && \ + INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \ + --no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \ + instlibdir` && \ + sed -e '1{' \ + -e ' s|#!.*python|#!$(PYTHON_PATH_SQ)|' \ + -e '}' \ + -e 's|^import sys.*|&; \\\ + import os; \\\ + sys.path[0] = os.environ.has_key("GITPYTHONLIB") and \\\ + os.environ["GITPYTHONLIB"] or \\\ + "@@INSTLIBDIR@@"|' \ + -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \ + $@.py >$@+ && \ + chmod +x $@+ && \ + mv $@+ $@ +else # NO_PYTHON +$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : unimplemented.sh + $(QUIET_GEN)$(RM) $@ $@+ && \ + sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|@@REASON@@|NO_PYTHON=$(NO_PYTHON)|g' \ + unimplemented.sh >$@+ && \ + chmod +x $@+ && \ + mv $@+ $@ +endif # NO_PYTHON + configure: configure.ac $(QUIET_GEN)$(RM) $@ $<+ && \ sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ @@ -1525,7 +1644,7 @@ git.o git.spec \ $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $< %.s: %.c GIT-CFLAGS $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $< -%.o: %.S +%.o: %.S GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $< exec_cmd.o: exec_cmd.c GIT-CFLAGS @@ -1634,6 +1753,7 @@ GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@ @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@ @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@ + @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@ ### Detect Tck/Tk interpreter path changes ifndef NO_TCLTK @@ -1651,19 +1771,30 @@ endif ### Testing rules -TEST_PROGRAMS += test-chmtime$X -TEST_PROGRAMS += test-ctype$X -TEST_PROGRAMS += test-date$X -TEST_PROGRAMS += test-delta$X -TEST_PROGRAMS += test-dump-cache-tree$X -TEST_PROGRAMS += test-genrandom$X -TEST_PROGRAMS += test-match-trees$X -TEST_PROGRAMS += test-parse-options$X -TEST_PROGRAMS += test-path-utils$X -TEST_PROGRAMS += test-sha1$X -TEST_PROGRAMS += test-sigchain$X +TEST_PROGRAMS_NEED_X += test-chmtime +TEST_PROGRAMS_NEED_X += test-ctype +TEST_PROGRAMS_NEED_X += test-date +TEST_PROGRAMS_NEED_X += test-delta +TEST_PROGRAMS_NEED_X += test-dump-cache-tree +TEST_PROGRAMS_NEED_X += test-genrandom +TEST_PROGRAMS_NEED_X += test-match-trees +TEST_PROGRAMS_NEED_X += test-parse-options +TEST_PROGRAMS_NEED_X += test-path-utils +TEST_PROGRAMS_NEED_X += test-sha1 +TEST_PROGRAMS_NEED_X += test-sigchain -all:: $(TEST_PROGRAMS) +TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X)) + +test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X)) + +all:: $(TEST_PROGRAMS) $(test_bindir_programs) + +bin-wrappers/%: wrap-for-bin.sh + @mkdir -p bin-wrappers + $(QUIET_GEN)sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|@@BUILD_DIR@@|$(shell pwd)|' \ + -e 's|@@PROG@@|$(@F)|' < $< > $@ && \ + chmod +x $@ # GNU make supports exporting all variables by "export" without parameters. # However, the environment gets quite big, and some programs have problems @@ -1724,15 +1855,20 @@ endif gitexec_instdir_SQ = $(subst ','\'',$(gitexec_instdir)) export gitexec_instdir +install_bindir_programs := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X)) $(BINDIR_PROGRAMS_NO_X) + install: all $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)' $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' - $(INSTALL) git$X git-upload-pack$X git-receive-pack$X git-upload-archive$X git-shell$X git-cvsserver '$(DESTDIR_SQ)$(bindir_SQ)' + $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install ifndef NO_PERL $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install endif +ifndef NO_PYTHON + $(MAKE) -C git_remote_helpers prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install +endif ifndef NO_TCLTK $(MAKE) -C gitk-git install $(MAKE) -C git-gui gitexecdir='$(gitexec_instdir_SQ)' install @@ -1835,10 +1971,11 @@ distclean: clean $(RM) configure clean: - $(RM) *.o block-sha1/*.o arm/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \ + $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \ $(LIB_FILE) $(XDIFF_LIB) $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) + $(RM) -r bin-wrappers $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope* $(RM) -r autom4te.cache $(RM) config.log config.mak.autogen config.mak.append config.status config.cache @@ -1850,6 +1987,9 @@ ifndef NO_PERL $(RM) gitweb/gitweb.cgi $(MAKE) -C perl clean endif +ifndef NO_PYTHON + $(MAKE) -C git_remote_helpers clean +endif $(MAKE) -C templates/ clean $(MAKE) -C t/ clean ifndef NO_TCLTK @@ -1 +1 @@ -Documentation/RelNotes-1.6.5.5.txt
\ No newline at end of file +Documentation/RelNotes-1.7.0.txt
\ No newline at end of file @@ -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; } @@ -813,11 +813,11 @@ static void handle_skipped_merge_base(const unsigned char *mb) char *bad_hex = sha1_to_hex(current_bad_sha1); char *good_hex = join_sha1_array_hex(&good_revs, ' '); - fprintf(stderr, "Warning: the merge base between %s and [%s] " + warning("the merge base between %s and [%s] " "must be skipped.\n" "So we cannot be sure the first bad commit is " "between %s and %s.\n" - "We continue anyway.\n", + "We continue anyway.", bad_hex, good_hex, mb_hex, bad_hex); free(good_hex); } diff --git a/builtin-archive.c b/builtin-archive.c index 12351e9dd5..446d6bff30 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -106,13 +106,17 @@ int cmd_archive(int argc, const char **argv, const char *prefix) 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[]. + * We have enough room in argv[] to muck it in place, + * 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 + * it, i.e. we can add back one element to the array. + * But argv[] may contain "--"; we should make it the + * first option. */ - argv[argc++] = fmt_opt; - argv[argc] = NULL; + memmove(argv + 2, argv + 1, sizeof(*argv) * argc); + argv[1] = fmt_opt; + argv[++argc] = NULL; } if (remote) diff --git a/builtin-blame.c b/builtin-blame.c index dd16b22297..6408ec8ee6 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1305,6 +1305,7 @@ static void get_ac_line(const char *inbuf, const char *what, error_out: /* Ugh */ *tz = "(unknown)"; + strcpy(person, *tz); strcpy(mail, *tz); *time = 0; return; @@ -1314,20 +1315,26 @@ static void get_ac_line(const char *inbuf, const char *what, tmp = person; tmp += len; *tmp = 0; - while (*tmp != ' ') + while (person < tmp && *tmp != ' ') tmp--; + if (tmp <= person) + goto error_out; *tz = tmp+1; tzlen = (person+len)-(tmp+1); *tmp = 0; - while (*tmp != ' ') + while (person < tmp && *tmp != ' ') tmp--; + if (tmp <= person) + goto error_out; *time = strtoul(tmp, NULL, 10); timepos = tmp; *tmp = 0; - while (*tmp != ' ') + while (person < tmp && *tmp != ' ') tmp--; + if (tmp <= person) + return; mailpos = tmp + 1; *tmp = 0; maillen = timepos - tmp; @@ -2358,6 +2365,7 @@ parse_done: die_errno("cannot stat path '%s'", path); } + revs.disable_stdin = 1; setup_revisions(argc, argv, &revs, NULL); memset(&sb, 0, sizeof(sb)); diff --git a/builtin-branch.c b/builtin-branch.c index 9f57992062..ddc9f2dab7 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -65,7 +65,7 @@ static int parse_branch_color_slot(const char *var, int ofs) return BRANCH_COLOR_LOCAL; if (!strcasecmp(var+ofs, "current")) return BRANCH_COLOR_CURRENT; - die("bad config variable '%s'", var); + return -1; } static int git_branch_config(const char *var, const char *value, void *cb) @@ -76,6 +76,8 @@ static int git_branch_config(const char *var, const char *value, void *cb) } if (!prefixcmp(var, "color.branch.")) { int slot = parse_branch_color_slot(var, 13); + if (slot < 0) + return 0; if (!value) return config_error_nonbool(var); color_parse(value, var, branch_colors[slot]); @@ -387,8 +389,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; } @@ -635,10 +638,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix) rename_branch(head, argv[0], rename > 1); else if (rename && (argc == 2)) rename_branch(argv[0], argv[1], rename > 1); - else if (argc <= 2) + else if (argc <= 2) { + if (kinds != REF_LOCAL_BRANCH) + die("-a and -r options to 'git branch' do not make sense with a branch name"); create_branch(head, argv[0], (argc == 2) ? argv[1] : head, force_create, reflog, track); - else + } else usage_with_options(builtin_branch_usage, options); return 0; diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c index a5ba4eae5a..b106c65d80 100644 --- a/builtin-check-ref-format.c +++ b/builtin-check-ref-format.c @@ -8,11 +8,36 @@ #include "strbuf.h" static const char builtin_check_ref_format_usage[] = -"git check-ref-format <refname>\n" +"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; @@ -21,6 +46,15 @@ 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(builtin_check_ref_format_usage); return !!check_ref_format(argv[1]); diff --git a/builtin-checkout.c b/builtin-checkout.c index d050c3789f..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); @@ -572,6 +573,40 @@ static int interactive_checkout(const char *revision, const char **pathspec, 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) { @@ -582,6 +617,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) 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"), @@ -597,6 +633,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) 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; @@ -630,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); @@ -655,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). @@ -677,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 */ @@ -715,6 +771,10 @@ 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); diff --git a/builtin-clone.c b/builtin-clone.c index caf3025031..5df8b0f72c 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -362,9 +362,10 @@ 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, *remote_head, *mapped_refs; + const struct ref *refs, *remote_head; const struct ref *remote_head_points_at; const struct ref *our_head_points_at; + struct ref *mapped_refs; struct strbuf key = STRBUF_INIT, value = STRBUF_INIT; struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; struct transport *transport = NULL; 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 2299dc75ce..073fe90ba1 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -24,6 +24,7 @@ #include "string-list.h" #include "rerere.h" #include "unpack-trees.h" +#include "quote.h" static const char * const builtin_commit_usage[] = { "git commit [options] [--] <filepattern>...", @@ -35,7 +36,7 @@ static const char * const builtin_status_usage[] = { NULL }; -static unsigned char head_sha1[20], merge_head_sha1[20]; +static unsigned char head_sha1[20]; static char *use_message_buffer; static const char commit_editmsg[] = "COMMIT_EDITMSG"; static struct lock_file index_lock; /* real index */ @@ -51,8 +52,8 @@ static const char *template_file; static char *edit_message, *use_message; static char *author_name, *author_email, *author_date; static int all, edit_flag, also, interactive, only, amend, signoff; -static int quiet, verbose, no_verify, allow_empty, dry_run; -static char *untracked_files_arg; +static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; +static char *untracked_files_arg, *force_date; /* * The default commit message cleanup mode will remove the lines * beginning with # (shell comments) and leading and trailing @@ -71,6 +72,13 @@ static int use_editor = 1, initial_commit, in_merge; static const char *only_include_assumed; static struct strbuf message; +static int null_termination; +static enum { + STATUS_FORMAT_LONG, + STATUS_FORMAT_SHORT, + STATUS_FORMAT_PORCELAIN, +} status_format = STATUS_FORMAT_LONG; + static int opt_parse_m(const struct option *opt, const char *arg, int unset) { struct strbuf *buf = opt->value; @@ -86,16 +94,20 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset) static struct option builtin_commit_options[] = { OPT__QUIET(&quiet), OPT__VERBOSE(&verbose), - OPT_GROUP("Commit message options"), + OPT_GROUP("Commit message options"), OPT_FILENAME('F', "file", &logfile, "read log from file"), OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"), + OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"), OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m), - OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit "), + OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"), OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"), + OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"), OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_FILENAME('t', "template", &template_file, "use specified template file"), OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"), + OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"), + /* end commit message options */ OPT_GROUP("Commit contents options"), OPT_BOOLEAN('a', "all", &all, "commit all changed files"), @@ -104,10 +116,16 @@ static struct option builtin_commit_options[] = { OPT_BOOLEAN('o', "only", &only, "commit only specified files"), OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"), OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"), + OPT_SET_INT(0, "short", &status_format, "show status concisely", + STATUS_FORMAT_SHORT), + OPT_SET_INT(0, "porcelain", &status_format, + "show porcelain output format", STATUS_FORMAT_PORCELAIN), + OPT_BOOLEAN('z', "null", &null_termination, + "terminate entries with NUL"), OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"), - OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"), + /* end commit contents options */ OPT_END() }; @@ -305,7 +323,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int */ commit_style = COMMIT_PARTIAL; - if (file_exists(git_path("MERGE_HEAD"))) + if (in_merge) die("cannot do a partial commit during a merge."); memset(&partial, 0, sizeof(partial)); @@ -346,6 +364,8 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn, struct wt_status *s) { + unsigned char sha1[20]; + if (s->relative_paths) s->prefix = prefix; @@ -357,8 +377,21 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int s->index_file = index_file; s->fp = fp; s->nowarn = nowarn; + s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; - wt_status_print(s); + wt_status_collect(s); + + switch (status_format) { + case STATUS_FORMAT_SHORT: + wt_shortstatus_print(s, null_termination); + break; + case STATUS_FORMAT_PORCELAIN: + wt_porcelain_print(s, null_termination); + break; + case STATUS_FORMAT_LONG: + wt_status_print(s); + break; + } return s->commitable; } @@ -381,7 +414,7 @@ static void determine_author_info(void) email = getenv("GIT_AUTHOR_EMAIL"); date = getenv("GIT_AUTHOR_DATE"); - if (use_message) { + if (use_message && !renew_authorship) { const char *a, *lb, *rb, *eol; a = strstr(use_message_buffer, "\nauthor "); @@ -409,11 +442,55 @@ static void determine_author_info(void) email = xstrndup(lb + 2, rb - (lb + 2)); } + if (force_date) + date = force_date; + author_name = name; author_email = email; 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 +566,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,13 +761,30 @@ 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); } + +static void handle_untracked_files_arg(struct wt_status *s) +{ + if (!untracked_files_arg) + ; /* default already initialized */ + else if (!strcmp(untracked_files_arg, "no")) + s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; + else if (!strcmp(untracked_files_arg, "normal")) + s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; + else if (!strcmp(untracked_files_arg, "all")) + s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; + else + die("Invalid untracked files mode '%s'", untracked_files_arg); +} + static int parse_and_validate_options(int argc, const char *argv[], const char * const usage[], const char *prefix, @@ -704,6 +798,9 @@ static int parse_and_validate_options(int argc, const char *argv[], if (force_author && !strchr(force_author, '>')) force_author = find_author_by_nickname(force_author); + if (force_author && renew_authorship) + die("Using both --reset-author and --author does not make sense"); + if (logfile || message.len || use_message) use_editor = 0; if (edit_flag) @@ -714,9 +811,6 @@ static int parse_and_validate_options(int argc, const char *argv[], if (get_sha1("HEAD", head_sha1)) initial_commit = 1; - if (!get_sha1("MERGE_HEAD", merge_head_sha1)) - in_merge = 1; - /* Sanity check options */ if (amend && initial_commit) die("You have nothing to amend."); @@ -737,6 +831,8 @@ static int parse_and_validate_options(int argc, const char *argv[], use_message = edit_message; if (amend && !use_message) use_message = "HEAD"; + if (!use_message && renew_authorship) + die("--reset-author can be used only with -C, -c or --amend."); if (use_message) { unsigned char sha1[20]; static char utf8[] = "UTF-8"; @@ -794,22 +890,18 @@ static int parse_and_validate_options(int argc, const char *argv[], else die("Invalid cleanup mode %s", cleanup_arg); - if (!untracked_files_arg) - ; /* default already initialized */ - else if (!strcmp(untracked_files_arg, "no")) - s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; - else if (!strcmp(untracked_files_arg, "normal")) - s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; - else if (!strcmp(untracked_files_arg, "all")) - s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; - else - die("Invalid untracked files mode '%s'", untracked_files_arg); + handle_untracked_files_arg(s); if (all && argc > 0) die("Paths with -a does not make sense."); else if (interactive && argc > 0) die("Paths with --interactive does not make sense."); + if (null_termination && status_format == STATUS_FORMAT_LONG) + status_format = STATUS_FORMAT_PORCELAIN; + if (status_format != STATUS_FORMAT_LONG) + dry_run = 1; + return argc; } @@ -841,7 +933,7 @@ static int parse_status_slot(const char *var, int offset) return WT_STATUS_NOBRANCH; if (!strcasecmp(var+offset, "unmerged")) return WT_STATUS_UNMERGED; - die("bad config variable '%s'", var); + return -1; } static int git_status_config(const char *k, const char *v, void *cb) @@ -861,6 +953,8 @@ static int git_status_config(const char *k, const char *v, void *cb) } if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) { int slot = parse_status_slot(k, 13); + if (slot < 0) + return 0; if (!v) return config_error_nonbool(k); color_parse(v, k, s->color_palette[slot]); @@ -889,17 +983,63 @@ static int git_status_config(const char *k, const char *v, void *cb) int cmd_status(int argc, const char **argv, const char *prefix) { struct wt_status s; + unsigned char sha1[20]; + static struct option builtin_status_options[] = { + OPT__VERBOSE(&verbose), + OPT_SET_INT('s', "short", &status_format, + "show status concisely", STATUS_FORMAT_SHORT), + OPT_SET_INT(0, "porcelain", &status_format, + "show porcelain output format", + STATUS_FORMAT_PORCELAIN), + OPT_BOOLEAN('z', "null", &null_termination, + "terminate entries with NUL"), + { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, + "mode", + "show untracked files, optional modes: all, normal, no. (Default: all)", + PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + OPT_END(), + }; + + if (null_termination && status_format == STATUS_FORMAT_LONG) + status_format = STATUS_FORMAT_PORCELAIN; wt_status_prepare(&s); git_config(git_status_config, &s); + in_merge = file_exists(git_path("MERGE_HEAD")); + argc = parse_options(argc, argv, prefix, + builtin_status_options, + builtin_status_usage, 0); + handle_untracked_files_arg(&s); + + if (*argv) + s.pathspec = get_pathspec(prefix, argv); + + read_cache(); + refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED); + s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; + s.in_merge = in_merge; + wt_status_collect(&s); + + if (s.relative_paths) + s.prefix = prefix; if (s.use_color == -1) s.use_color = git_use_color_default; if (diff_use_color_default == -1) diff_use_color_default = git_use_color_default; - argc = parse_and_validate_options(argc, argv, builtin_status_usage, - prefix, &s); - return dry_run_commit(argc, argv, prefix, &s); + switch (status_format) { + case STATUS_FORMAT_SHORT: + wt_shortstatus_print(&s, null_termination); + break; + case STATUS_FORMAT_PORCELAIN: + wt_porcelain_print(&s, null_termination); + break; + case STATUS_FORMAT_LONG: + s.verbose = verbose; + wt_status_print(&s); + break; + } + return 0; } static void print_summary(const char *prefix, const unsigned char *sha1) @@ -942,8 +1082,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); } @@ -973,10 +1115,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix) wt_status_prepare(&s); git_config(git_commit_config, &s); + in_merge = file_exists(git_path("MERGE_HEAD")); + s.in_merge = in_merge; if (s.use_color == -1) s.use_color = git_use_color_default; - argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix, &s); if (dry_run) { diff --git a/builtin-count-objects.c b/builtin-count-objects.c index 1b0b6c84ea..2bdd8ebde1 100644 --- a/builtin-count-objects.c +++ b/builtin-count-objects.c @@ -11,7 +11,7 @@ static void count_objects(DIR *d, char *path, int len, int verbose, unsigned long *loose, - unsigned long *loose_size, + off_t *loose_size, unsigned long *packed_loose, unsigned long *garbage) { @@ -77,7 +77,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) int len = strlen(objdir); char *path = xmalloc(len + 50); unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0; - unsigned long loose_size = 0; + off_t loose_size = 0; struct option opts[] = { OPT__VERBOSE(&verbose), OPT_END(), @@ -103,7 +103,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) if (verbose) { struct packed_git *p; unsigned long num_pack = 0; - unsigned long size_pack = 0; + off_t size_pack = 0; if (!packed_git) prepare_packed_git(); for (p = packed_git; p; p = p->next) { @@ -116,15 +116,15 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) num_pack++; } printf("count: %lu\n", loose); - printf("size: %lu\n", loose_size / 1024); + printf("size: %lu\n", (unsigned long) (loose_size / 1024)); printf("in-pack: %lu\n", packed); printf("packs: %lu\n", num_pack); - printf("size-pack: %lu\n", size_pack / 1024); + printf("size-pack: %lu\n", (unsigned long) (size_pack / 1024)); printf("prune-packable: %lu\n", packed_loose); printf("garbage: %lu\n", garbage); } else printf("%lu objects, %lu kilobytes\n", - loose, loose_size / 1024); + loose, (unsigned long) (loose_size / 1024)); return 0; } diff --git a/builtin-describe.c b/builtin-describe.c index 7542b5705c..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 && !always) - 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-diff-tree.c b/builtin-diff-tree.c index 79cedb72c4..2380c21951 100644 --- a/builtin-diff-tree.c +++ b/builtin-diff-tree.c @@ -104,6 +104,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ opt->abbrev = 0; opt->diff = 1; + opt->disable_stdin = 1; argc = setup_revisions(argc, argv, opt, NULL); while (--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 cb48c57ca3..8654fa7a2d 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -14,6 +14,9 @@ static const char * const builtin_fetch_usage[] = { "git fetch [options] [<repository> <refspec>...]", + "git fetch [options] <group>", + "git fetch --multiple [options] [<repository> | <group>]...", + "git fetch --all [options]", NULL }; @@ -23,7 +26,7 @@ enum { TAGS_SET = 2 }; -static int append, force, keep, update_head_ok, verbosity; +static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity; static int tags = TAGS_DEFAULT; static const char *depth; static const char *upload_pack; @@ -32,16 +35,24 @@ static struct transport *transport; static struct option builtin_fetch_options[] = { OPT__VERBOSITY(&verbosity), + OPT_BOOLEAN(0, "all", &all, + "fetch from all remotes"), OPT_BOOLEAN('a', "append", &append, "append to .git/FETCH_HEAD instead of overwriting"), OPT_STRING(0, "upload-pack", &upload_pack, "PATH", "path to upload pack on remote end"), OPT_BOOLEAN('f', "force", &force, "force overwrite of local branch"), + OPT_BOOLEAN('m', "multiple", &multiple, + "fetch from multiple remotes"), OPT_SET_INT('t', "tags", &tags, "fetch all tags and associated objects", TAGS_SET), OPT_SET_INT('n', NULL, &tags, "do not fetch all tags (--no-tags)", TAGS_UNSET), + OPT_BOOLEAN('p', "prune", &prune, + "prune tracking branches no longer on remote"), + OPT_BOOLEAN(0, "dry-run", &dry_run, + "dry run"), OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"), OPT_BOOLEAN('u', "update-head-ok", &update_head_ok, "allow updating of HEAD ref"), @@ -178,6 +189,8 @@ static int s_update_ref(const char *action, char *rla = getenv("GIT_REFLOG_ACTION"); static struct ref_lock *lock; + if (dry_run) + return 0; if (!rla) rla = default_rla.buf; snprintf(msg, sizeof(msg), "%s: %s", rla, action); @@ -269,7 +282,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 +300,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; @@ -303,13 +316,16 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, char note[1024]; const char *what, *kind; struct ref *rm; - char *url, *filename = git_path("FETCH_HEAD"); + char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); fp = fopen(filename, "a"); if (!fp) return error("cannot open %s: %s\n", filename, strerror(errno)); - url = transport_anonymize_url(raw_url); + if (raw_url) + url = transport_anonymize_url(raw_url); + else + url = xstrdup("foreign"); for (rm = ref_map; rm; rm = rm->next) { struct ref *ref = NULL; @@ -485,11 +501,34 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map) return ret; } +static int prune_refs(struct transport *transport, struct ref *ref_map) +{ + int result = 0; + struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map); + const char *dangling_msg = dry_run + ? " (%s will become dangling)\n" + : " (%s has become dangling)\n"; + + for (ref = stale_refs; ref; ref = ref->next) { + if (!dry_run) + result |= delete_ref(ref->name, NULL, 0); + if (verbosity >= 0) { + fprintf(stderr, " x %-*s %-*s -> %s\n", + SUMMARY_WIDTH, "[deleted]", + REFCOL_WIDTH, "(none)", prettify_refname(ref->name)); + warn_dangling_symref(stderr, dangling_msg, ref->name); + } + } + free_refs(stale_refs); + return result; +} + 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 +543,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 +654,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) @@ -586,7 +671,7 @@ static int do_fetch(struct transport *transport, die("Don't know how to fetch from %s", transport->url); /* if not appending, truncate FETCH_HEAD */ - if (!append) { + if (!append && !dry_run) { char *filename = git_path("FETCH_HEAD"); FILE *fp = fopen(filename, "w"); if (!fp) @@ -599,8 +684,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) @@ -609,6 +699,8 @@ static int do_fetch(struct transport *transport, free_refs(ref_map); return 1; } + if (prune) + prune_refs(transport, ref_map); free_refs(ref_map); /* if neither --no-tags nor --tags was specified, do automated tag @@ -639,33 +731,100 @@ static void set_option(const char *name, const char *value) name, transport->url); } -int cmd_fetch(int argc, const char **argv, const char *prefix) +static int get_one_remote_for_fetch(struct remote *remote, void *priv) +{ + struct string_list *list = priv; + if (!remote->skip_default_update) + string_list_append(remote->name, list); + return 0; +} + +struct remote_group_data { + const char *name; + struct string_list *list; +}; + +static int get_remote_group(const char *key, const char *value, void *priv) +{ + struct remote_group_data *g = priv; + + if (!prefixcmp(key, "remotes.") && + !strcmp(key + 8, g->name)) { + /* split list by white space */ + int space = strcspn(value, " \t\n"); + while (*value) { + if (space > 1) { + string_list_append(xstrndup(value, space), + g->list); + } + value += space + (value[space] != '\0'); + space = strcspn(value, " \t\n"); + } + } + + return 0; +} + +static int add_remote_or_group(const char *name, struct string_list *list) +{ + int prev_nr = list->nr; + struct remote_group_data g = { name, list }; + + git_config(get_remote_group, &g); + if (list->nr == prev_nr) { + struct remote *remote; + if (!remote_is_configured(name)) + return 0; + remote = remote_get(name); + string_list_append(remote->name, list); + } + return 1; +} + +static int fetch_multiple(struct string_list *list) +{ + int i, result = 0; + const char *argv[] = { "fetch", NULL, NULL, NULL, NULL, NULL, NULL }; + int argc = 1; + + if (dry_run) + argv[argc++] = "--dry-run"; + if (prune) + argv[argc++] = "--prune"; + if (verbosity >= 2) + argv[argc++] = "-v"; + if (verbosity >= 1) + argv[argc++] = "-v"; + else if (verbosity < 0) + argv[argc++] = "-q"; + + for (i = 0; i < list->nr; i++) { + const char *name = list->items[i].string; + argv[argc] = name; + if (verbosity >= 0) + printf("Fetching %s\n", name); + if (run_command_v_opt(argv, RUN_GIT_CMD)) { + error("Could not fetch %s", name); + result = 1; + } + } + + return result; +} + +static int fetch_one(struct remote *remote, int argc, const char **argv) { - struct remote *remote; int i; static const char **refs = NULL; int ref_nr = 0; int exit_code; - /* Record the command line for the reflog */ - strbuf_addstr(&default_rla, "fetch"); - for (i = 1; i < argc; i++) - strbuf_addf(&default_rla, " %s", argv[i]); - - argc = parse_options(argc, argv, prefix, - builtin_fetch_options, builtin_fetch_usage, 0); - - if (argc == 0) - remote = remote_get(NULL); - else - remote = remote_get(argv[0]); - if (!remote) die("Where do you want to fetch from today?"); - transport = transport_get(remote, remote->url[0]); + transport = transport_get(remote, NULL); if (verbosity >= 2) - transport->verbose = 1; + transport->verbose = verbosity <= 3 ? verbosity : 3; if (verbosity < 0) transport->verbose = -1; if (upload_pack) @@ -675,10 +834,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (depth) set_option(TRANS_OPT_DEPTH, depth); - if (argc > 1) { + if (argc > 0) { int j = 0; refs = xcalloc(argc + 1, sizeof(const char *)); - for (i = 1; i < argc; i++) { + for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "tag")) { char *ref; i++; @@ -705,3 +864,57 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) transport = NULL; return exit_code; } + +int cmd_fetch(int argc, const char **argv, const char *prefix) +{ + int i; + struct string_list list = { NULL, 0, 0, 0 }; + struct remote *remote; + int result = 0; + + /* Record the command line for the reflog */ + strbuf_addstr(&default_rla, "fetch"); + for (i = 1; i < argc; i++) + strbuf_addf(&default_rla, " %s", argv[i]); + + argc = parse_options(argc, argv, prefix, + builtin_fetch_options, builtin_fetch_usage, 0); + + if (all) { + if (argc == 1) + die("fetch --all does not take a repository argument"); + else if (argc > 1) + die("fetch --all does not make sense with refspecs"); + (void) for_each_remote(get_one_remote_for_fetch, &list); + result = fetch_multiple(&list); + } else if (argc == 0) { + /* No arguments -- use default remote */ + remote = remote_get(NULL); + result = fetch_one(remote, argc, argv); + } else if (multiple) { + /* All arguments are assumed to be remotes or groups */ + for (i = 0; i < argc; i++) + if (!add_remote_or_group(argv[i], &list)) + die("No such remote or remote group: %s", argv[i]); + result = fetch_multiple(&list); + } else { + /* Single remote or group */ + (void) add_remote_or_group(argv[0], &list); + if (list.nr > 1) { + /* More than one remote */ + if (argc > 1) + die("Fetching a group and specifying refspecs does not make sense"); + result = fetch_multiple(&list); + } else { + /* Zero or one remotes */ + remote = remote_get(argv[0]); + result = fetch_one(remote, argc-1, argv+1); + } + } + + /* All names were strdup()ed or strndup()ed */ + list.strdup_strings = 1; + string_list_clear(&list, 0); + + return result; +} diff --git a/builtin-fsck.c b/builtin-fsck.c index c58b0e337e..0e5faae381 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]; @@ -47,6 +47,7 @@ static void objreport(struct object *obj, const char *severity, fputs("\n", stderr); } +__attribute__((format (printf, 2, 3))) static int objerror(struct object *obj, const char *err, ...) { va_list params; @@ -57,6 +58,7 @@ static int objerror(struct object *obj, const char *err, ...) return -1; } +__attribute__((format (printf, 3, 4))) static int fsck_error_func(struct object *obj, int type, const char *err, ...) { va_list params; diff --git a/builtin-grep.c b/builtin-grep.c index d79a6260a4..a5b6719a1a 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -433,7 +433,11 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) if (opt->color_external && strlen(opt->color_external) > 0) push_arg(opt->color_external); + } else { + unsetenv("GREP_COLOR"); + unsetenv("GREP_COLORS"); } + unsetenv("GREP_OPTIONS"); hit = 0; argc = nr; @@ -788,6 +792,13 @@ 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; diff --git a/builtin-log.c b/builtin-log.c index 0cf978e094..41b6df490f 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) @@ -561,7 +567,7 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev) get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename); - if (!DIFF_OPT_TST(&rev->diffopt, QUIET)) + if (!DIFF_OPT_TST(&rev->diffopt, QUICK)) fprintf(realstdout, "%s\n", filename.buf + outdir_offset); if (freopen(filename.buf, "w", stdout) == NULL) @@ -921,10 +927,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback }, OPT_BOOLEAN(0, "no-binary", &no_binary_diff, "don't output binary diffs"), - OPT_BOOLEAN('p', NULL, &use_patch_format, - "show patch format instead of default (patch + stat)"), 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, @@ -1031,11 +1038,20 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (argc > 1) die ("unrecognized argument: %s", argv[1]); - if (use_patch_format) - rev.diffopt.output_format |= DIFF_FORMAT_PATCH; - else 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); @@ -1243,6 +1259,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]; @@ -1310,8 +1329,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-remote.c b/builtin-ls-remote.c index b5bad0c184..70f5622d9d 100644 --- a/builtin-ls-remote.c +++ b/builtin-ls-remote.c @@ -89,7 +89,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) remote = remote_get(dest); if (!remote->url_nr) die("remote %s has no configured URL", dest); - transport = transport_get(remote, remote->url[0]); + transport = transport_get(remote, NULL); 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 3c4f0753fe..a50ac2256c 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -10,6 +10,7 @@ static FILE *cmitmsg, *patchfile, *fin, *fout; static int keep_subject; +static int keep_non_patch_brackets_in_subject; static const char *metainfo_charset; static struct strbuf line = STRBUF_INIT; static struct strbuf name = STRBUF_INIT; @@ -221,35 +222,41 @@ static int is_multipart_boundary(const struct strbuf *line) static void cleanup_subject(struct strbuf *subject) { - char *pos; - size_t remove; - while (subject->len) { - switch (*subject->buf) { + size_t at = 0; + + while (at < subject->len) { + char *pos; + size_t remove; + + switch (subject->buf[at]) { case 'r': case 'R': - if (subject->len <= 3) + if (subject->len <= at + 3) break; - if (!memcmp(subject->buf + 1, "e:", 2)) { - strbuf_remove(subject, 0, 3); + if (!memcmp(subject->buf + at + 1, "e:", 2)) { + strbuf_remove(subject, at, 3); continue; } + at++; break; case ' ': case '\t': case ':': - strbuf_remove(subject, 0, 1); + strbuf_remove(subject, at, 1); continue; case '[': - if ((pos = strchr(subject->buf, ']'))) { - remove = pos - subject->buf; - if (remove <= (subject->len - remove) * 2) { - strbuf_remove(subject, 0, remove + 1); - continue; - } - } else - strbuf_remove(subject, 0, 1); - break; + pos = strchr(subject->buf + at, ']'); + if (!pos) + break; + remove = pos - subject->buf + at + 1; + if (!keep_non_patch_brackets_in_subject || + (7 <= remove && + memmem(subject->buf + at, remove, "PATCH", 5))) + strbuf_remove(subject, at, remove); + else + at += remove; + continue; } - strbuf_trim(subject); - return; + break; } + strbuf_trim(subject); } static void cleanup_space(struct strbuf *sb) @@ -1014,7 +1021,7 @@ static int git_mailinfo_config(const char *var, const char *value, void *unused) } static const char mailinfo_usage[] = - "git mailinfo [-k] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info"; + "git mailinfo [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info"; int cmd_mailinfo(int argc, const char **argv, const char *prefix) { @@ -1031,6 +1038,8 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) while (1 < argc && argv[1][0] == '-') { if (!strcmp(argv[1], "-k")) keep_subject = 1; + else if (!strcmp(argv[1], "-b")) + keep_non_patch_brackets_in_subject = 1; else if (!strcmp(argv[1], "-u")) metainfo_charset = def_charset; else if (!strcmp(argv[1], "-n")) diff --git a/builtin-mailsplit.c b/builtin-mailsplit.c index dfe5b151e6..207e358ed1 100644 --- a/builtin-mailsplit.c +++ b/builtin-mailsplit.c @@ -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 d3eb5092c8..f1c84d759d 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"); @@ -650,6 +656,7 @@ static int checkout_fast_forward(unsigned char *head, unsigned char *remote) opts.verbose_update = 1; opts.merge = 1; opts.fn = twoway_merge; + opts.msgs = get_porcelain_error_msgs(); trees[nr_trees] = parse_tree_indirect(head); if (!trees[nr_trees++]) @@ -873,6 +880,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); @@ -1014,7 +1024,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)"); @@ -1032,16 +1042,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"); @@ -1080,6 +1090,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..82471869a0 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); @@ -169,9 +169,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) * check both source and destination */ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { - fprintf(stderr, "Warning: %s;" - " will overwrite!\n", - bad); + warning("%s; will overwrite!", bad); bad = NULL; } else bad = "Cannot overwrite"; diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 7938202170..4429d53a1e 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1630,6 +1630,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; @@ -2330,11 +2332,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 - if (progress && all_progress_implied) progress = 2; diff --git a/builtin-push.c b/builtin-push.c index 752121f247..dcfb53f188 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -87,6 +87,37 @@ static void setup_default_push_refspecs(void) } } +static int push_with_options(struct transport *transport, int flags) +{ + int err; + int nonfastforward; + if (receivepack) + transport_set_option(transport, + TRANS_OPT_RECEIVEPACK, receivepack); + if (thin) + transport_set_option(transport, TRANS_OPT_THIN, "yes"); + + if (flags & TRANSPORT_PUSH_VERBOSE) + fprintf(stderr, "Pushing to %s\n", transport->url); + err = transport_push(transport, refspec_nr, refspec, flags, + &nonfastforward); + if (err != 0) + error("failed to push some refs to '%s'", transport->url); + + err |= transport_disconnect(transport); + + if (!err) + return 0; + + 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"); + } + + return 1; +} + static int do_push(const char *repo, int flags) { int i, errs; @@ -135,33 +166,19 @@ static int do_push(const char *repo, int flags) url = remote->url; url_nr = remote->url_nr; } - for (i = 0; i < url_nr; i++) { - struct transport *transport = - transport_get(remote, url[i]); - int err; - int nonfastforward; - if (receivepack) - transport_set_option(transport, - TRANS_OPT_RECEIVEPACK, receivepack); - if (thin) - transport_set_option(transport, TRANS_OPT_THIN, "yes"); - - if (flags & TRANSPORT_PUSH_VERBOSE) - fprintf(stderr, "Pushing to %s\n", url[i]); - err = transport_push(transport, refspec_nr, refspec, flags, - &nonfastforward); - err |= transport_disconnect(transport); - - if (!err) - continue; - - error("failed to push some refs to '%s'", url[i]); - 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"); + if (url_nr) { + for (i = 0; i < url_nr; i++) { + struct transport *transport = + transport_get(remote, url[i]); + if (push_with_options(transport, flags)) + errs++; } - errs++; + } else { + struct transport *transport = + transport_get(remote, NULL); + + if (push_with_options(transport, flags)) + errs++; } return !!errs; } 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 e8bde02c27..4320c93e70 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -204,59 +204,47 @@ static int is_ref_checked_out(const char *ref) return !strcmp(head_name, ref); } -static char *warn_unconfigured_deny_msg[] = { - "Updating the currently checked out branch may cause confusion,", - "as the index and work tree do not reflect changes that are in HEAD.", - "As a result, you may see the changes you just pushed into it", - "reverted when you run 'git diff' over there, and you may want", - "to run 'git reset --hard' before starting to work to recover.", +static char *refuse_unconfigured_deny_msg[] = { + "By default, updating the current branch in a non-bare repository", + "is denied, because it will make the index and work tree inconsistent", + "with what you pushed, and will require 'git reset --hard' to match", + "the work tree to HEAD.", "", "You can set 'receive.denyCurrentBranch' configuration variable to", - "'refuse' in the remote repository to forbid pushing into its", - "current branch." + "'ignore' or 'warn' in the remote repository to allow pushing into", + "its current branch; however, this is not recommended unless you", + "arranged to update its work tree to match what you pushed in some", + "other way.", "", - "To allow pushing into the current branch, you can set it to 'ignore';", - "but this is not recommended unless you arranged to update its work", - "tree to match what you pushed in some other way.", - "", - "To squelch this message, you can set it to 'warn'.", - "", - "Note that the default will change in a future version of git", - "to refuse updating the current branch unless you have the", - "configuration variable set to either 'ignore' or 'warn'." + "To squelch this message and still keep the default behaviour, set", + "'receive.denyCurrentBranch' configuration variable to 'refuse'." }; -static void warn_unconfigured_deny(void) +static void refuse_unconfigured_deny(void) { int i; - for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++) - warning("%s", warn_unconfigured_deny_msg[i]); + for (i = 0; i < ARRAY_SIZE(refuse_unconfigured_deny_msg); i++) + error("%s", refuse_unconfigured_deny_msg[i]); } -static char *warn_unconfigured_deny_delete_current_msg[] = { - "Deleting the current branch can cause confusion by making the next", - "'git clone' not check out any file.", +static char *refuse_unconfigured_deny_delete_current_msg[] = { + "By default, deleting the current branch is denied, because the next", + "'git clone' won't result in any file checked out, causing confusion.", "", "You can set 'receive.denyDeleteCurrent' configuration variable to", - "'refuse' in the remote repository to disallow deleting the current", - "branch.", - "", - "You can set it to 'ignore' to allow such a delete without a warning.", + "'warn' or 'ignore' in the remote repository to allow deleting the", + "current branch, with or without a warning message.", "", - "To make this warning message less loud, you can set it to 'warn'.", - "", - "Note that the default will change in a future version of git", - "to refuse deleting the current branch unless you have the", - "configuration variable set to either 'ignore' or 'warn'." + "To squelch this message, you can set it to 'refuse'." }; -static void warn_unconfigured_deny_delete_current(void) +static void refuse_unconfigured_deny_delete_current(void) { int i; for (i = 0; - i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg); + i < ARRAY_SIZE(refuse_unconfigured_deny_delete_current_msg); i++) - warning("%s", warn_unconfigured_deny_delete_current_msg[i]); + error("%s", refuse_unconfigured_deny_delete_current_msg[i]); } static const char *update(struct command *cmd) @@ -276,14 +264,14 @@ static const char *update(struct command *cmd) switch (deny_current_branch) { case DENY_IGNORE: break; - case DENY_UNCONFIGURED: case DENY_WARN: warning("updating the current branch"); - if (deny_current_branch == DENY_UNCONFIGURED) - warn_unconfigured_deny(); break; case DENY_REFUSE: + case DENY_UNCONFIGURED: error("refusing to update checked out branch: %s", name); + if (deny_current_branch == DENY_UNCONFIGURED) + refuse_unconfigured_deny(); return "branch is currently checked out"; } } @@ -305,12 +293,12 @@ static const char *update(struct command *cmd) case DENY_IGNORE: break; case DENY_WARN: - case DENY_UNCONFIGURED: - if (deny_delete_current == DENY_UNCONFIGURED) - warn_unconfigured_deny_delete_current(); warning("deleting the current branch"); break; case DENY_REFUSE: + case DENY_UNCONFIGURED: + if (deny_delete_current == DENY_UNCONFIGURED) + refuse_unconfigured_deny_delete_current(); error("refusing to delete the current branch: %s", name); return "deletion of the current branch prohibited"; } @@ -341,9 +329,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)) { @@ -627,6 +615,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; @@ -635,7 +625,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) @@ -664,12 +662,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) { diff --git a/builtin-reflog.c b/builtin-reflog.c index e23b5ef979..749821078d 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -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 67761d5ab3..a5019397ff 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -261,32 +261,10 @@ struct ref_states { int queried; }; -static int handle_one_branch(const char *refname, - const unsigned char *sha1, int flags, void *cb_data) -{ - struct ref_states *states = cb_data; - struct refspec refspec; - - memset(&refspec, 0, sizeof(refspec)); - refspec.dst = (char *)refname; - if (!remote_find_tracking(states->remote, &refspec)) { - struct string_list_item *item; - const char *name = abbrev_branch(refspec.src); - /* symbolic refs pointing nowhere were handled already */ - if ((flags & REF_ISSYMREF) || - string_list_has_string(&states->tracked, name) || - string_list_has_string(&states->new, name)) - return 0; - item = string_list_append(name, &states->stale); - item->util = xstrdup(refname); - } - return 0; -} - static int get_ref_states(const struct ref *remote_refs, struct ref_states *states) { struct ref *fetch_map = NULL, **tail = &fetch_map; - struct ref *ref; + struct ref *ref, *stale_refs; int i; for (i = 0; i < states->remote->fetch_refspec_nr; i++) @@ -294,7 +272,9 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat die("Could not get fetch map for refspec %s", states->remote->fetch_refspec[i]); - states->new.strdup_strings = states->tracked.strdup_strings = 1; + states->new.strdup_strings = 1; + states->tracked.strdup_strings = 1; + states->stale.strdup_strings = 1; for (ref = fetch_map; ref; ref = ref->next) { unsigned char sha1[20]; if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1)) @@ -302,11 +282,17 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat else string_list_append(abbrev_branch(ref->name), &states->tracked); } + stale_refs = get_stale_heads(states->remote, fetch_map); + for (ref = stale_refs; ref; ref = ref->next) { + struct string_list_item *item = + string_list_append(abbrev_branch(ref->name), &states->stale); + item->util = xstrdup(ref->name); + } + free_refs(stale_refs); free_refs(fetch_map); sort_string_list(&states->new); sort_string_list(&states->tracked); - for_each_ref(handle_one_branch, states); sort_string_list(&states->stale); return 0; @@ -784,7 +770,7 @@ static void clear_push_info(void *util, const char *string) static void free_remote_ref_states(struct ref_states *states) { string_list_clear(&states->new, 0); - string_list_clear(&states->stale, 0); + string_list_clear(&states->stale, 1); string_list_clear(&states->tracked, 0); string_list_clear(&states->heads, 0); string_list_clear_func(&states->push, clear_push_info); @@ -987,7 +973,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"; @@ -1213,94 +1199,62 @@ static int prune_remote(const char *remote, int dry_run) printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned", abbrev_ref(refname, "refs/remotes/")); - warn_dangling_symref(dangling_msg, refname); + warn_dangling_symref(stdout, dangling_msg, refname); } free_remote_ref_states(&states); return result; } -static int get_one_remote_for_update(struct remote *remote, void *priv) -{ - struct string_list *list = priv; - if (!remote->skip_default_update) - string_list_append(remote->name, list); - return 0; -} - -static struct remote_group { - const char *name; - struct string_list *list; -} remote_group; - -static int get_remote_group(const char *key, const char *value, void *num_hits) +static int get_remote_default(const char *key, const char *value, void *priv) { - if (!prefixcmp(key, "remotes.") && - !strcmp(key + 8, remote_group.name)) { - /* split list by white space */ - int space = strcspn(value, " \t\n"); - while (*value) { - if (space > 1) { - string_list_append(xstrndup(value, space), - remote_group.list); - ++*((int *)num_hits); - } - value += space + (value[space] != '\0'); - space = strcspn(value, " \t\n"); - } + if (strcmp(key, "remotes.default") == 0) { + int *found = priv; + *found = 1; } - return 0; } static int update(int argc, const char **argv) { - int i, result = 0, prune = 0; - struct string_list list = { NULL, 0, 0, 0 }; - static const char *default_argv[] = { NULL, "default", NULL }; + int i, prune = 0; struct option options[] = { OPT_BOOLEAN('p', "prune", &prune, "prune remotes after fetching"), OPT_END() }; + const char **fetch_argv; + int fetch_argc = 0; + int default_defined = 0; + + fetch_argv = xmalloc(sizeof(char *) * (argc+5)); argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage, PARSE_OPT_KEEP_ARGV0); - if (argc < 2) { - argc = 2; - argv = default_argv; - } - remote_group.list = &list; - for (i = 1; i < argc; i++) { - int groups_found = 0; - remote_group.name = argv[i]; - result = git_config(get_remote_group, &groups_found); - if (!groups_found && (i != 1 || strcmp(argv[1], "default"))) { - struct remote *remote; - if (!remote_is_configured(argv[i])) - die("No such remote or remote group: %s", - argv[i]); - remote = remote_get(argv[i]); - string_list_append(remote->name, remote_group.list); - } - } + fetch_argv[fetch_argc++] = "fetch"; - if (!result && !list.nr && argc == 2 && !strcmp(argv[1], "default")) - result = for_each_remote(get_one_remote_for_update, &list); + if (prune) + fetch_argv[fetch_argc++] = "--prune"; + if (verbose) + fetch_argv[fetch_argc++] = "-v"; + if (argc < 2) { + fetch_argv[fetch_argc++] = "default"; + } else { + fetch_argv[fetch_argc++] = "--multiple"; + for (i = 1; i < argc; i++) + fetch_argv[fetch_argc++] = argv[i]; + } - for (i = 0; i < list.nr; i++) { - int err = fetch_remote(list.items[i].string); - result |= err; - if (!err && prune) - result |= prune_remote(list.items[i].string, 0); + if (strcmp(fetch_argv[fetch_argc-1], "default") == 0) { + git_config(get_remote_default, &default_defined); + if (!default_defined) + fetch_argv[fetch_argc-1] = "--all"; } - /* all names were strdup()ed or strndup()ed */ - list.strdup_strings = 1; - string_list_clear(&list, 0); + fetch_argv[fetch_argc] = NULL; - return result; + return run_command_v_opt(fetch_argv, RUN_GIT_CMD); } static int get_one_entry(struct remote *remote, void *priv) diff --git a/builtin-rerere.c b/builtin-rerere.c index 31fda73ae7..2be9ffb77b 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -108,6 +108,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-rev-list.c b/builtin-rev-list.c index 4ba1c12e0b..cd97ded4d2 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) @@ -305,7 +306,6 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) struct rev_info revs; struct rev_list_info info; int i; - int read_from_stdin = 0; int bisect_list = 0; int bisect_show_vars = 0; int bisect_find_all = 0; @@ -319,8 +319,10 @@ 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); + quiet = DIFF_OPT_TST(&revs.diffopt, QUICK); for (i = 1 ; i < argc; i++) { const char *arg = argv[i]; @@ -348,12 +350,6 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) bisect_show_vars = 1; continue; } - if (!strcmp(arg, "--stdin")) { - if (read_from_stdin++) - die("--stdin given twice?"); - read_revisions_from_stdin(&revs); - continue; - } usage(rev_list_usage); } 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 2c4eaae684..8fffdbf200 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 b98edc3ba6..b3b055f68c 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -141,9 +141,9 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) const char *author = NULL, *buffer; struct strbuf buf = STRBUF_INIT; struct strbuf ufbuf = STRBUF_INIT; + struct pretty_print_context ctx = {0}; - pretty_print_commit(CMIT_FMT_RAW, commit, &buf, - 0, NULL, NULL, DATE_NORMAL, 0); + pretty_print_commit(CMIT_FMT_RAW, commit, &buf, &ctx); buffer = buf.buf; while (*buffer && *buffer != '\n') { const char *eol = strchr(buffer, '\n'); @@ -161,8 +161,12 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) die("Missing author: %s", sha1_to_hex(commit->object.sha1)); if (log->user_format) { - pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &ufbuf, - 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, &ufbuf, &ctx); buffer = ufbuf.buf; } else if (*buffer) { buffer++; diff --git a/builtin-show-branch.c b/builtin-show-branch.c index be95930b78..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] ")) 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-update-index.c b/builtin-update-index.c index 92beaaf4b3..a6b7f2d636 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -27,6 +27,7 @@ static int mark_valid_only; #define MARK_VALID 1 #define UNMARK_VALID 2 +__attribute__((format (printf, 1, 2))) static void report(const char *fmt, ...) { va_list vp; diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c index 29446e84cc..73f788ef24 100644 --- a/builtin-upload-archive.c +++ b/builtin-upload-archive.c @@ -67,6 +67,7 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix) return write_archive(sent_argc, sent_argv, prefix, 0); } +__attribute__((format (printf, 1, 2))) static void error_clnt(const char *fmt, ...) { char buf[1024]; @@ -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); @@ -204,7 +204,6 @@ int create_bundle(struct bundle_header *header, const char *path, int i, ref_count = 0; char buffer[1024]; struct rev_info revs; - int read_from_stdin = 0; struct child_process rls; FILE *rls_fout; @@ -256,15 +255,8 @@ int create_bundle(struct bundle_header *header, const char *path, /* write references */ argc = setup_revisions(argc, argv, &revs, NULL); - for (i = 1; i < argc; i++) { - if (!strcmp(argv[i], "--stdin")) { - if (read_from_stdin++) - die("--stdin given twice?"); - read_revisions_from_stdin(&revs); - continue; - } - return error("unrecognized argument: %s'", argv[i]); - } + if (argc > 1) + return error("unrecognized argument: %s'", argv[1]); object_array_remove_duplicates(&revs.pending); @@ -369,9 +369,12 @@ static inline enum object_type object_type(unsigned int mode) #define CONFIG_ENVIRONMENT "GIT_CONFIG" #define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH" #define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES" +#define NO_REPLACE_OBJECTS_ENVIRONMENT "GIT_NO_REPLACE_OBJECTS" #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); @@ -396,6 +399,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); @@ -567,6 +571,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 @@ -657,6 +663,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 *); @@ -751,6 +758,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; @@ -857,7 +866,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]; @@ -962,7 +970,9 @@ extern void *alloc_object_node(void); extern void alloc_report(void); /* trace.c */ +__attribute__((format (printf, 1, 2))) extern void trace_printf(const char *format, ...); +__attribute__((format (printf, 2, 3))) extern void trace_argv_printf(const char **argv, const char *format, ...); /* convert.c */ @@ -4,6 +4,11 @@ /* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */ #define COLOR_MAXLEN 24 +/* + * IMPORTANT: Due to the way these color codes are emulated on Windows, + * write them only using printf(), fprintf(), and fputs(). In particular, + * do not use puts() or write(). + */ #define GIT_COLOR_NORMAL "" #define GIT_COLOR_RESET "\033[m" #define GIT_COLOR_BOLD "\033[1m" @@ -29,7 +34,9 @@ int git_color_default_config(const char *var, const char *value, void *cb); int git_config_colorbool(const char *var, const char *value, int stdout_is_tty); void color_parse(const char *value, const char *var, char *dst); void color_parse_mem(const char *value, int len, const char *var, char *dst); +__attribute__((format (printf, 3, 4))) int color_fprintf(FILE *fp, const char *color, const char *fmt, ...); +__attribute__((format (printf, 3, 4))) int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...); int color_fwrite_lines(FILE *fp, const char *color, size_t count, const char *buf); diff --git a/combine-diff.c b/combine-diff.c index 5b63af1eeb..61626912e3 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -524,6 +524,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, int i; unsigned long lno = 0; const char *c_frag = diff_get_color(use_color, DIFF_FRAGINFO); + const char *c_func = diff_get_color(use_color, DIFF_FUNCINFO); const char *c_new = diff_get_color(use_color, DIFF_FILE_NEW); const char *c_old = diff_get_color(use_color, DIFF_FILE_OLD); const char *c_plain = diff_get_color(use_color, DIFF_PLAIN); @@ -588,7 +589,9 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, comment_end = i; } if (comment_end) - putchar(' '); + printf("%s%s %s%s", c_reset, + c_plain, c_reset, + c_func); for (i = 0; i < comment_end; i++) putchar(hunk_comment[i]); } diff --git a/command-list.txt b/command-list.txt index fb03a2ebb5..95bf18cf06 100644 --- a/command-list.txt +++ b/command-list.txt @@ -49,6 +49,7 @@ git-grep mainporcelain common git-gui mainporcelain git-hash-object plumbingmanipulators git-help ancillaryinterrogators +git-http-backend synchingrepositories git-http-fetch synchelpers git-http-push synchelpers git-imap-send foreignscminterface @@ -74,6 +75,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 +94,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; @@ -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_str_in_full(fd, "\n") != 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); diff --git a/compat/bswap.h b/compat/bswap.h index 5cc4acbfcc..f3b8c44181 100644 --- a/compat/bswap.h +++ b/compat/bswap.h @@ -24,10 +24,20 @@ static inline uint32_t default_swab32(uint32_t val) if (__builtin_constant_p(x)) { \ __res = default_swab32(x); \ } else { \ - __asm__("bswap %0" : "=r" (__res) : "0" (x)); \ + __asm__("bswap %0" : "=r" (__res) : "0" ((uint32_t)(x))); \ } \ __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 6b5b5b2c70..0d73f15fa8 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -903,19 +903,195 @@ char **make_augmented_environ(const char *const *vars) return env; } -/* this is the first function to call into WS_32; initialize it */ -#undef gethostbyname -struct hostent *mingw_gethostbyname(const char *host) +/* + * Note, this isn't a complete replacement for getaddrinfo. It assumes + * that service contains a numerical port, or that it it is null. It + * does a simple search using gethostbyname, and returns one IPv4 host + * if one was found. + */ +static int WSAAPI getaddrinfo_stub(const char *node, const char *service, + const struct addrinfo *hints, + struct addrinfo **res) +{ + struct hostent *h = gethostbyname(node); + struct addrinfo *ai; + struct sockaddr_in *sin; + + if (!h) + return WSAGetLastError(); + + ai = xmalloc(sizeof(struct addrinfo)); + *res = ai; + ai->ai_flags = 0; + ai->ai_family = AF_INET; + ai->ai_socktype = hints->ai_socktype; + switch (hints->ai_socktype) { + case SOCK_STREAM: + ai->ai_protocol = IPPROTO_TCP; + break; + case SOCK_DGRAM: + ai->ai_protocol = IPPROTO_UDP; + break; + default: + ai->ai_protocol = 0; + break; + } + ai->ai_addrlen = sizeof(struct sockaddr_in); + ai->ai_canonname = strdup(h->h_name); + + sin = xmalloc(ai->ai_addrlen); + memset(sin, 0, ai->ai_addrlen); + sin->sin_family = AF_INET; + if (service) + sin->sin_port = htons(atoi(service)); + sin->sin_addr = *(struct in_addr *)h->h_addr; + ai->ai_addr = (struct sockaddr *)sin; + ai->ai_next = 0; + return 0; +} + +static void WSAAPI freeaddrinfo_stub(struct addrinfo *res) +{ + free(res->ai_canonname); + free(res->ai_addr); + free(res); +} + +static int WSAAPI getnameinfo_stub(const struct sockaddr *sa, socklen_t salen, + char *host, DWORD hostlen, + char *serv, DWORD servlen, int flags) +{ + const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; + if (sa->sa_family != AF_INET) + return EAI_FAMILY; + if (!host && !serv) + return EAI_NONAME; + + if (host && hostlen > 0) { + struct hostent *ent = NULL; + if (!(flags & NI_NUMERICHOST)) + ent = gethostbyaddr((const char *)&sin->sin_addr, + sizeof(sin->sin_addr), AF_INET); + + if (ent) + snprintf(host, hostlen, "%s", ent->h_name); + else if (flags & NI_NAMEREQD) + return EAI_NONAME; + else + snprintf(host, hostlen, "%s", inet_ntoa(sin->sin_addr)); + } + + if (serv && servlen > 0) { + struct servent *ent = NULL; + if (!(flags & NI_NUMERICSERV)) + ent = getservbyport(sin->sin_port, + flags & NI_DGRAM ? "udp" : "tcp"); + + if (ent) + snprintf(serv, servlen, "%s", ent->s_name); + else + snprintf(serv, servlen, "%d", ntohs(sin->sin_port)); + } + + return 0; +} + +static HMODULE ipv6_dll = NULL; +static void (WSAAPI *ipv6_freeaddrinfo)(struct addrinfo *res); +static int (WSAAPI *ipv6_getaddrinfo)(const char *node, const char *service, + const struct addrinfo *hints, + struct addrinfo **res); +static int (WSAAPI *ipv6_getnameinfo)(const struct sockaddr *sa, socklen_t salen, + char *host, DWORD hostlen, + char *serv, DWORD servlen, int flags); +/* + * gai_strerror is an inline function in the ws2tcpip.h header, so we + * don't need to try to load that one dynamically. + */ + +static void socket_cleanup(void) +{ + WSACleanup(); + if (ipv6_dll) + FreeLibrary(ipv6_dll); + ipv6_dll = NULL; + ipv6_freeaddrinfo = freeaddrinfo_stub; + ipv6_getaddrinfo = getaddrinfo_stub; + ipv6_getnameinfo = getnameinfo_stub; +} + +static void ensure_socket_initialization(void) { WSADATA wsa; + static int initialized = 0; + const char *libraries[] = { "ws2_32.dll", "wship6.dll", NULL }; + const char **name; + + if (initialized) + return; if (WSAStartup(MAKEWORD(2,2), &wsa)) die("unable to initialize winsock subsystem, error %d", WSAGetLastError()); - atexit((void(*)(void)) WSACleanup); + + for (name = libraries; *name; name++) { + ipv6_dll = LoadLibrary(*name); + if (!ipv6_dll) + continue; + + ipv6_freeaddrinfo = (void (WSAAPI *)(struct addrinfo *)) + GetProcAddress(ipv6_dll, "freeaddrinfo"); + ipv6_getaddrinfo = (int (WSAAPI *)(const char *, const char *, + const struct addrinfo *, + struct addrinfo **)) + GetProcAddress(ipv6_dll, "getaddrinfo"); + ipv6_getnameinfo = (int (WSAAPI *)(const struct sockaddr *, + socklen_t, char *, DWORD, + char *, DWORD, int)) + GetProcAddress(ipv6_dll, "getnameinfo"); + if (!ipv6_freeaddrinfo || !ipv6_getaddrinfo || !ipv6_getnameinfo) { + FreeLibrary(ipv6_dll); + ipv6_dll = NULL; + } else + break; + } + if (!ipv6_freeaddrinfo || !ipv6_getaddrinfo || !ipv6_getnameinfo) { + ipv6_freeaddrinfo = freeaddrinfo_stub; + ipv6_getaddrinfo = getaddrinfo_stub; + ipv6_getnameinfo = getnameinfo_stub; + } + + atexit(socket_cleanup); + initialized = 1; +} + +#undef gethostbyname +struct hostent *mingw_gethostbyname(const char *host) +{ + ensure_socket_initialization(); return gethostbyname(host); } +void mingw_freeaddrinfo(struct addrinfo *res) +{ + ipv6_freeaddrinfo(res); +} + +int mingw_getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) +{ + ensure_socket_initialization(); + return ipv6_getaddrinfo(node, service, hints, res); +} + +int mingw_getnameinfo(const struct sockaddr *sa, socklen_t salen, + char *host, DWORD hostlen, char *serv, DWORD servlen, + int flags) +{ + ensure_socket_initialization(); + return ipv6_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags); +} + int mingw_socket(int domain, int type, int protocol) { int sockfd; @@ -1000,6 +1176,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]; diff --git a/compat/mingw.h b/compat/mingw.h index 5b5258bceb..b3d299f5bc 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -1,4 +1,5 @@ #include <winsock2.h> +#include <ws2tcpip.h> /* * things that are not available in header files @@ -124,6 +125,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 */ @@ -157,6 +179,18 @@ char *mingw_getenv(const char *name); struct hostent *mingw_gethostbyname(const char *host); #define gethostbyname mingw_gethostbyname +void mingw_freeaddrinfo(struct addrinfo *res); +#define freeaddrinfo mingw_freeaddrinfo + +int mingw_getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res); +#define getaddrinfo mingw_getaddrinfo + +int mingw_getnameinfo(const struct sockaddr *sa, socklen_t salen, + char *host, DWORD hostlen, char *serv, DWORD servlen, + int flags); +#define getnameinfo mingw_getnameinfo + int mingw_socket(int domain, int type, int protocol); #define socket mingw_socket @@ -166,7 +200,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 diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl index f9528c0ea1..8a2112f22f 100644 --- a/compat/vcbuild/scripts/clink.pl +++ b/compat/vcbuild/scripts/clink.pl @@ -29,6 +29,9 @@ while (@ARGV) { 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); 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; @@ -477,6 +477,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); diff --git a/configure.ac b/configure.ac index b09b8e446f..78345ebb60 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,12 +247,38 @@ 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) # # Define PERL_PATH to provide path to Perl. GIT_ARG_SET_PATH(perl) # +# Define PYTHON_PATH to provide path to Python. +GIT_ARG_SET_PATH(python) +# # Define ZLIB_PATH to provide path to zlib. GIT_ARG_SET_PATH(zlib) # @@ -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; @@ -630,6 +609,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig, GIT_WORK_TREE_ENVIRONMENT, GRAFT_ENVIRONMENT, INDEX_ENVIRONMENT, + NO_REPLACE_OBJECTS_ENVIRONMENT, NULL }; conn->env = env; diff --git a/contrib/buildsystems/Generators/Vcproj.pm b/contrib/buildsystems/Generators/Vcproj.pm index be94ba18d2..cfa74adcc2 100644 --- a/contrib/buildsystems/Generators/Vcproj.pm +++ b/contrib/buildsystems/Generators/Vcproj.pm @@ -178,6 +178,7 @@ sub createLibProject { MinimalRebuild="true" RuntimeLibrary="1" UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" WarningLevel="3" DebugInformationFormat="3" /> @@ -244,6 +245,7 @@ sub createLibProject { RuntimeLibrary="0" EnableFunctionLevelLinking="true" UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" WarningLevel="3" DebugInformationFormat="3" /> @@ -401,6 +403,7 @@ sub createAppProject { MinimalRebuild="true" RuntimeLibrary="1" UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" WarningLevel="3" DebugInformationFormat="3" /> @@ -472,6 +475,7 @@ sub createAppProject { RuntimeLibrary="0" EnableFunctionLevelLinking="true" UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" WarningLevel="3" DebugInformationFormat="3" /> diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl index 20bd061b3e..d506717bfd 100644 --- a/contrib/buildsystems/engine.pl +++ b/contrib/buildsystems/engine.pl @@ -315,6 +315,9 @@ sub handleLinkLine $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)$/) { diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 7cf8557468..3a6498c04b 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -21,13 +21,7 @@ # 2) Added the following line to your .bashrc: # source ~/.git-completion.sh # -# 3) You may want to make sure the git executable is available -# in your PATH before this script is sourced, as some caching -# is performed while the script loads. If git isn't found -# at source time then all lookups will be done on demand, -# which may be slightly slower. -# -# 4) Consider changing your PS1 to also show the current branch: +# 3) Consider changing your PS1 to also show the current branch: # PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' # # The argument to __git_ps1 will be displayed only if you @@ -169,11 +163,8 @@ __git_ps1 () fi fi - if [ -n "${1-}" ]; then - printf "$1" "$c${b##refs/heads/}$w$i$s$u$r" - else - printf " (%s)" "$c${b##refs/heads/}$w$i$s$u$r" - fi + local f="$w$i$s$u" + printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r" fi } @@ -324,12 +315,8 @@ __git_remotes () done } -__git_merge_strategies () +__git_list_merge_strategies () { - if [ -n "${__git_merge_strategylist-}" ]; then - echo "$__git_merge_strategylist" - return - fi git merge -s help 2>&1 | sed -n -e '/[Aa]vailable strategies are: /,/^$/{ s/\.$// @@ -339,8 +326,17 @@ __git_merge_strategies () p }' } -__git_merge_strategylist= -__git_merge_strategylist=$(__git_merge_strategies 2>/dev/null) + +__git_merge_strategies= +# 'git merge -s help' (and thus detection of the merge strategy +# list) fails, unfortunately, if run outside of any git working +# tree. __git_merge_strategies is set to the empty string in +# that case, and the detection will be repeated the next time it +# is needed. +__git_compute_merge_strategies () +{ + : ${__git_merge_strategies:=$(__git_list_merge_strategies)} +} __git_complete_file () { @@ -418,7 +414,17 @@ __git_complete_remote_or_refspec () while [ $c -lt $COMP_CWORD ]; do i="${COMP_WORDS[c]}" case "$i" in - --all|--mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;; + --mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;; + --all) + case "$cmd" in + push) no_complete_refspec=1 ;; + fetch) + COMPREPLY=() + return + ;; + *) ;; + esac + ;; -*) ;; *) remote="$i"; break ;; esac @@ -474,27 +480,24 @@ __git_complete_remote_or_refspec () __git_complete_strategy () { + __git_compute_merge_strategies case "${COMP_WORDS[COMP_CWORD-1]}" in -s|--strategy) - __gitcomp "$(__git_merge_strategies)" + __gitcomp "$__git_merge_strategies" return 0 esac local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --strategy=*) - __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}" + __gitcomp "$__git_merge_strategies" "" "${cur##--strategy=}" return 0 ;; esac return 1 } -__git_all_commands () +__git_list_all_commands () { - if [ -n "${__git_all_commandlist-}" ]; then - echo "$__git_all_commandlist" - return - fi local i IFS=" "$'\n' for i in $(git help -a|egrep '^ [a-zA-Z0-9]') do @@ -504,17 +507,18 @@ __git_all_commands () esac done } -__git_all_commandlist= -__git_all_commandlist="$(__git_all_commands 2>/dev/null)" -__git_porcelain_commands () +__git_all_commands= +__git_compute_all_commands () +{ + : ${__git_all_commands:=$(__git_list_all_commands)} +} + +__git_list_porcelain_commands () { - if [ -n "${__git_porcelain_commandlist-}" ]; then - echo "$__git_porcelain_commandlist" - return - fi local i IFS=" "$'\n' - for i in "help" $(__git_all_commands) + __git_compute_all_commands + for i in "help" $__git_all_commands do case $i in *--*) : helper pattern;; @@ -595,8 +599,13 @@ __git_porcelain_commands () esac done } -__git_porcelain_commandlist= -__git_porcelain_commandlist="$(__git_porcelain_commands 2>/dev/null)" + +__git_porcelain_commands= +__git_compute_porcelain_commands () +{ + __git_compute_all_commands + : ${__git_porcelain_commands:=$(__git_list_porcelain_commands)} +} __git_aliases () { @@ -894,11 +903,31 @@ _git_commit () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in + --cleanup=*) + __gitcomp "default strip verbatim whitespace + " "" "${cur##--cleanup=}" + return + ;; + --reuse-message=*) + __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}" + return + ;; + --reedit-message=*) + __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}" + return + ;; + --untracked-files=*) + __gitcomp "all no normal" "" "${cur##--untracked-files=}" + return + ;; --*) __gitcomp " --all --author= --signoff --verify --no-verify --edit --amend --include --only --interactive - --dry-run + --dry-run --reuse-message= --reedit-message= + --reset-author --file= --message= --template= + --cleanup= --untracked-files --untracked-files= + --verbose --quiet " return esac @@ -953,11 +982,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=*) @@ -965,16 +996,20 @@ _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=" --quiet --verbose --append --upload-pack --force --keep --depth= - --tags --no-tags + --tags --no-tags --all --prune --dry-run " _git_fetch () @@ -1069,7 +1104,8 @@ _git_grep () return ;; esac - COMPREPLY=() + + __gitcomp "$(__git_refs)" } _git_help () @@ -1081,7 +1117,8 @@ _git_help () return ;; esac - __gitcomp "$(__git_all_commands) + __git_compute_all_commands + __gitcomp "$__git_all_commands attributes cli core-tutorial cvs-migration diffcore gitk glossary hooks ignore modules repository-layout tutorial tutorial-2 @@ -1217,7 +1254,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 () @@ -1322,8 +1359,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)" @@ -1427,7 +1474,8 @@ _git_config () return ;; pull.twohead|pull.octopus) - __gitcomp "$(__git_merge_strategies)" + __git_compute_merge_strategies + __gitcomp "$__git_merge_strategies" return ;; color.branch|color.diff|color.interactive|\ @@ -1528,7 +1576,8 @@ _git_config () pager.*) local pfx="${cur%.*}." cur="${cur#*.}" - __gitcomp "$(__git_all_commands)" "$pfx" "$cur" + __git_compute_all_commands + __gitcomp "$__git_all_commands" "$pfx" "$cur" return ;; remote.*.*) @@ -1970,7 +2019,7 @@ _git_svn () init fetch clone rebase dcommit log find-rev set-tree commit-diff info create-ignore propget proplist show-ignore show-externals branch tag blame - migrate + migrate mkdirs reset gc " local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then @@ -2017,7 +2066,7 @@ _git_svn () __gitcomp "--stdin $cmt_opts $fc_opts" ;; create-ignore,--*|propget,--*|proplist,--*|show-ignore,--*|\ - show-externals,--*) + show-externals,--*|mkdirs,--*) __gitcomp "--revision=" ;; log,--*) @@ -2054,6 +2103,9 @@ _git_svn () --no-auth-cache --username= " ;; + reset,--*) + __gitcomp "--revision= --parent" + ;; *) COMPREPLY=() ;; @@ -2125,7 +2177,8 @@ _git () --help " ;; - *) __gitcomp "$(__git_porcelain_commands) $(__git_aliases)" ;; + *) __git_compute_porcelain_commands + __gitcomp "$__git_porcelain_commands $(__git_aliases)" ;; esac return fi 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/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 e710219ca5..48059d0aa7 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -729,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" diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl index 7001862bfd..95438e1ed4 100755 --- a/contrib/fast-import/import-tars.perl +++ b/contrib/fast-import/import-tars.perl @@ -20,7 +20,7 @@ use Getopt::Long; my $metaext = ''; -die "usage: import-tars [--metainfo=extension] *.tar.{gz,bz2,Z}\n" +die "usage: import-tars [--metainfo=extension] *.tar.{gz,bz2,lzma,xz,Z}\n" unless GetOptions('metainfo=s' => \$metaext) && @ARGV; my $branch_name = 'import-tars'; @@ -49,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 { 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 @@ -77,6 +77,7 @@ static void logreport(int priority, const char *err, va_list params) } } +__attribute__((format (printf, 1, 2))) static void logerror(const char *err, ...) { va_list params; @@ -85,6 +86,7 @@ static void logerror(const char *err, ...) va_end(params); } +__attribute__((format (printf, 1, 2))) static void loginfo(const char *err, ...) { va_list params; @@ -101,53 +103,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 +112,7 @@ static char *path_ok(char *directory) dir = directory; - if (avoid_alias(dir)) { + if (daemon_avoid_alias(dir)) { logerror("'%s': aliased", dir); return NULL; } diff --git a/diff-lib.c b/diff-lib.c index 0c74ef5cbe..349f612b61 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -73,7 +73,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) struct cache_entry *ce = active_cache[i]; int changed; - if (DIFF_OPT_TST(&revs->diffopt, QUIET) && + if (DIFF_OPT_TST(&revs->diffopt, QUICK) && DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) break; @@ -383,7 +383,7 @@ static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_op * For diffing, the index is more important, and we only have a * single tree. * - * We're supposed to return how many index entries we want to skip. + * We're supposed to advance o->pos to skip what we have already processed. * * This wrapper makes it all more readable, and takes care of all * the fairly complex unpack_trees() semantic requirements, including @@ -507,7 +507,7 @@ int index_differs_from(const char *def, int diff_flags) init_revisions(&rev, NULL); setup_revisions(0, NULL, &rev, def); - DIFF_OPT_SET(&rev.diffopt, QUIET); + DIFF_OPT_SET(&rev.diffopt, QUICK); DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); rev.diffopt.flags |= diff_flags; run_diff_index(&rev, 1); 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 @@ -38,6 +39,7 @@ static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_GREEN, /* NEW */ GIT_COLOR_YELLOW, /* COMMIT */ GIT_COLOR_BG_RED, /* WHITESPACE */ + GIT_COLOR_NORMAL, /* FUNCINFO */ }; static void diff_filespec_load_driver(struct diff_filespec *one); @@ -59,7 +61,9 @@ static int parse_diff_color_slot(const char *var, int ofs) return DIFF_COMMIT; if (!strcasecmp(var+ofs, "whitespace")) return DIFF_WHITESPACE; - die("bad config variable '%s'", var); + if (!strcasecmp(var+ofs, "func")) + return DIFF_FUNCINFO; + return -1; } static int git_config_rename(const char *var, const char *value) @@ -118,6 +122,8 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) { int slot = parse_diff_color_slot(var, 11); + if (slot < 0) + return 0; if (!value) return config_error_nonbool(var); color_parse(value, var, diff_colors[slot]); @@ -188,6 +194,7 @@ struct emit_callback { struct diff_words_data *diff_words; int *found_changesp; FILE *file; + struct strbuf *header; }; static int count_lines(const char *data, int size) @@ -294,12 +301,13 @@ static void emit_line_0(FILE *file, const char *set, const char *reset, nofirst = 0; } - fputs(set, file); - - if (!nofirst) - fputc(first, file); - fwrite(line, len, 1, file); - fputs(reset, file); + if (len || !nofirst) { + 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) @@ -343,6 +351,42 @@ static void emit_add_line(const char *reset, } } +static void emit_hunk_header(struct emit_callback *ecbdata, + const char *line, int len) +{ + const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN); + const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO); + const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO); + const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); + static const char atat[2] = { '@', '@' }; + const char *cp, *ep; + + /* + * As a hunk header must begin with "@@ -<old>, +<new> @@", + * it always is at least 10 bytes long. + */ + if (len < 10 || + memcmp(line, atat, 2) || + !(ep = memmem(line + 2, len - 2, atat, 2))) { + emit_line(ecbdata->file, plain, reset, line, len); + return; + } + ep += 2; /* skip over @@ */ + + /* The hunk header in fraginfo color */ + emit_line(ecbdata->file, frag, reset, line, ep - line); + + /* blank before the func header */ + for (cp = ep; ep - line < len; ep++) + if (*ep != ' ' && *ep != '\t') + break; + if (ep != cp) + emit_line(ecbdata->file, plain, reset, cp, ep - cp); + + if (ep < line + len) + emit_line(ecbdata->file, func, reset, ep, line + len - ep); +} + static struct diff_tempfile *claim_diff_tempfile(void) { int i; for (i = 0; i < ARRAY_SIZE(diff_temp); i++) @@ -754,6 +798,11 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN); const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); + if (ecbdata->header) { + fprintf(ecbdata->file, "%s", ecbdata->header->buf); + strbuf_reset(ecbdata->header); + ecbdata->header = NULL; + } *(ecbdata->found_changesp) = 1; if (ecbdata->label_path[0]) { @@ -780,9 +829,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) 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); + emit_hunk_header(ecbdata, line, len); if (line[len-1] != '\n') putc('\n', ecbdata->file); return; @@ -1114,7 +1161,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; @@ -1560,6 +1607,18 @@ static void builtin_diff(const char *name_a, const char *reset = diff_get_color_opt(o, DIFF_RESET); const char *a_prefix, *b_prefix; const char *textconv_one = NULL, *textconv_two = NULL; + struct strbuf header = STRBUF_INIT; + + 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); @@ -1583,25 +1642,26 @@ static void builtin_diff(const char *name_a, b_two = quote_two(b_prefix, name_b + (*name_b == '/')); lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null"; lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null"; - fprintf(o->file, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset); + strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset); if (lbl[0][0] == '/') { /* /dev/null */ - fprintf(o->file, "%snew file mode %06o%s\n", set, two->mode, reset); + strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset); if (xfrm_msg && xfrm_msg[0]) - fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); + strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); } else if (lbl[1][0] == '/') { - fprintf(o->file, "%sdeleted file mode %06o%s\n", set, one->mode, reset); + strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset); if (xfrm_msg && xfrm_msg[0]) - fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); + strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); } else { if (one->mode != two->mode) { - fprintf(o->file, "%sold mode %06o%s\n", set, one->mode, reset); - fprintf(o->file, "%snew mode %06o%s\n", set, two->mode, reset); + strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset); + strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset); } if (xfrm_msg && xfrm_msg[0]) - fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); + strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); + /* * we do not run diff between different kind * of objects. @@ -1611,6 +1671,8 @@ static void builtin_diff(const char *name_a, if (complete_rewrite && (textconv_one || !diff_filespec_is_binary(one)) && (textconv_two || !diff_filespec_is_binary(two))) { + fprintf(o->file, "%s", header.buf); + strbuf_reset(&header); emit_rewrite_diff(name_a, name_b, one, two, textconv_one, textconv_two, o); o->found_changes = 1; @@ -1628,6 +1690,8 @@ static void builtin_diff(const char *name_a, if (mf1.size == mf2.size && !memcmp(mf1.ptr, mf2.ptr, mf1.size)) goto free_ab_and_return; + fprintf(o->file, "%s", header.buf); + strbuf_reset(&header); if (DIFF_OPT_TST(o, BINARY)) emit_binary_diff(o->file, &mf1, &mf2); else @@ -1644,6 +1708,11 @@ static void builtin_diff(const char *name_a, struct emit_callback ecbdata; const struct userdiff_funcname *pe; + if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS)) { + fprintf(o->file, "%s", header.buf); + strbuf_reset(&header); + } + if (textconv_one) { size_t size; mf1.ptr = run_textconv(textconv_one, one, &size); @@ -1673,6 +1742,7 @@ static void builtin_diff(const char *name_a, if (ecbdata.ws_rule & WS_BLANK_AT_EOF) check_blank_at_eof(&mf1, &mf2, &ecbdata); ecbdata.file = o->file; + ecbdata.header = header.len ? &header : NULL; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; @@ -1717,6 +1787,7 @@ static void builtin_diff(const char *name_a, } free_ab_and_return: + strbuf_release(&header); diff_free_filespec_data(one); diff_free_filespec_data(two); free(a_one); @@ -2499,6 +2570,20 @@ int diff_setup_done(struct diff_options *options) if (count > 1) die("--name-only, --name-status, --check and -s are mutually exclusive"); + /* + * Most of the time we can say "there are changes" + * only by checking if there are changed paths, but + * --ignore-whitespace* options force us to look + * inside contents. + */ + + if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) || + DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) || + DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL)) + DIFF_OPT_SET(options, DIFF_FROM_CONTENTS); + else + DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS); + if (DIFF_OPT_TST(options, FIND_COPIES_HARDER)) options->detect_rename = DIFF_DETECT_COPY; @@ -2559,7 +2644,7 @@ int diff_setup_done(struct diff_options *options) * to have found. It does not make sense not to return with * exit code in such a case either. */ - if (DIFF_OPT_TST(options, QUIET)) { + if (DIFF_OPT_TST(options, QUICK)) { options->output_format = DIFF_FORMAT_NO_OUTPUT; DIFF_OPT_SET(options, EXIT_WITH_STATUS); } @@ -2750,7 +2835,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--exit-code")) DIFF_OPT_SET(options, EXIT_WITH_STATUS); else if (!strcmp(arg, "--quiet")) - DIFF_OPT_SET(options, QUIET); + DIFF_OPT_SET(options, QUICK); else if (!strcmp(arg, "--ext-diff")) DIFF_OPT_SET(options, ALLOW_EXTERNAL); else if (!strcmp(arg, "--no-ext-diff")) @@ -2761,6 +2846,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")) @@ -3451,6 +3542,18 @@ free_queue: q->nr = q->alloc = 0; if (options->close_file) fclose(options->file); + + /* + * Report the content-level differences with HAS_CHANGES; + * diff_addremove/diff_change does not set the bit when + * DIFF_FROM_CONTENTS is in effect (e.g. with -w). + */ + if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) { + if (options->found_changes) + DIFF_OPT_SET(options, HAS_CHANGES); + else + DIFF_OPT_CLR(options, HAS_CHANGES); + } } static void diffcore_apply_filter(const char *filter) @@ -3587,7 +3690,7 @@ void diffcore_std(struct diff_options *options) diff_resolve_rename_copy(); diffcore_apply_filter(options->filter); - if (diff_queued_diff.nr) + if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) DIFF_OPT_SET(options, HAS_CHANGES); else DIFF_OPT_CLR(options, HAS_CHANGES); @@ -3647,7 +3750,8 @@ void diff_addremove(struct diff_options *options, fill_filespec(two, sha1, mode); diff_queue(&diff_queued_diff, one, two); - DIFF_OPT_SET(options, HAS_CHANGES); + if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) + DIFF_OPT_SET(options, HAS_CHANGES); } void diff_change(struct diff_options *options, @@ -3679,7 +3783,8 @@ void diff_change(struct diff_options *options, fill_filespec(two, new_sha1, new_mode); diff_queue(&diff_queued_diff, one, two); - DIFF_OPT_SET(options, HAS_CHANGES); + if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) + DIFF_OPT_SET(options, HAS_CHANGES); } void diff_unmerge(struct diff_options *options, @@ -3718,11 +3823,13 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, if (start_command(&child) != 0 || strbuf_read(&buf, child.out, 0) < 0 || finish_command(&child) != 0) { + close(child.out); strbuf_release(&buf); remove_tempfile(); error("error running textconv command '%s'", pgm); return NULL; } + close(child.out); remove_tempfile(); return strbuf_detach(&buf, outsize); @@ -55,7 +55,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_COLOR_DIFF (1 << 8) #define DIFF_OPT_COLOR_DIFF_WORDS (1 << 9) #define DIFF_OPT_HAS_CHANGES (1 << 10) -#define DIFF_OPT_QUIET (1 << 11) +#define DIFF_OPT_QUICK (1 << 11) #define DIFF_OPT_NO_INDEX (1 << 12) #define DIFF_OPT_ALLOW_EXTERNAL (1 << 13) #define DIFF_OPT_EXIT_WITH_STATUS (1 << 14) @@ -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_DIFF_FROM_CONTENTS (1 << 22) +#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) @@ -127,6 +130,7 @@ enum color_diff { DIFF_FILE_NEW = 5, DIFF_COMMIT = 6, DIFF_WHITESPACE = 7, + DIFF_FUNCINFO = 8, }; const char *diff_get_color(int diff_use_color, enum color_diff ix); #define diff_get_color_opt(o, ix) \ @@ -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..5171d9f9a4 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? */ @@ -83,6 +84,8 @@ static void setup_git_env(void) git_graft_file = getenv(GRAFT_ENVIRONMENT); if (!git_graft_file) git_graft_file = git_pathdup("info/grafts"); + if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT)) + read_replace_refs = 0; } int is_bare_repository(void) diff --git a/fast-import.c b/fast-import.c index 6faaaacb68..cd87049871 100644 --- a/fast-import.c +++ b/fast-import.c @@ -19,11 +19,11 @@ Format of STDIN stream: new_commit ::= 'commit' sp ref_str lf mark? - ('author' sp name sp '<' email '>' sp when lf)? - 'committer' sp name sp '<' email '>' sp when lf + ('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 - ('tagger' sp name sp '<' email '>' sp when 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; @@ -2006,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); @@ -2175,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 { @@ -2405,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, @@ -17,6 +17,7 @@ typedef int (*fsck_walk_func)(struct object *obj, int type, void *data); /* callback for fsck_object, type is FSCK_ERROR or FSCK_WARN */ typedef int (*fsck_error)(struct object *obj, int type, const char *err, ...); +__attribute__((format (printf, 3, 4))) int fsck_error_function(struct object *obj, int type, const char *fmt, ...); /* descend in all linked child objects diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 8ce1ec92c2..cd43c34912 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -990,8 +990,7 @@ sub edit_hunk_manually { 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) { @@ -1217,7 +1216,11 @@ sub patch_update_file { if (@{$mode->{TEXT}}) { unshift @hunk, $mode; } - if (@{$deletion->{TEXT}} && !@hunk) { + if (@{$deletion->{TEXT}}) { + foreach my $hunk (@hunk) { + push @{$deletion->{TEXT}}, @{$hunk->{TEXT}}; + push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}}; + } @hunk = ($deletion); } @@ -657,7 +657,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 0c422d5fb5..6e2acb8ef2 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> @@ -300,8 +300,7 @@ bisect_visualize() { esac fi - not=$(git for-each-ref --format='%(refname)' "refs/bisect/good-*") - eval '"$@"' refs/bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES") + eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES") } bisect_reset() { @@ -311,8 +310,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 ef60803384..5c596875c2 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -189,6 +189,7 @@ extern char *gitbasename(char *); /* General helper functions */ 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))); diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 8ef1bde710..cb9d2022cc 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..718277a651 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] @@ -2023,6 +2052,11 @@ proc do_quit {{rc {1}}} { } set ret_code $rc + + # Briefly enable send again, working around Tk bug + # http://sourceforge.net/tracker/?func=detail&atid=112997&aid=1821174&group_id=12997 + tk appname [appname] + destroy . } @@ -3054,7 +3088,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 +3246,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 +3283,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/blame.tcl b/git-gui/lib/blame.tcl index 1f3b08f9ef..8525b79aaa 100644 --- a/git-gui/lib/blame.tcl +++ b/git-gui/lib/blame.tcl @@ -1245,6 +1245,18 @@ method _open_tooltip {cur_w} { $tooltip_t conf -state disabled _position_tooltip $this + + # On MacOS raising a window causes it to acquire focus. + # Tk 8.5 on MacOS seems to properly support wm transient, + # so we can safely counter the effect there. + if {$::have_tk85 && [is_MacOSX]} { + update + if {$w eq {}} { + raise . + } else { + raise $w + } + } } method _position_tooltip {} { @@ -1268,7 +1280,9 @@ method _position_tooltip {} { append g $pos_y wm geometry $tooltip_wm $g - raise $tooltip_wm + if {![is_MacOSX]} { + raise $tooltip_wm + } } method _hide_tooltip {} { diff --git a/git-gui/lib/database.tcl b/git-gui/lib/database.tcl index a18ac8b430..d4e0bed0b6 100644 --- a/git-gui/lib/database.tcl +++ b/git-gui/lib/database.tcl @@ -89,27 +89,26 @@ proc do_fsck_objects {} { } proc hint_gc {} { - set object_limit 8 + set ndirs 1 + set limit 8 if {[is_Windows]} { - set object_limit 1 + set ndirs 4 + set limit 1 } - set objects_current [llength [glob \ - -directory [gitdir objects 42] \ + set count [llength [glob \ -nocomplain \ - -tails \ -- \ - *]] + [gitdir objects 4\[0-[expr {$ndirs-1}]\]/*]]] - if {$objects_current >= $object_limit} { - set objects_current [expr {$objects_current * 250}] - set object_limit [expr {$object_limit * 250}] + if {$count >= $limit * $ndirs} { + set objects_current [expr {$count * 256/$ndirs}] if {[ask_popup \ [mc "This repository currently has approximately %i loose objects. -To maintain optimal performance it is strongly recommended that you compress the database when more than %i loose objects exist. +To maintain optimal performance it is strongly recommended that you compress the database. -Compress the database now?" $objects_current $object_limit]] eq yes} { +Compress the database now?" $objects_current]] eq yes} { do_gc } } 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..241642062e 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 @@ -248,6 +250,8 @@ method _write_url {args} { set urltype url } method _write_check_head {args} { set checktype head } method _write_head_list {args} { + global current_branch + $head_m delete 0 end foreach abr $head_list { $head_m insert end radiobutton \ @@ -256,7 +260,11 @@ method _write_head_list {args} { -variable @check_head } if {[lsearch -exact -sorted $head_list $check_head] < 0} { - set check_head {} + if {[lsearch -exact -sorted $head_list $current_branch] < 0} { + set check_head {} + } else { + set check_head $current_branch + } } } 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 e9fb5aa67a..b8e6456208 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -322,7 +322,21 @@ EOF resolve_full_httpd list_mods=$(echo "$full_httpd" | sed "s/-f$/-l/") $list_mods | sane_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> @@ -380,8 +394,15 @@ gitweb_css () { EOFGITWEB } +gitweb_js () { + cat > "$1" <<\EOFGITWEB +@@GITWEB_JS@@ +EOFGITWEB +} + gitweb_cgi "$GIT_DIR/gitweb/gitweb.cgi" gitweb_css "$GIT_DIR/gitweb/gitweb.css" +gitweb_js "$GIT_DIR/gitweb/gitweb.js" case "$httpd" in *lighttpd*) 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 334af7c347..5b6278572a 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 case "${VISUAL:-$EDITOR}" in *vim*) 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 fc78592ae0..9e69ada413 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) @@ -88,45 +91,63 @@ error_on_no_merge_candidates () { esac done + if test true = "$rebase" + then + op_type=rebase + op_prep=against + else + op_type=merge + op_prep=with + fi + curr_branch=${curr_branch#refs/heads/} upstream=$(git config "branch.$curr_branch.merge") remote=$(git config "branch.$curr_branch.remote") if [ $# -gt 1 ]; then - echo "There are no candidates for merging in the refs that you just fetched." + if [ "$rebase" = true ]; then + printf "There is no candidate for rebasing against " + else + printf "There are no candidates for merging " + fi + echo "among 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 "a branch. 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 "Please specify which remote branch you want to use on the command" echo "line and try again (e.g. 'git pull <repository> <refspec>')." echo "See git-pull(1) for details." 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" - echo "specify which branch you want to merge on the command line and" + echo "want to $op_type $op_prep, and 'branch.${curr_branch}.merge' in" + echo "your configuration file does not tell me, either. Please" + echo "specify which branch you want to use on the command line and" echo "try again (e.g. 'git pull <repository> <refspec>')." echo "See git-pull(1) for details." echo - echo "If you often merge with the same branch, you may want to" - echo "configure the following variables in your configuration" - echo "file:" + echo "If you often $op_type $op_prep the same branch, you may want to" + echo "use something like the following in your configuration file:" + echo + echo " [branch \"${curr_branch}\"]" + echo " remote = <nickname>" + echo " merge = <remote-ref>" + test rebase = "$op_type" && + echo " rebase = true" echo - echo " branch.${curr_branch}.remote = <nickname>" - echo " branch.${curr_branch}.merge = <remote-ref>" - echo " remote.<nickname>.url = <url>" - echo " remote.<nickname>.fetch = <refspec>" + echo " [remote \"<nickname>\"]" + echo " url = <url>" + echo " 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." + echo "Your configuration specifies to $op_type $op_prep the ref '${upstream#refs/heads/}'" + echo "from the remote, but no such ref was fetched." fi exit 1 } @@ -171,7 +192,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" || @@ -215,5 +236,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 3853b513b5..d52932878c 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 @@ -757,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 # @@ -770,7 +779,7 @@ EOF cp "$TODO" "$TODO".backup git_editor "$TODO" || - die "Could not execute editor" + die_abort "Could not execute editor" has_action "$TODO" || die_abort "Nothing to do" diff --git a/git-rebase.sh b/git-rebase.sh index 0ec435558f..b121f4537c 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -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 a0279de687..e05455f74c 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -71,7 +71,7 @@ git send-email [options] <file | directory | rev-list options > --suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, all. --[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on. --[no-]suppress-from * Send to self. Default off. - --[no-]chain-reply-to * Chain In-Reply-To: fields. Default on. + --[no-]chain-reply-to * Chain In-Reply-To: fields. Default off. --[no-]thread * Use In-Reply-To: field. Default on. Administering: @@ -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 { @@ -186,9 +187,11 @@ my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts); my ($validate, $confirm); my (@suppress_cc); +my $not_set_by_user = "true but not set by the user"; + my %config_bool_settings = ( "thread" => [\$thread, 1], - "chainreplyto" => [\$chain_reply_to, 1], + "chainreplyto" => [\$chain_reply_to, $not_set_by_user], "suppressfrom" => [\$suppress_from, undef], "signedoffbycc" => [\$signed_off_by_cc, undef], "signedoffcc" => [\$signed_off_by_cc, undef], # Deprecated @@ -213,6 +216,19 @@ my %config_settings = ( "from" => \$sender, ); +# Help users prepare for 1.7.0 +sub chain_reply_to { + if (defined $chain_reply_to && + $chain_reply_to eq $not_set_by_user) { + print STDERR + "In git 1.7.0, the default has changed to --no-chain-reply-to\n" . + "Set sendemail.chainreplyto configuration variable to true if\n" . + "you want to keep --chain-reply-to as your default.\n"; + $chain_reply_to = 0; + } + return $chain_reply_to; +} + # Handle Uncouth Termination sub signal_handler { @@ -861,7 +877,9 @@ X-Mailer: git-send-email $gitversion my @sendmail_parameters = ('-i', @recipients); my $raw_from = $sanitized_sender; - $raw_from = $envelope_sender if (defined $envelope_sender); + if (defined $envelope_sender && $envelope_sender ne "auto") { + $raw_from = $envelope_sender; + } $raw_from = extract_valid_address($raw_from); unshift (@sendmail_parameters, '-f', $raw_from) if(defined $envelope_sender); @@ -1156,7 +1174,7 @@ foreach my $t (@files) { # set up for the next message if ($thread && $message_was_sent && - ($chain_reply_to || !defined $reply_to || length($reply_to) == 0)) { + (chain_reply_to() || !defined $reply_to || length($reply_to) == 0)) { $reply_to = $message_id; if (length $references > 0) { $references .= "\n $message_id"; diff --git a/git-sh-setup.sh b/git-sh-setup.sh index aa07cc3d18..dfcb8078f5 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" '"$@"' } sane_grep () { diff --git a/git-stash.sh b/git-stash.sh index 4febbbfa5d..3a0685f189 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 [-k|--keep-index] [-q|--quiet] [<message>]] + or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]] or: $dashless clear" SUBDIRECTORY_OK=Yes @@ -205,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 () { @@ -383,11 +382,6 @@ test -n "$seen_non_option" || set "save" "$@" case "$1" in list) shift - if test $# = 0 - then - set x -n 10 - shift - fi list_stash "$@" ;; show) diff --git a/git-submodule.sh b/git-submodule.sh index b7ccd12d72..77d223292c 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>...] @@ -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 eb4b75aff3..650c9e5f02 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'); } @@ -389,9 +392,11 @@ sub cmd_clone { $path = $url; } $path = basename($url) if !defined $path || !length $path; + my $authors_absolute = $_authors ? File::Spec->rel2abs($_authors) : ""; cmd_init($url, $path); + command_oneline('config', 'svn.authorsfile', $authors_absolute) + if $_authors; Git::SVN::fetch_all($Git::SVN::default_repo_id); - command_oneline('config', 'svn.authorsfile', $_authors) if $_authors; } sub cmd_init { @@ -425,6 +430,7 @@ sub cmd_fetch { if (@_ > 1) { die "Usage: $0 fetch [--all] [--parent] [svn-remote]\n"; } + $Git::SVN::no_reuse_existing = undef; if ($_fetch_parent) { my ($url, $rev, $uuid, $gs) = working_head_info('HEAD'); unless ($gs) { @@ -657,7 +663,8 @@ sub cmd_branch { } $head ||= 'HEAD'; - my ($src, $rev, undef, $gs) = working_head_info($head); + my (undef, $rev, undef, $gs) = working_head_info($head); + my $src = $gs->full_url; my $remote = Git::SVN::read_all_remotes()->{$gs->{repo_id}}; my $allglobs = $remote->{ $_tag ? 'tags' : 'branches' }; @@ -769,6 +776,7 @@ sub cmd_rebase { $_fetch_all ? $gs->fetch_all : $gs->fetch; } command_noisy(rebase_cmd(), $gs->refname); + $gs->mkemptydirs; } sub cmd_show_ignore { @@ -830,6 +838,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; @@ -946,6 +960,7 @@ sub cmd_multi_init { } sub cmd_multi_fetch { + $Git::SVN::no_reuse_existing = undef; my $remotes = Git::SVN::read_all_remotes(); foreach my $repo_id (sort keys %$remotes) { if ($remotes->{$repo_id}->{url}) { @@ -1196,6 +1211,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 { @@ -1321,9 +1337,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 $!; { @@ -1390,8 +1405,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) { @@ -1621,6 +1635,7 @@ use Carp qw/croak/; use File::Path qw/mkpath/; use File::Copy qw/copy/; use IPC::Open3; +use Memoize; # core since 5.8.0, Jul 2002 my ($_gc_nr, $_gc_period); @@ -1728,7 +1743,11 @@ sub fetch_all { my $ra = Git::SVN::Ra->new($url); my $uuid = $ra->get_uuid; my $head = $ra->get_latest_revnum; - $ra->get_log("", $head, 0, 1, 0, 1, sub { $head = $_[1] }); + + # ignore errors, $head revision may not even exist anymore + eval { $ra->get_log("", $head, 0, 1, 0, 1, sub { $head = $_[1] }) }; + warn "W: $@\n" if $@; + my $base = defined $fetch ? $head : 0; # read the max revs for wildcard expansion (branches/*, tags/*) @@ -1765,7 +1784,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); @@ -1979,7 +1998,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#^\./?#); @@ -2433,12 +2452,6 @@ sub get_commit_parents { next if $seen{$p}; $seen{$p} = 1; push @ret, $p; - # MAXPARENT is defined to 16 in commit-tree.c: - last if @ret >= 16; - } - if (@tmp) { - die "r$log_entry->{revision}: No room for parents:\n\t", - join("\n\t", @tmp), "\n"; } @ret; } @@ -2725,6 +2738,61 @@ sub do_fetch { $self->make_log_entry($rev, \@parents, $ed); } +sub mkemptydirs { + my ($self, $r) = @_; + + sub scan { + my ($r, $empty_dirs, $line) = @_; + if (defined $r && $line =~ /^r(\d+)$/) { + return 0 if $1 > $r; + } elsif ($line =~ /^ \+empty_dir: (.+)$/) { + $empty_dirs->{$1} = 1; + } elsif ($line =~ /^ \-empty_dir: (.+)$/) { + my @d = grep {m[^\Q$1\E(/|$)]} (keys %$empty_dirs); + delete @$empty_dirs{@d}; + } + 1; # continue + }; + + my %empty_dirs = (); + my $gz_file = "$self->{dir}/unhandled.log.gz"; + if (-f $gz_file) { + if (!$can_compress) { + warn "Compress::Zlib could not be found; ", + "empty directories in $gz_file will not be read\n"; + } else { + my $gz = Compress::Zlib::gzopen($gz_file, "rb") or + die "Unable to open $gz_file: $!\n"; + my $line; + while ($gz->gzreadline($line) > 0) { + scan($r, \%empty_dirs, $line) or last; + } + $gz->gzclose; + } + } + + if (open my $fh, '<', "$self->{dir}/unhandled.log") { + binmode $fh or croak "binmode: $!"; + while (<$fh>) { + scan($r, \%empty_dirs, $_) or last; + } + close $fh; + } + + my $strip = qr/\A\Q$self->{path}\E(?:\/|$)/; + foreach my $d (sort keys %empty_dirs) { + $d = uri_decode($d); + $d =~ s/$strip//; + 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; @@ -2878,14 +2946,260 @@ 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, [ $rev, $commit ]; + } + } + } + # Ordering matters; highest-numbered commit merge tickets + # first, as they may account for later merge ticket additions + # or changes. + @known_parents = map {$_->[1]} sort {$b->[0] <=> $a->[0]} @known_parents; + 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; + } + } +} + +sub lookup_svn_merge { + my $uuid = shift; + my $url = shift; + my $merge = shift; + + 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"; + return; + } + my @ranges = split ",", $revs; + my ($tip, $tip_commit); + my @merged_commit_ranges; + # find the tip + for my $range ( @ranges ) { + my ($bottom, $top) = split "-", $range; + $top ||= $bottom; + my $bottom_commit = $gs->find_rev_after( $bottom, 1, $top ); + my $top_commit = $gs->find_rev_before( $top, 1, $bottom ); + + 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; + } + } + return ($tip_commit, @merged_commit_ranges); +} + +sub _rev_list { + my ($msg_fh, $ctx) = command_output_pipe( + "rev-list", @_, + ); + my @rv; + while ( <$msg_fh> ) { + chomp; + push @rv, $_; + } + command_close_pipe($msg_fh, $ctx); + @rv; +} + +sub check_cherry_pick { + my $base = shift; + my $tip = shift; + my @ranges = @_; + my %commits = map { $_ => 1 } + _rev_list("--no-merges", $tip, "--not", $base); + for my $range ( @ranges ) { + delete @commits{_rev_list($range)}; + } + return (keys %commits); +} + +BEGIN { + memoize 'lookup_svn_merge'; + memoize 'check_cherry_pick'; +} + +sub parents_exclude { + my $parents = shift; + my @commits = @_; + return unless @commits; + + my @excluded; + my $excluded; + do { + my @cmd = ('rev-list', "-1", @commits, "--not", @$parents ); + $excluded = command_oneline(@cmd); + if ( $excluded ) { + my @new; + my $found; + for my $commit ( @commits ) { + if ( $commit eq $excluded ) { + push @excluded, $commit; + $found++; + last; + } + else { + push @new, $commit; + } + } + die "saw commit '$excluded' in rev-list output, " + ."but we didn't ask for that commit (wanted: @commits --not @$parents)" + unless $found; + @commits = @new; + } + } + while ($excluded and @commits); + + return @excluded; +} + + +# 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 $url = $self->rewrite_root || $self->{url}; + my $uuid = $self->ra_uuid; + my %ranges; + for my $merge ( @merges ) { + my ($tip_commit, @ranges) = + lookup_svn_merge( $uuid, $url, $merge ); + unless (!$tip_commit or + grep { $_ eq $tip_commit } @$parents ) { + push @merge_tips, $tip_commit; + $ranges{$tip_commit} = \@ranges; + } else { + push @merge_tips, undef; + } + } + + my %excluded = map { $_ => 1 } + parents_exclude($parents, grep { defined } @merge_tips); + + # check merge tips for new parents + my @new_parents; + for my $merge_tip ( @merge_tips ) { + my $spec = shift @merges; + next unless $merge_tip and $excluded{$merge_tip}; + + my $ranges = $ranges{$merge_tip}; + + # check out 'new' tips + my $merge_base = command_oneline( + "merge-base", + @$parents, $merge_tip, + ); + + # double check that there are no missing non-merge commits + my (@incomplete) = check_cherry_pick( + $merge_base, $merge_tip, + @$ranges, + ); + + if ( @incomplete ) { + warn "W:svn cherry-pick ignored ($spec) - missing " + .@incomplete." commit(s) (eg $incomplete[0])\n"; + } else { + warn + "Found merge parent (svn:mergeinfo prop): ", + $merge_tip, "\n"; + push @new_parents, $merge_tip; + } + } + + # cater for merges which merge commits from multiple branches + if ( @new_parents > 1 ) { + for ( my $i = 0; $i <= $#new_parents; $i++ ) { + for ( my $j = 0; $j <= $#new_parents; $j++ ) { + next if $i == $j; + next unless $new_parents[$i]; + next unless $new_parents[$j]; + my $revs = command_oneline( + "rev-list", "-1", + "$new_parents[$i]..$new_parents[$j]", + ); + if ( !$revs ) { + undef($new_parents[$i]); + } + } + } + } + push @$parents, grep { defined } @new_parents; +} + 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; @@ -3411,6 +3725,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}; } @@ -3692,11 +4012,11 @@ sub delete_entry { } print "\tD\t$gpath/\n" unless $::_q; command_close_pipe($ls, $ctx); - $self->{empty}->{$path} = 0 } else { $self->{gii}->remove($gpath); print "\tD\t$gpath\n" unless $::_q; } + $self->{empty}->{$path} = 0; undef; } @@ -5029,10 +5349,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); @@ -6,7 +6,7 @@ const char git_usage_string[] = "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n" - " [-p|--paginate|--no-pager]\n" + " [-p|--paginate|--no-pager] [--no-replace-objects]\n" " [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]\n" " [--help] COMMAND [ARGS]"; @@ -87,6 +87,11 @@ 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; + setenv(NO_REPLACE_OBJECTS_ENVIRONMENT, "1", 1); + if (envchanged) + *envchanged = 1; } else if (!strcmp(cmd, "--git-dir")) { if (*argc < 2) { fprintf(stderr, "No directory given for --git-dir.\n" ); @@ -227,21 +232,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"); @@ -302,7 +310,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 }, diff --git a/git_remote_helpers/.gitignore b/git_remote_helpers/.gitignore new file mode 100644 index 0000000000..2247d5f95a --- /dev/null +++ b/git_remote_helpers/.gitignore @@ -0,0 +1,2 @@ +/build +/dist diff --git a/git_remote_helpers/Makefile b/git_remote_helpers/Makefile new file mode 100644 index 0000000000..c62dfd0f4d --- /dev/null +++ b/git_remote_helpers/Makefile @@ -0,0 +1,35 @@ +# +# Makefile for the git_remote_helpers python support modules +# +pysetupfile:=setup.py + +# Shell quote (do not use $(call) to accommodate ancient setups); +DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) + +ifndef PYTHON_PATH + PYTHON_PATH = /usr/bin/python +endif +ifndef prefix + prefix = $(HOME) +endif +ifndef V + QUIET = @ + QUIETSETUP = --quiet +endif + +PYLIBDIR=$(shell $(PYTHON_PATH) -c \ + "import sys; \ + print 'lib/python%i.%i/site-packages' % sys.version_info[:2]") + +all: $(pysetupfile) + $(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) build + +install: $(pysetupfile) + $(PYTHON_PATH) $(pysetupfile) install --prefix $(DESTDIR_SQ)$(prefix) + +instlibdir: $(pysetupfile) + @echo "$(DESTDIR_SQ)$(prefix)/$(PYLIBDIR)" + +clean: + $(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) clean -a + $(RM) *.pyo *.pyc diff --git a/git_remote_helpers/__init__.py b/git_remote_helpers/__init__.py new file mode 100644 index 0000000000..00f69cbeda --- /dev/null +++ b/git_remote_helpers/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +"""Support library package for git remote helpers. + +Git remote helpers are helper commands that interfaces with a non-git +repository to provide automatic import of non-git history into a Git +repository. + +This package provides the support library needed by these helpers.. +The following modules are included: + +- git.git - Interaction with Git repositories + +- util - General utility functionality use by the other modules in + this package, and also used directly by the helpers. +""" diff --git a/git_remote_helpers/git/__init__.py b/git_remote_helpers/git/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/git_remote_helpers/git/__init__.py diff --git a/git_remote_helpers/git/git.py b/git_remote_helpers/git/git.py new file mode 100644 index 0000000000..a383e6c08d --- /dev/null +++ b/git_remote_helpers/git/git.py @@ -0,0 +1,678 @@ +#!/usr/bin/env python + +"""Functionality for interacting with Git repositories. + +This module provides classes for interfacing with a Git repository. +""" + +import os +import re +import time +from binascii import hexlify +from cStringIO import StringIO +import unittest + +from git_remote_helpers.util import debug, error, die, start_command, run_command + + +def get_git_dir (): + """Return the path to the GIT_DIR for this repo.""" + args = ("git", "rev-parse", "--git-dir") + exit_code, output, errors = run_command(args) + if exit_code: + die("Failed to retrieve git dir") + assert not errors + return output.strip() + + +def parse_git_config (): + """Return a dict containing the parsed version of 'git config -l'.""" + exit_code, output, errors = run_command(("git", "config", "-z", "-l")) + if exit_code: + die("Failed to retrieve git configuration") + assert not errors + return dict([e.split('\n', 1) for e in output.split("\0") if e]) + + +def git_config_bool (value): + """Convert the given git config string value to True or False. + + Raise ValueError if the given string was not recognized as a + boolean value. + + """ + norm_value = str(value).strip().lower() + if norm_value in ("true", "1", "yes", "on", ""): + return True + if norm_value in ("false", "0", "no", "off", "none"): + return False + raise ValueError("Failed to parse '%s' into a boolean value" % (value)) + + +def valid_git_ref (ref_name): + """Return True iff the given ref name is a valid git ref name.""" + # The following is a reimplementation of the git check-ref-format + # command. The rules were derived from the git check-ref-format(1) + # manual page. This code should be replaced by a call to + # check_ref_format() in the git library, when such is available. + if ref_name.endswith('/') or \ + ref_name.startswith('.') or \ + ref_name.count('/.') or \ + ref_name.count('..') or \ + ref_name.endswith('.lock'): + return False + for c in ref_name: + if ord(c) < 0x20 or ord(c) == 0x7f or c in " ~^:?*[": + return False + return True + + +class GitObjectFetcher(object): + + """Provide parsed access to 'git cat-file --batch'. + + This provides a read-only interface to the Git object database. + + """ + + def __init__ (self): + """Initiate a 'git cat-file --batch' session.""" + self.queue = [] # List of object names to be submitted + self.in_transit = None # Object name currently in transit + + # 'git cat-file --batch' produces binary output which is likely + # to be corrupted by the default "rU"-mode pipe opened by + # start_command. (Mode == "rU" does universal new-line + # conversion, which mangles carriage returns.) Therefore, we + # open an explicitly binary-safe pipe for transferring the + # output from 'git cat-file --batch'. + pipe_r_fd, pipe_w_fd = os.pipe() + pipe_r = os.fdopen(pipe_r_fd, "rb") + pipe_w = os.fdopen(pipe_w_fd, "wb") + self.proc = start_command(("git", "cat-file", "--batch"), + stdout = pipe_w) + self.f = pipe_r + + def __del__ (self): + """Verify completed communication with 'git cat-file --batch'.""" + assert not self.queue + assert self.in_transit is None + self.proc.stdin.close() + assert self.proc.wait() == 0 # Zero exit code + assert self.f.read() == "" # No remaining output + + def _submit_next_object (self): + """Submit queue items to the 'git cat-file --batch' process. + + If there are items in the queue, and there is currently no item + currently in 'transit', then pop the first item off the queue, + and submit it. + + """ + if self.queue and self.in_transit is None: + self.in_transit = self.queue.pop(0) + print >> self.proc.stdin, self.in_transit[0] + + def push (self, obj, callback): + """Push the given object name onto the queue. + + The given callback function will at some point in the future + be called exactly once with the following arguments: + - self - this GitObjectFetcher instance + - obj - the object name provided to push() + - sha1 - the SHA1 of the object, if 'None' obj is missing + - t - the type of the object (tag/commit/tree/blob) + - size - the size of the object in bytes + - data - the object contents + + """ + self.queue.append((obj, callback)) + self._submit_next_object() # (Re)start queue processing + + def process_next_entry (self): + """Read the next entry off the queue and invoke callback.""" + obj, cb = self.in_transit + self.in_transit = None + header = self.f.readline() + if header == "%s missing\n" % (obj): + cb(self, obj, None, None, None, None) + return + sha1, t, size = header.split(" ") + assert len(sha1) == 40 + assert t in ("tag", "commit", "tree", "blob") + assert size.endswith("\n") + size = int(size.strip()) + data = self.f.read(size) + assert self.f.read(1) == "\n" + cb(self, obj, sha1, t, size, data) + self._submit_next_object() + + def process (self): + """Process the current queue until empty.""" + while self.in_transit is not None: + self.process_next_entry() + + # High-level convenience methods: + + def get_sha1 (self, objspec): + """Return the SHA1 of the object specified by 'objspec'. + + Return None if 'objspec' does not specify an existing object. + + """ + class _ObjHandler(object): + """Helper class for getting the returned SHA1.""" + def __init__ (self, parser): + self.parser = parser + self.sha1 = None + + def __call__ (self, parser, obj, sha1, t, size, data): + # FIXME: Many unused arguments. Could this be cheaper? + assert parser == self.parser + self.sha1 = sha1 + + handler = _ObjHandler(self) + self.push(objspec, handler) + self.process() + return handler.sha1 + + def open_obj (self, objspec): + """Return a file object wrapping the contents of a named object. + + The caller is responsible for calling .close() on the returned + file object. + + Raise KeyError if 'objspec' does not exist in the repo. + + """ + class _ObjHandler(object): + """Helper class for parsing the returned git object.""" + def __init__ (self, parser): + """Set up helper.""" + self.parser = parser + self.contents = StringIO() + self.err = None + + def __call__ (self, parser, obj, sha1, t, size, data): + """Git object callback (see GitObjectFetcher documentation).""" + assert parser == self.parser + if not sha1: # Missing object + self.err = "Missing object '%s'" % obj + else: + assert size == len(data) + self.contents.write(data) + + handler = _ObjHandler(self) + self.push(objspec, handler) + self.process() + if handler.err: + raise KeyError(handler.err) + handler.contents.seek(0) + return handler.contents + + def walk_tree (self, tree_objspec, callback, prefix = ""): + """Recursively walk the given Git tree object. + + Recursively walk all subtrees of the given tree object, and + invoke the given callback passing three arguments: + (path, mode, data) with the path, permission bits, and contents + of all the blobs found in the entire tree structure. + + """ + class _ObjHandler(object): + """Helper class for walking a git tree structure.""" + def __init__ (self, parser, cb, path, mode = None): + """Set up helper.""" + self.parser = parser + self.cb = cb + self.path = path + self.mode = mode + self.err = None + + def parse_tree (self, treedata): + """Parse tree object data, yield tree entries. + + Each tree entry is a 3-tuple (mode, sha1, path) + + self.path is prepended to all paths yielded + from this method. + + """ + while treedata: + mode = int(treedata[:6], 10) + # Turn 100xxx into xxx + if mode > 100000: + mode -= 100000 + assert treedata[6] == " " + i = treedata.find("\0", 7) + assert i > 0 + path = treedata[7:i] + sha1 = hexlify(treedata[i + 1: i + 21]) + yield (mode, sha1, self.path + path) + treedata = treedata[i + 21:] + + def __call__ (self, parser, obj, sha1, t, size, data): + """Git object callback (see GitObjectFetcher documentation).""" + assert parser == self.parser + if not sha1: # Missing object + self.err = "Missing object '%s'" % (obj) + return + assert size == len(data) + if t == "tree": + if self.path: + self.path += "/" + # Recurse into all blobs and subtrees + for m, s, p in self.parse_tree(data): + parser.push(s, + self.__class__(self.parser, self.cb, p, m)) + elif t == "blob": + self.cb(self.path, self.mode, data) + else: + raise ValueError("Unknown object type '%s'" % (t)) + + self.push(tree_objspec, _ObjHandler(self, callback, prefix)) + self.process() + + +class GitRefMap(object): + + """Map Git ref names to the Git object names they currently point to. + + Behaves like a dictionary of Git ref names -> Git object names. + + """ + + def __init__ (self, obj_fetcher): + """Create a new Git ref -> object map.""" + self.obj_fetcher = obj_fetcher + self._cache = {} # dict: refname -> objname + + def _load (self, ref): + """Retrieve the object currently bound to the given ref. + + The name of the object pointed to by the given ref is stored + into this mapping, and also returned. + + """ + if ref not in self._cache: + self._cache[ref] = self.obj_fetcher.get_sha1(ref) + return self._cache[ref] + + def __contains__ (self, refname): + """Return True if the given refname is present in this cache.""" + return bool(self._load(refname)) + + def __getitem__ (self, refname): + """Return the git object name pointed to by the given refname.""" + commit = self._load(refname) + if commit is None: + raise KeyError("Unknown ref '%s'" % (refname)) + return commit + + def get (self, refname, default = None): + """Return the git object name pointed to by the given refname.""" + commit = self._load(refname) + if commit is None: + return default + return commit + + +class GitFICommit(object): + + """Encapsulate the data in a Git fast-import commit command.""" + + SHA1RE = re.compile(r'^[0-9a-f]{40}$') + + @classmethod + def parse_mode (cls, mode): + """Verify the given git file mode, and return it as a string.""" + assert mode in (644, 755, 100644, 100755, 120000) + return "%i" % (mode) + + @classmethod + def parse_objname (cls, objname): + """Return the given object name (or mark number) as a string.""" + if isinstance(objname, int): # Object name is a mark number + assert objname > 0 + return ":%i" % (objname) + + # No existence check is done, only checks for valid format + assert cls.SHA1RE.match(objname) # Object name is valid SHA1 + return objname + + @classmethod + def quote_path (cls, path): + """Return a quoted version of the given path.""" + path = path.replace("\\", "\\\\") + path = path.replace("\n", "\\n") + path = path.replace('"', '\\"') + return '"%s"' % (path) + + @classmethod + def parse_path (cls, path): + """Verify that the given path is valid, and quote it, if needed.""" + assert not isinstance(path, int) # Cannot be a mark number + + # These checks verify the rules on the fast-import man page + assert not path.count("//") + assert not path.endswith("/") + assert not path.startswith("/") + assert not path.count("/./") + assert not path.count("/../") + assert not path.endswith("/.") + assert not path.endswith("/..") + assert not path.startswith("./") + assert not path.startswith("../") + + if path.count('"') + path.count('\n') + path.count('\\'): + return cls.quote_path(path) + return path + + def __init__ (self, name, email, timestamp, timezone, message): + """Create a new Git fast-import commit, with the given metadata.""" + self.name = name + self.email = email + self.timestamp = timestamp + self.timezone = timezone + self.message = message + self.pathops = [] # List of path operations in this commit + + def modify (self, mode, blobname, path): + """Add a file modification to this Git fast-import commit.""" + self.pathops.append(("M", + self.parse_mode(mode), + self.parse_objname(blobname), + self.parse_path(path))) + + def delete (self, path): + """Add a file deletion to this Git fast-import commit.""" + self.pathops.append(("D", self.parse_path(path))) + + def copy (self, path, newpath): + """Add a file copy to this Git fast-import commit.""" + self.pathops.append(("C", + self.parse_path(path), + self.parse_path(newpath))) + + def rename (self, path, newpath): + """Add a file rename to this Git fast-import commit.""" + self.pathops.append(("R", + self.parse_path(path), + self.parse_path(newpath))) + + def note (self, blobname, commit): + """Add a note object to this Git fast-import commit.""" + self.pathops.append(("N", + self.parse_objname(blobname), + self.parse_objname(commit))) + + def deleteall (self): + """Delete all files in this Git fast-import commit.""" + self.pathops.append("deleteall") + + +class TestGitFICommit(unittest.TestCase): + + """GitFICommit selftests.""" + + def test_basic (self): + """GitFICommit basic selftests.""" + + def expect_fail (method, data): + """Verify that the method(data) raises an AssertionError.""" + try: + method(data) + except AssertionError: + return + raise AssertionError("Failed test for invalid data '%s(%s)'" % + (method.__name__, repr(data))) + + def test_parse_mode (self): + """GitFICommit.parse_mode() selftests.""" + self.assertEqual(GitFICommit.parse_mode(644), "644") + self.assertEqual(GitFICommit.parse_mode(755), "755") + self.assertEqual(GitFICommit.parse_mode(100644), "100644") + self.assertEqual(GitFICommit.parse_mode(100755), "100755") + self.assertEqual(GitFICommit.parse_mode(120000), "120000") + self.assertRaises(AssertionError, GitFICommit.parse_mode, 0) + self.assertRaises(AssertionError, GitFICommit.parse_mode, 123) + self.assertRaises(AssertionError, GitFICommit.parse_mode, 600) + self.assertRaises(AssertionError, GitFICommit.parse_mode, "644") + self.assertRaises(AssertionError, GitFICommit.parse_mode, "abc") + + def test_parse_objname (self): + """GitFICommit.parse_objname() selftests.""" + self.assertEqual(GitFICommit.parse_objname(1), ":1") + self.assertRaises(AssertionError, GitFICommit.parse_objname, 0) + self.assertRaises(AssertionError, GitFICommit.parse_objname, -1) + self.assertEqual(GitFICommit.parse_objname("0123456789" * 4), + "0123456789" * 4) + self.assertEqual(GitFICommit.parse_objname("2468abcdef" * 4), + "2468abcdef" * 4) + self.assertRaises(AssertionError, GitFICommit.parse_objname, + "abcdefghij" * 4) + + def test_parse_path (self): + """GitFICommit.parse_path() selftests.""" + self.assertEqual(GitFICommit.parse_path("foo/bar"), "foo/bar") + self.assertEqual(GitFICommit.parse_path("path/with\n and \" in it"), + '"path/with\\n and \\" in it"') + self.assertRaises(AssertionError, GitFICommit.parse_path, 1) + self.assertRaises(AssertionError, GitFICommit.parse_path, 0) + self.assertRaises(AssertionError, GitFICommit.parse_path, -1) + self.assertRaises(AssertionError, GitFICommit.parse_path, "foo//bar") + self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/") + self.assertRaises(AssertionError, GitFICommit.parse_path, "/foo/bar") + self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/./bar") + self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/../bar") + self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/.") + self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/..") + self.assertRaises(AssertionError, GitFICommit.parse_path, "./foo/bar") + self.assertRaises(AssertionError, GitFICommit.parse_path, "../foo/bar") + + +class GitFastImport(object): + + """Encapsulate communication with git fast-import.""" + + def __init__ (self, f, obj_fetcher, last_mark = 0): + """Set up self to communicate with a fast-import process through f.""" + self.f = f # File object where fast-import stream is written + self.obj_fetcher = obj_fetcher # GitObjectFetcher instance + self.next_mark = last_mark + 1 # Next mark number + self.refs = set() # Keep track of the refnames we've seen + + def comment (self, s): + """Write the given comment in the fast-import stream.""" + assert "\n" not in s, "Malformed comment: '%s'" % (s) + self.f.write("# %s\n" % (s)) + + def commit (self, ref, commitdata): + """Make a commit on the given ref, with the given GitFICommit. + + Return the mark number identifying this commit. + + """ + self.f.write("""\ +commit %(ref)s +mark :%(mark)i +committer %(name)s <%(email)s> %(timestamp)i %(timezone)s +data %(msgLength)i +%(msg)s +""" % { + 'ref': ref, + 'mark': self.next_mark, + 'name': commitdata.name, + 'email': commitdata.email, + 'timestamp': commitdata.timestamp, + 'timezone': commitdata.timezone, + 'msgLength': len(commitdata.message), + 'msg': commitdata.message, +}) + + if ref not in self.refs: + self.refs.add(ref) + parent = ref + "^0" + if self.obj_fetcher.get_sha1(parent): + self.f.write("from %s\n" % (parent)) + + for op in commitdata.pathops: + self.f.write(" ".join(op)) + self.f.write("\n") + self.f.write("\n") + retval = self.next_mark + self.next_mark += 1 + return retval + + def blob (self, data): + """Import the given blob. + + Return the mark number identifying this blob. + + """ + self.f.write("blob\nmark :%i\ndata %i\n%s\n" % + (self.next_mark, len(data), data)) + retval = self.next_mark + self.next_mark += 1 + return retval + + def reset (self, ref, objname): + """Reset the given ref to point at the given Git object.""" + self.f.write("reset %s\nfrom %s\n\n" % + (ref, GitFICommit.parse_objname(objname))) + if ref not in self.refs: + self.refs.add(ref) + + +class GitNotes(object): + + """Encapsulate access to Git notes. + + Simulates a dictionary of object name (SHA1) -> Git note mappings. + + """ + + def __init__ (self, notes_ref, obj_fetcher): + """Create a new Git notes interface, bound to the given notes ref.""" + self.notes_ref = notes_ref + self.obj_fetcher = obj_fetcher # Used to get objects from repo + self.imports = [] # list: (objname, note data blob name) tuples + + def __del__ (self): + """Verify that self.commit_notes() was called before destruction.""" + if self.imports: + error("Missing call to self.commit_notes().") + error("%i notes are not committed!", len(self.imports)) + + def _load (self, objname): + """Return the note data associated with the given git object. + + The note data is returned in string form. If no note is found + for the given object, None is returned. + + """ + try: + f = self.obj_fetcher.open_obj("%s:%s" % (self.notes_ref, objname)) + ret = f.read() + f.close() + except KeyError: + ret = None + return ret + + def __getitem__ (self, objname): + """Return the note contents associated with the given object. + + Raise KeyError if given object has no associated note. + + """ + blobdata = self._load(objname) + if blobdata is None: + raise KeyError("Object '%s' has no note" % (objname)) + return blobdata + + def get (self, objname, default = None): + """Return the note contents associated with the given object. + + Return given default if given object has no associated note. + + """ + blobdata = self._load(objname) + if blobdata is None: + return default + return blobdata + + def import_note (self, objname, data, gfi): + """Tell git fast-import to store data as a note for objname. + + This method uses the given GitFastImport object to create a + blob containing the given note data. Also an entry mapping the + given object name to the created blob is stored until + commit_notes() is called. + + Note that this method only works if it is later followed by a + call to self.commit_notes() (which produces the note commit + that refers to the blob produced here). + + """ + if not data.endswith("\n"): + data += "\n" + gfi.comment("Importing note for object %s" % (objname)) + mark = gfi.blob(data) + self.imports.append((objname, mark)) + + def commit_notes (self, gfi, author, message): + """Produce a git fast-import note commit for the imported notes. + + This method uses the given GitFastImport object to create a + commit on the notes ref, introducing the notes previously + submitted to import_note(). + + """ + if not self.imports: + return + commitdata = GitFICommit(author[0], author[1], + time.time(), "0000", message) + for objname, blobname in self.imports: + assert isinstance(objname, int) and objname > 0 + assert isinstance(blobname, int) and blobname > 0 + commitdata.note(blobname, objname) + gfi.commit(self.notes_ref, commitdata) + self.imports = [] + + +class GitCachedNotes(GitNotes): + + """Encapsulate access to Git notes (cached version). + + Only use this class if no caching is done at a higher level. + + Simulates a dictionary of object name (SHA1) -> Git note mappings. + + """ + + def __init__ (self, notes_ref, obj_fetcher): + """Set up a caching wrapper around GitNotes.""" + GitNotes.__init__(self, notes_ref, obj_fetcher) + self._cache = {} # Cache: object name -> note data + + def __del__ (self): + """Verify that GitNotes' destructor is called.""" + GitNotes.__del__(self) + + def _load (self, objname): + """Extend GitNotes._load() with a local objname -> note cache.""" + if objname not in self._cache: + self._cache[objname] = GitNotes._load(self, objname) + return self._cache[objname] + + def import_note (self, objname, data, gfi): + """Extend GitNotes.import_note() with a local objname -> note cache.""" + if not data.endswith("\n"): + data += "\n" + assert objname not in self._cache + self._cache[objname] = data + GitNotes.import_note(self, objname, data, gfi) + + +if __name__ == '__main__': + unittest.main() diff --git a/git_remote_helpers/setup.py b/git_remote_helpers/setup.py new file mode 100644 index 0000000000..4d434b65cb --- /dev/null +++ b/git_remote_helpers/setup.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +"""Distutils build/install script for the git_remote_helpers package.""" + +from distutils.core import setup + +setup( + name = 'git_remote_helpers', + version = '0.1.0', + description = 'Git remote helper program for non-git repositories', + license = 'GPLv2', + author = 'The Git Community', + author_email = 'git@vger.kernel.org', + url = 'http://www.git-scm.com/', + package_dir = {'git_remote_helpers': ''}, + packages = ['git_remote_helpers', 'git_remote_helpers.git'], +) diff --git a/git_remote_helpers/util.py b/git_remote_helpers/util.py new file mode 100644 index 0000000000..dce83e6066 --- /dev/null +++ b/git_remote_helpers/util.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python + +"""Misc. useful functionality used by the rest of this package. + +This module provides common functionality used by the other modules in +this package. + +""" + +import sys +import os +import subprocess + + +# Whether or not to show debug messages +DEBUG = False + +def notify(msg, *args): + """Print a message to stderr.""" + print >> sys.stderr, msg % args + +def debug (msg, *args): + """Print a debug message to stderr when DEBUG is enabled.""" + if DEBUG: + print >> sys.stderr, msg % args + +def error (msg, *args): + """Print an error message to stderr.""" + print >> sys.stderr, "ERROR:", msg % args + +def warn(msg, *args): + """Print a warning message to stderr.""" + print >> sys.stderr, "warning:", msg % args + +def die (msg, *args): + """Print as error message to stderr and exit the program.""" + error(msg, *args) + sys.exit(1) + + +class ProgressIndicator(object): + + """Simple progress indicator. + + Displayed as a spinning character by default, but can be customized + by passing custom messages that overrides the spinning character. + + """ + + States = ("|", "/", "-", "\\") + + def __init__ (self, prefix = "", f = sys.stdout): + """Create a new ProgressIndicator, bound to the given file object.""" + self.n = 0 # Simple progress counter + self.f = f # Progress is written to this file object + self.prev_len = 0 # Length of previous msg (to be overwritten) + self.prefix = prefix # Prefix prepended to each progress message + self.prefix_lens = [] # Stack of prefix string lengths + + def pushprefix (self, prefix): + """Append the given prefix onto the prefix stack.""" + self.prefix_lens.append(len(self.prefix)) + self.prefix += prefix + + def popprefix (self): + """Remove the last prefix from the prefix stack.""" + prev_len = self.prefix_lens.pop() + self.prefix = self.prefix[:prev_len] + + def __call__ (self, msg = None, lf = False): + """Indicate progress, possibly with a custom message.""" + if msg is None: + msg = self.States[self.n % len(self.States)] + msg = self.prefix + msg + print >> self.f, "\r%-*s" % (self.prev_len, msg), + self.prev_len = len(msg.expandtabs()) + if lf: + print >> self.f + self.prev_len = 0 + self.n += 1 + + def finish (self, msg = "done", noprefix = False): + """Finalize progress indication with the given message.""" + if noprefix: + self.prefix = "" + self(msg, True) + + +def start_command (args, cwd = None, shell = False, add_env = None, + stdin = subprocess.PIPE, stdout = subprocess.PIPE, + stderr = subprocess.PIPE): + """Start the given command, and return a subprocess object. + + This provides a simpler interface to the subprocess module. + + """ + env = None + if add_env is not None: + env = os.environ.copy() + env.update(add_env) + return subprocess.Popen(args, bufsize = 1, stdin = stdin, stdout = stdout, + stderr = stderr, cwd = cwd, shell = shell, + env = env, universal_newlines = True) + + +def run_command (args, cwd = None, shell = False, add_env = None, + flag_error = True): + """Run the given command to completion, and return its results. + + This provides a simpler interface to the subprocess module. + + The results are formatted as a 3-tuple: (exit_code, output, errors) + + If flag_error is enabled, Error messages will be produced if the + subprocess terminated with a non-zero exit code and/or stderr + output. + + The other arguments are passed on to start_command(). + + """ + process = start_command(args, cwd, shell, add_env) + (output, errors) = process.communicate() + exit_code = process.returncode + if flag_error and errors: + error("'%s' returned errors:\n---\n%s---", " ".join(args), errors) + if flag_error and exit_code: + error("'%s' returned exit code %i", " ".join(args), exit_code) + return (exit_code, output, errors) + + +def file_reader_method (missing_ok = False): + """Decorator for simplifying reading of files. + + If missing_ok is True, a failure to open a file for reading will + not raise the usual IOError, but instead the wrapped method will be + called with f == None. The method must in this case properly + handle f == None. + + """ + def _wrap (method): + """Teach given method to handle both filenames and file objects. + + The given method must take a file object as its second argument + (the first argument being 'self', of course). This decorator + will take a filename given as the second argument and promote + it to a file object. + + """ + def _wrapped_method (self, filename, *args, **kwargs): + if isinstance(filename, file): + f = filename + else: + try: + f = open(filename, 'r') + except IOError: + if missing_ok: + f = None + else: + raise + try: + return method(self, f, *args, **kwargs) + finally: + if not isinstance(filename, file) and f: + f.close() + return _wrapped_method + return _wrap + + +def file_writer_method (method): + """Decorator for simplifying writing of files. + + Enables the given method to handle both filenames and file objects. + + The given method must take a file object as its second argument + (the first argument being 'self', of course). This decorator will + take a filename given as the second argument and promote it to a + file object. + + """ + def _new_method (self, filename, *args, **kwargs): + if isinstance(filename, file): + f = filename + else: + # Make sure the containing directory exists + parent_dir = os.path.dirname(filename) + if not os.path.isdir(parent_dir): + os.makedirs(parent_dir) + f = open(filename, 'w') + try: + return method(self, f, *args, **kwargs) + finally: + if not isinstance(filename, file): + f.close() + return _new_method diff --git a/gitk-git/gitk b/gitk-git/gitk index a0214b7004..364c7a84cb 100644 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -2,11 +2,13 @@ # Tcl ignores the next line -*- tcl -*- \ exec wish "$0" -- "$@" -# Copyright © 2005-2008 Paul Mackerras. All rights reserved. +# Copyright © 2005-2009 Paul Mackerras. All rights reserved. # This program is free software; it may be used, copied, modified # and distributed under the terms of the GNU General Public Licence, # either version 2, or (at your option) any later version. +package require Tk + proc gitdir {} { global env if {[info exists env(GIT_DIR)]} { @@ -265,7 +267,7 @@ proc parseviewrevs {view revs} { } lappend badrev $line } - } + } error_popup "[mc "Error parsing revisions:"] $err" return {} } @@ -987,6 +989,18 @@ proc removefakerow {id} { drawvisible } +proc real_children {vp} { + global children nullid nullid2 + + set kids {} + foreach id $children($vp) { + if {$id ne $nullid && $id ne $nullid2} { + lappend kids $id + } + } + return $kids +} + proc first_real_child {vp} { global children nullid nullid2 @@ -1769,6 +1783,15 @@ proc removehead {id name} { unset headids($name) } +proc ttk_toplevel {w args} { + global use_ttk + eval [linsert $args 0 ::toplevel $w] + if {$use_ttk} { + place [ttk::frame $w._toplevel_background] -x 0 -y 0 -relwidth 1 -relheight 1 + } + return $w +} + proc make_transient {window origin} { global have_tk85 @@ -1787,10 +1810,13 @@ proc make_transient {window origin} { } } -proc show_error {w top msg} { +proc show_error {w top msg {mc mc}} { + global NS + if {![info exists NS]} {set NS ""} + if {[wm state $top] eq "withdrawn"} { wm deiconify $top } message $w.m -text $msg -justify center -aspect 400 pack $w.m -side top -fill x -padx 20 -pady 20 - button $w.ok -text [mc OK] -command "destroy $top" + ${NS}::button $w.ok -default active -text [$mc OK] -command "destroy $top" pack $w.ok -side bottom -fill x bind $top <Visibility> "grab $top; focus $top" bind $top <Key-Return> "destroy $top" @@ -1800,45 +1826,56 @@ proc show_error {w top msg} { } proc error_popup {msg {owner .}} { - set w .error - toplevel $w - make_transient $w $owner - show_error $w $w $msg + if {[tk windowingsystem] eq "win32"} { + tk_messageBox -icon error -type ok -title [wm title .] \ + -parent $owner -message $msg + } else { + set w .error + ttk_toplevel $w + make_transient $w $owner + show_error $w $w $msg + } } proc confirm_popup {msg {owner .}} { - global confirm_ok + global confirm_ok NS set confirm_ok 0 set w .confirm - toplevel $w + ttk_toplevel $w make_transient $w $owner message $w.m -text $msg -justify center -aspect 400 pack $w.m -side top -fill x -padx 20 -pady 20 - button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w" + ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w" pack $w.ok -side left -fill x - button $w.cancel -text [mc Cancel] -command "destroy $w" + ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w" pack $w.cancel -side right -fill x bind $w <Visibility> "grab $w; focus $w" bind $w <Key-Return> "set confirm_ok 1; destroy $w" bind $w <Key-space> "set confirm_ok 1; destroy $w" bind $w <Key-Escape> "destroy $w" + tk::PlaceWindow $w widget $owner tkwait window $w return $confirm_ok } proc setoptions {} { - option add *Panedwindow.showHandle 1 startupFile - option add *Panedwindow.sashRelief raised startupFile + if {[tk windowingsystem] ne "win32"} { + option add *Panedwindow.showHandle 1 startupFile + option add *Panedwindow.sashRelief raised startupFile + if {[tk windowingsystem] ne "aqua"} { + option add *Menu.font uifont startupFile + } + } else { + option add *Menu.TearOff 0 startupFile + } option add *Button.font uifont startupFile option add *Checkbutton.font uifont startupFile option add *Radiobutton.font uifont startupFile - if {[tk windowingsystem] ne "aqua"} { - option add *Menu.font uifont startupFile - } option add *Menubutton.font uifont startupFile option add *Label.font uifont startupFile option add *Message.font uifont startupFile option add *Entry.font uifont startupFile + option add *Labelframe.font uifont startupFile } # Make a menu and submenus. @@ -1895,6 +1932,22 @@ proc mca {str} { return [string map {&& & & {}} [mc $str]] } +proc makedroplist {w varname args} { + global use_ttk + if {$use_ttk} { + set width 0 + foreach label $args { + set cx [string length $label] + if {$cx > $width} {set width $cx} + } + set gm [ttk::combobox $w -width $width -state readonly\ + -textvariable $varname -values $args] + } else { + set gm [eval [linsert $args 0 tk_optionMenu $w $varname]] + } + return $gm +} + proc makewindow {} { global canv canv2 canv3 linespc charspc ctext cflist cscroll global tabstop @@ -1910,7 +1963,7 @@ proc makewindow {} { global headctxmenu progresscanv progressitem progresscoords statusw global fprogitem fprogcoord lastprogupdate progupdatepending global rprogitem rprogcoord rownumsel numcommits - global have_tk85 + global have_tk85 use_ttk NS # The "mc" arguments here are purely so that xgettext # sees the following string as needing to be translated @@ -1962,8 +2015,13 @@ proc makewindow {} { makemenu .bar $bar . configure -menu .bar + if {$use_ttk} { + # cover the non-themed toplevel with a themed frame. + place [ttk::frame ._main_background] -x 0 -y 0 -relwidth 1 -relheight 1 + } + # the gui has upper and lower half, parts of a paned window. - panedwindow .ctop -orient vertical + ${NS}::panedwindow .ctop -orient vertical # possibly use assumed geometry if {![info exists geometry(pwsash0)]} { @@ -1971,14 +2029,17 @@ proc makewindow {} { set geometry(topwidth) [expr {80 * $charspc}] set geometry(botheight) [expr {15 * $linespc}] set geometry(botwidth) [expr {50 * $charspc}] - set geometry(pwsash0) "[expr {40 * $charspc}] 2" - set geometry(pwsash1) "[expr {60 * $charspc}] 2" + set geometry(pwsash0) [list [expr {40 * $charspc}] 2] + set geometry(pwsash1) [list [expr {60 * $charspc}] 2] } # the upper half will have a paned window, a scroll bar to the right, and some stuff below - frame .tf -height $geometry(topheight) -width $geometry(topwidth) - frame .tf.histframe - panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4 + ${NS}::frame .tf -height $geometry(topheight) -width $geometry(topwidth) + ${NS}::frame .tf.histframe + ${NS}::panedwindow .tf.histframe.pwclist -orient horizontal + if {!$use_ttk} { + .tf.histframe.pwclist configure -sashpad 0 -handlesize 4 + } # create three canvases set cscroll .tf.histframe.csb @@ -1998,19 +2059,28 @@ proc makewindow {} { -selectbackground $selectbgcolor \ -background $bgcolor -bd 0 -yscrollincr $linespc .tf.histframe.pwclist add $canv3 - eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0) - eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1) + if {$use_ttk} { + bind .tf.histframe.pwclist <Map> { + bind %W <Map> {} + .tf.histframe.pwclist sashpos 1 [lindex $::geometry(pwsash1) 0] + .tf.histframe.pwclist sashpos 0 [lindex $::geometry(pwsash0) 0] + } + } else { + eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0) + eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1) + } # a scroll bar to rule them - scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0 + ${NS}::scrollbar $cscroll -command {allcanvs yview} + if {!$use_ttk} {$cscroll configure -highlightthickness 0} pack $cscroll -side right -fill y bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w} lappend bglist $canv $canv2 $canv3 pack .tf.histframe.pwclist -fill both -expand 1 -side left # we have two button bars at bottom of top frame. Bar 1 - frame .tf.bar - frame .tf.lbar -height 15 + ${NS}::frame .tf.bar + ${NS}::frame .tf.lbar -height 15 set sha1entry .tf.bar.sha1 set entries $sha1entry @@ -2019,7 +2089,7 @@ proc makewindow {} { -command gotocommit -width 8 $sha1but conf -disabledforeground [$sha1but cget -foreground] pack .tf.bar.sha1label -side left - entry $sha1entry -width 40 -font textfont -textvariable sha1string + ${NS}::entry $sha1entry -width 40 -font textfont -textvariable sha1string trace add variable sha1string write sha1change pack $sha1entry -side left -pady 2 @@ -2039,36 +2109,43 @@ proc makewindow {} { 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c, 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01}; } - button .tf.bar.leftbut -image bm-left -command goback \ + ${NS}::button .tf.bar.leftbut -image bm-left -command goback \ -state disabled -width 26 pack .tf.bar.leftbut -side left -fill y - button .tf.bar.rightbut -image bm-right -command goforw \ + ${NS}::button .tf.bar.rightbut -image bm-right -command goforw \ -state disabled -width 26 pack .tf.bar.rightbut -side left -fill y - label .tf.bar.rowlabel -text [mc "Row"] + ${NS}::label .tf.bar.rowlabel -text [mc "Row"] set rownumsel {} - label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \ + ${NS}::label .tf.bar.rownum -width 7 -textvariable rownumsel \ -relief sunken -anchor e - label .tf.bar.rowlabel2 -text "/" - label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \ + ${NS}::label .tf.bar.rowlabel2 -text "/" + ${NS}::label .tf.bar.numcommits -width 7 -textvariable numcommits \ -relief sunken -anchor e pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \ -side left + if {!$use_ttk} { + foreach w {rownum numcommits} {.tf.bar.$w configure -font textfont} + } global selectedline trace add variable selectedline write selectedline_change # Status label and progress bar set statusw .tf.bar.status - label $statusw -width 15 -relief sunken + ${NS}::label $statusw -width 15 -relief sunken pack $statusw -side left -padx 5 - set h [expr {[font metrics uifont -linespace] + 2}] - set progresscanv .tf.bar.progress - canvas $progresscanv -relief sunken -height $h -borderwidth 2 - set progressitem [$progresscanv create rect -1 0 0 $h -fill green] - set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow] - set rprogitem [$progresscanv create rect -1 0 0 $h -fill red] - pack $progresscanv -side right -expand 1 -fill x + if {$use_ttk} { + set progresscanv [ttk::progressbar .tf.bar.progress] + } else { + set h [expr {[font metrics uifont -linespace] + 2}] + set progresscanv .tf.bar.progress + canvas $progresscanv -relief sunken -height $h -borderwidth 2 + set progressitem [$progresscanv create rect -1 0 0 $h -fill green] + set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow] + set rprogitem [$progresscanv create rect -1 0 0 $h -fill red] + } + pack $progresscanv -side right -expand 1 -fill x -padx {0 2} set progresscoords {0 0} set fprogcoord 0 set rprogcoord 0 @@ -2077,14 +2154,14 @@ proc makewindow {} { set progupdatepending 0 # build up the bottom bar of upper window - label .tf.lbar.flabel -text "[mc "Find"] " - button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1} - button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1} - label .tf.lbar.flab2 -text " [mc "commit"] " + ${NS}::label .tf.lbar.flabel -text "[mc "Find"] " + ${NS}::button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1} + ${NS}::button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1} + ${NS}::label .tf.lbar.flab2 -text " [mc "commit"] " pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \ -side left -fill y set gdttype [mc "containing:"] - set gm [tk_optionMenu .tf.lbar.gdttype gdttype \ + set gm [makedroplist .tf.lbar.gdttype gdttype \ [mc "containing:"] \ [mc "touching paths:"] \ [mc "adding/removing string:"]] @@ -2094,14 +2171,14 @@ proc makewindow {} { set findstring {} set fstring .tf.lbar.findstring lappend entries $fstring - entry $fstring -width 30 -font textfont -textvariable findstring + ${NS}::entry $fstring -width 30 -font textfont -textvariable findstring trace add variable findstring write find_change set findtype [mc "Exact"] - set findtypemenu [tk_optionMenu .tf.lbar.findtype \ - findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]] + set findtypemenu [makedroplist .tf.lbar.findtype \ + findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]] trace add variable findtype write findcom_change set findloc [mc "All fields"] - tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \ + makedroplist .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \ [mc "Comments"] [mc "Author"] [mc "Committer"] trace add variable findloc write find_change pack .tf.lbar.findloc -side right @@ -2113,38 +2190,41 @@ proc makewindow {} { pack .tf.bar -in .tf -side bottom -fill x pack .tf.histframe -fill both -side top -expand 1 .ctop add .tf - .ctop paneconfigure .tf -height $geometry(topheight) - .ctop paneconfigure .tf -width $geometry(topwidth) + if {!$use_ttk} { + .ctop paneconfigure .tf -height $geometry(topheight) + .ctop paneconfigure .tf -width $geometry(topwidth) + } # now build up the bottom - panedwindow .pwbottom -orient horizontal + ${NS}::panedwindow .pwbottom -orient horizontal # lower left, a text box over search bar, scroll bar to the right # if we know window height, then that will set the lower text height, otherwise # we set lower text height which will drive window height if {[info exists geometry(main)]} { - frame .bleft -width $geometry(botwidth) + ${NS}::frame .bleft -width $geometry(botwidth) } else { - frame .bleft -width $geometry(botwidth) -height $geometry(botheight) + ${NS}::frame .bleft -width $geometry(botwidth) -height $geometry(botheight) } - frame .bleft.top - frame .bleft.mid - frame .bleft.bottom + ${NS}::frame .bleft.top + ${NS}::frame .bleft.mid + ${NS}::frame .bleft.bottom - button .bleft.top.search -text [mc "Search"] -command dosearch + ${NS}::button .bleft.top.search -text [mc "Search"] -command dosearch pack .bleft.top.search -side left -padx 5 set sstring .bleft.top.sstring - entry $sstring -width 20 -font textfont -textvariable searchstring + set searchstring "" + ${NS}::entry $sstring -width 20 -font textfont -textvariable searchstring lappend entries $sstring trace add variable searchstring write incrsearch pack $sstring -side left -expand 1 -fill x - radiobutton .bleft.mid.diff -text [mc "Diff"] \ + ${NS}::radiobutton .bleft.mid.diff -text [mc "Diff"] \ -command changediffdisp -variable diffelide -value {0 0} - radiobutton .bleft.mid.old -text [mc "Old version"] \ + ${NS}::radiobutton .bleft.mid.old -text [mc "Old version"] \ -command changediffdisp -variable diffelide -value {0 1} - radiobutton .bleft.mid.new -text [mc "New version"] \ + ${NS}::radiobutton .bleft.mid.new -text [mc "New version"] \ -command changediffdisp -variable diffelide -value {1 0} - label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: " + ${NS}::label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: " pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left spinbox .bleft.mid.diffcontext -width 5 -font textfont \ -from 0 -increment 1 -to 10000000 \ @@ -2154,7 +2234,7 @@ proc makewindow {} { trace add variable diffcontextstring write diffcontextchange lappend entries .bleft.mid.diffcontext pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left - checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \ + ${NS}::checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \ -command changeignorespace -variable ignorespace pack .bleft.mid.ignspace -side left -padx 5 set ctext .bleft.bottom.ctext @@ -2165,9 +2245,8 @@ proc makewindow {} { if {$have_tk85} { $ctext conf -tabstyle wordprocessor } - scrollbar .bleft.bottom.sb -command "$ctext yview" - scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \ - -width 10 + ${NS}::scrollbar .bleft.bottom.sb -command "$ctext yview" + ${NS}::scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h pack .bleft.top -side top -fill x pack .bleft.mid -side top -fill x grid $ctext .bleft.bottom.sb -sticky nsew @@ -2207,14 +2286,16 @@ proc makewindow {} { $ctext tag conf found -back yellow .pwbottom add .bleft - .pwbottom paneconfigure .bleft -width $geometry(botwidth) + if {!$use_ttk} { + .pwbottom paneconfigure .bleft -width $geometry(botwidth) + } # lower right - frame .bright - frame .bright.mode - radiobutton .bright.mode.patch -text [mc "Patch"] \ + ${NS}::frame .bright + ${NS}::frame .bright.mode + ${NS}::radiobutton .bright.mode.patch -text [mc "Patch"] \ -command reselectline -variable cmitmode -value "patch" - radiobutton .bright.mode.tree -text [mc "Tree"] \ + ${NS}::radiobutton .bright.mode.tree -text [mc "Tree"] \ -command reselectline -variable cmitmode -value "tree" grid .bright.mode.patch .bright.mode.tree -sticky ew pack .bright.mode -side top -fill x @@ -2230,7 +2311,7 @@ proc makewindow {} { -spacing1 1 -spacing3 1 lappend bglist $cflist lappend fglist $cflist - scrollbar .bright.sb -command "$cflist yview" + ${NS}::scrollbar .bright.sb -command "$cflist yview" pack .bright.sb -side right -fill y pack $cflist -side left -fill both -expand 1 $cflist tag configure highlight \ @@ -2265,6 +2346,17 @@ proc makewindow {} { set ::BM "2" } + if {$use_ttk} { + bind .ctop <Map> { + bind %W <Map> {} + %W sashpos 0 $::geometry(topheight) + } + bind .pwbottom <Map> { + bind %W <Map> {} + %W sashpos 0 $::geometry(botwidth) + } + } + bind .pwbottom <Configure> {resizecdetpanes %W %w} pack .ctop -fill both -expand 1 bindall <1> {selcanvline %W %x %y} @@ -2484,7 +2576,12 @@ proc click {w} { proc adjustprogress {} { global progresscanv progressitem progresscoords global fprogitem fprogcoord lastprogupdate progupdatepending - global rprogitem rprogcoord + global rprogitem rprogcoord use_ttk + + if {$use_ttk} { + $progresscanv configure -value [expr {int($fprogcoord * 100)}] + return + } set w [expr {[winfo width $progresscanv] - 4}] set x0 [expr {$w * [lindex $progresscoords 0]}] @@ -2519,9 +2616,9 @@ proc savestuff {w} { global maxwidth showneartags showlocalchanges global viewname viewfiles viewargs viewargscmd viewperm nextviewnum global cmitmode wrapcomment datetimeformat limitdiffs - global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor - global autoselect extdifftool perfile_attrs markbgcolor - global hideremotes + global colors uicolor bgcolor fgcolor diffcolors diffcontext selectbgcolor + global autoselect extdifftool perfile_attrs markbgcolor use_ttk + global hideremotes want_ttk if {$stuffsaved} return if {![winfo viewable .]} return @@ -2546,6 +2643,8 @@ proc savestuff {w} { puts $f [list set showlocalchanges $showlocalchanges] puts $f [list set datetimeformat $datetimeformat] puts $f [list set limitdiffs $limitdiffs] + puts $f [list set uicolor $uicolor] + puts $f [list set want_ttk $want_ttk] puts $f [list set bgcolor $bgcolor] puts $f [list set fgcolor $fgcolor] puts $f [list set colors $colors] @@ -2560,8 +2659,13 @@ proc savestuff {w} { puts $f "set geometry(state) [wm state .]" puts $f "set geometry(topwidth) [winfo width .tf]" puts $f "set geometry(topheight) [winfo height .tf]" - puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\"" - puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\"" + if {$use_ttk} { + puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sashpos 0] 1\"" + puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sashpos 1] 1\"" + } else { + puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\"" + puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\"" + } puts $f "set geometry(botwidth) [winfo width .bleft]" puts $f "set geometry(botheight) [winfo height .bleft]" @@ -2579,10 +2683,15 @@ proc savestuff {w} { } proc resizeclistpanes {win w} { - global oldwidth + global oldwidth use_ttk if {[info exists oldwidth($win)]} { - set s0 [$win sash coord 0] - set s1 [$win sash coord 1] + if {$use_ttk} { + set s0 [$win sashpos 0] + set s1 [$win sashpos 1] + } else { + set s0 [$win sash coord 0] + set s1 [$win sash coord 1] + } if {$w < 60} { set sash0 [expr {int($w/2 - 2)}] set sash1 [expr {int($w*5/6 - 2)}] @@ -2603,16 +2712,25 @@ proc resizeclistpanes {win w} { } } } - $win sash place 0 $sash0 [lindex $s0 1] - $win sash place 1 $sash1 [lindex $s1 1] + if {$use_ttk} { + $win sashpos 0 $sash0 + $win sashpos 1 $sash1 + } else { + $win sash place 0 $sash0 [lindex $s0 1] + $win sash place 1 $sash1 [lindex $s1 1] + } } set oldwidth($win) $w } proc resizecdetpanes {win w} { - global oldwidth + global oldwidth use_ttk if {[info exists oldwidth($win)]} { - set s0 [$win sash coord 0] + if {$use_ttk} { + set s0 [$win sashpos 0] + } else { + set s0 [$win sash coord 0] + } if {$w < 60} { set sash0 [expr {int($w*3/4 - 2)}] } else { @@ -2625,7 +2743,11 @@ proc resizecdetpanes {win w} { set sash0 [expr {$w - 15}] } } - $win sash place 0 $sash0 [lindex $s0 1] + if {$use_ttk} { + $win sashpos 0 $sash0 + } else { + $win sash place 0 $sash0 [lindex $s0 1] + } } set oldwidth($win) $w } @@ -2645,31 +2767,33 @@ proc bindall {event action} { } proc about {} { - global uifont + global uifont NS set w .about if {[winfo exists $w]} { raise $w return } - toplevel $w + ttk_toplevel $w wm title $w [mc "About gitk"] make_transient $w . message $w.m -text [mc " Gitk - a commit viewer for git -Copyright © 2005-2008 Paul Mackerras +Copyright \u00a9 2005-2009 Paul Mackerras Use and redistribute under the terms of the GNU General Public License"] \ -justify center -aspect 400 -border 2 -bg white -relief groove pack $w.m -side top -fill x -padx 2 -pady 2 - button $w.ok -text [mc "Close"] -command "destroy $w" -default active + ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active pack $w.ok -side bottom bind $w <Visibility> "focus $w.ok" bind $w <Key-Escape> "destroy $w" bind $w <Key-Return> "destroy $w" + tk::PlaceWindow $w widget . } proc keys {} { + global NS set w .keys if {[winfo exists $w]} { raise $w @@ -2680,7 +2804,7 @@ proc keys {} { } else { set M1T Ctrl } - toplevel $w + ttk_toplevel $w wm title $w [mc "Gitk key bindings"] make_transient $w . message $w.m -text " @@ -2724,7 +2848,7 @@ proc keys {} { " \ -justify left -bg white -border 2 -relief groove pack $w.m -side top -fill both -padx 2 -pady 2 - button $w.ok -text [mc "Close"] -command "destroy $w" -default active + ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active bind $w <Key-Escape> [list destroy $w] pack $w.ok -side bottom bind $w <Visibility> "focus $w.ok" @@ -3377,6 +3501,9 @@ proc index_sha1 {fname} { # Turn an absolute path into one relative to the current directory proc make_relative {f} { + if {[file pathtype $f] eq "relative"} { + return $f + } set elts [file split $f] set here [file split [pwd]] set ei 0 @@ -3839,16 +3966,16 @@ proc editview {} { proc vieweditor {top n title} { global newviewname newviewopts viewfiles bgcolor - global known_view_options + global known_view_options NS - toplevel $top + ttk_toplevel $top wm title $top [concat $title [mc "-- criteria for selecting revisions"]] make_transient $top . # View name - frame $top.nfr - label $top.nl -text [mc "View Name:"] - entry $top.name -width 20 -textvariable newviewname($n) + ${NS}::frame $top.nfr + ${NS}::label $top.nl -text [mc "View Name"] + ${NS}::entry $top.name -width 20 -textvariable newviewname($n) pack $top.nfr -in $top -fill x -pady 5 -padx 3 pack $top.nl -in $top.nfr -side left -padx {0 5} pack $top.name -in $top.nfr -side left -padx {0 25} @@ -3867,13 +3994,13 @@ proc vieweditor {top n title} { if {$flags eq "+" || $flags eq "*"} { set cframe $top.fr$cnt incr cnt - frame $cframe + ${NS}::frame $cframe pack $cframe -in $top -fill x -pady 3 -padx 3 set cexpand [expr {$flags eq "*"}] } elseif {$flags eq ".." || $flags eq "*."} { set cframe $top.fr$cnt incr cnt - frame $cframe + ${NS}::frame $cframe pack $cframe -in $top -fill x -pady 3 -padx [list 15 3] set cexpand [expr {$flags eq "*."}] } else { @@ -3881,31 +4008,31 @@ proc vieweditor {top n title} { } if {$type eq "l"} { - label $cframe.l_$id -text $title + ${NS}::label $cframe.l_$id -text $title pack $cframe.l_$id -in $cframe -side left -pady [list 3 0] -anchor w } elseif {$type eq "b"} { - checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id) + ${NS}::checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id) pack $cframe.c_$id -in $cframe -side left \ -padx [list $lxpad 0] -expand $cexpand -anchor w } elseif {[regexp {^r(\d+)$} $type type sz]} { regexp {^(.*_)} $id uselessvar button_id - radiobutton $cframe.c_$id -text $title -variable newviewopts($n,$button_id) -value $sz + ${NS}::radiobutton $cframe.c_$id -text $title -variable newviewopts($n,$button_id) -value $sz pack $cframe.c_$id -in $cframe -side left \ -padx [list $lxpad 0] -expand $cexpand -anchor w } elseif {[regexp {^t(\d+)$} $type type sz]} { - message $cframe.l_$id -aspect 1500 -text $title - entry $cframe.e_$id -width $sz -background $bgcolor \ + ${NS}::label $cframe.l_$id -text $title + ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \ -textvariable newviewopts($n,$id) pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0] pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x } elseif {[regexp {^t(\d+)=$} $type type sz]} { - message $cframe.l_$id -aspect 1500 -text $title - entry $cframe.e_$id -width $sz -background $bgcolor \ + ${NS}::label $cframe.l_$id -text $title + ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \ -textvariable newviewopts($n,$id) pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w pack $cframe.e_$id -in $cframe -side top -fill x } elseif {$type eq "path"} { - message $top.l -aspect 1500 -text $title + ${NS}::label $top.l -text $title pack $top.l -in $top -side top -pady [list 3 0] -anchor w -padx 3 text $top.t -width 40 -height 5 -background $bgcolor -font uifont if {[info exists viewfiles($n)]} { @@ -3920,10 +4047,10 @@ proc vieweditor {top n title} { } } - frame $top.buts - button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n] - button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1] - button $top.buts.can -text [mc "Cancel"] -command [list destroy $top] + ${NS}::frame $top.buts + ${NS}::button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n] + ${NS}::button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1] + ${NS}::button $top.buts.can -text [mc "Cancel"] -command [list destroy $top] bind $top <Control-Return> [list newviewok $top $n] bind $top <F5> [list newviewok $top $n 1] bind $top <Escape> [list destroy $top] @@ -6787,14 +6914,13 @@ proc selectline {l isnew {desired_loc {}}} { make_secsel $id if {$isnew} { - addtohistory [list selbyid $id] + addtohistory [list selbyid $id 0] savecmitpos } $sha1entry delete 0 end $sha1entry insert 0 $id if {$autoselect} { - $sha1entry selection from 0 - $sha1entry selection to end + $sha1entry selection range 0 end } rhighlight_sel $id @@ -6939,10 +7065,12 @@ proc reselectline {} { } } -proc addtohistory {cmd} { +proc addtohistory {cmd {saveproc {}}} { global history historyindex curview - set elt [list $curview $cmd] + unset_posvars + save_position + set elt [list $curview $cmd $saveproc {}] if {$historyindex > 0 && [lindex $history [expr {$historyindex - 1}]] == $elt} { return @@ -6962,14 +7090,45 @@ proc addtohistory {cmd} { .tf.bar.rightbut conf -state disabled } +# save the scrolling position of the diff display pane +proc save_position {} { + global historyindex history + + if {$historyindex < 1} return + set hi [expr {$historyindex - 1}] + set fn [lindex $history $hi 2] + if {$fn ne {}} { + lset history $hi 3 [eval $fn] + } +} + +proc unset_posvars {} { + global last_posvars + + if {[info exists last_posvars]} { + foreach {var val} $last_posvars { + global $var + catch {unset $var} + } + unset last_posvars + } +} + proc godo {elt} { - global curview + global curview last_posvars set view [lindex $elt 0] set cmd [lindex $elt 1] + set pv [lindex $elt 3] if {$curview != $view} { showview $view } + unset_posvars + foreach {var val} $pv { + global $var + set $var $val + } + set last_posvars $pv eval $cmd } @@ -6978,6 +7137,7 @@ proc goback {} { focus . if {$historyindex > 1} { + save_position incr historyindex -1 godo [lindex $history [expr {$historyindex - 1}]] .tf.bar.rightbut conf -state normal @@ -6992,6 +7152,7 @@ proc goforw {} { focus . if {$historyindex < [llength $history]} { + save_position set cmd [lindex $history $historyindex] incr historyindex godo $cmd @@ -7224,7 +7385,7 @@ proc diffcmd {ids flags} { set cmd [concat | git diff-index --cached $flags] if {[llength $ids] > 1} { # comparing index with specific revision - if {$i == 0} { + if {$j == 0} { lappend cmd -R [lindex $ids 1] } else { lappend cmd [lindex $ids 0] @@ -7343,7 +7504,11 @@ proc getblobdiffs {ids} { if {[package vcompare $git_version "1.6.1"] >= 0} { set textconv "--textconv" } - set cmd [diffcmd $ids "-p $textconv -C --cc --no-commit-id -U$diffcontext"] + set submodule {} + if {[package vcompare $git_version "1.6.6"] >= 0} { + set submodule "--submodule" + } + set cmd [diffcmd $ids "-p $textconv $submodule -C --cc --no-commit-id -U$diffcontext"] if {$ignorespace} { append cmd " -w" } @@ -7363,6 +7528,34 @@ proc getblobdiffs {ids} { filerun $bdf [list getblobdiffline $bdf $diffids] } +proc savecmitpos {} { + global ctext cmitmode + + if {$cmitmode eq "tree"} { + return {} + } + return [list target_scrollpos [$ctext index @0,0]] +} + +proc savectextpos {} { + global ctext + + return [list target_scrollpos [$ctext index @0,0]] +} + +proc maybe_scroll_ctext {ateof} { + global ctext target_scrollpos + + if {![info exists target_scrollpos]} return + if {!$ateof} { + set nlines [expr {[winfo height $ctext] + / [font metrics textfont -linespace]}] + if {[$ctext compare "$target_scrollpos + $nlines lines" <= end]} return + } + $ctext yview $target_scrollpos + unset target_scrollpos +} + proc setinlist {var i val} { global $var @@ -7481,6 +7674,21 @@ proc getblobdiffline {bdf ids} { set diffnparents [expr {[string length $ats] - 1}] set diffinhdr 0 + } elseif {![string compare -length 10 "Submodule " $line]} { + # start of a new submodule + if {[string compare [$ctext get "end - 4c" end] "\n \n\n"]} { + $ctext insert end "\n"; # Add newline after commit message + } + set curdiffstart [$ctext index "end - 1c"] + lappend ctext_file_names "" + set fname [string range $line 10 [expr [string last " " $line] - 1]] + lappend ctext_file_lines $fname + makediffhdr $fname $ids + $ctext insert end "\n$line\n" filesep + } elseif {![string compare -length 3 " >" $line]} { + $ctext insert end "$line\n" dresult + } elseif {![string compare -length 3 " <" $line]} { + $ctext insert end "$line\n" d0 } elseif {$diffinhdr} { if {![string compare -length 12 "rename from " $line]} { set fname [string range $line [expr 6 + [string first " from " $line] ] end] @@ -7558,6 +7766,7 @@ proc getblobdiffline {bdf ids} { if {[info exists seehere]} { mark_ctext_line [lindex [split $seehere .] 0] } + maybe_scroll_ctext [eof $bdf] $ctext conf -state disabled if {[eof $bdf]} { catch {close $bdf} @@ -8057,7 +8266,7 @@ proc lineclick {x y id isnew} { } if {$isnew} { - addtohistory [list lineclick $x $y $id 0] + addtohistory [list lineclick $x $y $id 0] savectextpos } # fill the details pane with info about this line $ctext conf -state normal @@ -8088,6 +8297,7 @@ proc lineclick {x y id isnew} { $ctext insert end "\n\t[mc "Date"]:\t$date\n" } } + maybe_scroll_ctext 1 $ctext conf -state disabled init_flist {} } @@ -8101,10 +8311,10 @@ proc normalline {} { } } -proc selbyid {id} { +proc selbyid {id {isnew 1}} { global curview if {[commitinview $id $curview]} { - selectline [rowofcommit $id] 1 + selectline [rowofcommit $id] $isnew } } @@ -8289,23 +8499,23 @@ proc do_cmp_commits {a b} { } } if {$skipa} { - if {[llength $children($curview,$a)] != 1} { + set kids [real_children $curview,$a] + if {[llength $kids] != 1} { $ctext insert end "\n" appendshortlink $a [mc "Commit "] \ - [mc " has %s children - stopping\n" \ - [llength $children($curview,$a)]] + [mc " has %s children - stopping\n" [llength $kids]] break } - set a [lindex $children($curview,$a) 0] + set a [lindex $kids 0] } if {$skipb} { - if {[llength $children($curview,$b)] != 1} { + set kids [real_children $curview,$b] + if {[llength $kids] != 1} { appendshortlink $b [mc "Commit "] \ - [mc " has %s children - stopping\n" \ - [llength $children($curview,$b)]] + [mc " has %s children - stopping\n" [llength $kids]] break } - set b [lindex $children($curview,$b) 0] + set b [lindex $kids 0] } } $ctext conf -state disabled @@ -8347,7 +8557,7 @@ proc diffvssel {dirn} { set oldid $rowmenuid set newid [commitonrow $selectedline] } - addtohistory [list doseldiff $oldid $newid] + addtohistory [list doseldiff $oldid $newid] savectextpos doseldiff $oldid $newid } @@ -8375,7 +8585,7 @@ proc doseldiff {oldid newid} { } proc mkpatch {} { - global rowmenuid currentid commitinfo patchtop patchnum + global rowmenuid currentid commitinfo patchtop patchnum NS if {![info exists currentid]} return set oldid $currentid @@ -8385,38 +8595,38 @@ proc mkpatch {} { set top .patch set patchtop $top catch {destroy $top} - toplevel $top + ttk_toplevel $top make_transient $top . - label $top.title -text [mc "Generate patch"] + ${NS}::label $top.title -text [mc "Generate patch"] grid $top.title - -pady 10 - label $top.from -text [mc "From:"] - entry $top.fromsha1 -width 40 -relief flat + ${NS}::label $top.from -text [mc "From:"] + ${NS}::entry $top.fromsha1 -width 40 $top.fromsha1 insert 0 $oldid $top.fromsha1 conf -state readonly grid $top.from $top.fromsha1 -sticky w - entry $top.fromhead -width 60 -relief flat + ${NS}::entry $top.fromhead -width 60 $top.fromhead insert 0 $oldhead $top.fromhead conf -state readonly grid x $top.fromhead -sticky w - label $top.to -text [mc "To:"] - entry $top.tosha1 -width 40 -relief flat + ${NS}::label $top.to -text [mc "To:"] + ${NS}::entry $top.tosha1 -width 40 $top.tosha1 insert 0 $newid $top.tosha1 conf -state readonly grid $top.to $top.tosha1 -sticky w - entry $top.tohead -width 60 -relief flat + ${NS}::entry $top.tohead -width 60 $top.tohead insert 0 $newhead $top.tohead conf -state readonly grid x $top.tohead -sticky w - button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5 - grid $top.rev x -pady 10 - label $top.flab -text [mc "Output file:"] - entry $top.fname -width 60 + ${NS}::button $top.rev -text [mc "Reverse"] -command mkpatchrev + grid $top.rev x -pady 10 -padx 5 + ${NS}::label $top.flab -text [mc "Output file:"] + ${NS}::entry $top.fname -width 60 $top.fname insert 0 [file normalize "patch$patchnum.patch"] incr patchnum grid $top.flab $top.fname -sticky w - frame $top.buts - button $top.buts.gen -text [mc "Generate"] -command mkpatchgo - button $top.buts.can -text [mc "Cancel"] -command mkpatchcan + ${NS}::frame $top.buts + ${NS}::button $top.buts.gen -text [mc "Generate"] -command mkpatchgo + ${NS}::button $top.buts.can -text [mc "Cancel"] -command mkpatchcan bind $top <Key-Return> mkpatchgo bind $top <Key-Escape> mkpatchcan grid $top.buts.gen $top.buts.can @@ -8467,30 +8677,30 @@ proc mkpatchcan {} { } proc mktag {} { - global rowmenuid mktagtop commitinfo + global rowmenuid mktagtop commitinfo NS set top .maketag set mktagtop $top catch {destroy $top} - toplevel $top + ttk_toplevel $top make_transient $top . - label $top.title -text [mc "Create tag"] + ${NS}::label $top.title -text [mc "Create tag"] grid $top.title - -pady 10 - label $top.id -text [mc "ID:"] - entry $top.sha1 -width 40 -relief flat + ${NS}::label $top.id -text [mc "ID:"] + ${NS}::entry $top.sha1 -width 40 $top.sha1 insert 0 $rowmenuid $top.sha1 conf -state readonly grid $top.id $top.sha1 -sticky w - entry $top.head -width 60 -relief flat + ${NS}::entry $top.head -width 60 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0] $top.head conf -state readonly grid x $top.head -sticky w - label $top.tlab -text [mc "Tag name:"] - entry $top.tag -width 60 + ${NS}::label $top.tlab -text [mc "Tag name:"] + ${NS}::entry $top.tag -width 60 grid $top.tlab $top.tag -sticky w - frame $top.buts - button $top.buts.gen -text [mc "Create"] -command mktaggo - button $top.buts.can -text [mc "Cancel"] -command mktagcan + ${NS}::frame $top.buts + ${NS}::button $top.buts.gen -text [mc "Create"] -command mktaggo + ${NS}::button $top.buts.can -text [mc "Cancel"] -command mktagcan bind $top <Key-Return> mktaggo bind $top <Key-Escape> mktagcan grid $top.buts.gen $top.buts.can @@ -8573,34 +8783,34 @@ proc mktaggo {} { } proc writecommit {} { - global rowmenuid wrcomtop commitinfo wrcomcmd + global rowmenuid wrcomtop commitinfo wrcomcmd NS set top .writecommit set wrcomtop $top catch {destroy $top} - toplevel $top + ttk_toplevel $top make_transient $top . - label $top.title -text [mc "Write commit to file"] + ${NS}::label $top.title -text [mc "Write commit to file"] grid $top.title - -pady 10 - label $top.id -text [mc "ID:"] - entry $top.sha1 -width 40 -relief flat + ${NS}::label $top.id -text [mc "ID:"] + ${NS}::entry $top.sha1 -width 40 $top.sha1 insert 0 $rowmenuid $top.sha1 conf -state readonly grid $top.id $top.sha1 -sticky w - entry $top.head -width 60 -relief flat + ${NS}::entry $top.head -width 60 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0] $top.head conf -state readonly grid x $top.head -sticky w - label $top.clab -text [mc "Command:"] - entry $top.cmd -width 60 -textvariable wrcomcmd + ${NS}::label $top.clab -text [mc "Command:"] + ${NS}::entry $top.cmd -width 60 -textvariable wrcomcmd grid $top.clab $top.cmd -sticky w -pady 10 - label $top.flab -text [mc "Output file:"] - entry $top.fname -width 60 + ${NS}::label $top.flab -text [mc "Output file:"] + ${NS}::entry $top.fname -width 60 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"] grid $top.flab $top.fname -sticky w - frame $top.buts - button $top.buts.gen -text [mc "Write"] -command wrcomgo - button $top.buts.can -text [mc "Cancel"] -command wrcomcan + ${NS}::frame $top.buts + ${NS}::button $top.buts.gen -text [mc "Write"] -command wrcomgo + ${NS}::button $top.buts.can -text [mc "Cancel"] -command wrcomcan bind $top <Key-Return> wrcomgo bind $top <Key-Escape> wrcomcan grid $top.buts.gen $top.buts.can @@ -8631,25 +8841,25 @@ proc wrcomcan {} { } proc mkbranch {} { - global rowmenuid mkbrtop + global rowmenuid mkbrtop NS set top .makebranch catch {destroy $top} - toplevel $top + ttk_toplevel $top make_transient $top . - label $top.title -text [mc "Create new branch"] + ${NS}::label $top.title -text [mc "Create new branch"] grid $top.title - -pady 10 - label $top.id -text [mc "ID:"] - entry $top.sha1 -width 40 -relief flat + ${NS}::label $top.id -text [mc "ID:"] + ${NS}::entry $top.sha1 -width 40 $top.sha1 insert 0 $rowmenuid $top.sha1 conf -state readonly grid $top.id $top.sha1 -sticky w - label $top.nlab -text [mc "Name:"] - entry $top.name -width 40 + ${NS}::label $top.nlab -text [mc "Name:"] + ${NS}::entry $top.name -width 40 grid $top.nlab $top.name -sticky w - frame $top.buts - button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top] - button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}" + ${NS}::frame $top.buts + ${NS}::button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top] + ${NS}::button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}" bind $top <Key-Return> [list mkbrgo $top] bind $top <Key-Escape> "catch {destroy $top}" grid $top.buts.go $top.buts.can @@ -8794,34 +9004,31 @@ proc cherrypick {} { } proc resethead {} { - global mainhead rowmenuid confirm_ok resettype + global mainhead rowmenuid confirm_ok resettype NS set confirm_ok 0 set w ".confirmreset" - toplevel $w + ttk_toplevel $w make_transient $w . wm title $w [mc "Confirm reset"] - message $w.m -text \ - [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \ - -justify center -aspect 1000 + ${NS}::label $w.m -text \ + [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] pack $w.m -side top -fill x -padx 20 -pady 20 - frame $w.f -relief sunken -border 2 - message $w.f.rt -text [mc "Reset type:"] -aspect 1000 - grid $w.f.rt -sticky w + ${NS}::labelframe $w.f -text [mc "Reset type:"] set resettype mixed - radiobutton $w.f.soft -value soft -variable resettype -justify left \ + ${NS}::radiobutton $w.f.soft -value soft -variable resettype \ -text [mc "Soft: Leave working tree and index untouched"] grid $w.f.soft -sticky w - radiobutton $w.f.mixed -value mixed -variable resettype -justify left \ + ${NS}::radiobutton $w.f.mixed -value mixed -variable resettype \ -text [mc "Mixed: Leave working tree untouched, reset index"] grid $w.f.mixed -sticky w - radiobutton $w.f.hard -value hard -variable resettype -justify left \ + ${NS}::radiobutton $w.f.hard -value hard -variable resettype \ -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"] grid $w.f.hard -sticky w - pack $w.f -side top -fill x - button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w" + pack $w.f -side top -fill x -padx 4 + ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w" pack $w.ok -side left -fill x -padx 20 -pady 20 - button $w.cancel -text [mc Cancel] -command "destroy $w" + ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w" bind $w <Key-Escape> [list destroy $w] pack $w.cancel -side right -fill x -padx 20 -pady 20 bind $w <Visibility> "grab $w; focus $w" @@ -8877,6 +9084,9 @@ proc headmenu {x y id head} { set headmenuid $id set headmenuhead $head set state normal + if {[string match "remotes/*" $head]} { + set state disabled + } if {$head eq $mainhead} { set state disabled } @@ -8969,7 +9179,7 @@ proc rmbranch {} { # Display a list of tags and heads proc showrefs {} { - global showrefstop bgcolor fgcolor selectbgcolor + global showrefstop bgcolor fgcolor selectbgcolor NS global bglist fglist reflistfilter reflist maincursor set top .showrefs @@ -8979,7 +9189,7 @@ proc showrefs {} { refill_reflist return } - toplevel $top + ttk_toplevel $top wm title $top [mc "Tags and heads: %s" [file tail [pwd]]] make_transient $top . text $top.list -background $bgcolor -foreground $fgcolor \ @@ -8990,19 +9200,19 @@ proc showrefs {} { $top.list tag configure highlight -background $selectbgcolor lappend bglist $top.list lappend fglist $top.list - scrollbar $top.ysb -command "$top.list yview" -orient vertical - scrollbar $top.xsb -command "$top.list xview" -orient horizontal + ${NS}::scrollbar $top.ysb -command "$top.list yview" -orient vertical + ${NS}::scrollbar $top.xsb -command "$top.list xview" -orient horizontal grid $top.list $top.ysb -sticky nsew grid $top.xsb x -sticky ew - frame $top.f - label $top.f.l -text "[mc "Filter"]: " - entry $top.f.e -width 20 -textvariable reflistfilter + ${NS}::frame $top.f + ${NS}::label $top.f.l -text "[mc "Filter"]: " + ${NS}::entry $top.f.e -width 20 -textvariable reflistfilter set reflistfilter "*" trace add variable reflistfilter write reflistfilter_change pack $top.f.e -side right -fill x -expand 1 pack $top.f.l -side left grid $top.f - -sticky ew -pady 2 - button $top.close -command [list destroy $top] -text [mc "Close"] + ${NS}::button $top.close -command [list destroy $top] -text [mc "Close"] bind $top <Key-Escape> [list destroy $top] grid $top.close - grid columnconfigure $top 0 -weight 1 @@ -9200,7 +9410,7 @@ proc getallclines {fd} { global allparents allchildren idtags idheads nextarc global arcnos arcids arctags arcout arcend arcstart archeads growing global seeds allcommits cachedarcs allcupdate - + set nid 0 while {[incr nid] <= 1000 && [gets $fd line] >= 0} { set id [lindex $line 0] @@ -10262,7 +10472,7 @@ proc showtag {tag isnew} { global ctext tagcontents tagids linknum tagobjid if {$isnew} { - addtohistory [list showtag $tag 0] + addtohistory [list showtag $tag 0] savectextpos } $ctext conf -state normal clear_ctext @@ -10279,6 +10489,7 @@ proc showtag {tag isnew} { set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)" } appendwithlinks $text {} + maybe_scroll_ctext 1 $ctext conf -state disabled init_flist {} } @@ -10297,19 +10508,20 @@ proc doquit {} { } proc mkfontdisp {font top which} { - global fontattr fontpref $font + global fontattr fontpref $font NS use_ttk set fontpref($font) [set $font] - button $top.${font}but -text $which -font optionfont \ + ${NS}::button $top.${font}but -text $which \ -command [list choosefont $font $which] - label $top.$font -relief flat -font $font \ + if {!$use_ttk} {$top.${font}but configure -font optionfont} + ${NS}::label $top.$font -relief flat -font $font \ -text $fontattr($font,family) -justify left grid x $top.${font}but $top.$font -sticky w } proc choosefont {font which} { global fontparam fontlist fonttop fontattr - global prefstop + global prefstop NS set fontparam(which) $which set fontparam(font) $font @@ -10322,21 +10534,21 @@ proc choosefont {font which} { if {![winfo exists $top]} { font create sample eval font config sample [font actual $font] - toplevel $top + ttk_toplevel $top make_transient $top $prefstop wm title $top [mc "Gitk font chooser"] - label $top.l -textvariable fontparam(which) + ${NS}::label $top.l -textvariable fontparam(which) pack $top.l -side top set fontlist [lsort [font families]] - frame $top.f + ${NS}::frame $top.f listbox $top.f.fam -listvariable fontlist \ -yscrollcommand [list $top.f.sb set] bind $top.f.fam <<ListboxSelect>> selfontfam - scrollbar $top.f.sb -command [list $top.f.fam yview] + ${NS}::scrollbar $top.f.sb -command [list $top.f.fam yview] pack $top.f.sb -side right -fill y pack $top.f.fam -side left -fill both -expand 1 pack $top.f -side top -fill both -expand 1 - frame $top.g + ${NS}::frame $top.g spinbox $top.g.size -from 4 -to 40 -width 4 \ -textvariable fontparam(size) \ -validatecommand {string is integer -strict %s} @@ -10354,9 +10566,9 @@ proc choosefont {font which} { -fill black -tags text bind $top.c <Configure> [list centertext $top.c] pack $top.c -side top -fill x - frame $top.buts - button $top.buts.ok -text [mc "OK"] -command fontok -default active - button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal + ${NS}::frame $top.buts + ${NS}::button $top.buts.ok -text [mc "OK"] -command fontok -default active + ${NS}::button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal bind $top <Key-Return> fontok bind $top <Key-Escape> fontcan grid $top.buts.ok $top.buts.can @@ -10392,7 +10604,7 @@ proc fontok {} { } set w $prefstop.$f $w conf -text $fontparam(family) -font $fontpref($f) - + fontcan } @@ -10407,6 +10619,28 @@ proc fontcan {} { } } +if {[package vsatisfies [package provide Tk] 8.6]} { + # In Tk 8.6 we have a native font chooser dialog. Overwrite the above + # function to make use of it. + proc choosefont {font which} { + tk fontchooser configure -title $which -font $font \ + -command [list on_choosefont $font $which] + tk fontchooser show + } + proc on_choosefont {font which newfont} { + global fontparam + puts stderr "$font $newfont" + array set f [font actual $newfont] + set fontparam(which) $which + set fontparam(font) $font + set fontparam(family) $f(-family) + set fontparam(size) $f(-size) + set fontparam(weight) $f(-weight) + set fontparam(slant) $f(-slant) + fontok + } +} + proc selfontfam {} { global fonttop fontparam @@ -10423,11 +10657,11 @@ proc chg_fontparam {v sub op} { } proc doprefs {} { - global maxwidth maxgraphpct + global maxwidth maxgraphpct use_ttk NS global oldprefs prefstop showneartags showlocalchanges - global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor + global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor global tabstop limitdiffs autoselect extdifftool perfile_attrs - global hideremotes + global hideremotes want_ttk have_ttk set top .gitkprefs set prefstop $top @@ -10436,109 +10670,131 @@ proc doprefs {} { return } foreach v {maxwidth maxgraphpct showneartags showlocalchanges \ - limitdiffs tabstop perfile_attrs hideremotes} { + limitdiffs tabstop perfile_attrs hideremotes want_ttk} { set oldprefs($v) [set $v] } - toplevel $top + ttk_toplevel $top wm title $top [mc "Gitk preferences"] make_transient $top . - label $top.ldisp -text [mc "Commit list display options"] + ${NS}::label $top.ldisp -text [mc "Commit list display options"] grid $top.ldisp - -sticky w -pady 10 - label $top.spacer -text " " - label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \ - -font optionfont + ${NS}::label $top.spacer -text " " + ${NS}::label $top.maxwidthl -text [mc "Maximum graph width (lines)"] spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w - label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \ - -font optionfont + ${NS}::label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct grid x $top.maxpctl $top.maxpct -sticky w - checkbutton $top.showlocal -text [mc "Show local changes"] \ - -font optionfont -variable showlocalchanges + ${NS}::checkbutton $top.showlocal -text [mc "Show local changes"] \ + -variable showlocalchanges grid x $top.showlocal -sticky w - checkbutton $top.autoselect -text [mc "Auto-select SHA1"] \ - -font optionfont -variable autoselect + ${NS}::checkbutton $top.autoselect -text [mc "Auto-select SHA1"] \ + -variable autoselect grid x $top.autoselect -sticky w + ${NS}::checkbutton $top.hideremotes -text [mc "Hide remote refs"] \ + -variable hideremotes + grid x $top.hideremotes -sticky w - label $top.ddisp -text [mc "Diff display options"] + ${NS}::label $top.ddisp -text [mc "Diff display options"] grid $top.ddisp - -sticky w -pady 10 - label $top.tabstopl -text [mc "Tab spacing"] -font optionfont + ${NS}::label $top.tabstopl -text [mc "Tab spacing"] spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop grid x $top.tabstopl $top.tabstop -sticky w - checkbutton $top.ntag -text [mc "Display nearby tags"] \ - -font optionfont -variable showneartags + ${NS}::checkbutton $top.ntag -text [mc "Display nearby tags"] \ + -variable showneartags grid x $top.ntag -sticky w - checkbutton $top.hideremotes -text [mc "Hide remote refs"] \ - -font optionfont -variable hideremotes - grid x $top.hideremotes -sticky w - checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \ - -font optionfont -variable limitdiffs + ${NS}::checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \ + -variable limitdiffs grid x $top.ldiff -sticky w - checkbutton $top.lattr -text [mc "Support per-file encodings"] \ - -font optionfont -variable perfile_attrs + ${NS}::checkbutton $top.lattr -text [mc "Support per-file encodings"] \ + -variable perfile_attrs grid x $top.lattr -sticky w - entry $top.extdifft -textvariable extdifftool - frame $top.extdifff - label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \ - -padx 10 - button $top.extdifff.b -text [mc "Choose..."] -font optionfont \ - -command choose_extdiff + ${NS}::entry $top.extdifft -textvariable extdifftool + ${NS}::frame $top.extdifff + ${NS}::label $top.extdifff.l -text [mc "External diff tool" ] + ${NS}::button $top.extdifff.b -text [mc "Choose..."] -command choose_extdiff pack $top.extdifff.l $top.extdifff.b -side left - grid x $top.extdifff $top.extdifft -sticky w + pack configure $top.extdifff.l -padx 10 + grid x $top.extdifff $top.extdifft -sticky ew + + ${NS}::label $top.lgen -text [mc "General options"] + grid $top.lgen - -sticky w -pady 10 + ${NS}::checkbutton $top.want_ttk -variable want_ttk \ + -text [mc "Use themed widgets"] + if {$have_ttk} { + ${NS}::label $top.ttk_note -text [mc "(change requires restart)"] + } else { + ${NS}::label $top.ttk_note -text [mc "(currently unavailable)"] + } + grid x $top.want_ttk $top.ttk_note -sticky w - label $top.cdisp -text [mc "Colors: press to choose"] + ${NS}::label $top.cdisp -text [mc "Colors: press to choose"] grid $top.cdisp - -sticky w -pady 10 + label $top.ui -padx 40 -relief sunk -background $uicolor + ${NS}::button $top.uibut -text [mc "Interface"] \ + -command [list choosecolor uicolor {} $top.ui [mc "interface"] setui] + grid x $top.uibut $top.ui -sticky w label $top.bg -padx 40 -relief sunk -background $bgcolor - button $top.bgbut -text [mc "Background"] -font optionfont \ + ${NS}::button $top.bgbut -text [mc "Background"] \ -command [list choosecolor bgcolor {} $top.bg [mc "background"] setbg] grid x $top.bgbut $top.bg -sticky w label $top.fg -padx 40 -relief sunk -background $fgcolor - button $top.fgbut -text [mc "Foreground"] -font optionfont \ + ${NS}::button $top.fgbut -text [mc "Foreground"] \ -command [list choosecolor fgcolor {} $top.fg [mc "foreground"] setfg] grid x $top.fgbut $top.fg -sticky w label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0] - button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \ + ${NS}::button $top.diffoldbut -text [mc "Diff: old lines"] \ -command [list choosecolor diffcolors 0 $top.diffold [mc "diff old lines"] \ [list $ctext tag conf d0 -foreground]] grid x $top.diffoldbut $top.diffold -sticky w label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1] - button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \ + ${NS}::button $top.diffnewbut -text [mc "Diff: new lines"] \ -command [list choosecolor diffcolors 1 $top.diffnew [mc "diff new lines"] \ [list $ctext tag conf dresult -foreground]] grid x $top.diffnewbut $top.diffnew -sticky w label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2] - button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \ + ${NS}::button $top.hunksepbut -text [mc "Diff: hunk header"] \ -command [list choosecolor diffcolors 2 $top.hunksep \ [mc "diff hunk header"] \ [list $ctext tag conf hunksep -foreground]] grid x $top.hunksepbut $top.hunksep -sticky w label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor - button $top.markbgbut -text [mc "Marked line bg"] -font optionfont \ + ${NS}::button $top.markbgbut -text [mc "Marked line bg"] \ -command [list choosecolor markbgcolor {} $top.markbgsep \ [mc "marked line background"] \ [list $ctext tag conf omark -background]] grid x $top.markbgbut $top.markbgsep -sticky w label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor - button $top.selbgbut -text [mc "Select bg"] -font optionfont \ + ${NS}::button $top.selbgbut -text [mc "Select bg"] \ -command [list choosecolor selectbgcolor {} $top.selbgsep [mc "background"] setselbg] grid x $top.selbgbut $top.selbgsep -sticky w - label $top.cfont -text [mc "Fonts: press to choose"] + ${NS}::label $top.cfont -text [mc "Fonts: press to choose"] grid $top.cfont - -sticky w -pady 10 mkfontdisp mainfont $top [mc "Main font"] mkfontdisp textfont $top [mc "Diff display font"] mkfontdisp uifont $top [mc "User interface font"] - frame $top.buts - button $top.buts.ok -text [mc "OK"] -command prefsok -default active - button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal + if {!$use_ttk} { + foreach w {maxpctl maxwidthl showlocal autoselect tabstopl ntag + ldiff lattr extdifff.l extdifff.b bgbut fgbut + diffoldbut diffnewbut hunksepbut markbgbut selbgbut + want_ttk ttk_note} { + $top.$w configure -font optionfont + } + } + + ${NS}::frame $top.buts + ${NS}::button $top.buts.ok -text [mc "OK"] -command prefsok -default active + ${NS}::button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal bind $top <Key-Return> prefsok bind $top <Key-Escape> prefscan grid $top.buts.ok $top.buts.can grid columnconfigure $top.buts 0 -weight 1 -uniform a grid columnconfigure $top.buts 1 -weight 1 -uniform a grid $top.buts - - -pady 10 -sticky ew + grid columnconfigure $top 2 -weight 1 bind $top <Visibility> "focus $top.buts.ok" } @@ -10572,6 +10828,20 @@ proc setselbg {c} { allcanvs itemconf secsel -fill $c } +# This sets the background color and the color scheme for the whole UI. +# For some reason, tk_setPalette chooses a nasty dark red for selectColor +# if we don't specify one ourselves, which makes the checkbuttons and +# radiobuttons look bad. This chooses white for selectColor if the +# background color is light, or black if it is dark. +proc setui {c} { + set bg [winfo rgb . $c] + set selc black + if {[lindex $bg 0] + 1.5 * [lindex $bg 1] + 0.5 * [lindex $bg 2] > 100000} { + set selc white + } + tk_setPalette background $c selectColor $selc +} + proc setbg {c} { global bglist @@ -10595,7 +10865,7 @@ proc prefscan {} { global oldprefs prefstop foreach v {maxwidth maxgraphpct showneartags showlocalchanges \ - limitdiffs tabstop perfile_attrs hideremotes} { + limitdiffs tabstop perfile_attrs hideremotes want_ttk} { global $v set $v $oldprefs($v) } @@ -11006,8 +11276,8 @@ proc get_path_encoding {path} { # First check that Tcl/Tk is recent enough if {[catch {package require Tk 8.4} err]} { - show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\ - Gitk requires at least Tcl/Tk 8.4."] + show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\ + Gitk requires at least Tcl/Tk 8.4." list exit 1 } @@ -11071,6 +11341,7 @@ set limitdiffs 1 set datetimeformat "%Y-%m-%d %H:%M:%S" set autoselect 1 set perfile_attrs 0 +set want_ttk 1 if {[tk windowingsystem] eq "aqua"} { set extdifftool "opendiff" @@ -11079,12 +11350,20 @@ if {[tk windowingsystem] eq "aqua"} { } set colors {green red blue magenta darkgrey brown orange} -set bgcolor white -set fgcolor black +if {[tk windowingsystem] eq "win32"} { + set uicolor SystemButtonFace + set bgcolor SystemWindow + set fgcolor SystemButtonText + set selectbgcolor SystemHighlight +} else { + set uicolor grey85 + set bgcolor white + set fgcolor black + set selectbgcolor gray85 +} set diffcolors {red "#00a000" blue} set diffcontext 3 set ignorespace 0 -set selectbgcolor gray85 set markbgcolor "#e0e0ff" set circlecolors {white blue gray blue blue} @@ -11130,6 +11409,8 @@ eval font create textfontbold [fontflags textfont 1] parsefont uifont $uifont eval font create uifont [fontflags uifont] +setui $uicolor + setoptions # check that we can find a .git directory somewhere... @@ -11207,6 +11488,12 @@ set nullid2 "0000000000000000000000000000000000000001" set nullfile "/dev/null" set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}] +if {![info exists have_ttk]} { + set have_ttk [llength [info commands ::ttk::style]] +} +set use_ttk [expr {$have_ttk && $want_ttk}] +set NS [expr {$use_ttk ? "ttk" : ""}] + set git_version [join [lrange [split [lindex [exec git version] end] .] 0 2] .] set runq {} diff --git a/gitk-git/po/ja.po b/gitk-git/po/ja.po new file mode 100644 index 0000000000..c0c92addb4 --- /dev/null +++ b/gitk-git/po/ja.po @@ -0,0 +1,1255 @@ +# Japanese translations for gitk package. +# Copyright (C) 2005-2009 Paul Mackerras +# This file is distributed under the same license as the gitk package. +# +# Mizar <mizar.jp@gmail.com>, 2009. +# Junio C Hamano <gitster@pobox.com>, 2009. +msgid "" +msgstr "" +"Project-Id-Version: gitk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-11-04 00:08+0900\n" +"PO-Revision-Date: 2009-11-06 01:45+0900\n" +"Last-Translator: Mizar <mizar.jp@gmail.com>\n" +"Language-Team: Japanese\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: gitk:113 +msgid "Couldn't get list of unmerged files:" +msgstr "マージされていないファイルのリストを取得できません:" + +#: gitk:269 +msgid "Error parsing revisions:" +msgstr "リビジョン解析エラー:" + +#: gitk:324 +msgid "Error executing --argscmd command:" +msgstr "--argscmd コマンド実行エラー:" + +#: gitk:337 +msgid "No files selected: --merge specified but no files are unmerged." +msgstr "" +"ファイル未選択: --merge が指定されましたが、マージされていないファイルはあり" +"ません。" + +#: gitk:340 +msgid "" +"No files selected: --merge specified but no unmerged files are within file " +"limit." +msgstr "" +"ファイル未選択: --merge が指定されましたが、ファイル制限内にマージされていな" +"いファイルはありません。" + +#: gitk:362 gitk:509 +msgid "Error executing git log:" +msgstr "git log 実行エラー:" + +#: gitk:380 gitk:525 +msgid "Reading" +msgstr "読み込み中" + +#: gitk:440 gitk:4132 +msgid "Reading commits..." +msgstr "コミット読み込み中..." + +#: gitk:443 gitk:1561 gitk:4135 +msgid "No commits selected" +msgstr "コミットが選択されていません" + +#: gitk:1437 +msgid "Can't parse git log output:" +msgstr "git log の出力を解析できません:" + +#: gitk:1657 +msgid "No commit information available" +msgstr "有効なコミットの情報がありません" + +#: gitk:1790 +msgid "mc" +msgstr "mc" + +#: gitk:1817 gitk:3925 gitk:8842 gitk:10378 gitk:10558 +msgid "OK" +msgstr "OK" + +#: gitk:1819 gitk:3927 gitk:8439 gitk:8513 gitk:8623 gitk:8672 gitk:8844 +#: gitk:10379 gitk:10559 +msgid "Cancel" +msgstr "キャンセル" + +#: gitk:1919 +msgid "Update" +msgstr "更新" + +#: gitk:1920 +msgid "Reload" +msgstr "リロード" + +#: gitk:1921 +msgid "Reread references" +msgstr "リファレンスを再読み込み" + +#: gitk:1922 +msgid "List references" +msgstr "リファレンスリストを表示" + +#: gitk:1924 +msgid "Start git gui" +msgstr "git gui の開始" + +#: gitk:1926 +msgid "Quit" +msgstr "終了" + +#: gitk:1918 +msgid "File" +msgstr "ファイル" + +#: gitk:1930 +msgid "Preferences" +msgstr "設定" + +#: gitk:1929 +msgid "Edit" +msgstr "編集" + +#: gitk:1934 +msgid "New view..." +msgstr "新規ビュー..." + +#: gitk:1935 +msgid "Edit view..." +msgstr "ビュー編集..." + +#: gitk:1936 +msgid "Delete view" +msgstr "ビュー削除" + +#: gitk:1938 +msgid "All files" +msgstr "全てのファイル" + +#: gitk:1933 gitk:3679 +msgid "View" +msgstr "ビュー" + +#: gitk:1943 gitk:1953 gitk:2656 +msgid "About gitk" +msgstr "gitk について" + +#: gitk:1944 gitk:1958 +msgid "Key bindings" +msgstr "キーバインディング" + +#: gitk:1942 gitk:1957 +msgid "Help" +msgstr "ヘルプ" + +#: gitk:2018 +msgid "SHA1 ID: " +msgstr "SHA1 ID: " + +#: gitk:2049 +msgid "Row" +msgstr "行" + +#: gitk:2080 +msgid "Find" +msgstr "検索" + +#: gitk:2081 +msgid "next" +msgstr "次" + +#: gitk:2082 +msgid "prev" +msgstr "前" + +#: gitk:2083 +msgid "commit" +msgstr "コミット" + +#: gitk:2086 gitk:2088 gitk:4293 gitk:4316 gitk:4340 gitk:6281 gitk:6353 +#: gitk:6437 +msgid "containing:" +msgstr "含む:" + +#: gitk:2089 gitk:3164 gitk:3169 gitk:4368 +msgid "touching paths:" +msgstr "パスの一部:" + +#: gitk:2090 gitk:4373 +msgid "adding/removing string:" +msgstr "追加/除去する文字列:" + +#: gitk:2099 gitk:2101 +msgid "Exact" +msgstr "英字の大小を区別する" + +#: gitk:2101 gitk:4448 gitk:6249 +msgid "IgnCase" +msgstr "英字の大小を区別しない" + +#: gitk:2101 gitk:4342 gitk:4446 gitk:6245 +msgid "Regexp" +msgstr "正規表現" + +#: gitk:2103 gitk:2104 gitk:4467 gitk:4497 gitk:4504 gitk:6373 gitk:6441 +msgid "All fields" +msgstr "全ての項目" + +#: gitk:2104 gitk:4465 gitk:4497 gitk:6312 +msgid "Headline" +msgstr "ヘッドライン" + +#: gitk:2105 gitk:4465 gitk:6312 gitk:6441 gitk:6875 +msgid "Comments" +msgstr "コメント" + +#: gitk:2105 gitk:4465 gitk:4469 gitk:4504 gitk:6312 gitk:6810 gitk:8091 +#: gitk:8106 +msgid "Author" +msgstr "作者" + +#: gitk:2105 gitk:4465 gitk:6312 gitk:6812 +msgid "Committer" +msgstr "コミット者" + +#: gitk:2134 +msgid "Search" +msgstr "検索" + +#: gitk:2141 +msgid "Diff" +msgstr "Diff" + +#: gitk:2143 +msgid "Old version" +msgstr "旧バージョン" + +#: gitk:2145 +msgid "New version" +msgstr "新バージョン" + +#: gitk:2147 +msgid "Lines of context" +msgstr "文脈行数" + +#: gitk:2157 +msgid "Ignore space change" +msgstr "空白の違いを無視" + +#: gitk:2215 +msgid "Patch" +msgstr "パッチ" + +#: gitk:2217 +msgid "Tree" +msgstr "ツリー" + +#: gitk:2361 gitk:2378 +msgid "Diff this -> selected" +msgstr "これと選択したコミットのdiffを見る" + +#: gitk:2362 gitk:2379 +msgid "Diff selected -> this" +msgstr "選択したコミットとこれのdiffを見る" + +#: gitk:2363 gitk:2380 +msgid "Make patch" +msgstr "パッチ作成" + +#: gitk:2364 gitk:8497 +msgid "Create tag" +msgstr "タグ生成" + +#: gitk:2365 gitk:8603 +msgid "Write commit to file" +msgstr "コミットをファイルに書き出す" + +#: gitk:2366 gitk:8660 +msgid "Create new branch" +msgstr "新規ブランチ生成" + +#: gitk:2367 +msgid "Cherry-pick this commit" +msgstr "このコミットをチェリーピックする" + +#: gitk:2368 +msgid "Reset HEAD branch to here" +msgstr "ブランチのHEADをここにリセットする" + +#: gitk:2369 +msgid "Mark this commit" +msgstr "このコミットにマークをつける" + +#: gitk:2370 +msgid "Return to mark" +msgstr "マークを付けた所に戻る" + +#: gitk:2371 +msgid "Find descendant of this and mark" +msgstr "これとマークをつけた所との子孫を見つける" + +#: gitk:2372 +msgid "Compare with marked commit" +msgstr "マークを付けたコミットと比較する" + +#: gitk:2386 +msgid "Check out this branch" +msgstr "このブランチをチェックアウトする" + +#: gitk:2387 +msgid "Remove this branch" +msgstr "このブランチを除去する" + +#: gitk:2394 +msgid "Highlight this too" +msgstr "これもハイライトさせる" + +#: gitk:2395 +msgid "Highlight this only" +msgstr "これだけをハイライトさせる" + +#: gitk:2396 +msgid "External diff" +msgstr "外部diffツール" + +#: gitk:2397 +msgid "Blame parent commit" +msgstr "親コミットから blame をかける" + +#: gitk:2404 +msgid "Show origin of this line" +msgstr "この行の出自を表示する" + +#: gitk:2405 +msgid "Run git gui blame on this line" +msgstr "この行に git gui で blame をかける" + +#: gitk:2658 +msgid "" +"\n" +"Gitk - a commit viewer for git\n" +"\n" +"Copyright © 2005-2008 Paul Mackerras\n" +"\n" +"Use and redistribute under the terms of the GNU General Public License" +msgstr "" +"\n" +"Gitk - gitコミットビューア\n" +"\n" +"Copyright © 2005-2008 Paul Mackerras\n" +"\n" +"使用および再配布は GNU General Public License に従ってください" + +#: gitk:2666 gitk:2728 gitk:9025 +msgid "Close" +msgstr "閉じる" + +#: gitk:2685 +msgid "Gitk key bindings" +msgstr "Gitk キーバインディング" + +#: gitk:2688 +msgid "Gitk key bindings:" +msgstr "Gitk キーバインディング:" + +#: gitk:2690 +#, tcl-format +msgid "<%s-Q>\t\tQuit" +msgstr "<%s-Q>\t\t終了" + +#: gitk:2691 +msgid "<Home>\t\tMove to first commit" +msgstr "<Home>\t\t最初のコミットに移動" + +#: gitk:2692 +msgid "<End>\t\tMove to last commit" +msgstr "<End>\t\t最後のコミットに移動" + +#: gitk:2693 +msgid "<Up>, p, i\tMove up one commit" +msgstr "<Up>, p, i\t一つ上のコミットに移動" + +#: gitk:2694 +msgid "<Down>, n, k\tMove down one commit" +msgstr "<Down>, n, k\t一つ下のコミットに移動" + +#: gitk:2695 +msgid "<Left>, z, j\tGo back in history list" +msgstr "<Left>, z, j\t履歴の前に戻る" + +#: gitk:2696 +msgid "<Right>, x, l\tGo forward in history list" +msgstr "<Right>, x, l\t履歴の次へ進む" + +#: gitk:2697 +msgid "<PageUp>\tMove up one page in commit list" +msgstr "<PageUp>\tコミットリストの一つ上のページに移動" + +#: gitk:2698 +msgid "<PageDown>\tMove down one page in commit list" +msgstr "<PageDown>\tコミットリストの一つ下のページに移動" + +#: gitk:2699 +#, tcl-format +msgid "<%s-Home>\tScroll to top of commit list" +msgstr "<%s-Home>\tコミットリストの一番上にスクロールする" + +#: gitk:2700 +#, tcl-format +msgid "<%s-End>\tScroll to bottom of commit list" +msgstr "<%s-End>\tコミットリストの一番下にスクロールする" + +#: gitk:2701 +#, tcl-format +msgid "<%s-Up>\tScroll commit list up one line" +msgstr "<%s-Up>\tコミットリストの一つ下の行にスクロールする" + +#: gitk:2702 +#, tcl-format +msgid "<%s-Down>\tScroll commit list down one line" +msgstr "<%s-Down>\tコミットリストの一つ下の行にスクロールする" + +#: gitk:2703 +#, tcl-format +msgid "<%s-PageUp>\tScroll commit list up one page" +msgstr "<%s-PageUp>\tコミットリストの上のページにスクロールする" + +#: gitk:2704 +#, tcl-format +msgid "<%s-PageDown>\tScroll commit list down one page" +msgstr "<%s-PageDown>\tコミットリストの下のページにスクロールする" + +#: gitk:2705 +msgid "<Shift-Up>\tFind backwards (upwards, later commits)" +msgstr "<Shift-Up>\t後方を検索 (上方の・新しいコミット)" + +#: gitk:2706 +msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" +msgstr "<Shift-Down>\t前方を検索(下方の・古いコミット)" + +#: gitk:2707 +msgid "<Delete>, b\tScroll diff view up one page" +msgstr "<Delete>, b\tdiff画面を上のページにスクロールする" + +#: gitk:2708 +msgid "<Backspace>\tScroll diff view up one page" +msgstr "<Backspace>\tdiff画面を上のページにスクロールする" + +#: gitk:2709 +msgid "<Space>\t\tScroll diff view down one page" +msgstr "<Space>\t\tdiff画面を下のページにスクロールする" + +#: gitk:2710 +msgid "u\t\tScroll diff view up 18 lines" +msgstr "u\t\tdiff画面を上に18行スクロールする" + +#: gitk:2711 +msgid "d\t\tScroll diff view down 18 lines" +msgstr "d\t\tdiff画面を下に18行スクロールする" + +#: gitk:2712 +#, tcl-format +msgid "<%s-F>\t\tFind" +msgstr "<%s-F>\t\t検索" + +#: gitk:2713 +#, tcl-format +msgid "<%s-G>\t\tMove to next find hit" +msgstr "<%s-G>\t\t次を検索して移動" + +#: gitk:2714 +msgid "<Return>\tMove to next find hit" +msgstr "<Return>\t次を検索して移動" + +#: gitk:2715 +msgid "/\t\tFocus the search box" +msgstr "/\t\t検索ボックスにフォーカス" + +#: gitk:2716 +msgid "?\t\tMove to previous find hit" +msgstr "?\t\t前を検索して移動" + +#: gitk:2717 +msgid "f\t\tScroll diff view to next file" +msgstr "f\t\t次のファイルにdiff画面をスクロールする" + +#: gitk:2718 +#, tcl-format +msgid "<%s-S>\t\tSearch for next hit in diff view" +msgstr "<%s-S>\t\tdiff画面の次を検索" + +#: gitk:2719 +#, tcl-format +msgid "<%s-R>\t\tSearch for previous hit in diff view" +msgstr "<%s-R>\t\tdiff画面の前を検索" + +#: gitk:2720 +#, tcl-format +msgid "<%s-KP+>\tIncrease font size" +msgstr "<%s-KP+>\t文字サイズを拡大" + +#: gitk:2721 +#, tcl-format +msgid "<%s-plus>\tIncrease font size" +msgstr "<%s-plus>\t文字サイズを拡大" + +#: gitk:2722 +#, tcl-format +msgid "<%s-KP->\tDecrease font size" +msgstr "<%s-KP->\t文字サイズを縮小" + +#: gitk:2723 +#, tcl-format +msgid "<%s-minus>\tDecrease font size" +msgstr "<%s-minus>\t文字サイズを縮小" + +#: gitk:2724 +msgid "<F5>\t\tUpdate" +msgstr "<F5>\t\t更新" + +#: gitk:3179 gitk:3188 +#, tcl-format +msgid "Error creating temporary directory %s:" +msgstr "一時ディレクトリ %s 生成時エラー:" + +#: gitk:3201 +#, tcl-format +msgid "Error getting \"%s\" from %s:" +msgstr "\"%s\" のエラーが %s に発生:" + +#: gitk:3264 +msgid "command failed:" +msgstr "コマンド失敗:" + +#: gitk:3410 +msgid "No such commit" +msgstr "そのようなコミットはありません" + +#: gitk:3424 +msgid "git gui blame: command failed:" +msgstr "git gui blame: コマンド失敗:" + +#: gitk:3455 +#, tcl-format +msgid "Couldn't read merge head: %s" +msgstr "マージする HEAD を読み込めません: %s" + +#: gitk:3463 +#, tcl-format +msgid "Error reading index: %s" +msgstr "インデックス読み込みエラー: %s" + +#: gitk:3488 +#, tcl-format +msgid "Couldn't start git blame: %s" +msgstr "git blame を始められません: %s" + +#: gitk:3491 gitk:6280 +msgid "Searching" +msgstr "検索中" + +#: gitk:3523 +#, tcl-format +msgid "Error running git blame: %s" +msgstr "git blame 実行エラー: %s" + +#: gitk:3551 +#, tcl-format +msgid "That line comes from commit %s, which is not in this view" +msgstr "コミット %s に由来するその行は、このビューに表示されていません" + +#: gitk:3565 +msgid "External diff viewer failed:" +msgstr "外部diffビューアが失敗:" + +#: gitk:3683 +msgid "Gitk view definition" +msgstr "Gitk ビュー定義" + +#: gitk:3687 +msgid "Remember this view" +msgstr "このビューを記憶する" + +#: gitk:3688 +msgid "References (space separated list):" +msgstr "リファレンス(スペース区切りのリスト):" + +#: gitk:3689 +msgid "Branches & tags:" +msgstr "ブランチ&タグ:" + +#: gitk:3690 +msgid "All refs" +msgstr "全てのリファレンス" + +#: gitk:3691 +msgid "All (local) branches" +msgstr "全ての(ローカルな)ブランチ" + +#: gitk:3692 +msgid "All tags" +msgstr "全てのタグ" + +#: gitk:3693 +msgid "All remote-tracking branches" +msgstr "全てのリモート追跡ブランチ" + +#: gitk:3694 +msgid "Commit Info (regular expressions):" +msgstr "コミット情報(正規表現):" + +#: gitk:3695 +msgid "Author:" +msgstr "作者:" + +#: gitk:3696 +msgid "Committer:" +msgstr "コミット者:" + +#: gitk:3697 +msgid "Commit Message:" +msgstr "コミットメッセージ:" + +#: gitk:3698 +msgid "Matches all Commit Info criteria" +msgstr "コミット情報の全ての条件に一致" + +#: gitk:3699 +msgid "Changes to Files:" +msgstr "変更したファイル:" + +#: gitk:3700 +msgid "Fixed String" +msgstr "固定文字列" + +#: gitk:3701 +msgid "Regular Expression" +msgstr "正規表現" + +#: gitk:3702 +msgid "Search string:" +msgstr "検索文字列:" + +#: gitk:3703 +msgid "" +"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" +msgstr "" +"コミット日時 (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" + +#: gitk:3704 +msgid "Since:" +msgstr "期間の始め:" + +#: gitk:3705 +msgid "Until:" +msgstr "期間の終わり:" + +#: gitk:3706 +msgid "Limit and/or skip a number of revisions (positive integer):" +msgstr "制限・省略するリビジョンの数(正の整数):" + +#: gitk:3707 +msgid "Number to show:" +msgstr "表示する数:" + +#: gitk:3708 +msgid "Number to skip:" +msgstr "省略する数:" + +#: gitk:3709 +msgid "Miscellaneous options:" +msgstr "その他のオプション:" + +#: gitk:3710 +msgid "Strictly sort by date" +msgstr "厳密に日付順で並び替え" + +#: gitk:3711 +msgid "Mark branch sides" +msgstr "側枝マーク" + +#: gitk:3712 +msgid "Limit to first parent" +msgstr "最初の親に制限" + +#: gitk:3713 +msgid "Simple history" +msgstr "簡易な履歴" + +#: gitk:3714 +msgid "Additional arguments to git log:" +msgstr "git log への追加の引数:" + +#: gitk:3715 +msgid "Enter files and directories to include, one per line:" +msgstr "含まれるファイル・ディレクトリを一行ごとに入力:" + +#: gitk:3716 +msgid "Command to generate more commits to include:" +msgstr "コミット追加コマンド:" + +#: gitk:3838 +msgid "Gitk: edit view" +msgstr "Gitk: ビュー編集" + +#: gitk:3846 +msgid "-- criteria for selecting revisions" +msgstr "― リビジョンの選択条件" + +#: gitk:3851 +msgid "View Name:" +msgstr "ビュー名:" + +#: gitk:3926 +msgid "Apply (F5)" +msgstr "適用 (F5)" + +#: gitk:3964 +msgid "Error in commit selection arguments:" +msgstr "コミット選択引数のエラー:" + +#: gitk:4017 gitk:4069 gitk:4517 gitk:4531 gitk:5792 gitk:11263 gitk:11264 +msgid "None" +msgstr "無し" + +#: gitk:4465 gitk:6312 gitk:8093 gitk:8108 +msgid "Date" +msgstr "日付" + +#: gitk:4465 gitk:6312 +msgid "CDate" +msgstr "作成日" + +#: gitk:4614 gitk:4619 +msgid "Descendant" +msgstr "子孫" + +#: gitk:4615 +msgid "Not descendant" +msgstr "非子孫" + +#: gitk:4622 gitk:4627 +msgid "Ancestor" +msgstr "祖先" + +#: gitk:4623 +msgid "Not ancestor" +msgstr "非祖先" + +#: gitk:4913 +msgid "Local changes checked in to index but not committed" +msgstr "ステージされた、コミット前のローカルな変更" + +#: gitk:4949 +msgid "Local uncommitted changes, not checked in to index" +msgstr "ステージされていない、コミット前のローカルな変更" + +#: gitk:6630 +msgid "many" +msgstr "多数" + +#: gitk:6814 +msgid "Tags:" +msgstr "タグ:" + +#: gitk:6831 gitk:6837 gitk:8086 +msgid "Parent" +msgstr "親" + +#: gitk:6842 +msgid "Child" +msgstr "子" + +#: gitk:6851 +msgid "Branch" +msgstr "ブランチ" + +#: gitk:6854 +msgid "Follows" +msgstr "下位" + +#: gitk:6857 +msgid "Precedes" +msgstr "上位" + +#: gitk:7359 +#, tcl-format +msgid "Error getting diffs: %s" +msgstr "diff取得エラー: %s" + +#: gitk:7914 +msgid "Goto:" +msgstr "Goto:" + +#: gitk:7916 +msgid "SHA1 ID:" +msgstr "SHA1 ID:" + +#: gitk:7935 +#, tcl-format +msgid "Short SHA1 id %s is ambiguous" +msgstr "%s を含む SHA1 ID は複数存在します" + +#: gitk:7942 +#, tcl-format +msgid "Revision %s is not known" +msgstr "リビジョン %s は不明です" + +#: gitk:7952 +#, tcl-format +msgid "SHA1 id %s is not known" +msgstr "SHA1 id %s は不明です" + +#: gitk:7954 +#, tcl-format +msgid "Revision %s is not in the current view" +msgstr "リビジョン %s は現在のビューにはありません" + +#: gitk:8096 +msgid "Children" +msgstr "子" + +#: gitk:8153 +#, tcl-format +msgid "Reset %s branch to here" +msgstr "%s ブランチをここにリセットする" + +#: gitk:8155 +msgid "Detached head: can't reset" +msgstr "切り離されたHEAD: リセットできません" + +#: gitk:8264 gitk:8270 +msgid "Skipping merge commit " +msgstr "コミットマージをスキップ: " + +#: gitk:8279 gitk:8284 +msgid "Error getting patch ID for " +msgstr "パッチ取得エラー: ID " + +#: gitk:8280 gitk:8285 +msgid " - stopping\n" +msgstr " - 停止\n" + +#: gitk:8290 gitk:8293 gitk:8301 gitk:8314 gitk:8323 +msgid "Commit " +msgstr "コミット " + +#: gitk:8294 +msgid "" +" is the same patch as\n" +" " +msgstr "" +" は下記のパッチと同等\n" +" " + +#: gitk:8302 +msgid "" +" differs from\n" +" " +msgstr "" +" 下記からのdiff\n" +" " + +#: gitk:8304 +msgid "" +"Diff of commits:\n" +"\n" +msgstr "" +"コミットのdiff:\n" +"\n" + +#: gitk:8315 gitk:8324 +#, tcl-format +msgid " has %s children - stopping\n" +msgstr " には %s の子があります - 停止\n" + +#: gitk:8344 +#, tcl-format +msgid "Error writing commit to file: %s" +msgstr "ファイルへのコミット書き出しエラー: %s" + +#: gitk:8350 +#, tcl-format +msgid "Error diffing commits: %s" +msgstr "コミットのdiff実行エラー: %s" + +#: gitk:8380 +msgid "Top" +msgstr "Top" + +#: gitk:8381 +msgid "From" +msgstr "From" + +#: gitk:8386 +msgid "To" +msgstr "To" + +#: gitk:8410 +msgid "Generate patch" +msgstr "パッチ生成" + +#: gitk:8412 +msgid "From:" +msgstr "From:" + +#: gitk:8421 +msgid "To:" +msgstr "To:" + +#: gitk:8430 +msgid "Reverse" +msgstr "逆" + +#: gitk:8432 gitk:8617 +msgid "Output file:" +msgstr "出力ファイル:" + +#: gitk:8438 +msgid "Generate" +msgstr "生成" + +#: gitk:8476 +msgid "Error creating patch:" +msgstr "パッチ生成エラー:" + +#: gitk:8499 gitk:8605 gitk:8662 +msgid "ID:" +msgstr "ID:" + +#: gitk:8508 +msgid "Tag name:" +msgstr "タグ名:" + +#: gitk:8512 gitk:8671 +msgid "Create" +msgstr "生成" + +#: gitk:8529 +msgid "No tag name specified" +msgstr "タグの名称が指定されていません" + +#: gitk:8533 +#, tcl-format +msgid "Tag \"%s\" already exists" +msgstr "タグ \"%s\" は既に存在します" + +#: gitk:8539 +msgid "Error creating tag:" +msgstr "タグ生成エラー:" + +#: gitk:8614 +msgid "Command:" +msgstr "コマンド:" + +#: gitk:8622 +msgid "Write" +msgstr "書き出し" + +#: gitk:8640 +msgid "Error writing commit:" +msgstr "コミット書き出しエラー:" + +#: gitk:8667 +msgid "Name:" +msgstr "名前:" + +#: gitk:8690 +msgid "Please specify a name for the new branch" +msgstr "新しいブランチの名前を指定してください" + +#: gitk:8695 +#, tcl-format +msgid "Branch '%s' already exists. Overwrite?" +msgstr "ブランチ '%s' は既に存在します。上書きしますか?" + +#: gitk:8761 +#, tcl-format +msgid "Commit %s is already included in branch %s -- really re-apply it?" +msgstr "" +"コミット %s は既にブランチ %s に含まれています ― 本当にこれを再適用しますか?" + +#: gitk:8766 +msgid "Cherry-picking" +msgstr "チェリーピック中" + +#: gitk:8775 +#, 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 "" +"ファイル '%s' のローカルな変更のためにチェリーピックは失敗しました。\n" +"あなたの変更に commit, reset, stash のいずれかを行ってからやり直してくださ" +"い。" + +#: gitk:8781 +msgid "" +"Cherry-pick failed because of merge conflict.\n" +"Do you wish to run git citool to resolve it?" +msgstr "" +"マージの衝突によってチェリーピックは失敗しました。\n" +"この解決のために git citool を実行したいですか?" + +#: gitk:8797 +msgid "No changes committed" +msgstr "何の変更もコミットされていません" + +#: gitk:8823 +msgid "Confirm reset" +msgstr "確認を取り消す" + +#: gitk:8825 +#, tcl-format +msgid "Reset branch %s to %s?" +msgstr "ブランチ %s を %s にリセットしますか?" + +#: gitk:8829 +msgid "Reset type:" +msgstr "Reset タイプ:" + +#: gitk:8833 +msgid "Soft: Leave working tree and index untouched" +msgstr "Soft: 作業ツリーもインデックスもそのままにする" + +#: gitk:8836 +msgid "Mixed: Leave working tree untouched, reset index" +msgstr "Mixed: 作業ツリーをそのままにして、インデックスをリセット" + +#: gitk:8839 +msgid "" +"Hard: Reset working tree and index\n" +"(discard ALL local changes)" +msgstr "" +"Hard: 作業ツリーやインデックスをリセット\n" +"(「全ての」ローカルな変更を破棄)" + +#: gitk:8856 +msgid "Resetting" +msgstr "リセット中" + +#: gitk:8913 +msgid "Checking out" +msgstr "チェックアウト" + +#: gitk:8966 +msgid "Cannot delete the currently checked-out branch" +msgstr "現在チェックアウトされているブランチを削除することはできません" + +#: gitk:8972 +#, tcl-format +msgid "" +"The commits on branch %s aren't on any other branch.\n" +"Really delete branch %s?" +msgstr "" +"ブランチ %s には他のブランチに存在しないコミットがあります。\n" +"本当にブランチ %s を削除しますか?" + +#: gitk:9003 +#, tcl-format +msgid "Tags and heads: %s" +msgstr "タグとHEAD: %s" + +#: gitk:9018 +msgid "Filter" +msgstr "フィルター" + +#: gitk:9313 +msgid "" +"Error reading commit topology information; branch and preceding/following " +"tag information will be incomplete." +msgstr "" +"コミット構造情報読み込みエラー; ブランチ及び上位/下位のタグ情報が不完全である" +"ようです。" + +#: gitk:10299 +msgid "Tag" +msgstr "タグ" + +#: gitk:10299 +msgid "Id" +msgstr "ID" + +#: gitk:10347 +msgid "Gitk font chooser" +msgstr "Gitk フォント選択" + +#: gitk:10364 +msgid "B" +msgstr "B" + +#: gitk:10367 +msgid "I" +msgstr "I" + +#: gitk:10463 +msgid "Gitk preferences" +msgstr "Gitk 設定" + +#: gitk:10465 +msgid "Commit list display options" +msgstr "コミットリスト表示オプション" + +#: gitk:10468 +msgid "Maximum graph width (lines)" +msgstr "最大グラフ幅(線の本数)" + +#: gitk:10472 +#, tcl-format +msgid "Maximum graph width (% of pane)" +msgstr "最大グラフ幅(ペインに対する%)" + +#: gitk:10476 +msgid "Show local changes" +msgstr "ローカルな変更を表示" + +#: gitk:10479 +msgid "Auto-select SHA1" +msgstr "SHA1 の自動選択" + +#: gitk:10483 +msgid "Diff display options" +msgstr "diff表示オプション" + +#: gitk:10485 +msgid "Tab spacing" +msgstr "タブ空白幅" + +#: gitk:10488 +msgid "Display nearby tags" +msgstr "近くのタグを表示する" + +#: gitk:10491 +msgid "Hide remote refs" +msgstr "リモートリファレンスを隠す" + +#: gitk:10494 +msgid "Limit diffs to listed paths" +msgstr "diff をリストのパスに制限" + +#: gitk:10497 +msgid "Support per-file encodings" +msgstr "ファイルごとのエンコーディングのサポート" + +#: gitk:10503 gitk:10572 +msgid "External diff tool" +msgstr "外部diffツール" + +#: gitk:10505 +msgid "Choose..." +msgstr "選択..." + +#: gitk:10510 +msgid "Colors: press to choose" +msgstr "色: ボタンを押して選択" + +#: gitk:10513 +msgid "Interface" +msgstr "インターフェイス" + +#: gitk:10514 +msgid "interface" +msgstr "インターフェイス" + +#: gitk:10517 +msgid "Background" +msgstr "背景" + +#: gitk:10518 gitk:10548 +msgid "background" +msgstr "背景" + +#: gitk:10521 +msgid "Foreground" +msgstr "前景" + +#: gitk:10522 +msgid "foreground" +msgstr "前景" + +#: gitk:10525 +msgid "Diff: old lines" +msgstr "Diff: 旧バージョン" + +#: gitk:10526 +msgid "diff old lines" +msgstr "diff 旧バージョン" + +#: gitk:10530 +msgid "Diff: new lines" +msgstr "Diff: 新バージョン" + +#: gitk:10531 +msgid "diff new lines" +msgstr "diff 新バージョン" + +#: gitk:10535 +msgid "Diff: hunk header" +msgstr "Diff: hunkヘッダ" + +#: gitk:10537 +msgid "diff hunk header" +msgstr "diff hunkヘッダ" + +#: gitk:10541 +msgid "Marked line bg" +msgstr "マーク行の背景" + +#: gitk:10543 +msgid "marked line background" +msgstr "マーク行の背景" + +#: gitk:10547 +msgid "Select bg" +msgstr "選択の背景" + +#: gitk:10551 +msgid "Fonts: press to choose" +msgstr "フォント: ボタンを押して選択" + +#: gitk:10553 +msgid "Main font" +msgstr "主フォント" + +#: gitk:10554 +msgid "Diff display font" +msgstr "Diff表示用フォント" + +#: gitk:10555 +msgid "User interface font" +msgstr "UI用フォント" + +#: gitk:10582 +#, tcl-format +msgid "Gitk: choose color for %s" +msgstr "Gitk: 「%s」 の色を選択" + +#: gitk:11168 +msgid "Cannot find a git repository here." +msgstr "ここにはgitリポジトリがありません。" + +#: gitk:11172 +#, tcl-format +msgid "Cannot find the git directory \"%s\"." +msgstr "gitディレクトリ \"%s\" を見つけられません。" + +#: gitk:11219 +#, tcl-format +msgid "Ambiguous argument '%s': both revision and filename" +msgstr "あいまいな引数 '%s': リビジョンとファイル名の両方に解釈できます" + +#: gitk:11231 +msgid "Bad arguments to gitk:" +msgstr "gitkへの不正な引数:" + +#: gitk:11316 +msgid "Command line" +msgstr "コマンド行" diff --git a/gitweb/README b/gitweb/README index 66c6a9391d..e34ee793ef 100644 --- a/gitweb/README +++ b/gitweb/README @@ -92,6 +92,11 @@ You can specify the following configuration variables when building GIT: web browsers that support favicons (website icons) may display them in the browser's URL bar and next to site name in bookmarks). Relative to base URI of gitweb. [Default: git-favicon.png] + * GITWEB_JS + Points to the localtion where you put gitweb.js on your web server + (or to be more generic URI of JavaScript code used by gitweb). + Relative to base URI of gitweb. [Default: gitweb.js (or gitweb.min.js + if JSMIN build variable is defined / JavaScript minifier is used)] * GITWEB_CONFIG This Perl file will be loaded using 'do' and can be used to override any of the options above as well as some other options -- see the "Runtime diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 8f68fe3091..50067f2e0d 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; @@ -75,6 +79,13 @@ div.page_footer_text { font-style: italic; } +div#generating_info { + margin: 4px; + font-size: smaller; + text-align: center; + color: #505050; +} + div.page_body { padding: 8px; font-family: monospace; @@ -250,6 +261,11 @@ tr.no-previous td.linenr { font-weight: bold; } +/* for 'blame_incremental', during processing */ +tr.color1 { background-color: #f6fff6; } +tr.color2 { background-color: #f6f6ff; } +tr.color3 { background-color: #fff6f6; } + td { padding: 2px 5px; font-size: 100%; @@ -341,6 +357,23 @@ td.mode { font-family: monospace; } +/* progress of blame_interactive */ +div#progress_bar { + height: 2px; + margin-bottom: -2px; + background-color: #d8d9d0; +} +div#progress_info { + float: right; + text-align: right; +} + +/* 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.js b/gitweb/gitweb.js new file mode 100644 index 0000000000..2a25b7cc47 --- /dev/null +++ b/gitweb/gitweb.js @@ -0,0 +1,870 @@ +// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com> +// 2007, Petr Baudis <pasky@suse.cz> +// 2008-2009, Jakub Narebski <jnareb@gmail.com> + +/** + * @fileOverview JavaScript code for gitweb (git web interface). + * @license GPLv2 or later + */ + +/* ============================================================ */ +/* functions for generic gitweb actions and views */ + +/** + * used to check if link has 'js' query parameter already (at end), + * and other reasons to not add 'js=1' param at the end of link + * @constant + */ +var jsExceptionsRe = /[;?]js=[01]$/; + +/** + * Add '?js=1' or ';js=1' to the end of every link in the document + * that doesn't have 'js' query parameter set already. + * + * Links with 'js=1' lead to JavaScript version of given action, if it + * exists (currently there is only 'blame_incremental' for 'blame') + * + * @globals jsExceptionsRe + */ +function fixLinks() { + var allLinks = document.getElementsByTagName("a") || document.links; + for (var i = 0, len = allLinks.length; i < len; i++) { + var link = allLinks[i]; + if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/; + link.href += + (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1'; + } + } +} + + +/* ============================================================ */ + +/* + * This code uses DOM methods instead of (nonstandard) innerHTML + * to modify page. + * + * innerHTML is non-standard IE extension, though supported by most + * browsers; however Firefox up to version 1.5 didn't implement it in + * a strict mode (application/xml+xhtml mimetype). + * + * Also my simple benchmarks show that using elem.firstChild.data = + * 'content' is slightly faster than elem.innerHTML = 'content'. It + * is however more fragile (text element fragment must exists), and + * less feature-rich (we cannot add HTML). + * + * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the + * equivalent using DOM 2 Core is usually shown in comments. + */ + + +/* ============================================================ */ +/* generic utility functions */ + + +/** + * pad number N with nonbreakable spaces on the left, to WIDTH characters + * example: padLeftStr(12, 3, '\u00A0') == '\u00A012' + * ('\u00A0' is nonbreakable space) + * + * @param {Number|String} input: number to pad + * @param {Number} width: visible width of output + * @param {String} str: string to prefix to string, e.g. '\u00A0' + * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR + */ +function padLeftStr(input, width, str) { + var prefix = ''; + + width -= input.toString().length; + while (width > 0) { + prefix += str; + width--; + } + return prefix + input; +} + +/** + * Pad INPUT on the left to SIZE width, using given padding character CH, + * for example padLeft('a', 3, '_') is '__a'. + * + * @param {String} input: input value converted to string. + * @param {Number} width: desired length of output. + * @param {String} ch: single character to prefix to string. + * + * @returns {String} Modified string, at least SIZE length. + */ +function padLeft(input, width, ch) { + var s = input + ""; + while (s.length < width) { + s = ch + s; + } + return s; +} + +/** + * Create XMLHttpRequest object in cross-browser way + * @returns XMLHttpRequest object, or null + */ +function createRequestObject() { + try { + return new XMLHttpRequest(); + } catch (e) {} + try { + return window.createRequest(); + } catch (e) {} + try { + return new ActiveXObject("Msxml2.XMLHTTP"); + } catch (e) {} + try { + return new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) {} + + return null; +} + + +/* ============================================================ */ +/* utility/helper functions (and variables) */ + +var xhr; // XMLHttpRequest object +var projectUrl; // partial query + separator ('?' or ';') + +// 'commits' is an associative map. It maps SHA1s to Commit objects. +var commits = {}; + +/** + * constructor for Commit objects, used in 'blame' + * @class Represents a blamed commit + * @param {String} sha1: SHA-1 identifier of a commit + */ +function Commit(sha1) { + if (this instanceof Commit) { + this.sha1 = sha1; + this.nprevious = 0; /* number of 'previous', effective parents */ + } else { + return new Commit(sha1); + } +} + +/* ............................................................ */ +/* progress info, timing, error reporting */ + +var blamedLines = 0; +var totalLines = '???'; +var div_progress_bar; +var div_progress_info; + +/** + * Detects how many lines does a blamed file have, + * This information is used in progress info + * + * @returns {Number|String} Number of lines in file, or string '...' + */ +function countLines() { + var table = + document.getElementById('blame_table') || + document.getElementsByTagName('table')[0]; + + if (table) { + return table.getElementsByTagName('tr').length - 1; // for header + } else { + return '...'; + } +} + +/** + * update progress info and length (width) of progress bar + * + * @globals div_progress_info, div_progress_bar, blamedLines, totalLines + */ +function updateProgressInfo() { + if (!div_progress_info) { + div_progress_info = document.getElementById('progress_info'); + } + if (!div_progress_bar) { + div_progress_bar = document.getElementById('progress_bar'); + } + if (!div_progress_info && !div_progress_bar) { + return; + } + + var percentage = Math.floor(100.0*blamedLines/totalLines); + + if (div_progress_info) { + div_progress_info.firstChild.data = blamedLines + ' / ' + totalLines + + ' (' + padLeftStr(percentage, 3, '\u00A0') + '%)'; + } + + if (div_progress_bar) { + //div_progress_bar.setAttribute('style', 'width: '+percentage+'%;'); + div_progress_bar.style.width = percentage + '%'; + } +} + + +var t_interval_server = ''; +var cmds_server = ''; +var t0 = new Date(); + +/** + * write how much it took to generate data, and to run script + * + * @globals t0, t_interval_server, cmds_server + */ +function writeTimeInterval() { + var info_time = document.getElementById('generating_time'); + if (!info_time || !t_interval_server) { + return; + } + var t1 = new Date(); + info_time.firstChild.data += ' + (' + + t_interval_server + ' sec server blame_data / ' + + (t1.getTime() - t0.getTime())/1000 + ' sec client JavaScript)'; + + var info_cmds = document.getElementById('generating_cmd'); + if (!info_time || !cmds_server) { + return; + } + info_cmds.firstChild.data += ' + ' + cmds_server; +} + +/** + * show an error message alert to user within page (in prohress info area) + * @param {String} str: plain text error message (no HTML) + * + * @globals div_progress_info + */ +function errorInfo(str) { + if (!div_progress_info) { + div_progress_info = document.getElementById('progress_info'); + } + if (div_progress_info) { + div_progress_info.className = 'error'; + div_progress_info.firstChild.data = str; + } +} + +/* ............................................................ */ +/* coloring rows during blame_data (git blame --incremental) run */ + +/** + * used to extract N from 'colorN', where N is a number, + * @constant + */ +var colorRe = /\bcolor([0-9]*)\b/; + +/** + * return N if <tr class="colorN">, otherwise return null + * (some browsers require CSS class names to begin with letter) + * + * @param {HTMLElement} tr: table row element to check + * @param {String} tr.className: 'class' attribute of tr element + * @returns {Number|null} N if tr.className == 'colorN', otherwise null + * + * @globals colorRe + */ +function getColorNo(tr) { + if (!tr) { + return null; + } + var className = tr.className; + if (className) { + var match = colorRe.exec(className); + if (match) { + return parseInt(match[1], 10); + } + } + return null; +} + +var colorsFreq = [0, 0, 0]; +/** + * return one of given possible colors (curently least used one) + * example: chooseColorNoFrom(2, 3) returns 2 or 3 + * + * @param {Number[]} arguments: one or more numbers + * assumes that 1 <= arguments[i] <= colorsFreq.length + * @returns {Number} Least used color number from arguments + * @globals colorsFreq + */ +function chooseColorNoFrom() { + // choose the color which is least used + var colorNo = arguments[0]; + for (var i = 1; i < arguments.length; i++) { + if (colorsFreq[arguments[i]-1] < colorsFreq[colorNo-1]) { + colorNo = arguments[i]; + } + } + colorsFreq[colorNo-1]++; + return colorNo; +} + +/** + * given two neigbour <tr> elements, find color which would be different + * from color of both of neighbours; used to 3-color blame table + * + * @param {HTMLElement} tr_prev + * @param {HTMLElement} tr_next + * @returns {Number} color number N such that + * colorN != tr_prev.className && colorN != tr_next.className + */ +function findColorNo(tr_prev, tr_next) { + var color_prev = getColorNo(tr_prev); + var color_next = getColorNo(tr_next); + + + // neither of neighbours has color set + // THEN we can use any of 3 possible colors + if (!color_prev && !color_next) { + return chooseColorNoFrom(1,2,3); + } + + // either both neighbours have the same color, + // or only one of neighbours have color set + // THEN we can use any color except given + var color; + if (color_prev === color_next) { + color = color_prev; // = color_next; + } else if (!color_prev) { + color = color_next; + } else if (!color_next) { + color = color_prev; + } + if (color) { + return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1); + } + + // neighbours have different colors + // THEN there is only one color left + return (3 - ((color_prev + color_next) % 3)); +} + +/* ............................................................ */ +/* coloring rows like 'blame' after 'blame_data' finishes */ + +/** + * returns true if given row element (tr) is first in commit group + * to be used only after 'blame_data' finishes (after processing) + * + * @param {HTMLElement} tr: table row + * @returns {Boolean} true if TR is first in commit group + */ +function isStartOfGroup(tr) { + return tr.firstChild.className === 'sha1'; +} + +/** + * change colors to use zebra coloring (2 colors) instead of 3 colors + * concatenate neighbour commit groups belonging to the same commit + * + * @globals colorRe + */ +function fixColorsAndGroups() { + var colorClasses = ['light', 'dark']; + var linenum = 1; + var tr, prev_group; + var colorClass = 0; + var table = + document.getElementById('blame_table') || + document.getElementsByTagName('table')[0]; + + while ((tr = document.getElementById('l'+linenum))) { + // index origin is 0, which is table header; start from 1 + //while ((tr = table.rows[linenum])) { // <- it is slower + if (isStartOfGroup(tr, linenum, document)) { + if (prev_group && + prev_group.firstChild.firstChild.href === + tr.firstChild.firstChild.href) { + // we have to concatenate groups + var prev_rows = prev_group.firstChild.rowSpan || 1; + var curr_rows = tr.firstChild.rowSpan || 1; + prev_group.firstChild.rowSpan = prev_rows + curr_rows; + //tr.removeChild(tr.firstChild); + tr.deleteCell(0); // DOM2 HTML way + } else { + colorClass = (colorClass + 1) % 2; + prev_group = tr; + } + } + var tr_class = tr.className; + tr.className = tr_class.replace(colorRe, colorClasses[colorClass]); + linenum++; + } +} + +/* ............................................................ */ +/* time and data */ + +/** + * used to extract hours and minutes from timezone info, e.g '-0900' + * @constant + */ +var tzRe = /^([+-][0-9][0-9])([0-9][0-9])$/; + +/** + * return date in local time formatted in iso-8601 like format + * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' + * + * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @returns {String} date in local time in iso-8601 like format + * + * @globals tzRe + */ +function formatDateISOLocal(epoch, timezoneInfo) { + var match = tzRe.exec(timezoneInfo); + // date corrected by timezone + var localDate = new Date(1000 * (epoch + + (parseInt(match[1],10)*3600 + parseInt(match[2],10)*60))); + var localDateStr = // e.g. '2005-08-07' + localDate.getUTCFullYear() + '-' + + padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' + + padLeft(localDate.getUTCDate(), 2, '0'); + var localTimeStr = // e.g. '21:49:46' + padLeft(localDate.getUTCHours(), 2, '0') + ':' + + padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + + padLeft(localDate.getUTCSeconds(), 2, '0'); + + return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; +} + +/* ............................................................ */ +/* unquoting/unescaping filenames */ + +/**#@+ + * @constant + */ +var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g; +var octEscRe = /^[0-7]{1,3}$/; +var maybeQuotedRe = /^\"(.*)\"$/; +/**#@-*/ + +/** + * unquote maybe git-quoted filename + * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a a' + * + * @param {String} str: git-quoted string + * @returns {String} Unquoted and unescaped string + * + * @globals escCodeRe, octEscRe, maybeQuotedRe + */ +function unquote(str) { + function unq(seq) { + var es = { + // character escape codes, aka escape sequences (from C) + // replacements are to some extent JavaScript specific + t: "\t", // tab (HT, TAB) + n: "\n", // newline (NL) + r: "\r", // return (CR) + f: "\f", // form feed (FF) + b: "\b", // backspace (BS) + a: "\x07", // alarm (bell) (BEL) + e: "\x1B", // escape (ESC) + v: "\v" // vertical tab (VT) + }; + + if (seq.search(octEscRe) !== -1) { + // octal char sequence + return String.fromCharCode(parseInt(seq, 8)); + } else if (seq in es) { + // C escape sequence, aka character escape code + return es[seq]; + } + // quoted ordinary character + return seq; + } + + var match = str.match(maybeQuotedRe); + if (match) { + str = match[1]; + // perhaps str = eval('"'+str+'"'); would be enough? + str = str.replace(escCodeRe, + function (substr, p1, offset, s) { return unq(p1); }); + } + return str; +} + +/* ============================================================ */ +/* main part: parsing response */ + +/** + * Function called for each blame entry, as soon as it finishes. + * It updates page via DOM manipulation, adding sha1 info, etc. + * + * @param {Commit} commit: blamed commit + * @param {Object} group: object representing group of lines, + * which blame the same commit (blame entry) + * + * @globals blamedLines + */ +function handleLine(commit, group) { + /* + This is the structure of the HTML fragment we are working + with: + + <tr id="l123" class=""> + <td class="sha1" title=""><a href=""> </a></td> + <td class="linenr"><a class="linenr" href="">123</a></td> + <td class="pre"># times (my ext3 doesn't).</td> + </tr> + */ + + var resline = group.resline; + + // format date and time string only once per commit + if (!commit.info) { + /* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */ + commit.info = commit.author + ', ' + + formatDateISOLocal(commit.authorTime, commit.authorTimezone); + } + + // color depends on group of lines, not only on blamed commit + var colorNo = findColorNo( + document.getElementById('l'+(resline-1)), + document.getElementById('l'+(resline+group.numlines)) + ); + + // loop over lines in commit group + for (var i = 0; i < group.numlines; i++, resline++) { + var tr = document.getElementById('l'+resline); + if (!tr) { + break; + } + /* + <tr id="l123" class=""> + <td class="sha1" title=""><a href=""> </a></td> + <td class="linenr"><a class="linenr" href="">123</a></td> + <td class="pre"># times (my ext3 doesn't).</td> + </tr> + */ + var td_sha1 = tr.firstChild; + var a_sha1 = td_sha1.firstChild; + var a_linenr = td_sha1.nextSibling.firstChild; + + /* <tr id="l123" class=""> */ + var tr_class = ''; + if (colorNo !== null) { + tr_class = 'color'+colorNo; + } + if (commit.boundary) { + tr_class += ' boundary'; + } + if (commit.nprevious === 0) { + tr_class += ' no-previous'; + } else if (commit.nprevious > 1) { + tr_class += ' multiple-previous'; + } + tr.className = tr_class; + + /* <td class="sha1" title="?" rowspan="?"><a href="?">?</a></td> */ + if (i === 0) { + td_sha1.title = commit.info; + td_sha1.rowSpan = group.numlines; + + a_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1; + if (a_sha1.firstChild) { + a_sha1.firstChild.data = commit.sha1.substr(0, 8); + } else { + a_sha1.appendChild( + document.createTextNode(commit.sha1.substr(0, 8))); + } + if (group.numlines >= 2) { + var fragment = document.createDocumentFragment(); + var br = document.createElement("br"); + var match = commit.author.match(/\b([A-Z])\B/g); + if (match) { + var text = document.createTextNode( + match.join('')); + } + if (br && text) { + var elem = fragment || td_sha1; + elem.appendChild(br); + elem.appendChild(text); + if (fragment) { + td_sha1.appendChild(fragment); + } + } + } + } else { + //tr.removeChild(td_sha1); // DOM2 Core way + tr.deleteCell(0); // DOM2 HTML way + } + + /* <td class="linenr"><a class="linenr" href="?">123</a></td> */ + var linenr_commit = + ('previous' in commit ? commit.previous : commit.sha1); + var linenr_filename = + ('file_parent' in commit ? commit.file_parent : commit.filename); + a_linenr.href = projectUrl + 'a=blame_incremental' + + ';hb=' + linenr_commit + + ';f=' + encodeURIComponent(linenr_filename) + + '#l' + (group.srcline + i); + + blamedLines++; + + //updateProgressInfo(); + } +} + +// ---------------------------------------------------------------------- + +var inProgress = false; // are we processing response + +/**#@+ + * @constant + */ +var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/; +var infoRe = /^([a-z-]+) ?(.*)/; +var endRe = /^END ?([^ ]*) ?(.*)/; +/**@-*/ + +var curCommit = new Commit(); +var curGroup = {}; + +var pollTimer = null; + +/** + * Parse output from 'git blame --incremental [...]', received via + * XMLHttpRequest from server (blamedataUrl), and call handleLine + * (which updates page) as soon as blame entry is completed. + * + * @param {String[]} lines: new complete lines from blamedata server + * + * @globals commits, curCommit, curGroup, t_interval_server, cmds_server + * @globals sha1Re, infoRe, endRe + */ +function processBlameLines(lines) { + var match; + + for (var i = 0, len = lines.length; i < len; i++) { + + if ((match = sha1Re.exec(lines[i]))) { + var sha1 = match[1]; + var srcline = parseInt(match[2], 10); + var resline = parseInt(match[3], 10); + var numlines = parseInt(match[4], 10); + + var c = commits[sha1]; + if (!c) { + c = new Commit(sha1); + commits[sha1] = c; + } + curCommit = c; + + curGroup.srcline = srcline; + curGroup.resline = resline; + curGroup.numlines = numlines; + + } else if ((match = infoRe.exec(lines[i]))) { + var info = match[1]; + var data = match[2]; + switch (info) { + case 'filename': + curCommit.filename = unquote(data); + // 'filename' information terminates the entry + handleLine(curCommit, curGroup); + updateProgressInfo(); + break; + case 'author': + curCommit.author = data; + break; + case 'author-time': + curCommit.authorTime = parseInt(data, 10); + break; + case 'author-tz': + curCommit.authorTimezone = data; + break; + case 'previous': + curCommit.nprevious++; + // store only first 'previous' header + if (!'previous' in curCommit) { + var parts = data.split(' ', 2); + curCommit.previous = parts[0]; + curCommit.file_parent = unquote(parts[1]); + } + break; + case 'boundary': + curCommit.boundary = true; + break; + } // end switch + + } else if ((match = endRe.exec(lines[i]))) { + t_interval_server = match[1]; + cmds_server = match[2]; + + } else if (lines[i] !== '') { + // malformed line + + } // end if (match) + + } // end for (lines) +} + +/** + * Process new data and return pointer to end of processed part + * + * @param {String} unprocessed: new data (from nextReadPos) + * @param {Number} nextReadPos: end of last processed data + * @return {Number} end of processed data (new value for nextReadPos) + */ +function processData(unprocessed, nextReadPos) { + var lastLineEnd = unprocessed.lastIndexOf('\n'); + if (lastLineEnd !== -1) { + var lines = unprocessed.substring(0, lastLineEnd).split('\n'); + nextReadPos += lastLineEnd + 1 /* 1 == '\n'.length */; + + processBlameLines(lines); + } // end if + + return nextReadPos; +} + +/** + * Handle XMLHttpRequest errors + * + * @param {XMLHttpRequest} xhr: XMLHttpRequest object + * + * @globals pollTimer, commits, inProgress + */ +function handleError(xhr) { + errorInfo('Server error: ' + + xhr.status + ' - ' + (xhr.statusText || 'Error contacting server')); + + clearInterval(pollTimer); + commits = {}; // free memory + + inProgress = false; +} + +/** + * Called after XMLHttpRequest finishes (loads) + * + * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused) + * + * @globals pollTimer, commits, inProgress + */ +function responseLoaded(xhr) { + clearInterval(pollTimer); + + fixColorsAndGroups(); + writeTimeInterval(); + commits = {}; // free memory + + inProgress = false; +} + +/** + * handler for XMLHttpRequest onreadystatechange event + * @see startBlame + * + * @globals xhr, inProgress + */ +function handleResponse() { + + /* + * xhr.readyState + * + * Value Constant (W3C) Description + * ------------------------------------------------------------------- + * 0 UNSENT open() has not been called yet. + * 1 OPENED send() has not been called yet. + * 2 HEADERS_RECEIVED send() has been called, and headers + * and status are available. + * 3 LOADING Downloading; responseText holds partial data. + * 4 DONE The operation is complete. + */ + + if (xhr.readyState !== 4 && xhr.readyState !== 3) { + return; + } + + // the server returned error + if (xhr.readyState === 3 && xhr.status !== 200) { + return; + } + if (xhr.readyState === 4 && xhr.status !== 200) { + handleError(xhr); + return; + } + + // In konqueror xhr.responseText is sometimes null here... + if (xhr.responseText === null) { + return; + } + + // in case we were called before finished processing + if (inProgress) { + return; + } else { + inProgress = true; + } + + // extract new whole (complete) lines, and process them + while (xhr.prevDataLength !== xhr.responseText.length) { + if (xhr.readyState === 4 && + xhr.prevDataLength === xhr.responseText.length) { + break; + } + + xhr.prevDataLength = xhr.responseText.length; + var unprocessed = xhr.responseText.substring(xhr.nextReadPos); + xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos); + } // end while + + // did we finish work? + if (xhr.readyState === 4 && + xhr.prevDataLength === xhr.responseText.length) { + responseLoaded(xhr); + } + + inProgress = false; +} + +// ============================================================ +// ------------------------------------------------------------ + +/** + * Incrementally update line data in blame_incremental view in gitweb. + * + * @param {String} blamedataUrl: URL to server script generating blame data. + * @param {String} bUrl: partial URL to project, used to generate links. + * + * Called from 'blame_incremental' view after loading table with + * file contents, a base for blame view. + * + * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer +*/ +function startBlame(blamedataUrl, bUrl) { + + xhr = createRequestObject(); + if (!xhr) { + errorInfo('ERROR: XMLHttpRequest not supported'); + return; + } + + t0 = new Date(); + projectUrl = bUrl + (bUrl.indexOf('?') === -1 ? '?' : ';'); + if ((div_progress_bar = document.getElementById('progress_bar'))) { + //div_progress_bar.setAttribute('style', 'width: 100%;'); + div_progress_bar.style.cssText = 'width: 100%;'; + } + totalLines = countLines(); + updateProgressInfo(); + + /* add extra properties to xhr object to help processing response */ + xhr.prevDataLength = -1; // used to detect if we have new data + xhr.nextReadPos = 0; // where unread part of response starts + + xhr.onreadystatechange = handleResponse; + //xhr.onreadystatechange = function () { handleResponse(xhr); }; + + xhr.open('GET', blamedataUrl); + xhr.setRequestHeader('Accept', 'text/plain'); + xhr.send(null); + + // not all browsers call onreadystatechange event on each server flush + // poll response using timer every second to handle this issue + pollTimer = setInterval(xhr.onreadystatechange, 1000); +} + +// end of gitweb.js diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c77cd0341d..7e477af956 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -18,6 +18,12 @@ use File::Find qw(); use File::Basename qw(basename); binmode STDOUT, ':utf8'; +our $t0; +if (eval { require Time::HiRes; 1; }) { + $t0 = [Time::HiRes::gettimeofday()]; +} +our $number_of_git_cmds = 0; + BEGIN { CGI->compile() if $ENV{'MOD_PERL'}; } @@ -90,6 +96,8 @@ our $stylesheet = undef; our $logo = "++GITWEB_LOGO++"; # URI of GIT favicon, assumed to be image/png type our $favicon = "++GITWEB_FAVICON++"; +# URI of gitweb.js (JavaScript code for gitweb) +our $javascript = "++GITWEB_JS++"; # URI and label (title) of GIT logo link #our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/"; @@ -297,6 +305,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 @@ -404,6 +425,20 @@ our %feature = ( 'sub' => \&feature_avatar, 'override' => 0, 'default' => ['']}, + + # Enable displaying how much time and how many git commands + # it took to generate and display page. Disabled by default. + # Project specific override is not supported. + 'timed' => { + 'override' => 0, + 'default' => [0]}, + + # Enable turning some links into links to actions which require + # JavaScript to run (like 'blame_incremental'). Not enabled by + # default. Project specific override is currently not supported. + 'javascript-actions' => { + 'override' => 0, + 'default' => [0]}, ); sub gitweb_get_feature { @@ -518,6 +553,7 @@ if (-e $GITWEB_CONFIG) { # version of the core git binary our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown"; +$number_of_git_cmds++; $projects_list ||= $projectroot; @@ -555,12 +591,16 @@ our @cgi_param_mapping = ( snapshot_format => "sf", extra_options => "opt", search_use_regexp => "sr", + # this must be last entry (for manipulation from JavaScript) + javascript => "js" ); our %cgi_param_mapping = @cgi_param_mapping; # we will also need to know the possible actions, for validation our %actions = ( "blame" => \&git_blame, + "blame_incremental" => \&git_blame_incremental, + "blame_data" => \&git_blame_data, "blobdiff" => \&git_blobdiff, "blobdiff_plain" => \&git_blobdiff_plain, "blob" => \&git_blob, @@ -1594,6 +1634,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). @@ -1602,8 +1665,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) ..." @@ -1968,6 +2033,7 @@ sub get_feed_info { # returns path to the core git executable and the --git-dir parameter as list sub git_cmd { + $number_of_git_cmds++; return $GIT, '--git-dir='.$git_dir; } @@ -1982,16 +2048,27 @@ sub quote_command { # get HEAD ref of given project as hash sub git_get_head_hash { - my $project = shift; + return git_get_full_hash(shift, 'HEAD'); +} + +sub git_get_full_hash { + return git_get_hash(@_); +} + +sub git_get_short_hash { + return git_get_hash(@_, '--short=7'); +} + +sub git_get_hash { + my ($project, $hash, @options) = @_; my $o_git_dir = $git_dir; my $retval = undef; $git_dir = "$projectroot/$project"; - if (open my $fd, "-|", git_cmd(), "rev-parse", "--verify", "HEAD") { - my $head = <$fd>; + if (open my $fd, '-|', git_cmd(), 'rev-parse', + '--verify', '-q', @options, $hash) { + $retval = <$fd>; + chomp $retval if defined $retval; close $fd; - if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) { - $retval = $1; - } } if (defined $o_git_dir) { $git_dir = $o_git_dir; @@ -2763,16 +2840,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; @@ -3217,10 +3309,36 @@ sub git_footer_html { } print "</div>\n"; # class="page_footer" + if (defined $t0 && gitweb_check_feature('timed')) { + print "<div id=\"generating_info\">\n"; + print 'This page took '. + '<span id="generating_time" class="time_span">'. + Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]). + ' seconds </span>'. + ' and '. + '<span id="generating_cmd">'. + $number_of_git_cmds. + '</span> git commands '. + " to generate.\n"; + print "</div>\n"; # class="page_footer" + } + if (-f $site_footer) { insert_file($site_footer); } + print qq!<script type="text/javascript" src="$javascript"></script>\n!; + if ($action eq 'blame_incremental') { + print qq!<script type="text/javascript">\n!. + qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!. + qq! "!. href() .qq!");\n!. + qq!</script>\n!; + } elsif (gitweb_check_feature('javascript-actions')) { + print qq!<script type="text/javascript">\n!. + qq!window.onload = fixLinks;\n!. + qq!</script>\n!; + } + print "</body>\n" . "</html>"; } @@ -3310,22 +3428,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) { @@ -3372,10 +3486,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) @@ -3394,8 +3509,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>" . @@ -3563,6 +3682,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'}, @@ -3608,12 +3730,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 " | " . @@ -4298,6 +4422,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) = @_; @@ -4344,7 +4508,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}); @@ -4378,7 +4543,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) { @@ -4770,7 +4935,13 @@ sub git_tag { git_footer_html(); } -sub git_blame { +sub git_blame_common { + my $format = shift || 'porcelain'; + if ($format eq 'porcelain' && $cgi->param('js')) { + $format = 'incremental'; + $action = 'blame_incremental'; # for page title etc + } + # permissions gitweb_check_feature('blame') or die_error(403, "Blame view not allowed"); @@ -4792,123 +4963,220 @@ sub git_blame { } } - # run git-blame --porcelain - open my $fd, "-|", git_cmd(), "blame", '-p', - $hash_base, '--', $file_name - or die_error(500, "Open git-blame failed"); + my $fd; + if ($format eq 'incremental') { + # get file contents (as base) + open $fd, "-|", git_cmd(), 'cat-file', 'blob', $hash + or die_error(500, "Open git-cat-file failed"); + } elsif ($format eq 'data') { + # run git-blame --incremental + open $fd, "-|", git_cmd(), "blame", "--incremental", + $hash_base, "--", $file_name + or die_error(500, "Open git-blame --incremental failed"); + } else { + # run git-blame --porcelain + open $fd, "-|", git_cmd(), "blame", '-p', + $hash_base, '--', $file_name + or die_error(500, "Open git-blame --porcelain failed"); + } + + # incremental blame data returns early + if ($format eq 'data') { + print $cgi->header( + -type=>"text/plain", -charset => "utf-8", + -status=> "200 OK"); + local $| = 1; # output autoflush + print while <$fd>; + close $fd + or print "ERROR $!\n"; + + print 'END'; + if (defined $t0 && gitweb_check_feature('timed')) { + print ' '. + Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]). + ' '.$number_of_git_cmds; + } + print "\n"; + + return; + } # page header git_header_html(); my $formats_nav = $cgi->a({-href => href(action=>"blob", -replay=>1)}, "blob") . + " | "; + if ($format eq 'incremental') { + $formats_nav .= + $cgi->a({-href => href(action=>"blame", javascript=>0, -replay=>1)}, + "blame") . " (non-incremental)"; + } else { + $formats_nav .= + $cgi->a({-href => href(action=>"blame_incremental", -replay=>1)}, + "blame") . " (incremental)"; + } + $formats_nav .= " | " . $cgi->a({-href => href(action=>"history", -replay=>1)}, "history") . " | " . - $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, + $cgi->a({-href => href(action=>$action, file_name=>$file_name)}, "HEAD"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype, $hash_base); # page body + if ($format eq 'incremental') { + print "<noscript>\n<div class=\"error\"><center><b>\n". + "This page requires JavaScript to run.\n Use ". + $cgi->a({-href => href(action=>'blame',javascript=>0,-replay=>1)}, + 'this page'). + " instead.\n". + "</b></center></div>\n</noscript>\n"; + + print qq!<div id="progress_bar" style="width: 100%; background-color: yellow"></div>\n!; + } + + print qq!<div class="page_body">\n!; + print qq!<div id="progress_info">... / ...</div>\n! + if ($format eq 'incremental'); + print qq!<table id="blame_table" class="blame" width="100%">\n!. + #qq!<col width="5.5em" /><col width="2.5em" /><col width="*" />\n!. + qq!<thead>\n!. + qq!<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n!. + qq!</thead>\n!. + qq!<tbody>\n!; + my @rev_color = qw(light dark); my $num_colors = scalar(@rev_color); my $current_color = 0; - my %metainfo = (); - print <<HTML; -<div class="page_body"> -<table class="blame"> -<tr><th>Commit</th><th>Line</th><th>Data</th></tr> -HTML - LINE: - while (my $line = <$fd>) { - chomp $line; - # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>] - # no <lines in group> for subsequent lines in group of lines - my ($full_rev, $orig_lineno, $lineno, $group_size) = - ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/); - if (!exists $metainfo{$full_rev}) { - $metainfo{$full_rev} = { 'nprevious' => 0 }; - } - my $meta = $metainfo{$full_rev}; - my $data; - while ($data = <$fd>) { - chomp $data; - last if ($data =~ s/^\t//); # contents of line - if ($data =~ /^(\S+)(?: (.*))?$/) { - $meta->{$1} = $2 unless exists $meta->{$1}; + if ($format eq 'incremental') { + my $color_class = $rev_color[$current_color]; + + #contents of a file + my $linenr = 0; + LINE: + while (my $line = <$fd>) { + chomp $line; + $linenr++; + + print qq!<tr id="l$linenr" class="$color_class">!. + qq!<td class="sha1"><a href=""> </a></td>!. + qq!<td class="linenr">!. + qq!<a class="linenr" href="">$linenr</a></td>!; + print qq!<td class="pre">! . esc_html($line) . "</td>\n"; + print qq!</tr>\n!; + } + + } else { # porcelain, i.e. ordinary blame + my %metainfo = (); # saves information about commits + + # blame data + LINE: + while (my $line = <$fd>) { + chomp $line; + # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>] + # no <lines in group> for subsequent lines in group of lines + my ($full_rev, $orig_lineno, $lineno, $group_size) = + ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/); + if (!exists $metainfo{$full_rev}) { + $metainfo{$full_rev} = { 'nprevious' => 0 }; } - if ($data =~ /^previous /) { - $meta->{'nprevious'}++; + my $meta = $metainfo{$full_rev}; + my $data; + while ($data = <$fd>) { + chomp $data; + last if ($data =~ s/^\t//); # contents of line + if ($data =~ /^(\S+)(?: (.*))?$/) { + $meta->{$1} = $2 unless exists $meta->{$1}; + } + if ($data =~ /^previous /) { + $meta->{'nprevious'}++; + } } - } - my $short_rev = substr($full_rev, 0, 8); - my $author = $meta->{'author'}; - my %date = - parse_date($meta->{'author-time'}, $meta->{'author-tz'}); - my $date = $date{'iso-tz'}; - if ($group_size) { - $current_color = ($current_color + 1) % $num_colors; - } - my $tr_class = $rev_color[$current_color]; - $tr_class .= ' boundary' if (exists $meta->{'boundary'}); - $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0); - $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1); - print "<tr id=\"l$lineno\" class=\"$tr_class\">\n"; - if ($group_size) { - print "<td class=\"sha1\""; - print " title=\"". esc_html($author) . ", $date\""; - print " rowspan=\"$group_size\"" if ($group_size > 1); - print ">"; - print $cgi->a({-href => href(action=>"commit", - hash=>$full_rev, - file_name=>$file_name)}, - esc_html($short_rev)); - if ($group_size >= 2) { - my @author_initials = ($author =~ /\b([[:upper:]])\B/g); - if (@author_initials) { - print "<br />" . - esc_html(join('', @author_initials)); - # or join('.', ...) + my $short_rev = substr($full_rev, 0, 8); + my $author = $meta->{'author'}; + my %date = + parse_date($meta->{'author-time'}, $meta->{'author-tz'}); + my $date = $date{'iso-tz'}; + if ($group_size) { + $current_color = ($current_color + 1) % $num_colors; + } + my $tr_class = $rev_color[$current_color]; + $tr_class .= ' boundary' if (exists $meta->{'boundary'}); + $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0); + $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1); + print "<tr id=\"l$lineno\" class=\"$tr_class\">\n"; + if ($group_size) { + print "<td class=\"sha1\""; + print " title=\"". esc_html($author) . ", $date\""; + print " rowspan=\"$group_size\"" if ($group_size > 1); + print ">"; + print $cgi->a({-href => href(action=>"commit", + hash=>$full_rev, + file_name=>$file_name)}, + esc_html($short_rev)); + if ($group_size >= 2) { + my @author_initials = ($author =~ /\b([[:upper:]])\B/g); + if (@author_initials) { + print "<br />" . + esc_html(join('', @author_initials)); + # or join('.', ...) + } } + print "</td>\n"; } - print "</td>\n"; - } - # 'previous' <sha1 of parent commit> <filename at commit> - if (exists $meta->{'previous'} && - $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) { - $meta->{'parent'} = $1; - $meta->{'file_parent'} = unquote($2); - } - my $linenr_commit = - exists($meta->{'parent'}) ? - $meta->{'parent'} : $full_rev; - my $linenr_filename = - exists($meta->{'file_parent'}) ? - $meta->{'file_parent'} : unquote($meta->{'filename'}); - my $blamed = href(action => 'blame', - file_name => $linenr_filename, - hash_base => $linenr_commit); - print "<td class=\"linenr\">"; - print $cgi->a({ -href => "$blamed#l$orig_lineno", - -class => "linenr" }, - esc_html($lineno)); - print "</td>"; - print "<td class=\"pre\">" . esc_html($data) . "</td>\n"; - print "</tr>\n"; + # 'previous' <sha1 of parent commit> <filename at commit> + if (exists $meta->{'previous'} && + $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) { + $meta->{'parent'} = $1; + $meta->{'file_parent'} = unquote($2); + } + my $linenr_commit = + exists($meta->{'parent'}) ? + $meta->{'parent'} : $full_rev; + my $linenr_filename = + exists($meta->{'file_parent'}) ? + $meta->{'file_parent'} : unquote($meta->{'filename'}); + my $blamed = href(action => 'blame', + file_name => $linenr_filename, + hash_base => $linenr_commit); + print "<td class=\"linenr\">"; + print $cgi->a({ -href => "$blamed#l$orig_lineno", + -class => "linenr" }, + esc_html($lineno)); + print "</td>"; + print "<td class=\"pre\">" . esc_html($data) . "</td>\n"; + print "</tr>\n"; + } # end while + } - print "</table>\n"; - print "</div>"; + + # footer + print "</tbody>\n". + "</table>\n"; # class="blame" + print "</div>\n"; # class="blame_body" close $fd or print "Reading blob failed\n"; - # page footer git_footer_html(); } +sub git_blame { + git_blame_common(); +} + +sub git_blame_incremental { + git_blame_common('incremental'); +} + +sub git_blame_data { + git_blame_common('data'); +} + sub git_tags { my $head = git_get_head_hash($project); git_header_html(); @@ -5088,10 +5356,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 +5374,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 +5389,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 +5423,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 +5435,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"; @@ -5179,6 +5453,43 @@ sub git_tree { git_footer_html(); } +sub snapshot_name { + my ($project, $hash) = @_; + + # path/to/project.git -> project + # path/to/project/.git -> project + my $name = to_utf8($project); + $name =~ s,([^/])/*\.git$,$1,; + $name = basename($name); + # sanitize name + $name =~ s/[[:cntrl:]]/?/g; + + my $ver = $hash; + if ($hash =~ /^[0-9a-fA-F]+$/) { + # shorten SHA-1 hash + my $full_hash = git_get_full_hash($project, $hash); + if ($full_hash =~ /^$hash/ && length($hash) > 7) { + $ver = git_get_short_hash($project, $hash); + } + } elsif ($hash =~ m!^refs/tags/(.*)$!) { + # tags don't need shortened SHA-1 hash + $ver = $1; + } else { + # branches and other need shortened SHA-1 hash + if ($hash =~ m!^refs/(?:heads|remotes)/(.*)$!) { + $ver = $1; + } + $ver .= '-' . git_get_short_hash($project, $hash); + } + # in case of hierarchical branch names + $ver =~ s!/!.!g; + + # name = project-version_string + $name = "$name-$ver"; + + return wantarray ? ($name, $name) : $name; +} + sub git_snapshot { my $format = $input_params{'snapshot_format'}; if (!@snapshot_fmts) { @@ -5196,28 +5507,27 @@ sub git_snapshot { die_error(403, "Unsupported snapshot format"); } - if (!defined $hash) { - $hash = git_get_head_hash($project); + my $type = git_get_type("$hash^{}"); + if (!$type) { + die_error(404, 'Object does not exist'); + } elsif ($type eq 'blob') { + die_error(400, 'Object is not a tree-ish'); } - my $name = $project; - $name =~ s,([^/])/*\.git$,$1,; - $name = basename($name); - my $filename = to_utf8($name); - $name =~ s/\047/\047\\\047\047/g; - my $cmd; - $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}"; - $cmd = quote_command( + my ($name, $prefix) = snapshot_name($project, $hash); + my $filename = "$name$known_snapshot_formats{$format}{'suffix'}"; + my $cmd = quote_command( git_cmd(), 'archive', "--format=$known_snapshot_formats{$format}{'format'}", - "--prefix=$name/", $hash); + "--prefix=$prefix/", $hash); if (exists $known_snapshot_formats{$format}{'compressor'}) { $cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}}); } + $filename =~ s/(["\\])/\\$1/g; print $cgi->header( -type => $known_snapshot_formats{$format}{'type'}, - -content_disposition => 'inline; filename="' . "$filename" . '"', + -content_disposition => 'inline; filename="' . $filename . '"', -status => '200 OK'); open my $fd, "-|", $cmd @@ -5228,22 +5538,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 +5597,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) @@ -5832,70 +6153,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 +6419,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); } ## ...................................................................... @@ -297,6 +297,9 @@ static void add_cmd_list(struct cmdnames *cmds, struct cmdnames *old) old->names = NULL; } +/* An empirically derived magic number */ +#define SIMILAR_ENOUGH(x) ((x) < 6) + const char *help_unknown_cmd(const char *cmd) { int i, n, best_similarity = 0; @@ -331,7 +334,7 @@ const char *help_unknown_cmd(const char *cmd) n = 1; while (n < main_cmds.cnt && best_similarity == main_cmds.names[n]->len) ++n; - if (autocorrect && n == 1) { + if (autocorrect && n == 1 && SIMILAR_ENOUGH(best_similarity)) { const char *assumed = main_cmds.names[0]->name; main_cmds.names[0] = NULL; clean_cmdnames(&main_cmds); @@ -349,7 +352,7 @@ const char *help_unknown_cmd(const char *cmd) fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd); - if (best_similarity < 6) { + if (SIMILAR_ENOUGH(best_similarity)) { fprintf(stderr, "\nDid you mean %s?\n", n < 2 ? "this": "one of these"); 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/http-fetch.c b/http-fetch.c index e8f44babd9..ffd0ad7e29 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -1,6 +1,10 @@ #include "cache.h" +#include "exec_cmd.h" #include "walker.h" +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; @@ -19,9 +23,7 @@ int main(int argc, const char **argv) int get_verbosely = 0; int get_recover = 0; - prefix = setup_git_directory(); - - git_config(git_default_config, NULL); + git_extract_argv0_path(argv[0]); while (arg < argc && argv[arg][0] == '-') { if (argv[arg][1] == 't') { @@ -37,6 +39,8 @@ int main(int argc, const char **argv) } 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")) { @@ -44,10 +48,8 @@ int main(int argc, const char **argv) } 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 { @@ -55,6 +57,11 @@ int main(int argc, const char **argv) 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..432b20f2d9 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; @@ -407,10 +408,10 @@ static void start_put(struct transfer_request *request) curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &request->buffer); #endif curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); + curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT); curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); curl_easy_setopt(slot->curl, CURLOPT_PUT, 1); - curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); curl_easy_setopt(slot->curl, CURLOPT_URL, request->url); if (start_active_slot(slot)) { @@ -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); @@ -1237,7 +1244,7 @@ int finish_http_object_request(struct http_object_request *freq) process_http_object_request(freq); if (freq->http_code == 416) { - fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n"); + warning("requested range invalid; we may already have all the data."); } else if (freq->curl_result != CURLE_OK) { if (stat(freq->tmpfile, &st) == 0) if (st.st_size == 0) @@ -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 f805c6ed81..de8114bac0 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 @@ -100,13 +104,16 @@ struct msg_data { static int Verbose, Quiet; +__attribute__((format (printf, 1, 2))) static void imap_info(const char *, ...); +__attribute__((format (printf, 1, 2))) static void imap_warn(const char *, ...); static char *next_arg(char **); static void free_generic_messages(struct message *); +__attribute__((format (printf, 3, 4))) static int nfsnprintf(char *buf, int blen, const char *fmt, ...); static int nfvasprintf(char **strp, const char *fmt, va_list ap) @@ -123,9 +130,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 +158,7 @@ struct imap_list { }; struct imap_socket { - int fd; + int fd[2]; SSL *ssl; }; @@ -308,8 +312,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; } @@ -331,11 +339,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; } @@ -348,11 +357,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; } @@ -365,7 +375,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 */ @@ -493,52 +504,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) @@ -600,6 +565,7 @@ static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx, return cmd; } +__attribute__((format (printf, 3, 4))) static struct imap_cmd *issue_imap_cmd(struct imap_store *ctx, struct imap_cmd_cb *cb, const char *fmt, ...) @@ -613,6 +579,7 @@ static struct imap_cmd *issue_imap_cmd(struct imap_store *ctx, return ret; } +__attribute__((format (printf, 3, 4))) static int imap_exec(struct imap_store *ctx, struct imap_cmd_cb *cb, const char *fmt, ...) { @@ -628,6 +595,7 @@ static int imap_exec(struct imap_store *ctx, struct imap_cmd_cb *cb, return get_cmd_result(ctx, cmdp); } +__attribute__((format (printf, 3, 4))) static int imap_exec_m(struct imap_store *ctx, struct imap_cmd_cb *cb, const char *fmt, ...) { @@ -918,7 +886,7 @@ static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd) if (!strcmp("NO", arg)) { if (cmdp->cb.create && cmd && (cmdp->cb.trycreate || !memcmp(cmd, "[TRYCREATE]", 11))) { /* SELECT, APPEND or UID COPY */ p = strchr(cmdp->cmd, '"'); - if (!issue_imap_cmd(ctx, NULL, "CREATE \"%.*s\"", strchr(p + 1, '"') - p + 1, p)) { + if (!issue_imap_cmd(ctx, NULL, "CREATE \"%.*s\"", (int)(strchr(p + 1, '"') - p + 1), p)) { resp = RESP_BAD; goto normal; } @@ -964,7 +932,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); } @@ -986,40 +954,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 { @@ -1096,7 +1059,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)) { @@ -1202,88 +1166,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) { @@ -1292,26 +1188,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; } @@ -1487,7 +1371,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; @@ -1495,8 +1378,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); @@ -1544,7 +1427,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 b4f8278659..190f372dd8 100644 --- a/index-pack.c +++ b/index-pack.c @@ -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/log-tree.c b/log-tree.c index 1618f3c79a..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) @@ -404,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 1870448d98..dd4fbd0e6b 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -87,6 +87,7 @@ static void flush_output(struct merge_options *o) } } +__attribute__((format (printf, 3, 4))) static void output(struct merge_options *o, int v, const char *fmt, ...) { int len; @@ -171,23 +172,6 @@ static int git_merge_trees(int index_only, int rc; struct tree_desc t[3]; struct unpack_trees_options opts; - 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, - }; - if (advice_commit_before_merge) { - msgs.would_overwrite = msgs.not_uptodate_file = - "Your local changes to '%s' would be overwritten by merge. Aborting.\n" - "Please, commit your changes or stash them before you can merge."; - } memset(&opts, 0, sizeof(opts)); if (index_only) @@ -199,7 +183,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; + opts.msgs = get_porcelain_error_msgs(); init_tree_desc_from_tree(t+0, common); init_tree_desc_from_tree(t+1, head); @@ -220,7 +204,8 @@ struct tree *write_tree_from_memory(struct merge_options *o) for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; if (ce_stage(ce)) - output(o, 0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name); + output(o, 0, "%d %.*s", ce_stage(ce), + (int)ce_namelen(ce), ce->name); } return NULL; } @@ -1186,6 +1171,28 @@ static int process_entry(struct merge_options *o, return clean_merge; } +struct unpack_trees_error_msgs get_porcelain_error_msgs(void) +{ + 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, + }; + if (advice_commit_before_merge) { + msgs.would_overwrite = msgs.not_uptodate_file = + "Your local changes to '%s' would be overwritten by merge. Aborting.\n" + "Please, commit your changes or stash them before you can merge."; + } + return msgs; +} + int merge_trees(struct merge_options *o, struct tree *head, struct tree *merge, diff --git a/merge-recursive.h b/merge-recursive.h index fd138ca140..d8bc7299ee 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -17,6 +17,9 @@ struct merge_options { struct string_list current_directory_set; }; +/* Return a list of user-friendly error messages to be used by merge */ +struct unpack_trees_error_msgs get_porcelain_error_msgs(void); + /* merge_trees() but with recursive ancestor consolidation */ int merge_recursive(struct merge_options *o, struct commit *h1, diff --git a/notes.c b/notes.c new file mode 100644 index 0000000000..023adce982 --- /dev/null +++ b/notes.c @@ -0,0 +1,431 @@ +#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) { + if (!S_ISDIR(entry.mode)) + continue; /* entry cannot be subtree */ + 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. @@ -44,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); @@ -58,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 */ @@ -581,3 +581,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/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,8 +599,37 @@ static void format_decoration(struct strbuf *sb, const struct commit *commit) strbuf_addch(sb, ')'); } -static size_t format_commit_item(struct strbuf *sb, const char *placeholder, - void *context) +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_one(struct strbuf *sb, const char *placeholder, + void *context) { struct format_commit_context *c = context; const struct commit *commit = c->commit; @@ -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; @@ -739,16 +816,56 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, return 0; /* unknown placeholder */ } +static size_t format_commit_item(struct strbuf *sb, const char *placeholder, + void *context) +{ + int consumed; + size_t orig_len; + enum { + NO_MAGIC, + ADD_LF_BEFORE_NON_EMPTY, + DEL_LF_BEFORE_EMPTY, + } magic = NO_MAGIC; + + switch (placeholder[0]) { + case '-': + magic = DEL_LF_BEFORE_EMPTY; + break; + case '+': + magic = ADD_LF_BEFORE_NON_EMPTY; + break; + default: + break; + } + if (magic != NO_MAGIC) + placeholder++; + + orig_len = sb->len; + consumed = format_commit_one(sb, placeholder, context); + if (magic == NO_MAGIC) + return consumed; + + if ((orig_len == sb->len) && magic == DEL_LF_BEFORE_EMPTY) { + while (sb->len && sb->buf[sb->len - 1] == '\n') + strbuf_setlen(sb, sb->len - 1); + } else if ((orig_len != sb->len) && magic == ADD_LF_BEFORE_NON_EMPTY) { + strbuf_insert(sb, orig_len, "\n", 1); + } + return consumed + 1; +} + 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 +1017,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 +1063,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 +1074,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 +1093,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/read-cache.c b/read-cache.c index 1bbaf1cffb..9033dd3ab9 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1322,7 +1322,7 @@ int read_index_from(struct index_state *istate, const char *path) * extension name (4-byte) and section length * in 4-byte network byte order. */ - unsigned long extsize; + uint32_t extsize; memcpy(&extsize, (char *)mmap + src_offset + 4, 4); extsize = ntohl(extsize); if (read_index_extension(istate, 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 @@ -286,6 +286,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) } struct warn_if_dangling_data { + FILE *fp; const char *refname; const char *msg_fmt; }; @@ -304,13 +305,13 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha if (!resolves_to || strcmp(resolves_to, d->refname)) return 0; - printf(d->msg_fmt, refname); + fprintf(d->fp, d->msg_fmt, refname); return 0; } -void warn_dangling_symref(const char *msg_fmt, const char *refname) +void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname) { - struct warn_if_dangling_data data = { refname, msg_fmt }; + struct warn_if_dangling_data data = { fp, refname, msg_fmt }; for_each_rawref(warn_if_dangling_symref, &data); } @@ -29,7 +29,7 @@ extern int for_each_replace_ref(each_ref_fn, void *); /* can be used to learn about broken ref and symref */ extern int for_each_rawref(each_ref_fn, void *); -extern void warn_dangling_symref(const char *msg_fmt, const char *refname); +extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname); /* * Extra refs will be listed by for_each_ref() before any actual refs diff --git a/remote-curl.c b/remote-curl.c index ebdab3603e..a331bae6c8 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -4,23 +4,122 @@ #include "walker.h" #include "http.h" #include "exec_cmd.h" +#include "run-command.h" +#include "pkt-line.h" +#include "sideband.h" -static struct ref *get_refs(struct walker *walker, const char *url) +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; - char *data, *start, *mid; - char *ref_name; + struct discovery *last = last_discovery; char *refs_url; - int i = 0; - int http_ret; + int http_ret, is_http = 0; - struct ref *refs = NULL; - struct ref *ref = NULL; - struct ref *last_ref = NULL; + if (last && !strcmp(service, last->service)) + return last; + free_discovery(last); - refs_url = xmalloc(strlen(url) + 11); - sprintf(refs_url, "%s/info/refs", url); + 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: @@ -33,10 +132,86 @@ static struct ref *get_refs(struct walker *walker, const char *url) die("HTTP request failed"); } - data = buffer.buf; + 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 < buffer.len) { + while (i < heads->len) { if (!start) { start = &data[i]; } @@ -60,8 +235,7 @@ static struct ref *get_refs(struct walker *walker, const char *url) i++; } - strbuf_release(&buffer); - + init_walker(); ref = alloc_ref("HEAD"); if (!walker->fetch_ref(walker, ref) && !resolve_remote_symref(ref, refs)) { @@ -71,17 +245,506 @@ static struct ref *get_refs(struct walker *walker, const char *url) free(ref); } - strbuf_release(&buffer); - free(refs_url); 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_NOBODY, 0); + curl_easy_setopt(slot->curl, CURLOPT_POST, 1); + 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 remote *remote; struct strbuf buf = STRBUF_INIT; - const char *url; - struct walker *walker = NULL; int nongit; git_extract_argv0_path(argv[0]); @@ -91,6 +754,10 @@ int main(int argc, const char **argv) return 1; } + options.verbosity = 1; + options.progress = !!isatty(2); + options.thin = 1; + remote = remote_get(argv[1]); if (argc > 2) { @@ -103,36 +770,40 @@ int main(int argc, const char **argv) if (strbuf_getline(&buf, stdin, '\n') == EOF) break; if (!prefixcmp(buf.buf, "fetch ")) { - char *obj = buf.buf + strlen("fetch "); if (nongit) die("Fetch attempted without a local repo"); - if (!walker) - walker = get_http_walker(url, remote); - walker->get_all = 1; - walker->get_tree = 1; - walker->get_history = 1; - walker->get_verbosely = 0; - walker->get_recover = 0; - if (walker_fetch(walker, 1, &obj, NULL, NULL)) - die("Fetch failed."); - printf("\n"); - fflush(stdout); - } else if (!strcmp(buf.buf, "list")) { - struct ref *refs; - struct ref *posn; - if (!walker) - walker = get_http_walker(url, remote); - refs = get_refs(walker, url); - 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"); + 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 { @@ -6,6 +6,7 @@ #include "revision.h" #include "dir.h" #include "tag.h" +#include "string-list.h" static struct refspec s_tag_refspec = { 0, @@ -52,6 +53,11 @@ static struct rewrites rewrites_push; #define BUF_SIZE (2048) static char buffer[BUF_SIZE]; +static int valid_remote(const struct remote *remote) +{ + return (!!remote->url) || (!!remote->foreign_vcs); +} + static const char *alias_url(const char *url, struct rewrites *r) { int i, j; @@ -396,7 +402,8 @@ static int handle_config(const char *key, const char *value, void *cb) remote->mirror = git_config_bool(key, value); else if (!strcmp(subkey, ".skipdefaultupdate")) remote->skip_default_update = git_config_bool(key, value); - + else if (!strcmp(subkey, ".skipfetchall")) + remote->skip_default_update = git_config_bool(key, value); else if (!strcmp(subkey, ".url")) { const char *v; if (git_config_string(&v, key, value)) @@ -439,6 +446,8 @@ static int handle_config(const char *key, const char *value, void *cb) } else if (!strcmp(subkey, ".proxy")) { return git_config_string((const char **)&remote->http_proxy, key, value); + } else if (!strcmp(subkey, ".vcs")) { + return git_config_string(&remote->foreign_vcs, key, value); } return 0; } @@ -666,6 +675,16 @@ static struct refspec *parse_push_refspec(int nr_refspec, const char **refspec) return parse_refspec_internal(nr_refspec, refspec, 0, 0); } +void free_refspec(int nr_refspec, struct refspec *refspec) +{ + int i; + for (i = 0; i < nr_refspec; i++) { + free(refspec[i].src); + free(refspec[i].dst); + } + free(refspec); +} + static int valid_remote_nick(const char *name) { if (!name[0] || is_dot_or_dotdot(name)) @@ -688,14 +707,14 @@ struct remote *remote_get(const char *name) ret = make_remote(name, 0); if (valid_remote_nick(name)) { - if (!ret->url) + if (!valid_remote(ret)) read_remotes_file(ret); - if (!ret->url) + if (!valid_remote(ret)) read_branches_file(ret); } - if (name_given && !ret->url) + if (name_given && !valid_remote(ret)) add_url_alias(ret, name); - if (!ret->url) + if (!valid_remote(ret)) return NULL; ret->fetch = parse_fetch_refspec(ret->fetch_refspec_nr, ret->fetch_refspec); ret->push = parse_push_refspec(ret->push_refspec_nr, ret->push_refspec); @@ -734,29 +753,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) @@ -804,6 +827,23 @@ static int match_name_with_pattern(const char *key, const char *name, return ret; } +char *apply_refspecs(struct refspec *refspecs, int nr_refspec, + const char *name) +{ + int i; + char *ret = NULL; + for (i = 0; i < nr_refspec; i++) { + struct refspec *refspec = refspecs + i; + if (refspec->pattern) { + if (match_name_with_pattern(refspec->src, name, + refspec->dst, &ret)) + return ret; + } else if (!strcmp(refspec->src, name)) + return strdup(refspec->dst); + } + return NULL; +} + int remote_find_tracking(struct remote *remote, struct refspec *refspec) { int find_src = refspec->src == NULL; @@ -1586,3 +1626,42 @@ struct ref *guess_remote_head(const struct ref *head, return list; } + +struct stale_heads_info { + struct remote *remote; + struct string_list *ref_names; + struct ref **stale_refs_tail; +}; + +static int get_stale_heads_cb(const char *refname, + const unsigned char *sha1, int flags, void *cb_data) +{ + struct stale_heads_info *info = cb_data; + struct refspec refspec; + memset(&refspec, 0, sizeof(refspec)); + refspec.dst = (char *)refname; + if (!remote_find_tracking(info->remote, &refspec)) { + if (!((flags & REF_ISSYMREF) || + string_list_has_string(info->ref_names, refspec.src))) { + struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail); + hashcpy(ref->new_sha1, sha1); + } + } + return 0; +} + +struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map) +{ + struct ref *ref, *stale_refs = NULL; + struct string_list ref_names = { NULL, 0, 0, 0 }; + struct stale_heads_info info; + info.remote = remote; + info.ref_names = &ref_names; + info.stale_refs_tail = &stale_refs; + for (ref = fetch_map; ref; ref = ref->next) + string_list_append(ref->name, &ref_names); + sort_string_list(&ref_names); + for_each_ref(get_stale_heads_cb, &info); + string_list_clear(&ref_names, 0); + return stale_refs; +} @@ -11,6 +11,8 @@ struct remote { const char *name; int origin; + const char *foreign_vcs; + const char **url; int url_nr; int url_alloc; @@ -89,6 +91,11 @@ void ref_remove_duplicates(struct ref *ref_map); int valid_fetch_refspec(const char *refspec); struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec); +void free_refspec(int nr_refspec, struct refspec *refspec); + +char *apply_refspecs(struct refspec *refspecs, int nr_refspec, + const char *name); + int match_refs(struct ref *src, struct ref **dst, int nr_refspec, const char **refspec, int all); @@ -154,4 +161,7 @@ struct ref *guess_remote_head(const struct ref *head, const struct ref *refs, int all); +/* Return refs which no longer exist on remote */ +struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map); + #endif diff --git a/revision.c b/revision.c index 9fc4e8d381..25fa14d93e 100644 --- a/revision.c +++ b/revision.c @@ -791,7 +791,7 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->ignore_merges = 1; revs->simplify_history = 1; DIFF_OPT_SET(&revs->pruning, RECURSIVE); - DIFF_OPT_SET(&revs->pruning, QUIET); + DIFF_OPT_SET(&revs->pruning, QUICK); revs->pruning.add_remove = file_add_remove; revs->pruning.change = file_change; revs->lifo = 1; @@ -953,21 +953,59 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, return 0; } -void read_revisions_from_stdin(struct rev_info *revs) +static void read_pathspec_from_stdin(struct rev_info *revs, struct strbuf *sb, const char ***prune_data) { - char line[1000]; + const char **prune = *prune_data; + int prune_nr; + int prune_alloc; - while (fgets(line, sizeof(line), stdin) != NULL) { - int len = strlen(line); - if (len && line[len - 1] == '\n') - line[--len] = '\0'; + /* count existing ones */ + if (!prune) + prune_nr = 0; + else + for (prune_nr = 0; prune[prune_nr]; prune_nr++) + ; + prune_alloc = prune_nr; /* not really, but we do not know */ + + while (strbuf_getwholeline(sb, stdin, '\n') != EOF) { + int len = sb->len; + if (len && sb->buf[len - 1] == '\n') + sb->buf[--len] = '\0'; + ALLOC_GROW(prune, prune_nr+1, prune_alloc); + prune[prune_nr++] = xstrdup(sb->buf); + } + if (prune) { + ALLOC_GROW(prune, prune_nr+1, prune_alloc); + prune[prune_nr] = NULL; + } + *prune_data = prune; +} + +static void read_revisions_from_stdin(struct rev_info *revs, const char ***prune) +{ + struct strbuf sb; + int seen_dashdash = 0; + + strbuf_init(&sb, 1000); + while (strbuf_getwholeline(&sb, stdin, '\n') != EOF) { + int len = sb.len; + if (len && sb.buf[len - 1] == '\n') + sb.buf[--len] = '\0'; if (!len) break; - if (line[0] == '-') + if (sb.buf[0] == '-') { + if (len == 2 && sb.buf[1] == '-') { + seen_dashdash = 1; + break; + } die("options not supported in --stdin mode"); - if (handle_revision_arg(line, revs, 0, 1)) - die("bad revision '%s'", line); + } + if (handle_revision_arg(sb.buf, revs, 0, 1)) + die("bad revision '%s'", sb.buf); } + if (seen_dashdash) + read_pathspec_from_stdin(revs, &sb, prune); + strbuf_release(&sb); } static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what) @@ -994,7 +1032,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; @@ -1218,6 +1257,44 @@ 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); +} + +static void append_prune_data(const char ***prune_data, const char **av) +{ + const char **prune = *prune_data; + int prune_nr; + int prune_alloc; + + if (!prune) { + *prune_data = av; + return; + } + + /* count existing ones */ + for (prune_nr = 0; prune[prune_nr]; prune_nr++) + ; + prune_alloc = prune_nr; /* not really, but we do not know */ + + while (*av) { + ALLOC_GROW(prune, prune_nr+1, prune_alloc); + prune[prune_nr++] = *av; + av++; + } + if (prune) { + ALLOC_GROW(prune, prune_nr+1, prune_alloc); + prune[prune_nr] = NULL; + } + *prune_data = prune; +} + /* * Parse revision information, filling in the "rev_info" structure, * and removing the used arguments from the argument list. @@ -1227,7 +1304,8 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx, */ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def) { - int i, flags, left, seen_dashdash; + int i, flags, left, seen_dashdash, read_from_stdin; + const char **prune_data = NULL; /* First, search for "--" */ seen_dashdash = 0; @@ -1238,13 +1316,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch argv[i] = NULL; argc = i; if (argv[i + 1]) - revs->prune_data = get_pathspec(revs->prefix, argv + i + 1); + prune_data = argv + i + 1; seen_dashdash = 1; break; } /* Second, deal with arguments and options */ flags = 0; + read_from_stdin = 0; for (left = i = 1; i < argc; i++) { const char *arg = argv[i]; if (*arg == '-') { @@ -1259,6 +1338,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; @@ -1283,6 +1368,16 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->no_walk = 0; continue; } + if (!strcmp(arg, "--stdin")) { + if (revs->disable_stdin) { + argv[left++] = arg; + continue; + } + if (read_from_stdin++) + die("--stdin given twice?"); + read_revisions_from_stdin(revs, &prune_data); + continue; + } opts = handle_revision_opt(revs, argc - i, argv + i, &left, argv); if (opts > 0) { @@ -1308,12 +1403,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch for (j = i; j < argc; j++) verify_filename(revs->prefix, argv[j]); - revs->prune_data = get_pathspec(revs->prefix, - argv + i); + append_prune_data(&prune_data, argv + i); break; } } + if (prune_data) + revs->prune_data = get_pathspec(revs->prefix, prune_data); + if (revs->def == NULL) revs->def = def; if (revs->show_merge) diff --git a/revision.h b/revision.h index b6421a6432..d368003159 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 */ @@ -83,6 +84,8 @@ struct rev_info { use_terminator:1, missing_newline:1, date_mode_explicit:1; + unsigned int disable_stdin:1; + enum date_mode date_mode; unsigned int abbrev; @@ -128,8 +131,6 @@ struct rev_info { #define REV_TREE_DIFFERENT 3 /* Mixed changes */ /* revision.c */ -void read_revisions_from_stdin(struct rev_info *revs); - typedef void (*show_early_output_fn_t)(struct rev_info *, struct commit_list *); extern volatile show_early_output_fn_t show_early_output; 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, @@ -18,9 +18,12 @@ const char *prefix_path(const char *prefix, int len, const char *path) if (normalize_path_copy(sanitized, sanitized)) goto error_out; if (is_absolute_path(orig)) { + size_t len, total; const char *work_tree = get_git_work_tree(); - size_t len = strlen(work_tree); - size_t total = strlen(sanitized) + 1; + if (!work_tree) + goto error_out; + len = strlen(work_tree); + total = strlen(sanitized) + 1; if (strncmp(sanitized, work_tree, len) || (sanitized[len] != '\0' && sanitized[len] != '/')) { error_out: @@ -61,6 +64,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 +86,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 +101,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/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; @@ -117,7 +117,7 @@ struct strbuf_expand_dict_entry { }; extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context); -__attribute__((format(printf,2,3))) +__attribute__((format (printf,2,3))) extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...); extern size_t strbuf_fread(struct strbuf *, size_t, FILE *); 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 @@ -75,6 +75,28 @@ appropriately before running "make". As the names depend on the tests' file names, it is safe to run the tests with this option in parallel. +--with-dashes:: + By default tests are run without dashed forms of + commands (like git-commit) in the PATH (it only uses + wrappers from ../bin-wrappers). Use this option to include + the build directory (..) in the PATH, which contains all + the dashed forms of commands. This option is currently + implied by other options like --valgrind and + GIT_TEST_INSTALLED. + +You can also set the GIT_TEST_INSTALLED environment variable to +the bindir of an existing git installation to test that installation. +You still need to have built this git sandbox, from which various +test-* support programs, templates, and perl libraries are used. +If your installed git is incomplete, it will silently test parts of +your built version instead. + +When using GIT_TEST_INSTALLED, you can also set GIT_TEST_EXEC_PATH to +override the location of the dashed-form subcommands (what +GIT_EXEC_PATH would be used for during normal operation). +GIT_TEST_EXEC_PATH defaults to `$GIT_TEST_INSTALLED/git --exec-path`. + + Skipping Tests -------------- diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh index 845253274b..76d8b7b803 100644 --- a/t/gitweb-lib.sh +++ b/t/gitweb-lib.sh @@ -52,10 +52,24 @@ gitweb_run () { rm -f gitweb.log && perl -- "$SCRIPT_NAME" \ >gitweb.output 2>gitweb.log && + perl -w -e ' + open O, ">gitweb.headers"; + while (<>) { + print O; + last if (/^\r$/ || /^$/); + } + open O, ">gitweb.body"; + while (<>) { + print O; + } + close O; + ' gitweb.output && if grep '^[[]' gitweb.log >/dev/null 2>&1; then false; else true; fi # gitweb.log is left for debugging - # gitweb.output is used to parse http output + # gitweb.output is used to parse HTTP output + # gitweb.headers contains only HTTP headers + # gitweb.body contains body of message, without headers } . ./test-lib.sh 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-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/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..ab55eda158 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,35 @@ 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' ' + test_tick && + 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 +83,47 @@ 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 && + test_tick && + 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 && + test_tick && + 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 +134,132 @@ 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' ' + test_tick && + 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. +* [master] Some fun. + + [mybranch] Some work. +*+ [master^] 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 + test $(find .git/objects/[0-9a-f][0-9a-f] -type f -print 2>/dev/null | wc -l) = 0 ' 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/t1501-worktree.sh b/t/t1501-worktree.sh index f6a6f839a1..74e6443664 100755 --- a/t/t1501-worktree.sh +++ b/t/t1501-worktree.sh @@ -174,4 +174,19 @@ test_expect_success 'git grep' ' GIT_DIR=../.. GIT_WORK_TREE=.. git grep -l changed | grep dir/tracked) ' +test_expect_success 'git commit' ' + ( + cd repo.git && + GIT_DIR=. GIT_WORK_TREE=work git commit -a -m done + ) +' + +test_expect_success 'absolute pathspec should fail gracefully' ' + ( + cd repo.git || exit 1 + git config --unset core.worktree + test_must_fail git log HEAD -- /home + ) +' + test_done diff --git a/t/t2300-cd-to-toplevel.sh b/t/t2300-cd-to-toplevel.sh index 3b01ad2e4d..9965bc5c92 100755 --- a/t/t2300-cd-to-toplevel.sh +++ b/t/t2300-cd-to-toplevel.sh @@ -8,7 +8,7 @@ test_cd_to_toplevel () { test_expect_success $3 "$2" ' ( cd '"'$1'"' && - . git-sh-setup && + . "$(git --exec-path)"/git-sh-setup && cd_to_toplevel && [ "$(pwd -P)" = "$TOPLEVEL" ] ) 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/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/t3304-notes-mixed.sh b/t/t3304-notes-mixed.sh new file mode 100755 index 0000000000..256687ffb5 --- /dev/null +++ b/t/t3304-notes-mixed.sh @@ -0,0 +1,172 @@ +#!/bin/sh + +test_description='Test notes trees that also contain non-notes' + +. ./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 a couple of commits" ' + + test_tick && + cat <<INPUT_END >input && +commit refs/heads/master +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +commit #1 +COMMIT + +M 644 inline file +data <<EOF +file in commit #1 +EOF + +INPUT_END + + test_tick && + cat <<INPUT_END >>input && +commit refs/heads/master +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +commit #2 +COMMIT + +M 644 inline file +data <<EOF +file in commit #2 +EOF + +INPUT_END + git fast-import --quiet <input +' + +test_expect_success "create a notes tree with both notes and non-notes" ' + + commit1=$(git rev-parse refs/heads/master^) && + commit2=$(git rev-parse refs/heads/master) && + test_tick && + cat <<INPUT_END >input && +commit refs/notes/commits +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +notes commit #1 +COMMIT + +N inline $commit1 +data <<EOF +note for commit #1 +EOF + +N inline $commit2 +data <<EOF +note for commit #2 +EOF + +INPUT_END + test_tick && + cat <<INPUT_END >>input && +commit refs/notes/commits +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +notes commit #2 +COMMIT + +M 644 inline foobar/non-note.txt +data <<EOF +A non-note in a notes tree +EOF + +N inline $commit2 +data <<EOF +edited note for commit #2 +EOF + +INPUT_END + test_tick && + cat <<INPUT_END >>input && +commit refs/notes/commits +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +notes commit #3 +COMMIT + +N inline $commit1 +data <<EOF +edited note for commit #1 +EOF + +M 644 inline deadbeef +data <<EOF +non-note with SHA1-like name +EOF + +M 644 inline de/adbeef +data <<EOF +another non-note with SHA1-like name +EOF + +INPUT_END + git fast-import --quiet <input && + git config core.notesRef refs/notes/commits +' + +cat >expect <<EXPECT_END + commit #2 + edited note for commit #2 + commit #1 + edited note for commit #1 +EXPECT_END + +test_expect_success "verify contents of notes" ' + + git log | grep "^ " > actual && + test_cmp expect actual +' + +cat >expect_nn1 <<EXPECT_END +A non-note in a notes tree +EXPECT_END +cat >expect_nn2 <<EXPECT_END +non-note with SHA1-like name +EXPECT_END +cat >expect_nn3 <<EXPECT_END +another non-note with SHA1-like name +EXPECT_END + +test_expect_success "verify contents of non-notes" ' + + git cat-file -p refs/notes/commits:foobar/non-note.txt > actual_nn1 && + test_cmp expect_nn1 actual_nn1 && + git cat-file -p refs/notes/commits:deadbeef > actual_nn2 && + test_cmp expect_nn2 actual_nn2 && + git cat-file -p refs/notes/commits:de/adbeef > actual_nn3 && + test_cmp expect_nn3 actual_nn3 +' + +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/t3409-rebase-preserve-merges.sh b/t/t3409-rebase-preserve-merges.sh index 297d165476..8f785e7957 100755 --- a/t/t3409-rebase-preserve-merges.sh +++ b/t/t3409-rebase-preserve-merges.sh @@ -32,14 +32,14 @@ export GIT_AUTHOR_EMAIL test_expect_success 'setup for merge-preserving rebase' \ 'echo First > A && git add A && - git-commit -m "Add A1" && + git commit -m "Add A1" && git checkout -b topic && echo Second > B && git add B && - git-commit -m "Add B1" && + git commit -m "Add B1" && git checkout -f master && echo Third >> A && - git-commit -a -m "Modify A2" && + git commit -a -m "Modify A2" && git clone ./. clone1 && cd clone1 && diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index d86bc81abf..b6eba6a839 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -229,6 +229,26 @@ test_expect_success 'add first line works' ' ' cat >expected <<EOF +diff --git a/non-empty b/non-empty +deleted file mode 100644 +index d95f3ad..0000000 +--- a/non-empty ++++ /dev/null +@@ -1 +0,0 @@ +-content +EOF +test_expect_success 'deleting a non-empty file' ' + git reset --hard && + echo content >non-empty && + git add non-empty && + git commit -m non-empty && + rm non-empty && + echo y | git add -p non-empty && + git diff --cached >diff && + test_cmp expected diff +' + +cat >expected <<EOF diff --git a/empty b/empty deleted file mode 100644 index e69de29..0000000 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 74e5b63bbf..3bc1cccf88 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -536,6 +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 > output && + test 6 = $(grep "^diff --git a/" output | wc -l)' + test_expect_success 'format-patch -- <path>' ' git format-patch master..side -- file 2>error && ! grep "Use .--" error diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 8dd147d78f..90f3342373 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -93,8 +93,6 @@ git diff > out test_expect_success 'another test, without options' 'test_cmp expect out' cat << EOF > expect -diff --git a/x b/x -index d99af23..8b32fb5 100644 EOF git diff -w > out test_expect_success 'another test, with -w' 'test_cmp expect out' @@ -386,6 +384,18 @@ test_expect_success 'checkdiff allows new blank lines' ' git diff --check ' +cat <<EOF >expect +EOF +test_expect_success 'whitespace-only changes not reported' ' + git reset --hard && + echo >x "hello world" && + git add x && + git commit -m "hello 1" && + echo >x "hello world" && + git diff -b >actual && + test_cmp expect actual +' + test_expect_success 'combined diff with autocrlf conversion' ' git reset --hard && diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index 3a3663fbcb..f6d1f1ebab 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -20,11 +20,27 @@ test_expect_success setup ' blue_grep='7;34m' ;# ESC [ 7 ; 3 4 m +printf "\033[%s" "$blue_grep" >check-grep +if (grep "$blue_grep" <check-grep | grep "$blue_grep") >/dev/null 2>&1 +then + grep_a=grep +elif (grep -a "$blue_grep" <check-grep | grep -a "$blue_grep") >/dev/null 2>&1 +then + grep_a='grep -a' +else + grep_a=grep ;# expected to fail... +fi +rm -f check-grep + +prepare_output () { + git diff --color >output + $grep_a "$blue_grep" output >error + $grep_a -v "$blue_grep" output >normal +} + test_expect_success default ' - git diff --color >output - grep "$blue_grep" output >error - grep -v "$blue_grep" output >normal + prepare_output grep Eight normal >/dev/null && grep HT error >/dev/null && @@ -37,9 +53,7 @@ test_expect_success default ' test_expect_success 'without -trail' ' git config core.whitespace -trail - git diff --color >output - grep "$blue_grep" output >error - grep -v "$blue_grep" output >normal + prepare_output grep Eight normal >/dev/null && grep HT error >/dev/null && @@ -53,9 +67,7 @@ test_expect_success 'without -trail (attribute)' ' git config --unset core.whitespace echo "F whitespace=-trail" >.gitattributes - git diff --color >output - grep "$blue_grep" output >error - grep -v "$blue_grep" output >normal + prepare_output grep Eight normal >/dev/null && grep HT error >/dev/null && @@ -69,9 +81,7 @@ test_expect_success 'without -space' ' rm -f .gitattributes git config core.whitespace -space - git diff --color >output - grep "$blue_grep" output >error - grep -v "$blue_grep" output >normal + prepare_output grep Eight normal >/dev/null && grep HT normal >/dev/null && @@ -85,9 +95,7 @@ test_expect_success 'without -space (attribute)' ' git config --unset core.whitespace echo "F whitespace=-space" >.gitattributes - git diff --color >output - grep "$blue_grep" output >error - grep -v "$blue_grep" output >normal + prepare_output grep Eight normal >/dev/null && grep HT normal >/dev/null && @@ -101,9 +109,7 @@ test_expect_success 'with indent-non-tab only' ' rm -f .gitattributes git config core.whitespace indent,-trailing,-space - git diff --color >output - grep "$blue_grep" output >error - grep -v "$blue_grep" output >normal + prepare_output grep Eight error >/dev/null && grep HT normal >/dev/null && @@ -117,9 +123,7 @@ test_expect_success 'with indent-non-tab only (attribute)' ' git config --unset core.whitespace echo "F whitespace=indent,-trailing,-space" >.gitattributes - git diff --color >output - grep "$blue_grep" output >error - grep -v "$blue_grep" output >normal + prepare_output grep Eight error >/dev/null && grep HT normal >/dev/null && @@ -133,9 +137,7 @@ test_expect_success 'with cr-at-eol' ' rm -f .gitattributes git config core.whitespace cr-at-eol - git diff --color >output - grep "$blue_grep" output >error - grep -v "$blue_grep" output >normal + prepare_output grep Eight normal >/dev/null && grep HT error >/dev/null && @@ -149,9 +151,7 @@ test_expect_success 'with cr-at-eol (attribute)' ' git config --unset core.whitespace echo "F whitespace=trailing,cr-at-eol" >.gitattributes - git diff --color >output - grep "$blue_grep" output >error - grep -v "$blue_grep" output >normal + prepare_output grep Eight normal >/dev/null && grep HT error >/dev/null && @@ -195,7 +195,7 @@ test_expect_success 'color new trailing blank lines' ' 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) && + cnt=$($grep_a "${blue_grep}" output | wc -l) && test $cnt = 2 ' diff --git a/t/t4026-color.sh b/t/t4026-color.sh index b61e5169f4..5ade44c043 100755 --- a/t/t4026-color.sh +++ b/t/t4026-color.sh @@ -66,4 +66,21 @@ test_expect_success 'extra character after attribute' ' invalid_color "dimX" ' +test_expect_success 'unknown color slots are ignored (diff)' ' + git config --unset diff.color.new + git config color.diff.nosuchslotwilleverbedefined white && + git diff --color +' + +test_expect_success 'unknown color slots are ignored (branch)' ' + git config color.branch.nosuchslotwilleverbedefined white && + git branch -a +' + +test_expect_success 'unknown color slots are ignored (status)' ' + git config color.status.nosuchslotwilleverbedefined white || exit + git status + case $? in 0|1) : ok ;; *) false ;; esac +' + test_done diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index 21db6e95c4..2e2e103b31 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -8,21 +8,13 @@ test_expect_success setup ' git config diff.color.old red git config diff.color.new green + git config diff.color.func magenta ' -decrypt_color () { - sed \ - -e 's/.\[1m/<WHITE>/g' \ - -e 's/.\[31m/<RED>/g' \ - -e 's/.\[32m/<GREEN>/g' \ - -e 's/.\[36m/<BROWN>/g' \ - -e 's/.\[m/<RESET>/g' -} - word_diff () { test_must_fail git diff --no-index "$@" pre post > output && - decrypt_color < output > output.decrypted && + test_decode_color <output >output.decrypted && test_cmp expect output.decrypted } @@ -47,9 +39,9 @@ cat > expect <<\EOF <WHITE>index 330b04f..5ed8eff 100644<RESET> <WHITE>--- a/pre<RESET> <WHITE>+++ b/post<RESET> -<BROWN>@@ -1,3 +1,7 @@<RESET> +<CYAN>@@ -1,3 +1,7 @@<RESET> <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET> -<RESET> + a = b + c<RESET> <GREEN>aa = a<RESET> @@ -68,9 +60,9 @@ cat > expect <<\EOF <WHITE>index 330b04f..5ed8eff 100644<RESET> <WHITE>--- a/pre<RESET> <WHITE>+++ b/post<RESET> -<BROWN>@@ -1 +1 @@<RESET> +<CYAN>@@ -1 +1 @@<RESET> <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET> -<BROWN>@@ -3,0 +4,4 @@ a = b + c<RESET> +<CYAN>@@ -3,0 +4,4 @@<RESET> <RESET><MAGENTA>a = b + c<RESET> <GREEN>aa = a<RESET> @@ -88,9 +80,9 @@ cat > expect <<\EOF <WHITE>index 330b04f..5ed8eff 100644<RESET> <WHITE>--- a/pre<RESET> <WHITE>+++ b/post<RESET> -<BROWN>@@ -1,3 +1,7 @@<RESET> +<CYAN>@@ -1,3 +1,7 @@<RESET> h(4),<GREEN>hh<RESET>[44] -<RESET> + a = b + c<RESET> <GREEN>aa = a<RESET> @@ -124,9 +116,9 @@ cat > expect <<\EOF <WHITE>index 330b04f..5ed8eff 100644<RESET> <WHITE>--- a/pre<RESET> <WHITE>+++ b/post<RESET> -<BROWN>@@ -1,3 +1,7 @@<RESET> +<CYAN>@@ -1,3 +1,7 @@<RESET> h(4)<GREEN>,hh[44]<RESET> -<RESET> + a = b + c<RESET> <GREEN>aa = a<RESET> @@ -166,9 +158,9 @@ cat > expect <<\EOF <WHITE>index 330b04f..5ed8eff 100644<RESET> <WHITE>--- a/pre<RESET> <WHITE>+++ b/post<RESET> -<BROWN>@@ -1,3 +1,7 @@<RESET> +<CYAN>@@ -1,3 +1,7 @@<RESET> h(4),<GREEN>hh[44<RESET>] -<RESET> + a = b + c<RESET> <GREEN>aa = a<RESET> @@ -188,7 +180,7 @@ cat > expect <<\EOF <WHITE>index c29453b..be22f37 100644<RESET> <WHITE>--- a/pre<RESET> <WHITE>+++ b/post<RESET> -<BROWN>@@ -1 +1 @@<RESET> +<CYAN>@@ -1 +1 @@<RESET> aaa (aaa) <GREEN>aaa<RESET> EOF @@ -207,7 +199,7 @@ cat > expect <<\EOF <WHITE>index 289cb9d..2d06f37 100644<RESET> <WHITE>--- a/pre<RESET> <WHITE>+++ b/post<RESET> -<BROWN>@@ -1 +1 @@<RESET> +<CYAN>@@ -1 +1 @@<RESET> (<RED>:<RESET> EOF diff --git a/t/t4040-whitespace-status.sh b/t/t4040-whitespace-status.sh new file mode 100755 index 0000000000..a30b03bcf2 --- /dev/null +++ b/t/t4040-whitespace-status.sh @@ -0,0 +1,63 @@ +#!/bin/sh + +test_description='diff --exit-code with whitespace' +. ./test-lib.sh + +test_expect_success setup ' + mkdir a b && + echo >c && + echo >a/d && + echo >b/e && + git add . && + test_tick && + git commit -m initial && + echo " " >a/d && + test_tick && + git commit -a -m second && + echo " " >a/d && + echo " " >b/e && + git add a/d +' + +test_expect_success 'diff-tree --exit-code' ' + test_must_fail git diff --exit-code HEAD^ HEAD && + test_must_fail git diff-tree --exit-code HEAD^ HEAD +' + +test_expect_success 'diff-tree -b --exit-code' ' + git diff -b --exit-code HEAD^ HEAD && + git diff-tree -b -p --exit-code HEAD^ HEAD && + git diff-tree -b --exit-code HEAD^ HEAD +' + +test_expect_success 'diff-index --cached --exit-code' ' + test_must_fail git diff --cached --exit-code HEAD && + test_must_fail git diff-index --cached --exit-code HEAD +' + +test_expect_success 'diff-index -b -p --cached --exit-code' ' + git diff -b --cached --exit-code HEAD && + git diff-index -b -p --cached --exit-code HEAD +' + +test_expect_success 'diff-index --exit-code' ' + test_must_fail git diff --exit-code HEAD && + test_must_fail git diff-index --exit-code HEAD +' + +test_expect_success 'diff-index -b -p --exit-code' ' + git diff -b --exit-code HEAD && + git diff-index -b -p --exit-code HEAD +' + +test_expect_success 'diff-files --exit-code' ' + test_must_fail git diff --exit-code && + test_must_fail git diff-files --exit-code +' + +test_expect_success 'diff-files -b -p --exit-code' ' + git diff -b --exit-code && + git diff-files -b -p --exit-code +' + +test_done 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/t4107-apply-ignore-whitespace.sh b/t/t4107-apply-ignore-whitespace.sh index 484654d6e4..b04fc8fc12 100755 --- a/t/t4107-apply-ignore-whitespace.sh +++ b/t/t4107-apply-ignore-whitespace.sh @@ -136,37 +136,37 @@ void print_int(int num) { EOF test_expect_success 'file creation' ' - git-apply patch1.patch + git apply patch1.patch ' test_expect_success 'patch2 fails (retab)' ' - test_must_fail git-apply patch2.patch + test_must_fail git apply patch2.patch ' test_expect_success 'patch2 applies with --ignore-whitespace' ' - git-apply --ignore-whitespace patch2.patch + git apply --ignore-whitespace patch2.patch ' test_expect_success 'patch2 reverse applies with --ignore-space-change' ' - git-apply -R --ignore-space-change patch2.patch + git apply -R --ignore-space-change patch2.patch ' git config apply.ignorewhitespace change test_expect_success 'patch2 applies (apply.ignorewhitespace = change)' ' - git-apply patch2.patch + git apply patch2.patch ' test_expect_success 'patch3 fails (missing string at EOL)' ' - test_must_fail git-apply patch3.patch + test_must_fail git apply patch3.patch ' test_expect_success 'patch4 fails (missing EOL at EOF)' ' - test_must_fail git-apply patch4.patch + test_must_fail git apply patch4.patch ' test_expect_success 'patch5 applies (leading whitespace)' ' - git-apply patch5.patch + git apply patch5.patch ' test_expect_success 'patches do not mangle whitespace' ' @@ -175,11 +175,11 @@ test_expect_success 'patches do not mangle whitespace' ' test_expect_success 're-create file (with --ignore-whitespace)' ' rm -f main.c && - git-apply patch1.patch + git apply patch1.patch ' test_expect_success 'patch5 fails (--no-ignore-whitespace)' ' - test_must_fail git-apply --no-ignore-whitespace patch5.patch + test_must_fail git apply --no-ignore-whitespace patch5.patch ' test_done diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 1e952ca55b..779a5adf55 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -64,6 +64,27 @@ test_expect_success 'format' ' ' cat > expect << EOF + This is + the sixth + commit. + This is + the fifth + commit. +EOF + +test_expect_success 'format %w(12,1,2)' ' + + git log -2 --format="%w(12,1,2)This is the %s commit." > actual && + test_cmp expect actual +' + +test_expect_success 'format %w(,1,2)' ' + + git log -2 --format="%w(,1,2)This is%nthe %s%ncommit." > actual && + test_cmp expect actual +' + +cat > expect << EOF 804a787 sixth 394ef78 fifth 5d31159 fourth diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index f2d5581b12..c718253673 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -32,7 +32,7 @@ test_expect_success setup ' done && git update-ref HEAD "$commit" && git clone ./. victim && - ( cd victim && git log ) && + ( cd victim && git config receive.denyCurrentBranch warn && git log ) && git update-ref HEAD "$zero" && parent=$zero && i=0 && @@ -129,6 +129,7 @@ rewound_push_setup() { cd parent && git init && echo one >file && git add file && git commit -m one && + git config receive.denyCurrentBranch warn && echo two >file && git commit -a -m two ) && git clone parent child && @@ -190,16 +191,11 @@ test_expect_success 'pushing wildcard refspecs respects forcing' ' test "$parent_head" = "$child_head" ' -test_expect_success 'warn pushing to delete current branch' ' +test_expect_success 'deny pushing to delete current branch' ' rewound_push_setup && ( cd child && - git send-pack ../parent :refs/heads/master 2>errs - ) && - grep "warning: to refuse deleting" child/errs && - ( - cd parent && - test_must_fail git rev-parse --verify master + test_must_fail git send-pack ../parent :refs/heads/master 2>errs ) ' diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh index 64f66c94f3..325714e529 100755 --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh @@ -18,6 +18,7 @@ test_expect_success setup ' git update-ref refs/heads/master $commit0 && git update-ref refs/heads/tofail $commit1 && git clone ./. victim && + GIT_DIR=victim/.git git config receive.denyCurrentBranch warn && GIT_DIR=victim/.git git update-ref refs/heads/tofail $commit1 && git update-ref refs/heads/master $commit1 && git update-ref refs/heads/tofail $commit0 diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh index 5858b868ed..d05a9138b4 100755 --- a/t/t5403-post-checkout-hook.sh +++ b/t/t5403-post-checkout-hook.sh @@ -7,19 +7,19 @@ test_description='Test the post-checkout hook.' . ./test-lib.sh test_expect_success setup ' - echo Data for commit0. >a && - echo Data for commit0. >b && - git update-index --add a && - git update-index --add b && - tree0=$(git write-tree) && - commit0=$(echo setup | git commit-tree $tree0) && - git update-ref refs/heads/master $commit0 && - git clone ./. clone1 && - git clone ./. clone2 && - GIT_DIR=clone2/.git git branch -a new2 && - echo Data for commit1. >clone2/b && - GIT_DIR=clone2/.git git add clone2/b && - GIT_DIR=clone2/.git git commit -m new2 + echo Data for commit0. >a && + echo Data for commit0. >b && + git update-index --add a && + git update-index --add b && + tree0=$(git write-tree) && + commit0=$(echo setup | git commit-tree $tree0) && + git update-ref refs/heads/master $commit0 && + git clone ./. clone1 && + git clone ./. clone2 && + GIT_DIR=clone2/.git git branch new2 && + echo Data for commit1. >clone2/b && + GIT_DIR=clone2/.git git add clone2/b && + GIT_DIR=clone2/.git git commit -m new2 ' for clone in 1 2; do diff --git a/t/t5405-send-pack-rewind.sh b/t/t5405-send-pack-rewind.sh index cb9aacc7bc..4bda18a662 100755 --- a/t/t5405-send-pack-rewind.sh +++ b/t/t5405-send-pack-rewind.sh @@ -8,6 +8,7 @@ test_expect_success setup ' >file1 && git add file1 && test_tick && git commit -m Initial && + git config receive.denyCurrentBranch warn && mkdir another && ( cd another && diff --git a/t/t5501-post-upload-pack.sh b/t/t5501-post-upload-pack.sh deleted file mode 100755 index d89fb51bad..0000000000 --- a/t/t5501-post-upload-pack.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/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..fd166d9de3 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 @@ -365,6 +365,17 @@ test_expect_success 'update with arguments' ' ' +test_expect_success 'update --prune' ' + + (cd one && + git branch -m side2 side3) && + (cd test && + git remote update --prune && + (cd ../one && git branch -m side3 side2) + git rev-parse refs/remotes/origin/side3 && + test_must_fail git rev-parse refs/remotes/origin/side2) +' + cat > one/expect << EOF apis/master apis/side diff --git a/t/t5506-remote-groups.sh b/t/t5506-remote-groups.sh index 2a1806b0b4..b7b7ddaa40 100755 --- a/t/t5506-remote-groups.sh +++ b/t/t5506-remote-groups.sh @@ -51,7 +51,7 @@ test_expect_success 'nonexistant group produces error' ' ! repo_fetched two ' -test_expect_success 'updating group updates all members' ' +test_expect_success 'updating group updates all members (remote update)' ' mark group-all && update_repos && git config --add remotes.all one && @@ -61,7 +61,15 @@ test_expect_success 'updating group updates all members' ' repo_fetched two ' -test_expect_success 'updating group does not update non-members' ' +test_expect_success 'updating group updates all members (fetch)' ' + mark fetch-group-all && + update_repos && + git fetch all && + repo_fetched one && + repo_fetched two +' + +test_expect_success 'updating group does not update non-members (remote update)' ' mark group-some && update_repos && git config --add remotes.some one && @@ -70,6 +78,15 @@ test_expect_success 'updating group does not update non-members' ' ! repo_fetched two ' +test_expect_success 'updating group does not update non-members (fetch)' ' + mark fetch-group-some && + update_repos && + git config --add remotes.some one && + git remote update some && + repo_fetched one && + ! repo_fetched two +' + test_expect_success 'updating remote name updates that remote' ' mark remote-name && update_repos && 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/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh new file mode 100755 index 0000000000..b73733219d --- /dev/null +++ b/t/t5514-fetch-multiple.sh @@ -0,0 +1,154 @@ +#!/bin/sh + +test_description='fetch --all works correctly' + +. ./test-lib.sh + +setup_repository () { + mkdir "$1" && ( + cd "$1" && + git init && + >file && + git add file && + test_tick && + git commit -m "Initial" && + git checkout -b side && + >elif && + git add elif && + test_tick && + git commit -m "Second" && + git checkout master + ) +} + +test_expect_success setup ' + setup_repository one && + setup_repository two && + ( + cd two && git branch another + ) && + git clone --mirror two three + git clone one test +' + +cat > test/expect << EOF + one/master + one/side + origin/HEAD -> origin/master + origin/master + origin/side + three/another + three/master + three/side + two/another + two/master + two/side +EOF + +test_expect_success 'git fetch --all' ' + (cd test && + git remote add one ../one && + git remote add two ../two && + git remote add three ../three && + git fetch --all && + git branch -r > output && + test_cmp expect output) +' + +test_expect_success 'git fetch --all should continue if a remote has errors' ' + (git clone one test2 && + cd test2 && + git remote add bad ../non-existing && + git remote add one ../one && + git remote add two ../two && + git remote add three ../three && + test_must_fail git fetch --all && + git branch -r > output && + test_cmp ../test/expect output) +' + +test_expect_success 'git fetch --all does not allow non-option arguments' ' + (cd test && + test_must_fail git fetch --all origin && + test_must_fail git fetch --all origin master) +' + +cat > expect << EOF + origin/HEAD -> origin/master + origin/master + origin/side + three/another + three/master + three/side +EOF + +test_expect_success 'git fetch --multiple (but only one remote)' ' + (git clone one test3 && + cd test3 && + git remote add three ../three && + git fetch --multiple three && + git branch -r > output && + test_cmp ../expect output) +' + +cat > expect << EOF + one/master + one/side + two/another + two/master + two/side +EOF + +test_expect_success 'git fetch --multiple (two remotes)' ' + (git clone one test4 && + cd test4 && + git remote rm origin && + git remote add one ../one && + git remote add two ../two && + git fetch --multiple one two && + git branch -r > output && + test_cmp ../expect output) +' + +test_expect_success 'git fetch --multiple (bad remote names)' ' + (cd test4 && + test_must_fail git fetch --multiple four) +' + + +test_expect_success 'git fetch --all (skipFetchAll)' ' + (cd test4 && + for b in $(git branch -r) + do + git branch -r -d $b || break + done && + git remote add three ../three && + git config remote.three.skipFetchAll true && + git fetch --all && + git branch -r > output && + test_cmp ../expect output) +' + +cat > expect << EOF + one/master + one/side + three/another + three/master + three/side + two/another + two/master + two/side +EOF + +test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' ' + (cd test4 && + for b in $(git branch -r) + do + git branch -r -d $b || break + done && + git fetch --multiple one two three && + git branch -r > output && + test_cmp ../expect output) +' + +test_done diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 6889a53cf9..516127b539 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -12,6 +12,7 @@ mk_empty () { ( cd testrepo && git init && + git config receive.denyCurrentBranch warn && mv .git/hooks .git/hooks-disabled ) } diff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh index ea49dedbf8..e2ad260508 100755 --- a/t/t5517-push-mirror.sh +++ b/t/t5517-push-mirror.sh @@ -19,7 +19,8 @@ mk_repo_pair () { mkdir mirror && ( cd mirror && - git init + git init && + git config receive.denyCurrentBranch warn ) && mkdir 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/t5522-pull-symlink.sh b/t/t5522-pull-symlink.sh index 86bbd7d024..7206817ca1 100755 --- a/t/t5522-pull-symlink.sh +++ b/t/t5522-pull-symlink.sh @@ -20,13 +20,19 @@ fi # # The working directory is subdir-link. -mkdir subdir -echo file >subdir/file -git add subdir/file -git commit -q -m file -git clone -q . clone-repo -ln -s clone-repo/subdir/ subdir-link - +test_expect_success setup ' + mkdir subdir && + echo file >subdir/file && + git add subdir/file && + git commit -q -m file && + git clone -q . clone-repo && + ln -s clone-repo/subdir/ subdir-link && + ( + cd clone-repo && + git config receive.denyCurrentBranch warn + ) && + git config receive.denyCurrentBranch warn +' # Demonstrate that things work if we just avoid the symlink # 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/t5701-clone-local.sh b/t/t5701-clone-local.sh index 19b5c0d552..8b4c356cd2 100755 --- a/t/t5701-clone-local.sh +++ b/t/t5701-clone-local.sh @@ -119,7 +119,9 @@ test_expect_success 'bundle clone with nonexistent HEAD' ' test_expect_success 'clone empty repository' ' cd "$D" && mkdir empty && - (cd empty && git init) && + (cd empty && + git init && + git config receive.denyCurrentBranch warn) && git clone empty empty-clone && test_tick && (cd empty-clone diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 59d1f6283b..571931588e 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -162,4 +162,44 @@ test_expect_success 'empty email' ' } ' +test_expect_success 'del LF before empty (1)' ' + git show -s --pretty=format:"%s%n%-b%nThanks%n" HEAD^^ >actual && + test $(wc -l <actual) = 2 +' + +test_expect_success 'del LF before empty (2)' ' + git show -s --pretty=format:"%s%n%-b%nThanks%n" HEAD >actual && + test $(wc -l <actual) = 6 && + grep "^$" actual +' + +test_expect_success 'add LF before non-empty (1)' ' + git show -s --pretty=format:"%s%+b%nThanks%n" HEAD^^ >actual && + test $(wc -l <actual) = 2 +' + +test_expect_success 'add LF before non-empty (2)' ' + git show -s --pretty=format:"%s%+b%nThanks%n" HEAD >actual && + test $(wc -l <actual) = 6 && + grep "^$" actual +' + +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/t6017-rev-list-stdin.sh b/t/t6017-rev-list-stdin.sh new file mode 100755 index 0000000000..f1c32dba42 --- /dev/null +++ b/t/t6017-rev-list-stdin.sh @@ -0,0 +1,61 @@ +#!/bin/sh +# +# Copyright (c) 2009, Junio C Hamano +# + +test_description='log family learns --stdin' + +. ./test-lib.sh + +check () { + for cmd in rev-list "log --stat" + do + for i in "$@" + do + printf "%s\n" $i + done >input && + test_expect_success "check $cmd $*" ' + git $cmd $(cat input) >expect && + git $cmd --stdin <input >actual && + sed -e "s/^/input /" input && + sed -e "s/^/output /" expect && + test_cmp expect actual + ' + done +} + +them='1 2 3 4 5 6 7' + +test_expect_success setup ' + ( + for i in 0 $them + do + for j in $them + do + echo $i.$j >file-$j && + git add file-$j || exit + done && + test_tick && + git commit -m $i || exit + done && + for i in $them + do + git checkout -b side-$i master~$i && + echo updated $i >file-$i && + git add file-$i && + test_tick && + git commit -m side-$i || exit + done + ) +' + +check master +check side-1 ^side-4 +check side-1 ^side-7 -- +check side-1 ^side-7 -- file-1 +check side-1 ^side-7 -- file-2 +check side-3 ^side-4 -- file-3 +check side-3 ^side-2 +check side-3 ^side-2 -- file-1 + +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/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index def397c53a..c51865fdbc 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -423,7 +423,7 @@ test_expect_success 'skipped merge base when good and bad are siblings' ' grep "merge base must be tested" my_bisect_log.txt && grep $HASH4 my_bisect_log.txt && git bisect skip > my_bisect_log.txt 2>&1 && - grep "Warning" my_bisect_log.txt && + grep "warning" my_bisect_log.txt && grep $SIDE_HASH6 my_bisect_log.txt && git bisect reset ' diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index 00e1de9627..664b0f8052 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh @@ -69,7 +69,7 @@ test_expect_success 'status' ' cd test && git checkout b1 >/dev/null && # reports nothing to commit - test_must_fail git status + test_must_fail git commit --dry-run ) >actual && grep "have 1 and 1 different" actual ' diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index 8b8bd81c09..203ffdb17a 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -70,6 +70,18 @@ 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" +' + +test_expect_success 'test GIT_NO_REPLACE_OBJECTS env variable' ' + GIT_NO_REPLACE_OBJECTS=1 git cat-file commit $HASH2 | grep "author A U Thor" && + GIT_NO_REPLACE_OBJECTS=1 git show $HASH2 | grep "A U Thor" +' + cat >tag.sig <<EOF object $HASH2 type commit @@ -195,6 +207,18 @@ test_expect_success 'fetch branch with replacement' ' cd .. ' +test_expect_success 'bisect and replacements' ' + git bisect start $HASH7 $HASH1 && + test "$S" = "$(git rev-parse --verify HEAD)" && + git bisect reset && + GIT_NO_REPLACE_OBJECTS=1 git bisect start $HASH7 $HASH1 && + test "$HASH4" = "$(git rev-parse --verify HEAD)" && + git bisect reset && + git --no-replace-objects bisect start $HASH7 $HASH1 && + test "$HASH4" = "$(git rev-parse --verify HEAD)" && + git bisect reset +' + # # test_done diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index f5a1b615f6..065deadc29 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -92,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 @@ -125,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 3a103fec96..abd14bf819 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -214,6 +214,11 @@ test_expect_success 'grep -e A --and --not -e B' ' test_cmp expected actual ' +test_expect_success 'grep should ignore GREP_OPTIONS' ' + GREP_OPTIONS=-v git grep " mmap bar\$" >actual && + test_cmp expected actual +' + test_expect_success 'grep -f, non-existent file' ' test_must_fail git grep -f patterns ' 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/t7060-wtstatus.sh b/t/t7060-wtstatus.sh index 1044aa6549..fcac472598 100755 --- a/t/t7060-wtstatus.sh +++ b/t/t7060-wtstatus.sh @@ -31,8 +31,7 @@ test_expect_success 'Report new path with conflict' ' cat >expect <<EOF # On branch side # Unmerged paths: -# (use "git reset HEAD <file>..." to unstage) -# (use "git add <file>..." to mark resolution) +# (use "git add/rm <file>..." as appropriate to mark resolution) # # deleted by us: foo # @@ -50,9 +49,11 @@ test_expect_success 'M/D conflict does not segfault' ' git rm foo && git commit -m delete && test_must_fail git merge master && - test_must_fail git status > ../actual - ) && - test_cmp expect actual + test_must_fail git commit --dry-run >../actual && + test_cmp ../expect ../actual && + git status >../actual && + test_cmp ../expect ../actual + ) ' test_done diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh index e85ff02c3e..b8cf2603a1 100755 --- a/t/t7102-reset.sh +++ b/t/t7102-reset.sh @@ -139,19 +139,19 @@ test_expect_success \ test_expect_success \ 'resetting to HEAD with no changes should succeed and do nothing' ' git reset --hard && - check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc && git reset --hard HEAD && - check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc && git reset --soft && - check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc && git reset --soft HEAD && - check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc && git reset --mixed && - check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc && git reset --mixed HEAD && - check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc && git reset && - check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc && git reset HEAD && check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc ' 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/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 2d33d9efec..8e2449d244 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -14,8 +14,8 @@ submodule and "git submodule update --rebase/--merge" does not detach the HEAD. compare_head() { - sha_master=`git-rev-list --max-count=1 master` - sha_head=`git-rev-list --max-count=1 HEAD` + sha_master=`git rev-list --max-count=1 master` + sha_head=`git rev-list --max-count=1 HEAD` test "$sha_master" = "$sha_head" } diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index e2ef53228e..a529701241 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 @@ -211,6 +211,21 @@ test_expect_success 'amend commit to fix author' ' ' +test_expect_success 'amend commit to fix date' ' + + test_tick && + newtick=$GIT_AUTHOR_DATE && + git reset --hard && + git cat-file -p HEAD | + sed -e "s/author.*/author $author $newtick/" \ + -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \ + expected && + git commit --amend --date="$newtick" && + git cat-file -p HEAD > current && + test_cmp expected current + +' + test_expect_success 'sign off (1)' ' echo 1 >positive && @@ -247,6 +262,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/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index d9a08aac56..3ca17abad1 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -19,8 +19,8 @@ test_expect_success 'status clean' ' git status | grep "nothing to commit" ' -test_expect_success 'status -a clean' ' - git status -a | +test_expect_success 'commit --dry-run -a clean' ' + git commit --dry-run -a | grep "nothing to commit" ' test_expect_success 'rm submodule contents' ' @@ -31,7 +31,7 @@ test_expect_success 'status clean (empty submodule dir)' ' grep "nothing to commit" ' test_expect_success 'status -a clean (empty submodule dir)' ' - git status -a | + git commit --dry-run -a | grep "nothing to commit" ' diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 93f875f500..cf67fe3a4a 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -8,26 +8,26 @@ test_description='git status' . ./test-lib.sh test_expect_success 'setup' ' - : > tracked && - : > modified && + : >tracked && + : >modified && mkdir dir1 && - : > dir1/tracked && - : > dir1/modified && + : >dir1/tracked && + : >dir1/modified && mkdir dir2 && - : > dir1/tracked && - : > dir1/modified && + : >dir1/tracked && + : >dir1/modified && git add . && git status >output && test_tick && git commit -m initial && - : > untracked && - : > dir1/untracked && - : > dir2/untracked && - echo 1 > dir1/modified && - echo 2 > dir2/modified && - echo 3 > dir2/added && + : >untracked && + : >dir1/untracked && + : >dir2/untracked && + echo 1 >dir1/modified && + echo 2 >dir2/modified && + echo 3 >dir2/added && git add dir2/added ' @@ -37,7 +37,7 @@ test_expect_success 'status (1)' ' ' -cat > expect << \EOF +cat >expect <<\EOF # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) @@ -63,7 +63,25 @@ EOF test_expect_success 'status (2)' ' - git status > output && + git status >output && + test_cmp expect output + +' + +cat >expect <<\EOF + M dir1/modified +A dir2/added +?? dir1/untracked +?? dir2/modified +?? dir2/untracked +?? expect +?? output +?? untracked +EOF + +test_expect_success 'status -s (2)' ' + + git status -s >output && test_cmp expect output ' @@ -85,8 +103,8 @@ cat >expect <<EOF EOF test_expect_success 'status -uno' ' mkdir dir3 && - : > dir3/untracked1 && - : > dir3/untracked2 && + : >dir3/untracked1 && + : >dir3/untracked2 && git status -uno >output && test_cmp expect output ' @@ -97,6 +115,22 @@ test_expect_success 'status (status.showUntrackedFiles no)' ' test_cmp expect output ' +cat >expect << EOF + M dir1/modified +A dir2/added +EOF +test_expect_success 'status -s -uno' ' + git config --unset status.showuntrackedfiles + git status -s -uno >output && + test_cmp expect output +' + +test_expect_success 'status -s (status.showUntrackedFiles no)' ' + git config status.showuntrackedfiles no + git status -s >output && + test_cmp expect output +' + cat >expect <<EOF # On branch master # Changes to be committed: @@ -133,6 +167,29 @@ test_expect_success 'status (status.showUntrackedFiles normal)' ' ' cat >expect <<EOF + M dir1/modified +A dir2/added +?? dir1/untracked +?? dir2/modified +?? dir2/untracked +?? dir3/ +?? expect +?? output +?? untracked +EOF +test_expect_success 'status -s -unormal' ' + git config --unset status.showuntrackedfiles + git status -s -unormal >output && + test_cmp expect output +' + +test_expect_success 'status -s (status.showUntrackedFiles normal)' ' + git config status.showuntrackedfiles normal + git status -s >output && + test_cmp expect output +' + +cat >expect <<EOF # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) @@ -169,7 +226,30 @@ test_expect_success 'status (status.showUntrackedFiles all)' ' test_cmp expect output ' -cat > expect << \EOF +cat >expect <<EOF + M dir1/modified +A dir2/added +?? dir1/untracked +?? dir2/modified +?? dir2/untracked +?? expect +?? output +?? untracked +EOF +test_expect_success 'status -s -uall' ' + git config --unset status.showuntrackedfiles + git status -s -uall >output && + test_cmp expect output +' +test_expect_success 'status -s (status.showUntrackedFiles all)' ' + git config status.showuntrackedfiles all + git status -s >output && + rm -rf dir3 && + git config --unset status.showuntrackedfiles && + test_cmp expect output +' + +cat >expect <<\EOF # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) @@ -195,12 +275,156 @@ EOF test_expect_success 'status with relative paths' ' - (cd dir1 && git status) > output && + (cd dir1 && git status) >output && + test_cmp expect output + +' + +cat >expect <<\EOF + M modified +A ../dir2/added +?? untracked +?? ../dir2/modified +?? ../dir2/untracked +?? ../expect +?? ../output +?? ../untracked +EOF +test_expect_success 'status -s with relative paths' ' + + (cd dir1 && git status -s) >output && test_cmp expect output ' -cat > expect << \EOF +cat >expect <<\EOF + M dir1/modified +A dir2/added +?? dir1/untracked +?? dir2/modified +?? dir2/untracked +?? expect +?? output +?? untracked +EOF + +test_expect_success 'status --porcelain ignores relative paths setting' ' + + (cd dir1 && git status --porcelain) >output && + test_cmp expect output + +' + +test_expect_success 'setup unique colors' ' + + git config status.color.untracked blue + +' + +cat >expect <<\EOF +# On branch master +# Changes to be committed: +# (use "git reset HEAD <file>..." to unstage) +# +# <GREEN>new file: dir2/added<RESET> +# +# Changed but not updated: +# (use "git add <file>..." to update what will be committed) +# (use "git checkout -- <file>..." to discard changes in working directory) +# +# <RED>modified: dir1/modified<RESET> +# +# Untracked files: +# (use "git add <file>..." to include in what will be committed) +# +# <BLUE>dir1/untracked<RESET> +# <BLUE>dir2/modified<RESET> +# <BLUE>dir2/untracked<RESET> +# <BLUE>expect<RESET> +# <BLUE>output<RESET> +# <BLUE>untracked<RESET> +EOF + +test_expect_success 'status with color.ui' ' + + git config color.ui always && + git status | test_decode_color >output && + test_cmp expect output + +' + +test_expect_success 'status with color.status' ' + + git config --unset color.ui && + git config color.status always && + git status | test_decode_color >output && + test_cmp expect output + +' + +cat >expect <<\EOF + <RED>M<RESET> dir1/modified +<GREEN>A<RESET> dir2/added +<BLUE>??<RESET> dir1/untracked +<BLUE>??<RESET> dir2/modified +<BLUE>??<RESET> dir2/untracked +<BLUE>??<RESET> expect +<BLUE>??<RESET> output +<BLUE>??<RESET> untracked +EOF + +test_expect_success 'status -s with color.ui' ' + + git config --unset color.status && + git config color.ui always && + git status -s | test_decode_color >output && + test_cmp expect output + +' + +test_expect_success 'status -s with color.status' ' + + git config --unset color.ui && + git config color.status always && + git status -s | test_decode_color >output && + test_cmp expect output + +' + +cat >expect <<\EOF + M dir1/modified +A dir2/added +?? dir1/untracked +?? dir2/modified +?? dir2/untracked +?? expect +?? output +?? untracked +EOF + +test_expect_success 'status --porcelain ignores color.ui' ' + + git config --unset color.status && + git config color.ui always && + git status --porcelain | test_decode_color >output && + test_cmp expect output + +' + +test_expect_success 'status --porcelain ignores color.status' ' + + git config --unset color.ui && + git config color.status always && + git status --porcelain | test_decode_color >output && + test_cmp expect output + +' + +# recover unconditionally from color tests +git config --unset color.status +git config --unset color.ui + +cat >expect <<\EOF # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) @@ -224,10 +448,29 @@ cat > expect << \EOF # untracked EOF + test_expect_success 'status without relative paths' ' git config status.relativePaths false - (cd dir1 && git status) > output && + (cd dir1 && git status) >output && + test_cmp expect output + +' + +cat >expect <<\EOF + M dir1/modified +A dir2/added +?? dir1/untracked +?? dir2/modified +?? dir2/untracked +?? expect +?? output +?? untracked +EOF + +test_expect_success 'status -s without relative paths' ' + + (cd dir1 && git status -s) >output && test_cmp expect output ' @@ -248,8 +491,8 @@ cat <<EOF >expect # output # untracked EOF -test_expect_success 'status of partial commit excluding new file in index' ' - git status dir1/modified >output && +test_expect_success 'dry-run of partial commit excluding new file in index' ' + git commit --dry-run dir1/modified >output && test_cmp expect output ' @@ -298,6 +541,28 @@ test_expect_success 'status --untracked-files=all does not show submodule' ' test_cmp expect output ' +cat >expect <<EOF + M dir1/modified +A dir2/added +A sm +?? dir1/untracked +?? dir2/modified +?? dir2/untracked +?? expect +?? output +?? untracked +EOF +test_expect_success 'status -s submodule summary is disabled by default' ' + git status -s >output && + test_cmp expect output +' + +# we expect the same as the previous test +test_expect_success 'status -s --untracked-files=all does not show submodule' ' + git status -s --untracked-files=all >output && + test_cmp expect output +' + head=$(cd sm && git rev-parse --short=7 --verify HEAD) cat >expect <<EOF @@ -335,6 +600,21 @@ test_expect_success 'status submodule summary' ' test_cmp expect output ' +cat >expect <<EOF + M dir1/modified +A dir2/added +A sm +?? dir1/untracked +?? dir2/modified +?? dir2/untracked +?? expect +?? output +?? untracked +EOF +test_expect_success 'status -s submodule summary' ' + git status -s >output && + test_cmp expect output +' cat >expect <<EOF # On branch master @@ -358,7 +638,23 @@ EOF test_expect_success 'status submodule summary (clean submodule)' ' git commit -m "commit submodule" && git config status.submodulesummary 10 && - test_must_fail git status >output && + test_must_fail git commit --dry-run >output && + test_cmp expect output && + git status >output && + test_cmp expect output +' + +cat >expect <<EOF + M dir1/modified +?? dir1/untracked +?? dir2/modified +?? dir2/untracked +?? expect +?? output +?? untracked +EOF +test_expect_success 'status -s submodule summary (clean submodule)' ' + git status -s >output && test_cmp expect output ' @@ -391,9 +687,9 @@ cat >expect <<EOF # output # untracked EOF -test_expect_success 'status submodule summary (--amend)' ' +test_expect_success 'commit --dry-run submodule summary (--amend)' ' git config status.submodulesummary 10 && - git status --amend >output && + git commit --dry-run --amend >output && test_cmp expect output ' diff --git a/t/t7509-commit.sh b/t/t7509-commit.sh new file mode 100755 index 0000000000..d52c060b06 --- /dev/null +++ b/t/t7509-commit.sh @@ -0,0 +1,114 @@ +#!/bin/sh +# +# Copyright (c) 2009 Erick Mattos +# + +test_description='git commit --reset-author' + +. ./test-lib.sh + +author_header () { + git cat-file commit "$1" | + sed -n -e '/^$/q' -e '/^author /p' +} + +message_body () { + git cat-file commit "$1" | + sed -e '1,/^$/d' +} + +test_expect_success '-C option copies authorship and message' ' + echo "Initial" >foo && + git add foo && + test_tick && + git commit -m "Initial Commit" --author Frigate\ \<flying@over.world\> && + git tag Initial && + echo "Test 1" >>foo && + test_tick && + git commit -a -C Initial && + author_header Initial >expect && + author_header HEAD >actual && + test_cmp expect actual && + + message_body Initial >expect && + message_body HEAD >actual && + test_cmp expect actual +' + +test_expect_success '-C option copies only the message with --reset-author' ' + echo "Test 2" >>foo && + test_tick && + git commit -a -C Initial --reset-author && + echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect && + author_header HEAD >actual + test_cmp expect actual && + + message_body Initial >expect && + message_body HEAD >actual && + test_cmp expect actual +' + +test_expect_success '-c option copies authorship and message' ' + echo "Test 3" >>foo && + test_tick && + EDITOR=: VISUAL=: git commit -a -c Initial && + author_header Initial >expect && + author_header HEAD >actual && + test_cmp expect actual +' + +test_expect_success '-c option copies only the message with --reset-author' ' + echo "Test 4" >>foo && + test_tick && + EDITOR=: VISUAL=: git commit -a -c Initial --reset-author && + echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect && + author_header HEAD >actual && + test_cmp expect actual && + + message_body Initial >expect && + message_body HEAD >actual && + test_cmp expect actual +' + +test_expect_success '--amend option copies authorship' ' + git checkout Initial && + echo "Test 5" >>foo && + test_tick && + git commit -a --amend -m "amend test" && + author_header Initial >expect && + author_header HEAD >actual && + + echo "amend test" >expect && + message_body HEAD >actual && + test_cmp expect actual +' + +test_expect_success '--reset-author makes the commit ours even with --amend option' ' + git checkout Initial && + echo "Test 6" >>foo && + test_tick && + git commit -a --reset-author -m "Changed again" --amend && + echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect && + author_header HEAD >actual && + test_cmp expect actual && + + echo "Changed again" >expect && + message_body HEAD >actual && + test_cmp expect actual +' + +test_expect_success '--reset-author and --author are mutually exclusive' ' + git checkout Initial && + echo "Test 7" >>foo && + test_tick && + test_must_fail git commit -a --reset-author --author="Xyzzy <frotz@nitfol.xz>" +' + +test_expect_success '--reset-author should be rejected without -c/-C/--amend' ' + git checkout Initial && + echo "Test 7" >>foo && + test_tick && + test_must_fail git commit -a --reset-author -m done +' + +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/t8003-blame.sh b/t/t8003-blame.sh index 13c25f1d52..ad834f200a 100755 --- a/t/t8003-blame.sh +++ b/t/t8003-blame.sh @@ -144,4 +144,17 @@ test_expect_success 'blame path that used to be a directory' ' git blame HEAD^.. -- path ' +test_expect_success 'blame to a commit with no author name' ' + TREE=`git rev-parse HEAD:` + cat >badcommit <<EOF +tree $TREE +author <noname> 1234567890 +0000 +committer David Reiss <dreiss@facebook.com> 1234567890 +0000 + +some message +EOF + COMMIT=`git hash-object -t commit -w badcommit` + git --no-pager blame $COMMIT -- uno >/dev/null +' + test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 84a7f03d46..752adaac85 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -95,6 +95,40 @@ test_expect_success \ 'Verify commandline' \ 'test_cmp expected commandline1' +test_expect_success 'Send patches with --envelope-sender' ' + clean_fake_sendmail && + git send-email --envelope-sender="Patch Contributer <patch@example.com>" --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors +' + +cat >expected <<\EOF +!patch@example.com! +!-i! +!nobody@example.com! +!author@example.com! +!one@example.com! +!two@example.com! +EOF +test_expect_success \ + 'Verify commandline' \ + 'test_cmp expected commandline1' + +test_expect_success 'Send patches with --envelope-sender=auto' ' + clean_fake_sendmail && + git send-email --envelope-sender=auto --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors +' + +cat >expected <<\EOF +!nobody@example.com! +!-i! +!nobody@example.com! +!author@example.com! +!one@example.com! +!two@example.com! +EOF +test_expect_success \ + 'Verify commandline' \ + 'test_cmp expected commandline1' + cat >expected-show-all-headers <<\EOF 0001-Second.patch (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>' @@ -769,4 +803,53 @@ test_expect_success 'threading but no chain-reply-to' ' grep "In-Reply-To: " stdout ' +test_expect_success 'warning with an implicit --chain-reply-to' ' + git send-email \ + --dry-run \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + outdir/000?-*.patch 2>errors >out && + grep "no-chain-reply-to" errors +' + +test_expect_success 'no warning with an explicit --chain-reply-to' ' + git send-email \ + --dry-run \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --chain-reply-to \ + outdir/000?-*.patch 2>errors >out && + ! grep "no-chain-reply-to" errors +' + +test_expect_success 'no warning with an explicit --no-chain-reply-to' ' + git send-email \ + --dry-run \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --nochain-reply-to \ + outdir/000?-*.patch 2>errors >out && + ! grep "no-chain-reply-to" errors +' + +test_expect_success 'no warning with sendemail.chainreplyto = false' ' + git config sendemail.chainreplyto false && + git send-email \ + --dry-run \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + outdir/000?-*.patch 2>errors >out && + ! grep "no-chain-reply-to" errors +' + +test_expect_success 'no warning with sendemail.chainreplyto = true' ' + git config sendemail.chainreplyto true && + git send-email \ + --dry-run \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + outdir/000?-*.patch 2>errors >out && + ! grep "no-chain-reply-to" errors +' + test_done 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/t9146-git-svn-empty-dirs.sh b/t/t9146-git-svn-empty-dirs.sh new file mode 100755 index 0000000000..565365cbd3 --- /dev/null +++ b/t/t9146-git-svn-empty-dirs.sh @@ -0,0 +1,142 @@ +#!/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_expect_success 'initialize trunk' ' + for i in trunk trunk/a trunk/"weird file name" + do + svn_cmd mkdir -m "mkdir $i" "$svnrepo"/"$i" + done +' + +test_expect_success 'clone trunk' 'git svn clone -s "$svnrepo" trunk' + +test_expect_success 'empty directories in trunk exist' ' + ( + cd trunk && + for i in a "weird file name" + do + if ! test -d "$i" + then + echo >&2 "$i does not exist" + exit 1 + fi + done + ) +' + +test_expect_success 'remove a top-level directory from svn' ' + svn_cmd rm -m "remove d" "$svnrepo"/d +' + +test_expect_success 'removed top-level directory does not exist' ' + git svn clone "$svnrepo" removed && + test ! -e removed/d + +' +unhandled=.git/svn/refs/remotes/git-svn/unhandled.log +test_expect_success 'git svn gc-ed files work' ' + ( + cd removed && + git svn gc && + : Compress::Zlib may not be available && + if test -f "$unhandled".gz + then + svn_cmd mkdir -m gz "$svnrepo"/gz && + git reset --hard $(git rev-list HEAD | tail -1) && + git svn rebase && + test -f "$unhandled".gz && + test -f "$unhandled" && + for i in a b c "weird file name" gz "! !" + do + if ! test -d "$i" + then + echo >&2 "$i does not exist" + exit 1 + fi + done + fi + ) +' + +test_done diff --git a/t/t9150-svk-mergetickets.sh b/t/t9150-svk-mergetickets.sh new file mode 100755 index 0000000000..53581425c4 --- /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..359eeaa738 --- /dev/null +++ b/t/t9151-svn-mergeinfo.sh @@ -0,0 +1,41 @@ +#!/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 'all svn merges became git merge commits' ' + unmarked=$(git rev-list --parents --all --grep=Merge | + grep -v " .* " | cut -f1 -d" ") + [ -z "$unmarked" ] + ' + +test_expect_success 'cherry picks did not become git merge commits' ' + bad_cherries=$(git rev-list --parents --all --grep=Cherry | + grep " .* " | cut -f1 -d" ") + [ -z "$bad_cherries" ] + ' + +test_expect_success 'svn non-merge merge commits did not become git merge commits' ' + bad_non_merges=$(git rev-list --parents --all --grep=non-merge | + grep " .* " | cut -f1 -d" ") + [ -z "$bad_non_merges" ] + ' + +test_expect_success 'everything got merged in the end' ' + unmerged=$(git rev-list --all --not master) + [ -z "$unmerged" ] + ' + +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..d917717cf3 --- /dev/null +++ b/t/t9151/make-svnmerge-dump @@ -0,0 +1,161 @@ +#!/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 + +commit() { + i=$(( $1 + 1 )) + shift; + svn commit -m "(r$i) $*" >/dev/null || exit 1 + echo $i +} + +say() { + echo "[1m * $*[0m" +} + +i=0 +cd foo +mkdir trunk +mkdir branches +svn add trunk branches +i=$(commit $i "Setup trunk and branches") + +git cat-file blob 6683463e:Makefile > trunk/Makefile +svn add trunk/Makefile + +say "Committing ANCESTOR" +i=$(commit $i "ancestor") +svn cp trunk branches/left + +say "Committing BRANCH POINT" +i=$(commit $i "make left branch") +svn cp trunk branches/right + +say "Committing other BRANCH POINT" +i=$(commit $i "make right branch") + +say "Committing LEFT UPDATE" +git cat-file blob 5873b67e:Makefile > branches/left/Makefile +i=$(commit $i "left update 1") + +git cat-file blob 75118b13:Makefile > branches/right/Makefile +say "Committing RIGHT UPDATE" +pre_right_update_1=$i +i=$(commit $i "right update 1") + +say "Making more commits on LEFT" +git cat-file blob ff5ebe39:Makefile > branches/left/Makefile +i=$(commit $i "left update 2") +git cat-file blob b5039db6:Makefile > branches/left/Makefile +i=$(commit $i "left update 3") + +say "Making a LEFT SUB-BRANCH" +svn cp branches/left branches/left-sub +sub_left_make=$i +i=$(commit $i "make left sub-branch") + +say "Making a commit on LEFT SUB-BRANCH" +echo "crunch" > branches/left-sub/README +svn add branches/left-sub/README +i=$(commit $i "left sub-branch update 1") + +say "Merging LEFT to TRUNK" +svn update +cd trunk +svn merge ../branches/left --accept postpone +git cat-file blob b5039db6:Makefile > Makefile +svn resolved Makefile +i=$(commit $i "Merge left to trunk 1") +cd .. + +say "Making more commits on LEFT and RIGHT" +echo "touche" > branches/left/zlonk +svn add branches/left/zlonk +i=$(commit $i "left update 4") +echo "thwacke" > branches/right/bang +svn add branches/right/bang +i=$(commit $i "right update 2") + +say "Squash merge of RIGHT tip 2 commits onto TRUNK" +svn update +cd trunk +svn merge -r$pre_right_update_1:$i ../branches/right +i=$(commit $i "Cherry-pick right 2 commits to trunk") +cd .. + +say "Merging RIGHT to TRUNK" +svn update +cd trunk +svn merge ../branches/right --accept postpone +git cat-file blob b51ad431:Makefile > Makefile +svn resolved Makefile +i=$(commit $i "Merge right to trunk 1") +cd .. + +say "Making more commits on RIGHT and TRUNK" +echo "whamm" > branches/right/urkkk +svn add branches/right/urkkk +i=$(commit $i "right update 3") +echo "pow" > trunk/vronk +svn add trunk/vronk +i=$(commit $i "trunk update 1") + +say "Merging RIGHT to LEFT SUB-BRANCH" +svn update +cd branches/left-sub +svn merge ../right --accept postpone +git cat-file blob b51ad431:Makefile > Makefile +svn resolved Makefile +i=$(commit $i "Merge right to left sub-branch") +cd ../.. + +say "Making more commits on LEFT SUB-BRANCH and LEFT" +echo "zowie" > branches/left-sub/wham_eth +svn add branches/left-sub/wham_eth +pre_sub_left_update_2=$i +i=$(commit $i "left sub-branch update 2") +sub_left_update_2=$i +echo "eee_yow" > branches/left/glurpp +svn add branches/left/glurpp +i=$(commit $i "left update 5") + +say "Cherry pick LEFT SUB-BRANCH commit to LEFT" +svn update +cd branches/left +svn merge -r$pre_sub_left_update_2:$sub_left_update_2 ../left-sub +i=$(commit $i "Cherry-pick left sub-branch commit to left") +cd ../.. + +say "Merging LEFT SUB-BRANCH back to LEFT" +svn update +cd branches/left +# it's only a merge because the previous merge cherry-picked the top commit +svn merge -r$sub_left_make:$sub_left_update_2 ../left-sub --accept postpone +i=$(commit $i "Merge left sub-branch to left") +cd ../.. + +say "Merging EVERYTHING to TRUNK" +svn update +cd trunk +svn merge ../branches/left --accept postpone +svn resolved bang +i=$(commit $i "Merge left to trunk 2") +# this merge, svn happily updates the mergeinfo, but there is actually +# nothing to merge. git-svn will not make a meaningless merge commit. +svn merge ../branches/right --accept postpone +i=$(commit $i "non-merge right to trunk 2") +cd .. + +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..9543e31c96 --- /dev/null +++ b/t/t9151/svn-mergeinfo.dump @@ -0,0 +1,1625 @@ +SVN-fs-dump-format-version: 2 + +UUID: 64142547-0943-4db2-836a-d1e1eb2f9924 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2009-12-19T16:17:51.232640Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 128 +Content-length: 128 + +K 7 +svn:log +V 29 +(r1) Setup trunk and branches +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:51.831965Z +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: 112 +Content-length: 112 + +K 7 +svn:log +V 13 +(r2) ancestor +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:52.300075Z +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: 120 +Content-length: 120 + +K 7 +svn:log +V 21 +(r3) make left branch +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:52.768800Z +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: 121 +Content-length: 121 + +K 7 +svn:log +V 22 +(r4) make right branch +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:53.177879Z +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: 117 +Content-length: 117 + +K 7 +svn:log +V 18 +(r5) left update 1 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:53.604691Z +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: 118 +Content-length: 118 + +K 7 +svn:log +V 19 +(r6) right update 1 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:54.063555Z +PROPS-END + +Node-path: branches/right/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: 117 +Content-length: 117 + +K 7 +svn:log +V 18 +(r7) left update 2 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:54.523904Z +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: 117 +Content-length: 117 + +K 7 +svn:log +V 18 +(r8) left update 3 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:54.975970Z +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: 124 +Content-length: 124 + +K 7 +svn:log +V 25 +(r9) make left sub-branch +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:55.459904Z +PROPS-END + +Node-path: branches/left-sub +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 3 +Node-copyfrom-path: branches/left + + +Node-path: branches/left-sub/Makefile +Node-kind: file +Node-action: delete + +Node-path: branches/left-sub/Makefile +Node-kind: file +Node-action: add +Node-copyfrom-rev: 8 +Node-copyfrom-path: branches/left/Makefile +Text-copy-source-md5: 5ccff689fb290e00b85fe18ee50c54ba +Text-copy-source-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10 + + + + +Revision-number: 10 +Prop-content-length: 129 +Content-length: 129 + +K 7 +svn:log +V 30 +(r10) left sub-branch update 1 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:55.862113Z +PROPS-END + +Node-path: branches/left-sub/README +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 7 +Text-content-md5: fdbcfb6be9afe1121862143f226b51cf +Text-content-sha1: 1d1f5ea4ceb584337ffe59b8980d92e3b78dfef4 +Content-length: 17 + +PROPS-END +crunch + + +Revision-number: 11 +Prop-content-length: 126 +Content-length: 126 + +K 7 +svn:log +V 27 +(r11) Merge left to trunk 1 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:56.413416Z +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-10 +PROPS-END + + +Node-path: trunk/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: 118 +Content-length: 118 + +K 7 +svn:log +V 19 +(r12) left update 4 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:56.831014Z +PROPS-END + +Node-path: branches/left/zlonk +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 7 +Text-content-md5: 8b9d8c7c2aaa6167e7d3407a773bbbba +Text-content-sha1: 9716527ebd70a75c27625cacbeb2d897c6e86178 +Content-length: 17 + +PROPS-END +touche + + +Revision-number: 13 +Prop-content-length: 119 +Content-length: 119 + +K 7 +svn:log +V 20 +(r13) right update 2 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:57.341143Z +PROPS-END + +Node-path: branches/right/bang +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 8 +Text-content-md5: 34c28f1d2dc6a9adeccc4265bf7516cb +Text-content-sha1: 0bc5bb345c0e71d28f784f12e0bd2d384c283062 +Content-length: 18 + +PROPS-END +thwacke + + +Revision-number: 14 +Prop-content-length: 141 +Content-length: 141 + +K 7 +svn:log +V 42 +(r14) Cherry-pick right 2 commits to trunk +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:57.841851Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 75 +Content-length: 75 + +K 13 +svn:mergeinfo +V 40 +/branches/left:2-10 +/branches/right:6-13 +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 + + +Node-path: trunk/bang +Node-kind: file +Node-action: add +Node-copyfrom-rev: 13 +Node-copyfrom-path: branches/right/bang +Text-copy-source-md5: 34c28f1d2dc6a9adeccc4265bf7516cb +Text-copy-source-sha1: 0bc5bb345c0e71d28f784f12e0bd2d384c283062 + + +Revision-number: 15 +Prop-content-length: 127 +Content-length: 127 + +K 7 +svn:log +V 28 +(r15) Merge right to trunk 1 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:58.368520Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 75 +Content-length: 75 + +K 13 +svn:mergeinfo +V 40 +/branches/left:2-10 +/branches/right:2-14 +PROPS-END + + +Revision-number: 16 +Prop-content-length: 119 +Content-length: 119 + +K 7 +svn:log +V 20 +(r16) right update 3 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:58.779056Z +PROPS-END + +Node-path: branches/right/urkkk +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 6 +Text-content-md5: 5889c8392e16251b0c80927607a03036 +Text-content-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302 +Content-length: 16 + +PROPS-END +whamm + + +Revision-number: 17 +Prop-content-length: 119 +Content-length: 119 + +K 7 +svn:log +V 20 +(r17) trunk update 1 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:59.221851Z +PROPS-END + +Node-path: trunk/vronk +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: b2f80fa02a7f1364b9c29d3da44bf9f9 +Text-content-sha1: e994d980c0f2d7a3f76138bf96d57f36f9633828 +Content-length: 14 + +PROPS-END +pow + + +Revision-number: 18 +Prop-content-length: 135 +Content-length: 135 + +K 7 +svn:log +V 36 +(r18) Merge right to left sub-branch +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:17:59.781666Z +PROPS-END + +Node-path: branches/left-sub +Node-kind: dir +Node-action: change +Prop-content-length: 55 +Content-length: 55 + +K 13 +svn:mergeinfo +V 20 +/branches/right:2-17 +PROPS-END + + +Node-path: branches/left-sub/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 + + +Node-path: branches/left-sub/bang +Node-kind: file +Node-action: add +Node-copyfrom-rev: 17 +Node-copyfrom-path: branches/right/bang +Text-copy-source-md5: 34c28f1d2dc6a9adeccc4265bf7516cb +Text-copy-source-sha1: 0bc5bb345c0e71d28f784f12e0bd2d384c283062 + + +Node-path: branches/left-sub/urkkk +Node-kind: file +Node-action: add +Node-copyfrom-rev: 17 +Node-copyfrom-path: branches/right/urkkk +Text-copy-source-md5: 5889c8392e16251b0c80927607a03036 +Text-copy-source-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302 + + +Revision-number: 19 +Prop-content-length: 129 +Content-length: 129 + +K 7 +svn:log +V 30 +(r19) left sub-branch update 2 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:18:00.200531Z +PROPS-END + +Node-path: branches/left-sub/wham_eth +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 6 +Text-content-md5: 757bcd5818572ef3f9580052617c1c8b +Text-content-sha1: b165019b005c199237ba822c4404e771e93b654a +Content-length: 16 + +PROPS-END +zowie + + +Revision-number: 20 +Prop-content-length: 118 +Content-length: 118 + +K 7 +svn:log +V 19 +(r20) left update 5 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:18:00.659636Z +PROPS-END + +Node-path: branches/left/glurpp +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 8 +Text-content-md5: 14a169f628e0bb59df9c2160649d0a30 +Text-content-sha1: ef7d929e52177767ecfcd28941f6b7f04b4131e3 +Content-length: 18 + +PROPS-END +eee_yow + + +Revision-number: 21 +Prop-content-length: 147 +Content-length: 147 + +K 7 +svn:log +V 48 +(r21) Cherry-pick left sub-branch commit to left +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:18:01.194402Z +PROPS-END + +Node-path: branches/left +Node-kind: dir +Node-action: change +Prop-content-length: 56 +Content-length: 56 + +K 13 +svn:mergeinfo +V 21 +/branches/left-sub:19 +PROPS-END + + +Node-path: branches/left/wham_eth +Node-kind: file +Node-action: add +Node-copyfrom-rev: 19 +Node-copyfrom-path: branches/left-sub/wham_eth +Text-copy-source-md5: 757bcd5818572ef3f9580052617c1c8b +Text-copy-source-sha1: b165019b005c199237ba822c4404e771e93b654a + + +Revision-number: 22 +Prop-content-length: 134 +Content-length: 134 + +K 7 +svn:log +V 35 +(r22) Merge left sub-branch to left +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:18:01.679218Z +PROPS-END + +Node-path: branches/left +Node-kind: dir +Node-action: change +Prop-content-length: 79 +Content-length: 79 + +K 13 +svn:mergeinfo +V 44 +/branches/left-sub:4-19 +/branches/right:2-17 +PROPS-END + + +Node-path: branches/left/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 + + +Node-path: branches/left/README +Node-kind: file +Node-action: add +Node-copyfrom-rev: 18 +Node-copyfrom-path: branches/left-sub/README +Text-copy-source-md5: fdbcfb6be9afe1121862143f226b51cf +Text-copy-source-sha1: 1d1f5ea4ceb584337ffe59b8980d92e3b78dfef4 + + +Node-path: branches/left/bang +Node-kind: file +Node-action: add +Node-copyfrom-rev: 18 +Node-copyfrom-path: branches/left-sub/bang +Text-copy-source-md5: 34c28f1d2dc6a9adeccc4265bf7516cb +Text-copy-source-sha1: 0bc5bb345c0e71d28f784f12e0bd2d384c283062 + + +Node-path: branches/left/urkkk +Node-kind: file +Node-action: add +Node-copyfrom-rev: 18 +Node-copyfrom-path: branches/left-sub/urkkk +Text-copy-source-md5: 5889c8392e16251b0c80927607a03036 +Text-copy-source-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302 + + +Revision-number: 23 +Prop-content-length: 126 +Content-length: 126 + +K 7 +svn:log +V 27 +(r23) Merge left to trunk 2 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:18:02.212349Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 99 +Content-length: 99 + +K 13 +svn:mergeinfo +V 64 +/branches/left:2-22 +/branches/left-sub:4-19 +/branches/right:2-17 +PROPS-END + + +Node-path: trunk/README +Node-kind: file +Node-action: add +Node-copyfrom-rev: 22 +Node-copyfrom-path: branches/left/README +Text-copy-source-md5: fdbcfb6be9afe1121862143f226b51cf +Text-copy-source-sha1: 1d1f5ea4ceb584337ffe59b8980d92e3b78dfef4 + + +Node-path: trunk/glurpp +Node-kind: file +Node-action: add +Node-copyfrom-rev: 22 +Node-copyfrom-path: branches/left/glurpp +Text-copy-source-md5: 14a169f628e0bb59df9c2160649d0a30 +Text-copy-source-sha1: ef7d929e52177767ecfcd28941f6b7f04b4131e3 + + +Node-path: trunk/urkkk +Node-kind: file +Node-action: add +Node-copyfrom-rev: 22 +Node-copyfrom-path: branches/left/urkkk +Text-copy-source-md5: 5889c8392e16251b0c80927607a03036 +Text-copy-source-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302 + + +Node-path: trunk/wham_eth +Node-kind: file +Node-action: add +Node-copyfrom-rev: 22 +Node-copyfrom-path: branches/left/wham_eth +Text-copy-source-md5: 757bcd5818572ef3f9580052617c1c8b +Text-copy-source-sha1: b165019b005c199237ba822c4404e771e93b654a + + +Node-path: trunk/zlonk +Node-kind: file +Node-action: add +Node-copyfrom-rev: 22 +Node-copyfrom-path: branches/left/zlonk +Text-copy-source-md5: 8b9d8c7c2aaa6167e7d3407a773bbbba +Text-copy-source-sha1: 9716527ebd70a75c27625cacbeb2d897c6e86178 + + +Revision-number: 24 +Prop-content-length: 131 +Content-length: 131 + +K 7 +svn:log +V 32 +(r24) non-merge right to trunk 2 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-12-19T16:18:02.672148Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 99 +Content-length: 99 + +K 13 +svn:mergeinfo +V 64 +/branches/left:2-22 +/branches/left-sub:4-19 +/branches/right:2-22 +PROPS-END + + diff --git a/t/t9152-svn-empty-dirs-after-gc.sh b/t/t9152-svn-empty-dirs-after-gc.sh new file mode 100755 index 0000000000..301e779709 --- /dev/null +++ b/t/t9152-svn-empty-dirs-after-gc.sh @@ -0,0 +1,40 @@ +#!/bin/sh +# +# Copyright (c) 2009 Robert Zeh + +test_description='git svn creates empty directories, calls git gc, makes sure they are still empty' +. ./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 'git svn gc runs' ' + ( + cd cloned && + git svn gc + ) +' + +test_expect_success 'git svn mkdirs recreates empty directories after git svn gc' ' + ( + 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_done 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..0688a57e1d 100755 --- a/t/t9501-gitweb-standalone-http-status.sh +++ b/t/t9501-gitweb-standalone-http-status.sh @@ -75,4 +75,43 @@ test_expect_success \ test_debug 'cat gitweb.output' +# ---------------------------------------------------------------------- +# snapshot hash ids + +test_expect_success 'snapshots: good tree-ish id' ' + gitweb_run "p=.git;a=snapshot;h=master;sf=tgz" && + grep "Status: 200 OK" gitweb.output +' +test_debug 'cat gitweb.output' + +test_expect_success 'snapshots: bad tree-ish id' ' + gitweb_run "p=.git;a=snapshot;h=frizzumFrazzum;sf=tgz" && + grep "404 - Object does not exist" gitweb.output +' +test_debug 'cat gitweb.output' + +test_expect_success 'snapshots: bad tree-ish id (tagged object)' ' + echo object > tag-object && + git add tag-object && + git commit -m "Object to be tagged" && + git tag tagged-object `git hash-object tag-object` && + gitweb_run "p=.git;a=snapshot;h=tagged-object;sf=tgz" && + grep "400 - Object is not a tree-ish" gitweb.output +' +test_debug 'cat gitweb.output' + +test_expect_success 'snapshots: good object id' ' + ID=`git rev-parse --verify HEAD` && + gitweb_run "p=.git;a=snapshot;h=$ID;sf=tgz" && + grep "Status: 200 OK" gitweb.output +' +test_debug 'cat gitweb.output' + +test_expect_success 'snapshots: bad object id' ' + gitweb_run "p=.git;a=snapshot;h=abcdef01234;sf=tgz" && + grep "404 - Object does not exist" gitweb.output +' +test_debug 'cat gitweb.output' + + test_done diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh new file mode 100755 index 0000000000..dd83890001 --- /dev/null +++ b/t/t9502-gitweb-standalone-parse-output.sh @@ -0,0 +1,115 @@ +#!/bin/sh +# +# Copyright (c) 2009 Mark Rada +# + +test_description='gitweb as standalone script (parsing script output). + +This test runs gitweb (git web interface) as a CGI script from the +commandline, and checks that it produces the correct output, either +in the HTTP header or the actual script output.' + + +. ./gitweb-lib.sh + +# ---------------------------------------------------------------------- +# snapshot file name and prefix + +cat >>gitweb_config.perl <<\EOF + +$known_snapshot_formats{'tar'} = { + 'display' => 'tar', + 'type' => 'application/x-tar', + 'suffix' => '.tar', + 'format' => 'tar', +}; + +$feature{'snapshot'}{'default'} = ['tar']; +EOF + +# Call check_snapshot with the arguments "<basename> [<prefix>]" +# +# This will check that gitweb HTTP header contains proposed filename +# as <basename> with '.tar' suffix added, and that generated tarfile +# (gitweb message body) has <prefix> as prefix for al files in tarfile +# +# <prefix> default to <basename> +check_snapshot () { + basename=$1 + prefix=${2:-"$1"} + echo "basename=$basename" + grep "filename=.*$basename.tar" gitweb.headers >/dev/null 2>&1 && + "$TAR" tf gitweb.body >file_list && + ! grep -v "^$prefix/" file_list +} + +test_expect_success setup ' + test_commit first foo && + git branch xx/test && + FULL_ID=$(git rev-parse --verify HEAD) && + SHORT_ID=$(git rev-parse --verify --short=7 HEAD) +' +test_debug ' + echo "FULL_ID = $FULL_ID" + echo "SHORT_ID = $SHORT_ID" +' + +test_expect_success 'snapshot: full sha1' ' + gitweb_run "p=.git;a=snapshot;h=$FULL_ID;sf=tar" && + check_snapshot ".git-$SHORT_ID" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: shortened sha1' ' + gitweb_run "p=.git;a=snapshot;h=$SHORT_ID;sf=tar" && + check_snapshot ".git-$SHORT_ID" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: almost full sha1' ' + ID=$(git rev-parse --short=30 HEAD) && + gitweb_run "p=.git;a=snapshot;h=$ID;sf=tar" && + check_snapshot ".git-$SHORT_ID" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: HEAD' ' + gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tar" && + check_snapshot ".git-HEAD-$SHORT_ID" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: short branch name (master)' ' + gitweb_run "p=.git;a=snapshot;h=master;sf=tar" && + ID=$(git rev-parse --verify --short=7 master) && + check_snapshot ".git-master-$ID" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: short tag name (first)' ' + gitweb_run "p=.git;a=snapshot;h=first;sf=tar" && + ID=$(git rev-parse --verify --short=7 first) && + check_snapshot ".git-first-$ID" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: full branch name (refs/heads/master)' ' + gitweb_run "p=.git;a=snapshot;h=refs/heads/master;sf=tar" && + ID=$(git rev-parse --verify --short=7 master) && + check_snapshot ".git-master-$ID" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: full tag name (refs/tags/first)' ' + gitweb_run "p=.git;a=snapshot;h=refs/tags/first;sf=tar" && + check_snapshot ".git-first" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: hierarchical branch name (xx/test)' ' + gitweb_run "p=.git;a=snapshot;h=xx/test;sf=tar" && + ! grep "filename=.*/" gitweb.headers +' +test_debug 'cat gitweb.headers' + +test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index f2ca536472..c1476f9a23 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 @@ -105,6 +105,8 @@ do verbose=t; shift ;; -q|--q|--qu|--qui|--quie|--quiet) quiet=t; shift ;; + --with-dashes) + with_dashes=t; shift ;; --no-color) color=; shift ;; --no-python) @@ -207,8 +209,19 @@ trap 'die' EXIT test_set_editor () { FAKE_EDITOR="$1" export FAKE_EDITOR - VISUAL='"$FAKE_EDITOR"' - export VISUAL + EDITOR='"$FAKE_EDITOR"' + export EDITOR +} + +test_decode_color () { + sed -e 's/.\[1m/<WHITE>/g' \ + -e 's/.\[31m/<RED>/g' \ + -e 's/.\[32m/<GREEN>/g' \ + -e 's/.\[33m/<YELLOW>/g' \ + -e 's/.\[34m/<BLUE>/g' \ + -e 's/.\[35m/<MAGENTA>/g' \ + -e 's/.\[36m/<CYAN>/g' \ + -e 's/.\[m/<RESET>/g' } test_tick () { @@ -551,19 +564,8 @@ test_done () { # Test the binaries we have just built. The tests are kept in # t/ subdirectory and are run in 'trash directory' subdirectory. TEST_DIRECTORY=$(pwd) -if test -z "$valgrind" +if test -n "$valgrind" then - if test -z "$GIT_TEST_INSTALLED" - then - PATH=$TEST_DIRECTORY/..:$PATH - GIT_EXEC_PATH=$TEST_DIRECTORY/.. - else - GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) || - error "Cannot run git from $GIT_TEST_INSTALLED." - PATH=$GIT_TEST_INSTALLED:$TEST_DIRECTORY/..:$PATH - GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH} - fi -else make_symlink () { test -h "$2" && test "$1" = "$(readlink "$2")" || { @@ -625,6 +627,24 @@ else PATH=$GIT_VALGRIND/bin:$PATH GIT_EXEC_PATH=$GIT_VALGRIND/bin export GIT_VALGRIND +elif test -n "$GIT_TEST_INSTALLED" ; then + GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) || + error "Cannot run git from $GIT_TEST_INSTALLED." + PATH=$GIT_TEST_INSTALLED:$TEST_DIRECTORY/..:$PATH + GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH} +else # normal case, use ../bin-wrappers only unless $with_dashes: + git_bin_dir="$TEST_DIRECTORY/../bin-wrappers" + if ! test -x "$git_bin_dir/git" ; then + if test -z "$with_dashes" ; then + say "$git_bin_dir/git is not executable; using GIT_EXEC_PATH" + fi + with_dashes=t + fi + PATH="$git_bin_dir:$PATH" + GIT_EXEC_PATH=$TEST_DIRECTORY/.. + if test -n "$with_dashes" ; then + PATH="$TEST_DIRECTORY/..:$PATH" + fi fi GIT_TEMPLATE_DIR=$(pwd)/../templates/blt unset GIT_CONFIG @@ -632,20 +652,29 @@ GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_NOGLOBAL=1 export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL +. ../GIT-BUILD-OPTIONS + GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git export GITPERLLIB test -d ../templates/blt || { error "You haven't built things yet, have you?" } +if test -z "$GIT_TEST_INSTALLED" && test -z "$NO_PYTHON" +then + GITPYTHONLIB="$(pwd)/../git_remote_helpers/build/lib" + export GITPYTHONLIB + test -d ../git_remote_helpers/build || { + error "You haven't built git_remote_helpers yet, have you?" + } +fi + if ! test -x ../test-chmtime; then echo >&2 'You need to build test-chmtime:' echo >&2 'Run "make test-chmtime" in the source (toplevel) directory' exit 1 fi -. ../GIT-BUILD-OPTIONS - # Test repository test="trash directory.$(basename "$0" .sh)" test -n "$root" && test="$root/$test" @@ -729,6 +758,7 @@ case $(uname -s) in esac test -z "$NO_PERL" && test_set_prereq PERL +test -z "$NO_PYTHON" && test_set_prereq PYTHON # test whether the filesystem supports symbolic links ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS diff --git a/transport-helper.c b/transport-helper.c index f57e84c676..11f3d7ec52 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -1,16 +1,25 @@ #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" +#include "remote.h" struct helper_data { const char *name; struct child_process *helper; - unsigned fetch : 1; + FILE *out; + unsigned fetch : 1, + import : 1, + option : 1, + push : 1; + /* These go from remote name (as in "list") to private name */ + struct refspec *refspecs; + int refspec_nr; }; static struct child_process *get_helper(struct transport *transport) @@ -18,7 +27,9 @@ static struct child_process *get_helper(struct transport *transport) struct helper_data *data = transport->data; struct strbuf buf = STRBUF_INIT; struct child_process *helper; - FILE *file; + const char **refspecs = NULL; + int refspec_nr = 0; + int refspec_alloc = 0; if (data->helper) return data->helper; @@ -39,16 +50,38 @@ static struct child_process *get_helper(struct transport *transport) write_str_in_full(helper->in, "capabilities\n"); - file = xfdopen(helper->out, "r"); + data->out = xfdopen(helper->out, "r"); while (1) { - if (strbuf_getline(&buf, file, '\n') == EOF) + 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; + if (!strcmp(buf.buf, "import")) + data->import = 1; + if (!data->refspecs && !prefixcmp(buf.buf, "refspec ")) { + ALLOC_GROW(refspecs, + refspec_nr + 1, + refspec_alloc); + refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec ")); + } + } + if (refspecs) { + int i; + data->refspec_nr = refspec_nr; + data->refspecs = parse_fetch_refspec(refspec_nr, refspecs); + for (i = 0; i < refspec_nr; i++) { + free((char *)refspecs[i]); + } + free(refspecs); } + strbuf_release(&buf); return data->helper; } @@ -58,6 +91,7 @@ static int disconnect_helper(struct transport *transport) 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); @@ -67,14 +101,103 @@ static int disconnect_helper(struct transport *transport) return 0; } -static int fetch_with_fetch(struct transport *transport, - int nr_heads, const struct ref **to_fetch) +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); - FILE *file = xfdopen(helper->out, "r"); + 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 release_helper(struct transport *transport) +{ + struct helper_data *data = transport->data; + free_refspec(data->refspec_nr, data->refspecs); + data->refspecs = NULL; + disconnect_helper(transport); + free(transport->data); + return 0; +} + +static int fetch_with_fetch(struct transport *transport, + int nr_heads, 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) @@ -82,17 +205,91 @@ static int fetch_with_fetch(struct transport *transport, 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 get_importer(struct transport *transport, struct child_process *fastimport) +{ + struct child_process *helper = get_helper(transport); + memset(fastimport, 0, sizeof(*fastimport)); + fastimport->in = helper->out; + fastimport->argv = xcalloc(5, sizeof(*fastimport->argv)); + fastimport->argv[0] = "fast-import"; + fastimport->argv[1] = "--quiet"; + + fastimport->git_cmd = 1; + return start_command(fastimport); +} + +static int fetch_with_import(struct transport *transport, + int nr_heads, struct ref **to_fetch) +{ + struct child_process fastimport; + struct child_process *helper = get_helper(transport); + struct helper_data *data = transport->data; + int i; + struct ref *posn; + struct strbuf buf = STRBUF_INIT; + + if (get_importer(transport, &fastimport)) + die("Couldn't run fast-import"); + + for (i = 0; i < nr_heads; i++) { + posn = to_fetch[i]; + if (posn->status & REF_STATUS_UPTODATE) + continue; + + strbuf_addf(&buf, "import %s\n", posn->name); write_in_full(helper->in, buf.buf, buf.len); strbuf_reset(&buf); + } + disconnect_helper(transport); + finish_command(&fastimport); + free(fastimport.argv); + fastimport.argv = NULL; - if (strbuf_getline(&buf, file, '\n') == EOF) - exit(128); /* child died, message supplied already */ + for (i = 0; i < nr_heads; i++) { + char *private; + posn = to_fetch[i]; + if (posn->status & REF_STATUS_UPTODATE) + continue; + if (data->refspecs) + private = apply_refspecs(data->refspecs, data->refspec_nr, posn->name); + else + private = strdup(posn->name); + read_ref(private, posn->old_sha1); + free(private); } + strbuf_release(&buf); return 0; } static int fetch(struct transport *transport, - int nr_heads, const struct ref **to_fetch) + int nr_heads, struct ref **to_fetch) { struct helper_data *data = transport->data; int i, count; @@ -108,26 +305,173 @@ static int fetch(struct transport *transport, if (data->fetch) return fetch_with_fetch(transport, nr_heads, to_fetch); + if (data->import) + return fetch_with_import(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 int has_attribute(const char *attrs, const char *attr) { + int len; + if (!attrs) + return 0; + + len = strlen(attr); + for (;;) { + const char *space = strchrnul(attrs, ' '); + if (len == space - attrs && !strncmp(attrs, attr, len)) + return 1; + if (!*space) + return 0; + attrs = space + 1; + } +} + 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; - FILE *file; helper = get_helper(transport); - write_str_in_full(helper->in, "list\n"); + if (data->push && for_push) + write_str_in_full(helper->in, "list for-push\n"); + else + write_str_in_full(helper->in, "list\n"); - file = xfdopen(helper->out, "r"); while (1) { char *eov, *eon; - if (strbuf_getline(&buf, file, '\n') == EOF) + if (strbuf_getline(&buf, data->out, '\n') == EOF) exit(128); /* child died, message supplied already */ if (!*buf.buf) @@ -145,6 +489,12 @@ static struct ref *get_refs_list(struct transport *transport, int for_push) (*tail)->symref = xstrdup(buf.buf + 1); else if (buf.buf[0] != '?') get_sha1_hex(buf.buf, (*tail)->old_sha1); + if (eon) { + if (has_attribute(eon + 1, "unchanged")) { + (*tail)->status |= REF_STATUS_UPTODATE; + read_ref((*tail)->name, (*tail)->old_sha1); + } + } tail = &((*tail)->next); } strbuf_release(&buf); @@ -161,8 +511,10 @@ int transport_helper_init(struct transport *transport, const char *name) data->name = name; transport->data = data; + transport->set_option = set_helper_option; transport->get_refs_list = get_refs_list; transport->fetch = fetch; - transport->disconnect = disconnect_helper; + transport->push_refs = push_refs; + transport->disconnect = release_helper; return 0; } diff --git a/transport.c b/transport.c index 298dc46ec5..3eea836a33 100644 --- a/transport.c +++ b/transport.c @@ -204,7 +204,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport, int for_push) } static int fetch_objs_via_rsync(struct transport *transport, - int nr_objs, const struct ref **to_fetch) + int nr_objs, struct ref **to_fetch) { struct strbuf buf = STRBUF_INIT; struct child_process rsync; @@ -349,35 +349,6 @@ static int rsync_transport_push(struct transport *transport, return result; } -#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); -} - -#endif - struct bundle_transport_data { int fd; struct bundle_header header; @@ -408,7 +379,7 @@ static struct ref *get_refs_from_bundle(struct transport *transport, int for_pus } static int fetch_refs_from_bundle(struct transport *transport, - int nr_heads, const struct ref **to_fetch) + int nr_heads, struct ref **to_fetch) { struct bundle_transport_data *data = transport->data; return unbundle(&data->header, data->fd); @@ -486,7 +457,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus } static int fetch_refs_via_pack(struct transport *transport, - int nr_heads, const struct ref **to_fetch) + int nr_heads, struct ref **to_fetch) { struct git_transport_data *data = transport->data; char **heads = xmalloc(nr_heads * sizeof(*heads)); @@ -668,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, @@ -760,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; @@ -816,8 +788,26 @@ struct transport *transport_get(struct remote *remote, const char *url) die("No remote provided to transport_get()"); ret->remote = remote; + + if (!url && remote && remote->url) + url = remote->url[0]; ret->url = url; + /* maybe it is a foreign URL? */ + if (url) { + const char *p = url; + + while (isalnum(*p)) + p++; + if (!prefixcmp(p, "::")) + remote->foreign_vcs = xstrndup(url, p - url); + } + + if (remote && remote->foreign_vcs) { + transport_helper_init(ret, remote->foreign_vcs); + return ret; + } + if (!prefixcmp(url, "rsync:")) { ret->get_refs_list = get_refs_via_rsync; ret->fetch = fetch_objs_via_rsync; @@ -829,8 +819,6 @@ struct transport *transport_get(struct remote *remote, const char *url) transport_helper_init(ret, "curl"); #ifdef NO_CURL error("git was compiled without libcurl support."); -#else - ret->push = curl_transport_push; #endif } else if (is_local(url) && is_file(url)) { @@ -926,16 +914,17 @@ const struct ref *transport_get_remote_refs(struct transport *transport) return transport->remote_refs; } -int transport_fetch_refs(struct transport *transport, const struct ref *refs) +int transport_fetch_refs(struct transport *transport, struct ref *refs) { int rc; int nr_heads = 0, nr_alloc = 0, nr_refs = 0; - const struct ref **heads = NULL; - const struct ref *rm; + struct ref **heads = NULL; + struct ref *rm; for (rm = refs; rm; rm = rm->next) { nr_refs++; if (rm->peer_ref && + !is_null_sha1(rm->old_sha1) && !hashcmp(rm->peer_ref->old_sha1, rm->old_sha1)) continue; ALLOC_GROW(heads, nr_heads + 1, nr_alloc); diff --git a/transport.h b/transport.h index c14da6f1e5..9e74406fa2 100644 --- a/transport.h +++ b/transport.h @@ -18,14 +18,51 @@ struct transport { int (*set_option)(struct transport *connection, const char *name, const char *value); + /** + * Returns a list of the remote side's refs. In order to allow + * the transport to try to share connections, for_push is a + * hint as to whether the ultimate operation is a push or a fetch. + * + * If the transport is able to determine the remote hash for + * the ref without a huge amount of effort, it should store it + * in the ref's old_sha1 field; otherwise it should be all 0. + **/ struct ref *(*get_refs_list)(struct transport *transport, int for_push); - int (*fetch)(struct transport *transport, int refs_nr, const struct ref **refs); + + /** + * Fetch the objects for the given refs. Note that this gets + * an array, and should ignore the list structure. + * + * If the transport did not get hashes for refs in + * get_refs_list(), it should set the old_sha1 fields in the + * provided refs now. + **/ + int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs); + + /** + * Push the objects and refs. Send the necessary objects, and + * then, for any refs where peer_ref is set and + * peer_ref->new_sha1 is different from old_sha1, tell the + * remote side to update each ref in the list from old_sha1 to + * peer_ref->new_sha1. + * + * Where possible, set the status for each ref appropriately. + * + * The transport must modify new_sha1 in the ref to the new + * value if the remote accepted the change. Note that this + * could be a different value from peer_ref->new_sha1 if the + * process involved generating new commits. + **/ int (*push_refs)(struct transport *transport, struct ref *refs, int flags); int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags); + /** get_refs_list(), fetch(), and push_refs() can keep + * resources (such as a connection) reserved for futher + * use. disconnect() releases these resources. + **/ 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; }; @@ -74,7 +111,7 @@ int transport_push(struct transport *connection, const struct ref *transport_get_remote_refs(struct transport *transport); -int transport_fetch_refs(struct transport *transport, const struct ref *refs); +int transport_fetch_refs(struct transport *transport, struct ref *refs); void transport_unlock_pack(struct transport *transport); int transport_disconnect(struct transport *transport); char *transport_anonymize_url(const char *url); diff --git a/tree-diff.c b/tree-diff.c index 0459e54d3d..7d745b4406 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -286,7 +286,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru int baselen = strlen(base); for (;;) { - if (DIFF_OPT_TST(opt, QUIET) && DIFF_OPT_TST(opt, HAS_CHANGES)) + if (DIFF_OPT_TST(opt, QUICK) && + DIFF_OPT_TST(opt, HAS_CHANGES)) break; if (opt->nr_paths) { skip_uninteresting(t1, base, baselen, opt); 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 720f7a1616..dd5999c356 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -277,6 +277,17 @@ static int unpack_nondirectories(int n, unsigned long mask, return 0; } +static int unpack_failed(struct unpack_trees_options *o, const char *message) +{ + discard_index(&o->result); + if (!o->gently) { + if (message) + return error("%s", message); + return -1; + } + return -1; +} + static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info) { struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, }; @@ -294,7 +305,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str int cmp = compare_entry(ce, info, p); if (cmp < 0) { if (unpack_index_entry(ce, o) < 0) - return -1; + return unpack_failed(o, NULL); continue; } if (!cmp) { @@ -352,17 +363,6 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str return mask; } -static int unpack_failed(struct unpack_trees_options *o, const char *message) -{ - discard_index(&o->result); - if (!o->gently) { - if (message) - return error("%s", message); - return -1; - } - return -1; -} - /* * N-way merge "len" trees. Returns 0 on success, -1 on failure to manipulate the * resulting index, -2 on failure to reflect the changes to the work tree. @@ -617,7 +617,7 @@ static int verify_absent(struct cache_entry *ce, const char *action, * found "foo/." in the working tree. * This is tricky -- if we have modified * files that are in "foo/" we would lose - * it. + * them. */ ret = verify_clean_subdirectory(ce, action, o); if (ret < 0) @@ -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 953ebe1a60..df151813f9 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -39,6 +39,8 @@ static unsigned int timeout; */ static int use_sideband; static int debug_fd; +static int advertise_refs; +static int stateless_rpc; static void reset_timeout(void) { @@ -146,66 +148,8 @@ static int do_rev_list(int fd, void *create_full_pack) 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); @@ -213,12 +157,10 @@ 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, total_sz; + ssize_t 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; @@ -344,7 +286,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; @@ -381,16 +323,6 @@ 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: @@ -500,7 +432,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; @@ -511,25 +443,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; @@ -589,7 +526,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; @@ -683,7 +622,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) @@ -707,12 +646,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(); @@ -734,6 +693,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; @@ -7,14 +7,14 @@ static void report(const char *prefix, const char *err, va_list params) { - char msg[1024]; + char msg[4096]; vsnprintf(msg, sizeof(msg), err, 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,7 +36,7 @@ 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 NORETURN_PTR void (*usage_routine)(const char *err) = usage_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; @@ -46,9 +46,18 @@ void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list param 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,52 @@ 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); +} + +static void strbuf_add_indented_text(struct strbuf *buf, const char *text, + int indent, int indent2) +{ + if (indent < 0) + indent = 0; + while (*text) { + const char *eol = strchrnul(text, '\n'); + if (*eol == '\n') + eol++; + print_spaces(buf, indent); + strbuf_write(buf, text, eol - text); + text = eol; + indent = indent2; + } +} + +static size_t display_mode_esc_sequence_len(const char *s) +{ + const char *p = s; + if (*p++ != '\033') + return 0; + if (*p++ != '[') + return 0; + while (isdigit(*p) || *p == ';') + p++; + if (*p++ != 'm') + return 0; + return p - s; } /* @@ -295,36 +334,62 @@ 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) { + strbuf_add_indented_text(buf, text, indent, indent2); + return 1; + } + if (indent < 0) { w = -indent; space = text; } for (;;) { - char c = *text; + char c; + size_t skip; + + while ((skip = display_mode_esc_sequence_len(text))) + text += skip; + + c = *text; 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 +405,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) diff --git a/wrap-for-bin.sh b/wrap-for-bin.sh new file mode 100644 index 0000000000..c5075c9c61 --- /dev/null +++ b/wrap-for-bin.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# wrap-for-bin.sh: Template for git executable wrapper scripts +# to run test suite against sandbox, but with only bindir-installed +# executables in PATH. The Makefile copies this into various +# files in bin-wrappers, substituting +# @@BUILD_DIR@@ and @@PROG@@. + +GIT_EXEC_PATH='@@BUILD_DIR@@' +GIT_TEMPLATE_DIR='@@BUILD_DIR@@/templates/blt' +GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib' +PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH" +export GIT_EXEC_PATH GIT_TEMPLATE_DIR GITPERLLIB PATH + +exec "${GIT_EXEC_PATH}/@@PROG@@" "$@" diff --git a/wt-status.c b/wt-status.c index 38eb24536b..5d56988016 100644 --- a/wt-status.c +++ b/wt-status.c @@ -47,28 +47,33 @@ void wt_status_prepare(struct wt_status *s) 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) + if (s->in_merge) + ; + else if (!s->is_initial) color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); else color_fprintf_ln(s->fp, c, "# (use \"git rm --cached <file>...\" to unstage)"); - color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to mark resolution)"); + color_fprintf_ln(s->fp, c, "# (use \"git add/rm <file>...\" as appropriate to mark resolution)"); color_fprintf_ln(s->fp, c, "#"); } 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) { + if (s->in_merge) + ; /* NEEDSWORK: use "git reset --unresolve"??? */ + else if (!s->is_initial) color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); - } else { + else color_fprintf_ln(s->fp, c, "# (use \"git rm --cached <file>...\" to unstage)"); - } color_fprintf_ln(s->fp, c, "#"); } @@ -76,6 +81,7 @@ static void wt_status_print_dirty_header(struct wt_status *s, int has_deleted) { const char *c = color(WT_STATUS_HEADER, s); + color_fprintf_ln(s->fp, c, "# Changed but not updated:"); if (!advice_status_hints) return; @@ -277,6 +283,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = wt_status_collect_changed_cb; rev.diffopt.format_callback_data = s; + rev.prune_data = s->pathspec; run_diff_files(&rev, 0); } @@ -293,6 +300,7 @@ static void wt_status_collect_changes_index(struct wt_status *s) rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = 200; rev.diffopt.break_opt = 0; + rev.prune_data = s->pathspec; run_diff_index(&rev, 1); } @@ -305,6 +313,8 @@ static void wt_status_collect_changes_initial(struct wt_status *s) struct wt_status_change_data *d; struct cache_entry *ce = active_cache[i]; + if (!ce_path_match(ce, s->pathspec)) + continue; it = string_list_insert(ce->name, &s->change); d = it->util; if (!d) { @@ -338,6 +348,8 @@ static void wt_status_collect_untracked(struct wt_status *s) struct dir_entry *ent = dir.entries[i]; if (!cache_name_is_other(ent->name, ent->len)) continue; + if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL)) + continue; s->workdir_untracked = 1; string_list_insert(ent->name, &s->untracked); } @@ -541,10 +553,8 @@ static void wt_status_print_tracking(struct wt_status *s) void wt_status_print(struct wt_status *s) { - unsigned char sha1[20]; const char *branch_color = color(WT_STATUS_HEADER, s); - s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; if (s->branch) { const char *on_what = "On branch "; const char *branch_name = s->branch; @@ -561,8 +571,6 @@ void wt_status_print(struct wt_status *s) wt_status_print_tracking(s); } - wt_status_collect(s); - if (s->is_initial) { color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit"); @@ -598,3 +606,107 @@ void wt_status_print(struct wt_status *s) printf("nothing to commit (working directory clean)\n"); } } + +static void wt_shortstatus_unmerged(int null_termination, struct string_list_item *it, + struct wt_status *s) +{ + struct wt_status_change_data *d = it->util; + const char *how = "??"; + + switch (d->stagemask) { + case 1: how = "DD"; break; /* both deleted */ + case 2: how = "AU"; break; /* added by us */ + case 3: how = "UD"; break; /* deleted by them */ + case 4: how = "UA"; break; /* added by them */ + case 5: how = "DU"; break; /* deleted by us */ + case 6: how = "AA"; break; /* both added */ + case 7: how = "UU"; break; /* both modified */ + } + color_fprintf(s->fp, color(WT_STATUS_UNMERGED, s), "%s", how); + if (null_termination) { + fprintf(stdout, " %s%c", it->string, 0); + } else { + struct strbuf onebuf = STRBUF_INIT; + const char *one; + one = quote_path(it->string, -1, &onebuf, s->prefix); + printf(" %s\n", one); + strbuf_release(&onebuf); + } +} + +static void wt_shortstatus_status(int null_termination, struct string_list_item *it, + struct wt_status *s) +{ + struct wt_status_change_data *d = it->util; + + if (d->index_status) + color_fprintf(s->fp, color(WT_STATUS_UPDATED, s), "%c", d->index_status); + else + putchar(' '); + if (d->worktree_status) + color_fprintf(s->fp, color(WT_STATUS_CHANGED, s), "%c", d->worktree_status); + else + putchar(' '); + putchar(' '); + if (null_termination) { + fprintf(stdout, "%s%c", it->string, 0); + if (d->head_path) + fprintf(stdout, "%s%c", d->head_path, 0); + } else { + struct strbuf onebuf = STRBUF_INIT; + const char *one; + if (d->head_path) { + one = quote_path(d->head_path, -1, &onebuf, s->prefix); + printf("%s -> ", one); + strbuf_release(&onebuf); + } + one = quote_path(it->string, -1, &onebuf, s->prefix); + printf("%s\n", one); + strbuf_release(&onebuf); + } +} + +static void wt_shortstatus_untracked(int null_termination, struct string_list_item *it, + struct wt_status *s) +{ + if (null_termination) { + fprintf(stdout, "?? %s%c", it->string, 0); + } else { + struct strbuf onebuf = STRBUF_INIT; + const char *one; + one = quote_path(it->string, -1, &onebuf, s->prefix); + color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "??"); + printf(" %s\n", one); + strbuf_release(&onebuf); + } +} + +void wt_shortstatus_print(struct wt_status *s, int null_termination) +{ + int i; + for (i = 0; i < s->change.nr; i++) { + struct wt_status_change_data *d; + struct string_list_item *it; + + it = &(s->change.items[i]); + d = it->util; + if (d->stagemask) + wt_shortstatus_unmerged(null_termination, it, s); + else + wt_shortstatus_status(null_termination, it, s); + } + for (i = 0; i < s->untracked.nr; i++) { + struct string_list_item *it; + + it = &(s->untracked.items[i]); + wt_shortstatus_untracked(null_termination, it, s); + } +} + +void wt_porcelain_print(struct wt_status *s, int null_termination) +{ + s->use_color = 0; + s->relative_paths = 0; + s->prefix = NULL; + wt_shortstatus_print(s, null_termination); +} diff --git a/wt-status.h b/wt-status.h index a0e75177be..c60f40a34a 100644 --- a/wt-status.h +++ b/wt-status.h @@ -31,8 +31,10 @@ struct wt_status { int is_initial; char *branch; const char *reference; + const char **pathspec; int verbose; int amend; + int in_merge; int nowarn; int use_color; int relative_paths; @@ -55,4 +57,7 @@ void wt_status_prepare(struct wt_status *s); void wt_status_print(struct wt_status *s); void wt_status_collect(struct wt_status *s); +void wt_shortstatus_print(struct wt_status *s, int null_termination); +void wt_porcelain_print(struct wt_status *s, int null_termination); + #endif /* STATUS_H */ |