diff options
613 files changed, 45195 insertions, 9709 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..8df8f88bea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,185 +1,194 @@ -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-remote-http +/git-remote-https +/git-remote-ftp +/git-remote-ftps +/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-index-version +/test-match-trees +/test-parse-options +/test-path-utils +/test-run-command +/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 +198,5 @@ cscope* *.user *.idb *.pdb -Debug/ -Release/ +/Debug/ +/Release/ @@ -22,8 +22,8 @@ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -36,7 +36,7 @@ software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to +the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not @@ -76,7 +76,7 @@ patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. - + GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION @@ -131,7 +131,7 @@ above, provided that you also meet all of these conditions: License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) - + These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in @@ -189,7 +189,7 @@ access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. - + 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is @@ -246,7 +246,7 @@ impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - + 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License @@ -299,7 +299,7 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS - + How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest @@ -324,10 +324,9 @@ the "copyright" line and a pointer to where the full notice is found. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. @@ -357,5 +356,5 @@ necessary. Here is a sample; alter the names: This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General +library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. diff --git a/Documentation/Makefile b/Documentation/Makefile index 037220f544..8a8a3954dc 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))) @@ -203,7 +204,7 @@ install-pdf: pdf install-html: html '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(DESTDIR)$(htmldir) -../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE +../GIT-VERSION-FILE: FORCE $(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) GIT-VERSION-FILE -include ../GIT-VERSION-FILE @@ -336,4 +337,4 @@ quick-install-man: quick-install-html: '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir) -.PHONY: .FORCE-GIT-VERSION-FILE +.PHONY: FORCE diff --git a/Documentation/RelNotes-1.6.6.1.txt b/Documentation/RelNotes-1.6.6.1.txt new file mode 100644 index 0000000000..f1d0a4ae2d --- /dev/null +++ b/Documentation/RelNotes-1.6.6.1.txt @@ -0,0 +1,37 @@ +Git v1.6.6.1 Release Notes +========================== + +Fixes since v1.6.6 +------------------ + + * "git blame" did not work well when commit lacked the author name. + + * "git branch -a name" wasn't diagnosed as an error. + + * "git count-objects" did not handle packfiles that are bigger than 4G on + platforms with 32-bit off_t. + + * "git checkout -m other" while on a branch that does not have any commit + segfaulted, instead of failing. + + * "git fast-import" choked when fed a tag that do not point at a + commit. + + * "git grep" finding from work tree files could have fed garbage to + the underlying regexec(3). + + * "git grep -L" didn't show empty files (they should never match, and + they should always appear in -L output as unmatching). + + * "git rebase -i" did not abort cleanly if it failed to launch the editor. + + * "git reset --hard" did not work correctly when GIT_WORK_TREE environment + variable is used to point at the root of the true work tree. + + * http-backend was not listed in the command list in the documentation. + + * Building on FreeBSD (both 7 and 8) needs OLD_ICONV set in the Makefile + + * "git checkout -m some-branch" while on an unborn branch crashed. + +Other minor documentation updates are included. diff --git a/Documentation/RelNotes-1.6.6.2.txt b/Documentation/RelNotes-1.6.6.2.txt new file mode 100644 index 0000000000..4eaddc0106 --- /dev/null +++ b/Documentation/RelNotes-1.6.6.2.txt @@ -0,0 +1,46 @@ +Git v1.6.6.2 Release Notes +========================== + +Fixes since v1.6.6.1 +-------------------- + + * recursive merge didn't correctly diagnose its own programming errors, + and instead caused the caller to segfault. + + * The new "smart http" aware clients probed the web servers to see if + they support smart http, but did not fall back to dumb http transport + correctly with some servers. + + * Time based reflog syntax e.g. "@{yesterday}" didn't diagnose a misspelled + time specification and instead assumed "@{now}". + + * "git archive HEAD -- no-such-directory" produced an empty archive + without complaining. + + * "git blame -L start,end -- file" misbehaved when given a start that is + larger than the number of lines in the file. + + * "git checkout -m" didn't correctly call custom merge backend supplied + by the end user. + + * "git config -f <file>" misbehaved when run from a subdirectory. + + * "git cvsserver" didn't like having regex metacharacters (e.g. '+') in + CVSROOT environment. + + * "git fast-import" did not correctly handle large blobs that may + bust the pack size limit. + + * "git gui" is supposed to work even when launched from inside a .git + directory. + + * "git gui" misbehaved when applying a hunk that ends with deletion. + + * "git imap-send" did not honor imap.preformattedHTML as documented. + + * "git log" family incorrectly showed the commit notes unconditionally by + mistake, which was especially irritating when running "git log --oneline". + + * "git status" shouldn't require an write access to the repository. + +Other minor documentation updates are included. 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.1.txt b/Documentation/RelNotes-1.7.0.1.txt new file mode 100644 index 0000000000..8ff5bcada8 --- /dev/null +++ b/Documentation/RelNotes-1.7.0.1.txt @@ -0,0 +1,35 @@ +Git v1.7.0.1 Release Notes +========================== + +Fixes since v1.7.0 +------------------ + + * In a freshly created repository "rev-parse HEAD^0" complained that + it is dangling symref, even though "rev-parse HEAD" didn't. + + * "git show :no-such-name" tried to access the index without bounds + check, leading to a potential segfault. + + * Message from "git cherry-pick" was harder to read and use than necessary + when it stopped due to conflicting changes. + + * We referred to ".git/refs/" throughout the documentation when we + meant to talk about abstract notion of "ref namespace". Because + people's repositories often have packed refs these days, this was + confusing. + + * "git diff --output=/path/that/cannot/be/written" did not correctly + error out. + + * "git grep -e -pattern-that-begin-with-dash paths..." could not be + spelled as "git grep -- -pattern-that-begin-with-dash paths..." which + would be a GNU way to use "--" as "end of options". + + * "git grep" compiled with threading support tried to access an + uninitialized mutex on boxes with a single CPU. + + * "git stash pop -q --index" failed because the unnecessary --index + option was propagated to "git stash drop" that is internally run at the + end. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes-1.7.0.txt b/Documentation/RelNotes-1.7.0.txt new file mode 100644 index 0000000000..43e3f33615 --- /dev/null +++ b/Documentation/RelNotes-1.7.0.txt @@ -0,0 +1,214 @@ +Git v1.7.0 Release Notes +======================== + +Notes on behaviour change +------------------------- + + * "git push" into a branch that is currently checked out (i.e. pointed at 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 already 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 argument. + + * "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 + amount 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. + + * External diff and textconv helpers are now executed using the shell. + This makes them consistent with other programs executed by git, and + allows you to pass command-line parameters to the helpers. Any helper + paths containing spaces or other metacharacters now need to be + shell-quoted. The affected helpers are GIT_EXTERNAL_DIFF in the + environment, and diff.*.command and diff.*.textconv in the config + file. + + * The --max-pack-size argument to 'git repack', 'git pack-objects', and + 'git fast-import' was assuming the provided size to be expressed in MiB, + unlike the corresponding config variable and other similar options accepting + a size value. It is now expecting a size expressed in bytes, with a possible + unit suffix of 'k', 'm', or 'g'. + +Updates since v1.6.6 +-------------------- + +(subsystems) + + * "git fast-import" updates; adds "option" and "feature" to detect the + mismatch between fast-import and the frontends that produce the input + stream. + + * "git svn" support of subversion "merge tickets" and miscellaneous fixes. + + * "gitk" and "git gui" translation updates. + + * "gitweb" updates (code clean-up, load checking etc.) + +(portability) + + * Some more MSVC portability patches for msysgit port. + + * Minimum Pthreads emulation for msysgit port. + +(performance) + + * More performance improvement patches for msysgit port. + +(usability, bells and whistles) + + * More commands learned "--quiet" and "--[no-]progress" options. + + * Various commands given by the end user (e.g. diff.type.textconv, + and GIT_EDITOR) can be specified with command line arguments. E.g. it + is now possible to say "[diff "utf8doc"] textconv = nkf -w". + + * "sparse checkout" feature allows only part of the work tree to be + checked out. + + * HTTP transfer can use authentication scheme other than basic + (i.e./e.g. digest). + + * Switching from a version of superproject that used to have a submodule + to another version of superproject that no longer has it did not remove + the submodule directory when it should (namely, when you are not + interested in the submodule at all and didn't clone/checkout). + + * A new attribute conflict-marker-size can be used to change the size of + the conflict markers from the default 7; this is useful when tracked + contents (e.g. git-merge documentation) have strings that resemble the + conflict markers. + + * A new syntax "<branch>@{upstream}" can be used on the command line to + substitute the name of the "upstream" of the branch. Missing branch + defaults to the current branch, so "git fetch && git merge @{upstream}" + will be equivalent to "git pull". + + * "git am --resolved" has a synonym "git am --continue". + + * "git branch --set-upstream" can be used to update the (surprise!) upstream, + i.e. where the branch is supposed to pull and merge from (or rebase onto). + + * "git checkout A...B" is a way to detach HEAD at the merge base between + A and B. + + * "git checkout -m path" to reset the work tree file back into the + conflicted state works even when you already ran "git add path" and + resolved the conflicts. + + * "git commit --date='<date>'" can be used to override the author date + just like "git commit --author='<name> <email>'" can be used to + override the author identity. + + * "git commit --no-status" can be used to omit the listing of the index + and the work tree status in the editor used to prepare the log message. + + * "git commit" warns a bit more aggressively until you configure user.email, + whose default value almost always is not (and fundamentally cannot be) + what you want. + + * "git difftool" has been extended to make it easier to integrate it + with gitk. + + * "git fetch --all" can now be used in place of "git remote update". + + * "git grep" does not rely on external grep anymore. It can use more than + one thread to accelerate the operation. + + * "git grep" learned "--quiet" option. + + * "git log" and friends learned "--glob=heads/*" syntax that is a more + flexible way to complement "--branches/--tags/--remotes". + + * "git merge" learned to pass options specific to strategy-backends. E.g. + + - "git merge -Xsubtree=path/to/directory" can be used to tell the subtree + strategy how much to shift the trees explicitly. + + - "git merge -Xtheirs" can be used to auto-merge as much as possible, + while discarding your own changes and taking merged version in + conflicted regions. + + * "git push" learned "git push origin --delete branch", a syntactic sugar + for "git push origin :branch". + + * "git push" learned "git push --set-upstream origin forker:forkee" that + lets you configure your "forker" branch to later pull from "forkee" + branch at "origin". + + * "git rebase --onto A...B" means the history is replayed on top of the + merge base between A and B. + + * "git rebase -i" learned new action "fixup" that squashes the change + but does not affect existing log message. + + * "git rebase -i" also learned --autosquash option that is useful + together with the new "fixup" action. + + * "git remote" learned set-url subcommand that updates (surprise!) url + for an existing remote nickname. + + * "git rerere" learned "forget path" subcommand. Together with "git + checkout -m path" it will be useful when you recorded a wrong + resolution. + + * Use of "git reset --merge" has become easier when resetting away a + conflicted mess left in the work tree. + + * "git rerere" had rerere.autoupdate configuration but there was no way + to countermand it from the command line; --no-rerere-autoupdate option + given to "merge", "revert", etc. fixes this. + + * "git status" learned "-s(hort)" output format. + +(developers) + + * The infrastructure to build foreign SCM interface has been updated. + + * Many more commands are now built-in. + + * THREADED_DELTA_SEARCH is no more. If you build with threads, delta + compression will always take advantage of it. + +Fixes since v1.6.6 +------------------ + +All of the fixes in v1.6.6.X maintenance series are included in this +release, unless otherwise noted. + + * "git branch -d branch" used to refuse deleting the branch even when + the branch is fully merged to its upstream branch if it is not merged + to the current branch. It now deletes it in such a case. + + * "fiter-branch" command incorrectly said --prune-empty and --filter-commit + were incompatible; the latter should be read as --commit-filter. + + * When using "git status" or asking "git diff" to compare the work tree + with something, they used to consider that a checked-out submodule with + uncommitted changes is not modified; this could cause people to forget + committing these changes in the submodule before committing in the + superproject. They now consider such a change as a modification and + "git diff" will append a "-dirty" to the work tree side when generating + patch output or when used with the --submodule option. diff --git a/Documentation/RelNotes-1.7.1.txt b/Documentation/RelNotes-1.7.1.txt new file mode 100644 index 0000000000..8c18ca5d22 --- /dev/null +++ b/Documentation/RelNotes-1.7.1.txt @@ -0,0 +1,20 @@ +Git v1.7.1 Release Notes +======================== + +Updates since v1.7.0 +-------------------- + + * "git grep" learned "--no-index" option, to search inside contents that + are not managed by git. + +Fixes since v1.7.0 +------------------ + +All of the fixes in v1.7.0.X maintenance series are included in this +release, unless otherwise noted. + +--- +exec >/var/tmp/1 +echo O=$(git describe) +O=v1.7.0-36-gfaa3b47 +git shortlog --no-merges ^maint $O.. 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/blame-options.txt b/Documentation/blame-options.txt index 1625ffce6a..4833cac4b9 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -98,8 +98,10 @@ commit. files that were modified in the same commit. This is useful when you reorganize your program and move code around across files. When this option is given twice, - the command additionally looks for copies from all other - files in the parent for the commit that creates the file. + the command additionally looks for copies from other + files in the commit that creates the file. When this + option is given three times, the command additionally + looks for copies from other files in any commit. + <num> is optional but it is the lower bound on the number of alphanumeric characters that git must detect as moving diff --git a/Documentation/config.txt b/Documentation/config.txt index 35e26972e9..7103172ed3 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -64,7 +64,7 @@ The values following the equals sign in variable assign are all either a string, an integer, or a boolean. Boolean values may be given as yes/no, 0/1, true/false or on/off. Case is not significant in boolean values, when converting value to the canonical form using '--bool' type specifier; -'git-config' will ensure that the output is "true" or "false". +'git config' will ensure that the output is "true" or "false". String values may be entirely or partially enclosed in double quotes. You need to enclose variable values in double quotes if you want to @@ -130,12 +130,29 @@ advice.*:: Advice shown when linkgit:git-merge[1] refuses to merge to avoid overwritting local changes. Default: true. + resolveConflict:: + Advices shown by various commands when conflicts + prevent the operation from being performed. + Default: true. + implicitIdentity:: + Advice on how to set your identity configuration when + your information is guessed from the system username and + domain name. Default: true. + + detachedHead:: + Advice shown when you used linkgit::git-checkout[1] to + move to the detach HEAD state, to instruct how to create + a local branch after the fact. Default: true. -- core.fileMode:: If false, the executable bit differences between the index and the working copy are ignored; useful on broken filesystems like FAT. - 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 +165,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 +257,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 +310,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 @@ -382,6 +422,20 @@ You probably do not need to adjust this value. + Common unit suffixes of 'k', 'm', or 'g' are supported. +core.bigFileThreshold:: + Files larger than this size are stored deflated, without + attempting delta compression. Storing large files without + delta compression avoids excessive memory usage, at the + slight expense of increased disk usage. ++ +Default is 512 MiB on all platforms. This should be reasonable +for most projects as source code and other text files can still +be delta compressed, but larger binary media files won't be. ++ +Common unit suffixes of 'k', 'm', or 'g' are supported. ++ +Currently only linkgit:git-fast-import[1] honors this setting. + core.excludesfile:: In addition to '.gitignore' (per-directory) and '.git/info/exclude', git looks into this file for patterns @@ -393,9 +447,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 @@ -417,8 +469,8 @@ core.pager:: core.whitespace:: A comma separated list of common whitespace problems to - notice. 'git-diff' will use `color.diff.whitespace` to - highlight them, and 'git-apply --whitespace=error' will + notice. 'git diff' will use `color.diff.whitespace` to + highlight them, and 'git apply --whitespace=error' will consider them as errors. You can prefix `-` to disable any of them (e.g. `-trailing-space`): + @@ -464,8 +516,25 @@ 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. + +core.sparseCheckout:: + Enable "sparse checkout" feature. See section "Sparse checkout" in + linkgit:git-read-tree[1] for more information. + add.ignore-errors:: - Tells 'git-add' to continue adding files when some files cannot be + Tells 'git add' to continue adding files when some files cannot be added due to indexing errors. Equivalent to the '--ignore-errors' option of linkgit:git-add[1]. @@ -487,19 +556,19 @@ executed from the top-level directory of a repository, which may not necessarily be the current directory. apply.ignorewhitespace:: - When set to 'change', tells 'git-apply' to ignore changes in + When set to 'change', tells 'git apply' to ignore changes in whitespace, in the same way as the '--ignore-space-change' option. - When set to one of: no, none, never, false tells 'git-apply' to + When set to one of: no, none, never, false tells 'git apply' to respect all whitespace differences. See linkgit:git-apply[1]. apply.whitespace:: - Tells 'git-apply' how to handle whitespaces, in the same way + Tells 'git apply' how to handle whitespaces, in the same way as the '--whitespace' option. See linkgit:git-apply[1]. branch.autosetupmerge:: - Tells 'git-branch' and 'git-checkout' to setup new branches + Tells 'git branch' and 'git checkout' to set up new branches so that linkgit:git-pull[1] will appropriately merge from the starting point branch. Note that even if this option is not set, this behavior can be chosen per-branch using the `--track` @@ -510,7 +579,7 @@ branch.autosetupmerge:: branch. This option defaults to true. branch.autosetuprebase:: - When a new branch is created with 'git-branch' or 'git-checkout' + When a new branch is created with 'git branch' or 'git checkout' that tracks another branch, this variable tells git to set up pull to rebase instead of merge (see "branch.<name>.rebase"). When `never`, rebase is never automatically set to true. @@ -525,24 +594,24 @@ branch.autosetuprebase:: This option defaults to never. branch.<name>.remote:: - When in branch <name>, it tells 'git-fetch' and 'git-push' which + When in branch <name>, it tells 'git fetch' and 'git push' which remote to fetch from/push to. It defaults to `origin` if no remote is configured. `origin` is also used if you are not on any branch. branch.<name>.merge:: Defines, together with branch.<name>.remote, the upstream branch - for the given branch. It tells 'git-fetch'/'git-pull' which - branch to merge and can also affect 'git-push' (see push.default). - When in branch <name>, it tells 'git-fetch' the default + for the given branch. It tells 'git fetch'/'git pull' which + branch to merge and can also affect 'git push' (see push.default). + When in branch <name>, it tells 'git fetch' the default refspec to be marked for merging in FETCH_HEAD. The value is handled like the remote part of a refspec, and must match a ref which is fetched from the remote given by "branch.<name>.remote". - The merge information is used by 'git-pull' (which at first calls - 'git-fetch') to lookup the default branch for merging. Without - this option, 'git-pull' defaults to merge the first refspec fetched. + The merge information is used by 'git pull' (which at first calls + 'git fetch') to lookup the default branch for merging. Without + this option, 'git pull' defaults to merge the first refspec fetched. Specify multiple values to get an octopus merge. - If you wish to setup 'git-pull' so that it merges into <name> from + If you wish to setup 'git pull' so that it merges into <name> from another branch in the local repository, you can point branch.<name>.merge to the desired branch, and use the special setting `.` (a period) for branch.<name>.remote. @@ -604,29 +673,19 @@ 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 `never`), never. When set to `true` or `auto`, use color only when the output is written to the terminal. Defaults to `false`. -color.grep.external:: - The string value of this variable is passed to an external 'grep' - command as a command line option if match highlighting is turned - on. If set to an empty string, no option is passed at all, - turning off coloring for external 'grep' calls; this is the default. - For GNU grep, set it to `--color=always` to highlight matches even - when a pager is used. - color.grep.match:: Use customized color for matches. The value of this variable - may be specified as in color.branch.<slot>. It is passed using - the environment variables 'GREP_COLOR' and 'GREP_COLORS' when - calling an external 'grep'. + may be specified as in color.branch.<slot>. color.interactive:: When set to `always`, always use colors for interactive prompts @@ -635,7 +694,7 @@ color.interactive:: colors only when the output is to the terminal. Defaults to false. color.interactive.<slot>:: - Use customized color for 'git-add --interactive' + Use customized color for 'git add --interactive' output. `<slot>` may be `prompt`, `header`, `help` or `error`, for four distinct types of normal output from interactive commands. The values of these variables may be specified as @@ -674,20 +733,25 @@ color.ui:: terminal. When more specific variables of color.* are set, they always take precedence over this setting. Defaults to false. +commit.status:: + A boolean to enable/disable inclusion of status information in the + commit message template when using an editor to prepare the commit + message. Defaults to true. + commit.template:: Specify a file to use as the template for new commit messages. "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the specified user's home directory. diff.autorefreshindex:: - When using 'git-diff' to compare with work tree + When using 'git diff' to compare with work tree files, do not consider stat-only change as changed. Instead, silently run `git update-index --refresh` to update the cached stat information for paths whose contents in the work tree match the contents in the index. This option defaults to true. Note that this - affects only 'git-diff' Porcelain, and not lower level - 'diff' commands, such as 'git-diff-files'. + affects only 'git diff' Porcelain, and not lower level + 'diff' commands such as 'git diff-files'. diff.external:: If this config variable is set, diff generation is not @@ -699,24 +763,24 @@ diff.external:: your files, you might want to use linkgit:gitattributes[5] instead. diff.mnemonicprefix:: - If set, 'git-diff' uses a prefix pair that is different from the + If set, 'git diff' uses a prefix pair that is different from the standard "a/" and "b/" depending on what is being compared. When this configuration is in effect, reverse diff output also swaps the order of the prefixes: -'git-diff';; +`git diff`;; compares the (i)ndex and the (w)ork tree; -'git-diff HEAD';; +`git diff HEAD`;; compares a (c)ommit and the (w)ork tree; -'git diff --cached';; +`git diff --cached`;; compares a (c)ommit and the (i)ndex; -'git-diff HEAD:file1 file2';; +`git diff HEAD:file1 file2`;; compares an (o)bject and a (w)ork tree entity; -'git diff --no-index a b';; +`git diff --no-index a b`;; compares two non-git things (1) and (2). diff.renameLimit:: The number of files to consider when performing the copy/rename - detection; equivalent to the 'git-diff' option '-l'. + detection; equivalent to the 'git diff' option '-l'. diff.renames:: Tells git to detect renames. If set to any boolean value, it @@ -802,9 +866,9 @@ format.pretty:: linkgit:git-whatchanged[1]. format.thread:: - The default threading style for 'git-format-patch'. Can be - either a boolean value, `shallow` or `deep`. `shallow` - threading makes every mail a reply to the head of the series, + The default threading style for 'git format-patch'. Can be + a boolean value, or `shallow` or `deep`. `shallow` threading + makes every mail a reply to the head of the 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. @@ -820,7 +884,7 @@ format.signoff:: gc.aggressiveWindow:: The window size parameter used in the delta compression - algorithm used by 'git-gc --aggressive'. This defaults + algorithm used by 'git gc --aggressive'. This defaults to 10. gc.auto:: @@ -837,39 +901,36 @@ gc.autopacklimit:: default value is 50. Setting this to 0 disables it. gc.packrefs:: - 'git-gc' does not run `git pack-refs` in a bare repository by - default so that older dumb-transport clients can still fetch - from the repository. Setting this to `true` lets 'git-gc' - to run `git pack-refs`. Setting this to `false` tells - 'git-gc' never to run `git pack-refs`. The default setting is - `notbare`. Enable it only when you know you do not have to - support such clients. The default setting will change to `true` - at some stage, and setting this to `false` will continue to - prevent `git pack-refs` from being run from 'git-gc'. + Running `git pack-refs` in a repository renders it + unclonable by Git versions prior to 1.5.1.2 over dumb + transports such as HTTP. This variable determines whether + 'git gc' runs `git pack-refs`. This can be set to `nobare` + to enable it within all non-bare repos or it can be set to a + boolean value. The default is `true`. gc.pruneexpire:: - When 'git-gc' is run, it will call 'prune --expire 2.weeks.ago'. + When 'git gc' is run, it will call 'prune --expire 2.weeks.ago'. Override the grace period with this config variable. The value "now" may be used to disable this grace period and always prune unreachable objects immediately. gc.reflogexpire:: - 'git-reflog expire' removes reflog entries older than + 'git reflog expire' removes reflog entries older than this time; defaults to 90 days. gc.reflogexpireunreachable:: - 'git-reflog expire' removes reflog entries older than + 'git reflog expire' removes reflog entries older than this time and are not reachable from the current tip; defaults to 30 days. gc.rerereresolved:: Records of conflicted merge you resolved earlier are - kept for this many days when 'git-rerere gc' is run. + kept for this many days when 'git rerere gc' is run. The default is 60 days. See linkgit:git-rerere[1]. gc.rerereunresolved:: Records of conflicted merge you have not resolved are - kept for this many days when 'git-rerere gc' is run. + kept for this many days when 'git rerere gc' is run. The default is 15 days. See linkgit:git-rerere[1]. gitcvs.commitmsgannotation:: @@ -977,7 +1038,7 @@ gui.spellingdictionary:: off. gui.fastcopyblame:: - If true, 'git gui blame' uses '-C' instead of '-C -C' for original + If true, 'git gui blame' uses `-C` instead of `-C -C` for original location detection. It makes blame significantly faster on huge repositories at the expense of less thorough copy detection. @@ -1101,6 +1162,20 @@ 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.minSessions:: + The number of curl sessions (counted across slots) to be kept across + requests. They will not be ended with curl_easy_cleanup() until + http_cleanup() is invoked. If USE_CURL_MULTI is not defined, this + value will be capped at 1. Defaults to 1. + +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. @@ -1122,7 +1197,7 @@ i18n.commitEncoding:: i18n.logOutputEncoding:: Character encoding the commit messages are converted to when - running 'git-log' and friends. + running 'git log' and friends. imap:: The configuration variables in the 'imap' section are described @@ -1156,7 +1231,7 @@ interactive.singlekey:: log.date:: Set default date-time mode for the log command. Setting log.date - value is similar to using 'git-log'\'s --date option. The value is one of the + value is similar to using 'git log'\'s --date option. The value is one of the following alternatives: {relative,local,default,iso,rfc,short}. See linkgit:git-log[1]. @@ -1296,10 +1371,13 @@ you can use linkgit:git-index-pack[1] on the *.pack file to regenerate the `{asterisk}.idx` file. pack.packSizeLimit:: - The default maximum size of a pack. This setting only affects - packing to a file, i.e. the git:// protocol is unaffected. It - can be overridden by the `\--max-pack-size` option of - linkgit:git-repack[1]. + The maximum size of a pack. This setting only affects + packing to a file when repacking, i.e. the git:// protocol + is unaffected. It can be overridden by the `\--max-pack-size` + option of linkgit:git-repack[1]. The minimum size allowed is + limited to 1 MiB. The default is unlimited. + Common unit suffixes of 'k', 'm', or 'g' are + supported. pager.<cmd>:: Allows turning on or off pagination of the output of a @@ -1368,7 +1446,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 +1480,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 +1500,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-format.txt b/Documentation/diff-format.txt index b71712473e..15c7e794f4 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -19,7 +19,7 @@ git-diff-tree [-r] <tree-ish-1> <tree-ish-2> [<pattern>...]:: git-diff-files [<pattern>...]:: compares the index and the files on the filesystem. -The "git-diff-tree" command begins its ouput by printing the hash of +The "git-diff-tree" command begins its output by printing the hash of what is being compared. After that, all the commands print one output line per changed file. 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..fe716b2e42 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,9 +12,14 @@ `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>` + When 'git fetch' is used with `<rbranch>:<lbranch>` refspec, it refuses to update the local branch `<lbranch>` unless the remote branch `<rbranch>` it fetches is a descendant of `<lbranch>`. This option @@ -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[] @@ -43,16 +61,16 @@ endif::git-pull[] -u:: --update-head-ok:: - By default 'git-fetch' refuses to update the head which + By default 'git fetch' refuses to update the head which corresponds to the current branch. This flag disables the - check. This is purely for the internal use for 'git-pull' - to communicate with 'git-fetch', and unless you are + check. This is purely for the internal use for 'git pull' + to communicate with 'git fetch', and unless you are implementing your own Porcelain you are not supposed to use it. --upload-pack <upload-pack>:: When given, and the repository to fetch from is handled - by 'git-fetch-pack', '--exec=<upload-pack>' is passed to + by 'git fetch-pack', '--exec=<upload-pack>' is passed to the command to specify non-default path for the command run on the other end. diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 1f1b19996b..51cbeb7032 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -39,7 +39,7 @@ The `git add` command will not add ignored files by default. If any ignored files were explicitly specified on the command line, `git add` will fail with a list of ignored files. Ignored files reached by directory recursion or filename globbing performed by Git (quote your -globs before the shell) will be silently ignored. The `add` command can +globs before the shell) will be silently ignored. The 'git add' command can be used to add ignored files with the `-f` (force) option. Please see linkgit:git-commit[1] for alternative ways to add content to a @@ -153,7 +153,7 @@ EXAMPLES and its subdirectories: + ------------ -$ git add Documentation/\\*.txt +$ git add Documentation/\*.txt ------------ + Note that the asterisk `\*` is quoted from the shell in this diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 67ad5da9cc..c66c565bbe 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -15,7 +15,7 @@ SYNOPSIS [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>] [--reject] [-q | --quiet] [--scissors | --no-scissors] [<mbox> | <Maildir>...] -'git am' (--skip | --resolved | --abort) +'git am' (--continue | --skip | --abort) DESCRIPTION ----------- @@ -37,7 +37,7 @@ OPTIONS -k:: --keep:: - Pass `-k` flag to 'git-mailinfo' (see linkgit:git-mailinfo[1]). + Pass `-k` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]). -c:: --scissors:: @@ -53,7 +53,7 @@ OPTIONS -u:: --utf8:: - Pass `-u` flag to 'git-mailinfo' (see linkgit:git-mailinfo[1]). + Pass `-u` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]). The proposed commit log message taken from the e-mail is re-coded into UTF-8 encoding (configuration variable `i18n.commitencoding` can be used to specify project's @@ -63,7 +63,7 @@ This was optional in prior versions of git, but now it is the default. You can use `--no-utf8` to override this. --no-utf8:: - Pass `-n` flag to 'git-mailinfo' (see + Pass `-n` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]). -3:: @@ -81,7 +81,7 @@ default. You can use `--no-utf8` to override this. -p<n>:: --directory=<dir>:: --reject:: - These flags are passed to the 'git-apply' (see linkgit:git-apply[1]) + These flags are passed to the 'git apply' (see linkgit:git-apply[1]) program that applies the patch. @@ -107,6 +107,7 @@ default. You can use `--no-utf8` to override this. Skip the current patch. This is only meaningful when restarting an aborted patch. +--continue:: -r:: --resolved:: After a patch failure (e.g. attempting to apply @@ -121,7 +122,7 @@ default. You can use `--no-utf8` to override this. to the screen before exiting. This overrides the standard message informing you to use `--resolved` or `--skip` to handle the failure. This is solely - for internal use between 'git-rebase' and 'git-am'. + for internal use between 'git rebase' and 'git am'. --abort:: Restore the original branch and abort the patching operation. diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 5ee8c91f2d..8463439ac5 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,25 +51,25 @@ 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' + Newer 'git diff' output has embedded 'index information' for each blob to help identify the original version that the patch applies to. When this flag is given, and if the original versions of the blobs are available locally, @@ -80,18 +83,20 @@ the information is read from the current index instead. Apply the patch in reverse. --reject:: - For atomicity, 'git-apply' by default fails the whole patch and + For atomicity, 'git apply' by default fails the whole patch and does not touch the working tree when some of the hunks do not apply. This option makes it apply the parts of the patch that are applicable, and leave the 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 @@ -104,18 +109,18 @@ the information is read from the current index instead. ever ignored. --unidiff-zero:: - By default, 'git-apply' expects that the patch being + 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. --apply:: If you use any of the options marked "Turns off - 'apply'" above, 'git-apply' reads and outputs the + 'apply'" above, 'git apply' reads and outputs the requested information without actually applying the patch. Give this flag after those flags to also apply the patch. @@ -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 @@ -224,16 +229,16 @@ apply.whitespace:: Submodules ---------- -If the patch contains any changes to submodules then 'git-apply' +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-archimport.txt b/Documentation/git-archimport.txt index c7a6e3ec05..4d4325f222 100644 --- a/Documentation/git-archimport.txt +++ b/Documentation/git-archimport.txt @@ -29,17 +29,17 @@ branches that have different roots, it will refuse to run. In that case, edit your <archive/branch> parameters to define clearly the scope of the import. -'git-archimport' uses `tla` extensively in the background to access the +'git archimport' uses `tla` extensively in the background to access the Arch repository. Make sure you have a recent version of `tla` available in the path. `tla` must -know about the repositories you pass to 'git-archimport'. +know about the repositories you pass to 'git archimport'. -For the initial import, 'git-archimport' expects to find itself in an empty +For the initial import, 'git archimport' expects to find itself in an empty directory. To follow the development of a project that uses Arch, rerun -'git-archimport' with the same parameters as the initial import to perform +'git archimport' with the same parameters as the initial import to perform incremental imports. -While 'git-archimport' will try to create sensible branch names for the +While 'git archimport' will try to create sensible branch names for the archives that it imports, it is also possible to specify git branch names manually. To do so, write a git branch name after each <archive/branch> parameter, separated by a colon. This way, you can shorten the Arch @@ -84,7 +84,7 @@ OPTIONS -o:: Use this for compatibility with old-style branch names used by - earlier versions of 'git-archimport'. Old-style branch names + earlier versions of 'git archimport'. Old-style branch names were category--branch, whereas new-style branch names are archive,category--branch--version. In both cases, names given on the command-line will override the automatically-generated diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index e57979198b..8d3e66626f 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -21,13 +21,13 @@ structure for the named tree, and writes it out to the standard output. If <prefix> is specified it is prepended to the filenames in the archive. -'git-archive' behaves differently when given a tree ID versus when +'git archive' behaves differently when given a tree ID versus when given a commit ID or tag ID. In the first case the current time is used as the modification time of each file in the archive. In the latter case the commit time as recorded in the referenced commit object is used instead. Additionally the commit ID is stored in a global extended pax header if the tar format is used; it can be extracted -using 'git-get-tar-commit-id'. In ZIP files it is stored as a file +using 'git get-tar-commit-id'. In ZIP files it is stored as a file comment. OPTIONS @@ -112,6 +112,14 @@ export-subst:: expand several placeholders when adding this file to an archive. See linkgit:gitattributes[5] for details. +Note that attributes are by default taken from the `.gitattributes` files +in the tree that is being archived. If you want to tweak the way the +output is generated after the fact (e.g. you committed without adding an +appropriate export-ignore in its `.gitattributes`), adjust the checked out +`.gitattributes` file as necessary and use `--work-tree-attributes` +option. Alternatively you can keep necessary attributes that should apply +while archiving any tree in your `$GIT_DIR/info/attributes` file. + EXAMPLES -------- git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -):: diff --git a/Documentation/git-bisect-lk2009.txt b/Documentation/git-bisect-lk2009.txt new file mode 100644 index 0000000000..86b3015c13 --- /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 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-blame.txt b/Documentation/git-blame.txt index 8c7b7b0838..a27f43950f 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [--incremental] [-L n,m] - [-S <revs-file>] [-M] [-C] [-C] [--since=<date>] + [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] [<rev> | --contents <file> | --reverse <rev>] [--] <file> DESCRIPTION @@ -21,7 +21,7 @@ last modified the line. Optionally, start annotating from the given revision. The command can also limit the range of lines annotated. The report does not tell you anything about lines which have been deleted or -replaced; you need to use a tool such as 'git-diff' or the "pickaxe" +replaced; you need to use a tool such as 'git diff' or the "pickaxe" interface briefly mentioned in the following paragraph. Apart from supporting file annotation, git also supports searching the @@ -49,7 +49,7 @@ include::blame-options.txt[] file (see `-M`). The first number listed is the score. This is the number of alphanumeric characters detected as having been moved between or within files. This must be above - a certain threshold for 'git-blame' to consider those lines + a certain threshold for 'git blame' to consider those lines of code to have been moved. -f:: @@ -100,7 +100,7 @@ header elements later. SPECIFYING RANGES ----------------- -Unlike 'git-blame' and 'git-annotate' in older versions of git, the extent +Unlike 'git blame' and 'git annotate' in older versions of git, the extent of the annotation can be limited to both line ranges and revision ranges. When you are interested in finding the origin for lines 40-60 for file `foo`, you can use the `-L` option like so @@ -118,7 +118,7 @@ which limits the annotation to the body of the `hello` subroutine. When you are not interested in changes older than version v2.6.18, or changes older than 3 weeks, you can use revision -range specifiers similar to 'git-rev-list': +range specifiers similar to 'git rev-list': git blame v2.6.18.. -- foo git blame --since=3.weeks -- foo diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 0e836809c2..6b6c3da2d9 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -11,7 +11,7 @@ SYNOPSIS 'git branch' [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]] [(--merged | --no-merged | --contains) [<commit>]] -'git branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>] +'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>] 'git branch' (-m | -M) [<oldbranch>] <newbranch> 'git branch' (-d | -D) [-r] <branchname>... @@ -38,7 +38,7 @@ working tree to it; use "git checkout <newbranch>" to switch to the new branch. When a local branch is started off a remote branch, git sets up the -branch so that 'git-pull' will appropriately merge from +branch so that 'git pull' will appropriately merge from the remote branch. This behavior may be changed via the global `branch.autosetupmerge` configuration flag. That setting can be overridden by using the `--track` and `--no-track` options. @@ -55,7 +55,7 @@ has a reflog then the reflog will also be deleted. Use -r together with -d to delete remote-tracking branches. Note, that it only makes sense to delete remote-tracking branches if they no longer exist -in the remote repository or if 'git-fetch' was configured not to fetch +in the remote repository or if 'git fetch' was configured not to fetch them again. See also the 'prune' subcommand of linkgit:git-remote[1] for a way to clean up all obsolete remote-tracking branches. @@ -76,7 +76,7 @@ OPTIONS -f:: --force:: Reset <branchname> to <startpoint> if <branchname> exists - already. Without `-f` 'git-branch' refuses to change an existing branch. + already. Without `-f` 'git branch' refuses to change an existing branch. -m:: Move/rename a branch and the corresponding reflog. @@ -129,6 +129,12 @@ start-point is either a local or remote branch. Do not set up "upstream" configuration, even if the branch.autosetupmerge configuration variable is true. +--set-upstream:: + If specified branch does not exist yet or if '--force' has been + given, acts exactly like '--track'. Otherwise sets up configuration + like '--track' would when creating the branch, except that where + branch points to is not changed. + --contains <commit>:: Only list branches which contain the specified commit. diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt index c3a066e60c..a5ed8fb05b 100644 --- a/Documentation/git-bundle.txt +++ b/Documentation/git-bundle.txt @@ -21,9 +21,9 @@ Some workflows require that one or more branches of development on one machine be replicated on another machine, but the two machines cannot be directly connected, and therefore the interactive git protocols (git, ssh, rsync, http) cannot be used. This command provides support for -'git-fetch' and 'git-pull' to operate by packaging objects and references +'git fetch' and 'git pull' to operate by packaging objects and references in an archive at the originating machine, then importing those into -another repository using 'git-fetch' and 'git-pull' +another repository using 'git fetch' and 'git pull' after moving the archive by some means (e.g., by sneakernet). As no direct connection between the repositories exists, the user must specify a basis for the bundle that is held by the destination repository: the @@ -35,14 +35,14 @@ OPTIONS create <file>:: Used to create a bundle named 'file'. This requires the - 'git-rev-list' arguments to define the bundle contents. + 'git rev-list' arguments to define the bundle contents. verify <file>:: Used to check that a bundle file is valid and will apply cleanly to the current repository. This includes checks on the bundle format itself as well as checking that the prerequisite commits exist and are fully linked in the current repository. - 'git-bundle' prints a list of missing commits, if any, and exits + 'git bundle' prints a list of missing commits, if any, and exits with a non-zero status. list-heads <file>:: @@ -51,15 +51,15 @@ list-heads <file>:: printed out. unbundle <file>:: - Passes the objects in the bundle to 'git-index-pack' + Passes the objects in the bundle to 'git index-pack' for storage in the repository, then prints the names of all defined references. If a list of references is given, only references matching those in the list are printed. This command is - really plumbing, intended to be called only by 'git-fetch'. + really plumbing, intended to be called only by 'git fetch'. [git-rev-list-args...]:: - A list of arguments, acceptable to 'git-rev-parse' and - 'git-rev-list', that specifies the specific objects and references + A list of arguments, acceptable to 'git rev-parse' and + 'git rev-list', that specifies the specific objects and references to transport. For example, `master\~10..master` causes the current master reference to be packaged along with all objects added since its 10th ancestor commit. There is no explicit @@ -69,16 +69,16 @@ unbundle <file>:: [refname...]:: A list of references used to limit the references reported as - available. This is principally of use to 'git-fetch', which + available. This is principally of use to 'git fetch', which expects to receive only those references asked for and not - necessarily everything in the pack (in this case, 'git-bundle' acts - like 'git-fetch-pack'). + necessarily everything in the pack (in this case, 'git bundle' acts + like 'git fetch-pack'). SPECIFYING REFERENCES --------------------- -'git-bundle' will only package references that are shown by -'git-show-ref': this includes heads, tags, and remote heads. References +'git bundle' will only package references that are shown by +'git show-ref': this includes heads, tags, and remote heads. References such as `master\~1` cannot be packaged, but are perfectly suitable for defining the basis. More than one reference may be packaged, and more than one basis can be specified. The objects packaged are those not diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index e9b3b40af4..379eee6734 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 @@ -18,8 +19,9 @@ status if it is not. A reference is used in git to specify branches and tags. A branch head is stored under the `$GIT_DIR/refs/heads` directory, and -a tag is stored under the `$GIT_DIR/refs/tags` directory. git -imposes the following rules on how references are named: +a tag is stored under the `$GIT_DIR/refs/tags` directory (or, if refs +are packed by `git gc`, as entries in the `$GIT_DIR/packed-refs` file). +git imposes the following rules on how references are named: . They can include slash `/` for hierarchical (directory) grouping, but no slash-separated component can begin with a @@ -42,7 +44,7 @@ imposes the following rules on how references are named: . They cannot contain a sequence `@{`. -- They cannot contain a `\\`. +. They cannot contain a `\`. These rules make it easy for shell script based tools to parse reference names, pathname expansion by the shell when a reference name is used @@ -59,23 +61,35 @@ reference name expressions (see linkgit:git-rev-parse[1]): . A colon `:` is used as in `srcref:dstref` to mean "use srcref\'s value and store it in dstref" in fetch and push operations. It may also be used to select a specific object such as with - 'git-cat-file': "git cat-file blob v1.3.3:refs.c". + 'git cat-file': "git cat-file blob v1.3.3:refs.c". . 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-checkout-index.txt b/Documentation/git-checkout-index.txt index 62d84836b8..d6aa6e14eb 100644 --- a/Documentation/git-checkout-index.txt +++ b/Documentation/git-checkout-index.txt @@ -88,7 +88,7 @@ $ find . -name '*.h' -print0 | xargs -0 git checkout-index -f -- which will force all existing `*.h` files to be replaced with their cached copies. If an empty command line implied "all", then this would force-refresh everything in the index, which was not the point. But -since 'git-checkout-index' accepts --stdin it would be faster to use: +since 'git checkout-index' accepts --stdin it would be faster to use: ---------------- $ find . -name '*.h' -print0 | git checkout-index -f -z --stdin @@ -102,7 +102,7 @@ Using `--` is probably a good policy in scripts. Using --temp or --stage=all --------------------------- When `--temp` is used (or implied by `--stage=all`) -'git-checkout-index' will create a temporary file for each index +'git checkout-index' will create a temporary file for each index entry being checked out. The index will not be updated with stat information. These options can be useful if the caller needs all stages of all unmerged entries so that the unmerged files can be @@ -147,9 +147,9 @@ To update and refresh only the files already checked out:: $ git checkout-index -n -f -a && git update-index --ignore-missing --refresh ---------------- -Using 'git-checkout-index' to "export an entire tree":: +Using 'git checkout-index' to "export an entire tree":: The prefix ability basically makes it trivial to use - 'git-checkout-index' as an "export as tree" function. + 'git checkout-index' as an "export as tree" function. Just read the desired tree into the index, and do: + ---------------- diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index b764130d26..78f4714da0 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -24,7 +24,7 @@ OPTIONS -e:: --edit:: - With this option, 'git-cherry-pick' will let you edit the commit + With this option, 'git cherry-pick' will let you edit the commit message prior to committing. -x:: diff --git a/Documentation/git-cherry.txt b/Documentation/git-cherry.txt index 7deefdae8f..fed115acd0 100644 --- a/Documentation/git-cherry.txt +++ b/Documentation/git-cherry.txt @@ -14,7 +14,7 @@ DESCRIPTION The changeset (or "diff") of each commit between the fork-point and <head> is compared against each commit between the fork-point and <upstream>. The commits are compared with their 'patch id', obtained from -the 'git-patch-id' program. +the 'git patch-id' program. Every commit that doesn't exist in the <upstream> branch has its id (sha1) reported, prefixed by a symbol. The ones that have @@ -37,8 +37,8 @@ to and including <limit> are not reported: \__*__*__<limit>__-__+__> <head> -Because 'git-cherry' compares the changeset rather than the commit id -(sha1), you can use 'git-cherry' to find out if a commit you made locally +Because 'git cherry' compares the changeset rather than the commit id +(sha1), you can use 'git cherry' to find out if a commit you made locally has been applied <upstream> under a different commit id. For example, this will happen if you're feeding patches <upstream> via email rather than pushing or pulling commits directly. diff --git a/Documentation/git-citool.txt b/Documentation/git-citool.txt index 670cb02b6c..fb2753c97e 100644 --- a/Documentation/git-citool.txt +++ b/Documentation/git-citool.txt @@ -14,9 +14,9 @@ DESCRIPTION A Tcl/Tk based graphical interface to review modified files, stage them into the index, enter a commit message and record the new commit onto the current branch. This interface is an alternative -to the less interactive 'git-commit' program. +to the less interactive 'git commit' program. -'git-citool' is actually a standard alias for `git gui citool`. +'git citool' is actually a standard alias for `git gui citool`. See linkgit:git-gui[1] for more details. Author diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt index 9d291bdd26..a81cb6c280 100644 --- a/Documentation/git-clean.txt +++ b/Documentation/git-clean.txt @@ -33,8 +33,8 @@ OPTIONS -f:: --force:: - If the git configuration specifies clean.requireForce as true, - 'git-clean' will refuse to run unless given -f or -n. + If the git configuration variable clean.requireForce is not set + to false, 'git clean' will refuse to run unless given -f or -n. -n:: --dry-run:: @@ -48,7 +48,7 @@ OPTIONS -x:: Don't use the ignore rules. This allows removing all untracked files, including build products. This can be used (possibly in - conjunction with 'git-reset') to create a pristine + conjunction with 'git reset') to create a pristine working directory to test a clean build. -X:: diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 7ccd742a87..88ea6246a1 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -29,7 +29,7 @@ arguments will in addition merge the remote master branch into the current master branch, if any. This default configuration is achieved by creating references to -the remote branch heads under `$GIT_DIR/refs/remotes/origin` and +the remote branch heads under `refs/remotes/origin` and by initializing `remote.origin.url` and `remote.origin.fetch` configuration variables. @@ -96,13 +96,19 @@ objects from the source repository into a pack in the cloned repository. --quiet:: -q:: - Operate quietly. This flag is also passed to the `rsync' + Operate quietly. Progress is not reported to the standard + error stream. This flag is also passed to the `rsync' command when given. --verbose:: -v:: - Display the progress bar, even in case the standard output is not - a terminal. + Run verbosely. + +--progress:: + Progress status is reported on the standard error stream + by default when it is attached to a terminal, unless -q + is specified. This flag forces progress status even if the + standard error stream is not directed to a terminal. --no-checkout:: -n:: diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index b8834baced..61888547a1 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -70,9 +70,10 @@ is taken from the configuration items user.name and user.email, or, if not present, system user name and fully qualified hostname. A commit comment is read from stdin. If a changelog -entry is not provided via "<" redirection, 'git-commit-tree' will just wait +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..64fb458b45 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -9,9 +9,10 @@ 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>] [--status | --no-status] [--] + [[-i | -o ]<file>...] DESCRIPTION ----------- @@ -20,11 +21,11 @@ with a log message from the user describing the changes. The content to be added can be specified in several ways: -1. by using 'git-add' to incrementally "add" changes to the +1. by using 'git add' to incrementally "add" changes to the index before using the 'commit' command (Note: even modified files must be "added"); -2. by using 'git-rm' to remove files from the working tree +2. by using 'git rm' to remove files from the working tree and the index, again before using the 'commit' command; 3. by listing files as arguments to the 'commit' command, in which @@ -40,14 +41,14 @@ The content to be added can be specified in several ways: 5. by using the --interactive switch with the 'commit' command to decide one by one which files should be part of the commit, before finalizing the - operation. Currently, this is done by invoking 'git-add --interactive'. + operation. Currently, this is done by invoking 'git add --interactive'. The `--dry-run` option can be used to obtain a summary of what is included by any of the above for the next commit by giving the same set of parameters (options and paths). If you make a commit and then find a mistake immediately after -that, you can recover from it with 'git-reset'. +that, you can recover from it with 'git reset'. OPTIONS @@ -69,6 +70,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 +100,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. @@ -162,7 +185,7 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].) Make a commit only from the paths specified on the command line, disregarding any contents that have been staged so far. This is the default mode of operation of - 'git-commit' if any paths are given on the command line, + 'git commit' if any paths are given on the command line, in which case this option can be omitted. If this option is specified together with '--amend', then no paths need to be specified, which can be used to amend @@ -174,13 +197,13 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].) Show untracked files (Default: 'all'). + The mode parameter is optional, and is used to specify -the handling of untracked files. The possible options are: +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 @@ -202,6 +225,17 @@ specified. to be committed, paths with local changes that will be left uncommitted and paths that are untracked. +--status:: + Include the output of linkgit:git-status[1] in the commit + message template when using an editor to prepare the commit + message. Defaults to on, but can be used to override + configuration variable commit.status. + +--no-status:: + Do not include the output of linkgit:git-status[1] in the + commit message template when using an editor to prepare the + default commit message. + \--:: Do not interpret any more arguments as options. @@ -212,15 +246,17 @@ 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 -------- When recording your own work, the contents of modified files in your working tree are temporarily stored to a staging area -called the "index" with 'git-add'. A file can be +called the "index" with 'git add'. A file can be reverted back, only in the index but not in the working tree, to that of the last commit with `git reset HEAD -- <file>`, -which effectively reverts 'git-add' and prevents the changes to +which effectively reverts 'git add' and prevents the changes to this file from participating in the next commit. After building the state to be committed incrementally with these commands, `git commit` (without any pathname parameter) is used to record what @@ -276,13 +312,13 @@ $ git commit this second commit would record the changes to `hello.c` and `hello.h` as expected. -After a merge (initiated by 'git-merge' or 'git-pull') stops +After a merge (initiated by 'git merge' or 'git pull') stops because of conflicts, cleanly merged paths are already staged to be committed for you, and paths that conflicted are left in unmerged state. You would have to first -check which paths are conflicting with 'git-status' +check which paths are conflicting with 'git status' and after fixing them manually in your working tree, you would -stage the result as usual with 'git-add': +stage the result as usual with 'git add': ------------ $ git status | grep unmerged @@ -323,7 +359,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-config.txt b/Documentation/git-config.txt index f68b198205..543dd64a46 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -37,11 +37,12 @@ existing values that match the regexp are updated or unset. If you want to handle the lines that do *not* match the regex, just prepend a single exclamation mark in front (see also <<EXAMPLES>>). -The type specifier can be either '--int' or '--bool', which will make -'git-config' ensure that the variable(s) are of the given type and +The type specifier can be either '--int' or '--bool', to make +'git config' ensure that the variable(s) are of the given type and convert the value to the canonical form (simple decimal number for int, -a "true" or "false" string for bool). If no type specifier is passed, -no checks or transformations are performed on the value. +a "true" or "false" string for bool), or '--path', which does some +path expansion (see '--path' below). If no type specifier is passed, no +checks or transformations are performed on the value. The file-option can be one of '--system', '--global' or '--file' which specify where the values will be read from or written to. @@ -124,18 +125,25 @@ See also <<FILES>>. List all variables set in config file. --bool:: - 'git-config' will ensure that the output is "true" or "false" + 'git config' will ensure that the output is "true" or "false" --int:: - 'git-config' will ensure that the output is a simple + 'git config' will ensure that the output is a simple decimal number. An optional value suffix of 'k', 'm', or 'g' in the config file will cause the value to be multiplied by 1024, 1048576, or 1073741824 prior to output. --bool-or-int:: - 'git-config' will ensure that the output matches the format of + 'git config' will ensure that the output matches the format of either --bool or --int, as described above. +--path:: + 'git-config' will expand leading '{tilde}' to the value of + '$HOME', and '{tilde}user' to the home directory for the + specified user. This option has no effect when setting the + value (but you can use 'git config bla {tilde}/' from the + command line to let your shell do the expansion). + -z:: --null:: For all options that output values and/or keys, always @@ -173,7 +181,7 @@ FILES ----- If not set explicitly with '--file', there are three files where -'git-config' will search for configuration options: +'git config' will search for configuration options: $GIT_DIR/config:: Repository specific configuration file. (The filename is @@ -190,12 +198,12 @@ $(prefix)/etc/gitconfig:: If no further options are given, all reading options will read all of these files that are available. If the global or the system-wide configuration file are not available they will be ignored. If the repository configuration -file is not available or readable, 'git-config' will exit with a non-zero +file is not available or readable, 'git config' will exit with a non-zero error code. However, in neither case will an error message be issued. All writing options will per default write to the repository specific configuration file. Note that this also affects options like '--replace-all' -and '--unset'. *'git-config' will only ever change one file at a time*. +and '--unset'. *'git config' will only ever change one file at a time*. You can override these rules either by command line options or by environment variables. The '--global' and the '--system' options will limit the file used diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index abaaf273bb..b2696efae9 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -27,7 +27,7 @@ by default. Supports file additions, removals, and commits that affect binary files. -If the commit is a merge commit, you must tell 'git-cvsexportcommit' what +If the commit is a merge commit, you must tell 'git cvsexportcommit' what parent the changeset should be done against. OPTIONS diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt index 614e769f4e..ddfcb3d143 100644 --- a/Documentation/git-cvsimport.txt +++ b/Documentation/git-cvsimport.txt @@ -28,9 +28,9 @@ At least version 2.1 is required. Please see the section <<issues,ISSUES>> for further reference. You should *never* do any work of your own on the branches that are -created by 'git-cvsimport'. By default initial import will create and populate a +created by 'git cvsimport'. By default initial import will create and populate a "master" branch from the CVS repository's main branch which you're free -to work with; after that, you need to 'git-merge' incremental imports, or +to work with; after that, you need to 'git merge' incremental imports, or any CVS branches, yourself. It is advisable to specify a named remote via -r to separate and protect the incoming branches. @@ -49,13 +49,13 @@ OPTIONS -d <CVSROOT>:: The root of the CVS archive. May be local (a simple path) or remote; currently, only the :local:, :ext: and :pserver: access methods - are supported. If not given, 'git-cvsimport' will try to read it + are supported. If not given, 'git cvsimport' will try to read it from `CVS/Root`. If no such file exists, it checks for the `CVSROOT` environment variable. <CVS_module>:: The CVS module you want to import. Relative to <CVSROOT>. - If not given, 'git-cvsimport' tries to read it from + If not given, 'git cvsimport' tries to read it from `CVS/Repository`. -C <target-dir>:: @@ -65,14 +65,14 @@ OPTIONS -r <remote>:: The git remote to import this CVS repository into. Moves all CVS branches into remotes/<remote>/<branch> - akin to the way 'git-clone' uses 'origin' by default. + akin to the way 'git clone' uses 'origin' by default. -o <branch-for-HEAD>:: When no remote is specified (via -r) the 'HEAD' branch from CVS is imported to the 'origin' branch within the git repository, as 'HEAD' already has a special meaning for git. When a remote is specified the 'HEAD' branch is named - remotes/<remote>/master mirroring 'git-clone' behaviour. + remotes/<remote>/master mirroring 'git clone' behaviour. Use this option if you want to import into a different branch. + @@ -145,17 +145,17 @@ This option can be used several times to provide several detection regexes. --------- + -'git-cvsimport' will make it appear as those authors had +'git cvsimport' will make it appear as those authors had their GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL set properly all along. + For convenience, this data is saved to `$GIT_DIR/cvs-authors` each time the '-A' option is provided and read from that same -file each time 'git-cvsimport' is run. +file each time 'git cvsimport' is run. + It is not recommended to use this feature if you intend to export changes back to CVS again later with -'git-cvsexportcommit'. +'git cvsexportcommit'. -h:: Print a short usage message and exit. diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index 785779e221..dbb053ee17 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -22,7 +22,7 @@ cvspserver stream tcp nowait nobody /usr/bin/git-cvsserver git-cvsserver pserver Usage: [verse] -'git cvsserver' [options] [pserver|server] [<directory> ...] +'git-cvsserver' [options] [pserver|server] [<directory> ...] OPTIONS ------- @@ -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: @@ -266,6 +277,21 @@ In `dbdriver` and `dbuser` you can use the following variables: If no name can be determined, the numeric uid is used. +ENVIRONMENT +----------- + +These variables obviate the need for command-line options in some +circumstances, allowing easier restricted usage through git-shell. + +GIT_CVSSERVER_BASE_PATH takes the place of the argument to --base-path. + +GIT_CVSSERVER_ROOT specifies a single-directory whitelist. The +repository must still be configured to allow access through +git-cvsserver, as described above. + +When these environment variables are set, the corresponding +command-line arguments may not be used. + Eclipse CVS Client Notes ------------------------ @@ -283,7 +309,7 @@ To get a checkout with the Eclipse CVS client: Protocol notes: If you are using anonymous access via pserver, just select that. Those using SSH access should choose the 'ext' protocol, and configure 'ext' access on the Preferences->Team->CVS->ExtConnection pane. Set CVS_SERVER to -"'git cvsserver'". Note that password support is not good when using 'ext', +"`git cvsserver`". Note that password support is not good when using 'ext', you will definitely want to have SSH keys setup. Alternatively, you can just use the non-standard extssh protocol that Eclipse diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index a85121c689..01c9f8eb9e 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -28,36 +28,36 @@ that service if it is enabled. It verifies that the directory has the magic file "git-daemon-export-ok", and it will refuse to export any git directory that hasn't explicitly been marked for export this way (unless the '--export-all' parameter is specified). If you -pass some directory paths as 'git-daemon' arguments, you can further restrict +pass some directory paths as 'git daemon' arguments, you can further restrict the offers to a whitelist comprising of those. By default, only `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'. +'git fetch-pack' and 'git ls-remote' clients, which are invoked +from 'git fetch', 'git pull', and 'git clone'. This is ideally suited for read-only updates, i.e., pulling from git repositories. -An `upload-archive` also exists to serve 'git-archive'. +An `upload-archive` also exists to serve 'git archive'. OPTIONS ------- --strict-paths:: Match paths exactly (i.e. don't allow "/foo/repo" when the real path is "/foo/repo.git" or "/foo/repo/.git") and don't do user-relative paths. - 'git-daemon' will refuse to start when this option is enabled and no + 'git daemon' will refuse to start when this option is enabled and no whitelist is specified. --base-path=path:: Remap all the path requests as relative to the given path. - This is sort of "GIT root" - if you run 'git-daemon' with + This is sort of "GIT root" - if you run 'git daemon' with '--base-path=/srv/git' on example.com, then if you later try to pull - 'git://example.com/hello.git', 'git-daemon' will interpret the path + 'git://example.com/hello.git', 'git daemon' will interpret the path as '/srv/git/hello.git'. --base-path-relaxed:: If --base-path is enabled and repo lookup fails, with this option - 'git-daemon' will attempt to lookup without prefixing the base path. + 'git daemon' will attempt to lookup without prefixing the base path. This is useful for switching to --base-path usage, while still allowing the old paths. @@ -143,7 +143,7 @@ OPTIONS + Giving these options is an error when used with `--inetd`; use the facility of inet daemon to achieve the same before spawning -'git-daemon' if needed. +'git daemon' if needed. --enable=service:: --disable=service:: @@ -169,24 +169,24 @@ SERVICES These services can be globally enabled/disabled using the command line options of this command. If a finer-grained -control is desired (e.g. to allow 'git-archive' to be run +control is desired (e.g. to allow 'git archive' to be run against only in a few selected repositories the daemon serves), the per-repository configuration file can be used to enable or disable them. upload-pack:: - This serves 'git-fetch-pack' and 'git-ls-remote' + This serves 'git fetch-pack' and 'git ls-remote' clients. It is enabled by default, but a repository can disable it by setting `daemon.uploadpack` configuration item to `false`. upload-archive:: - This serves 'git-archive --remote'. It is disabled by + This serves 'git archive --remote'. It is disabled by default, but a repository can enable it by setting `daemon.uploadarch` configuration item to `true`. receive-pack:: - This serves 'git-send-pack' clients, allowing anonymous + This serves 'git send-pack' clients, allowing anonymous push. It is disabled by default, as there is _no_ authentication in the protocol (in other words, anybody can push anything into the repository, including removal @@ -204,8 +204,8 @@ $ grep 9418 /etc/services git 9418/tcp # Git Version Control System ------------ -'git-daemon' as inetd server:: - To set up 'git-daemon' as an inetd service that handles any +'git daemon' as inetd server:: + To set up 'git daemon' as an inetd service that handles any repository under the whitelisted set of directories, /pub/foo and /pub/bar, place an entry like the following into /etc/inetd all on one line: @@ -217,8 +217,8 @@ git 9418/tcp # Git Version Control System ------------------------------------------------ -'git-daemon' as inetd server for virtual hosts:: - To set up 'git-daemon' as an inetd service that handles +'git daemon' as inetd server for virtual hosts:: + To set up 'git daemon' as an inetd service that handles repositories for different virtual hosts, `www.example.com` and `www.example.org`, place an entry like the following into `/etc/inetd` all on one line: @@ -240,8 +240,8 @@ clients, a symlink from `/software` into the appropriate default repository could be made as well. -'git-daemon' as regular daemon for virtual hosts:: - To set up 'git-daemon' as a regular, non-inetd service that +'git daemon' as regular daemon for virtual hosts:: + To set up 'git daemon' as a regular, non-inetd service that handles repositories for multiple virtual hosts based on their IP addresses, start the daemon like this: + @@ -258,7 +258,7 @@ Repositories can still be accessed by hostname though, assuming they correspond to these IP addresses. selectively enable/disable services per repository:: - To enable 'git-archive --remote' and disable 'git-fetch' against + To enable 'git archive --remote' and disable 'git fetch' against a repository, have the following in the configuration file in the repository (that is the file 'config' next to 'HEAD', 'refs' and 'objects'). @@ -272,7 +272,7 @@ selectively enable/disable services per repository:: ENVIRONMENT ----------- -'git-daemon' will set REMOTE_ADDR to the IP address of the client +'git daemon' will set REMOTE_ADDR to the IP address of the client that connected to it, if the IP address is available. REMOTE_ADDR will be available in the environment of hooks called when services are performed. diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index 2f97916781..6fc5323ee6 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 @@ -99,7 +106,7 @@ of commits which would be displayed by "git log v1.0.4..parent". The hash suffix is "-g" + 7-char abbreviation for the tip commit of parent (which was `2414721b194453f058079d897d13c4e377f92dc6`). -Doing a 'git-describe' on a tag-name will just show the tag name: +Doing a 'git describe' on a tag-name will just show the tag name: [torvalds@g5 git]$ git describe v1.0.4 v1.0.4 @@ -129,13 +136,13 @@ be sufficient to disambiguate these commits. SEARCH STRATEGY --------------- -For each committish supplied, 'git-describe' will first look for +For each committish supplied, 'git describe' will first look for a tag which tags exactly that commit. Annotated tags will always be preferred over lightweight tags, and tags with newer dates will always be preferred over tags with older dates. If an exact match is found, its name will be output and searching will stop. -If an exact match was not found, 'git-describe' will walk back +If an exact match was not found, 'git describe' will walk back through the commit history to locate an ancestor commit which has been tagged. The ancestor's tag will be output along with an abbreviation of the input committish's SHA1. diff --git a/Documentation/git-diff-files.txt b/Documentation/git-diff-files.txt index 4ef03578eb..9cd8ccef37 100644 --- a/Documentation/git-diff-files.txt +++ b/Documentation/git-diff-files.txt @@ -15,7 +15,7 @@ DESCRIPTION Compares the files in the working tree and the index. When paths are specified, compares only those named paths. Otherwise all entries in the index are compared. The output format is the -same as for 'git-diff-index' and 'git-diff-tree'. +same as for 'git diff-index' and 'git diff-tree'. OPTIONS ------- diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt index 8b9ed29299..162cb741b3 100644 --- a/Documentation/git-diff-index.txt +++ b/Documentation/git-diff-index.txt @@ -31,7 +31,7 @@ include::diff-options.txt[] -m:: By default, files recorded in the index but not checked out are reported as deleted. This flag makes - 'git-diff-index' say that all non-checked-out files are up + 'git diff-index' say that all non-checked-out files are up to date. include::diff-format.txt[] @@ -48,7 +48,7 @@ Cached Mode If '--cached' is specified, it allows you to ask: show me the differences between HEAD and the current index - contents (the ones I'd write using 'git-write-tree') + contents (the ones I'd write using 'git write-tree') For example, let's say that you have worked on your working directory, updated some files in the index and are ready to commit. You want to see exactly @@ -60,7 +60,7 @@ object and compare it that way, and to do that, you just do Example: let's say I had renamed `commit.c` to `git-commit.c`, and I had done an `update-index` to make that effective in the index file. `git diff-files` wouldn't show anything at all, since the index file -matches my working directory. But doing a 'git-diff-index' does: +matches my working directory. But doing a 'git diff-index' does: torvalds@ppc970:~/git> git diff-index --cached HEAD -100644 blob 4161aecc6700a2eb579e842af0b7f22b98443f74 commit.c @@ -69,10 +69,10 @@ matches my working directory. But doing a 'git-diff-index' does: You can see easily that the above is a rename. In fact, `git diff-index --cached` *should* always be entirely equivalent to -actually doing a 'git-write-tree' and comparing that. Except this one is much +actually doing a 'git write-tree' and comparing that. Except this one is much nicer for the case where you just want to check where you are. -So doing a 'git-diff-index --cached' is basically very useful when you are +So doing a `git diff-index --cached` is basically very useful when you are asking yourself "what have I already marked for being committed, and what's the difference to a previous tree". @@ -80,20 +80,20 @@ Non-cached Mode --------------- The "non-cached" mode takes a different approach, and is potentially the more useful of the two in that what it does can't be emulated with -a 'git-write-tree' + 'git-diff-tree'. Thus that's the default mode. +a 'git write-tree' + 'git diff-tree'. Thus that's the default mode. The non-cached version asks the question: show me the differences between HEAD and the currently checked out tree - index contents _and_ files that aren't up-to-date which is obviously a very useful question too, since that tells you what -you *could* commit. Again, the output matches the 'git-diff-tree -r' +you *could* commit. Again, the output matches the 'git diff-tree -r' output to a tee, but with a twist. The twist is that if some file doesn't match the index, we don't have a backing store thing for it, and we use the magic "all-zero" sha1 to show that. So let's say that you have edited `kernel/sched.c`, but -have not actually done a 'git-update-index' on it yet - there is no +have not actually done a 'git update-index' on it yet - there is no "object" associated with the new state, and you get: torvalds@ppc970:~/v2.6/linux> git diff-index HEAD @@ -104,11 +104,11 @@ not up-to-date and may contain new stuff. The all-zero sha1 means that to get the real diff, you need to look at the object in the working directory directly rather than do an object-to-object diff. -NOTE: As with other commands of this type, 'git-diff-index' does not +NOTE: As with other commands of this type, 'git diff-index' does not actually look at the contents of the file at all. So maybe `kernel/sched.c` hasn't actually changed, and it's just that you touched it. In either case, it's a note that you need to -'git-update-index' it to make the index be in sync. +'git update-index' it to make the index be in sync. NOTE: You can have a mixture of files show up as "has been updated" and "is still dirty in the working directory" together. You can always diff --git a/Documentation/git-diff-tree.txt b/Documentation/git-diff-tree.txt index f2cef1260b..a7e37b875f 100644 --- a/Documentation/git-diff-tree.txt +++ b/Documentation/git-diff-tree.txt @@ -20,7 +20,7 @@ Compares the content and mode of the blobs found via two tree objects. If there is only one <tree-ish> given, the commit is compared with its parents (see --stdin below). -Note that 'git-diff-tree' can use the tree encapsulated in a commit object. +Note that 'git diff-tree' can use the tree encapsulated in a commit object. OPTIONS ------- @@ -67,25 +67,25 @@ The following flags further affect the behavior when comparing commits (but not trees). -m:: - By default, 'git-diff-tree --stdin' does not show + By default, 'git diff-tree --stdin' does not show differences for merge commits. With this flag, it shows differences to that commit from all of its parents. See also '-c'. -s:: - By default, 'git-diff-tree --stdin' shows differences, + By default, 'git diff-tree --stdin' shows differences, either in machine-readable form (without '-p') or in patch form (with '-p'). This output can be suppressed. It is only useful with '-v' flag. -v:: - This flag causes 'git-diff-tree --stdin' to also show + This flag causes 'git diff-tree --stdin' to also show the commit message before the differences. include::pretty-options.txt[] --no-commit-id:: - 'git-diff-tree' outputs a line with the commit ID when + 'git diff-tree' outputs a line with the commit ID when applicable. This flag suppressed the commit ID output. -c:: diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 0ac711230e..723a64872f 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -157,6 +157,10 @@ $ git diff -R <2> rewrites (very expensive). <2> Output diff in reverse. +SEE ALSO +-------- +linkgit:git-difftool[1]:: + Show changes using common diff tools Author ------ diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index 96a6c51a4b..8250bad2ce 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -7,13 +7,13 @@ git-difftool - Show changes using common diff tools SYNOPSIS -------- -'git difftool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<'git diff' options>] +'git difftool' [<options>] <commit>{0,2} [--] [<path>...] DESCRIPTION ----------- -'git-difftool' is a git command that allows you to compare and edit files +'git difftool' is a git command that allows you to compare and edit files between revisions using common diff tools. 'git difftool' is a frontend -to 'git-diff' and accepts the same options and arguments. +to 'git diff' and accepts the same options and arguments. OPTIONS ------- @@ -31,25 +31,25 @@ 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' +If a diff tool is not specified, 'git difftool' will use the configuration variable `diff.tool`. If the -configuration variable `diff.tool` is not set, 'git-difftool' +configuration variable `diff.tool` is not set, 'git difftool' will pick a suitable default. + You can explicitly provide a full path to the tool by setting the configuration variable `difftool.<tool>.path`. For example, you can configure the absolute path to kdiff3 by setting -`difftool.kdiff3.path`. Otherwise, 'git-difftool' assumes the +`difftool.kdiff3.path`. Otherwise, 'git difftool' assumes the tool is available in PATH. + Instead of running one of the known diff tools, -'git-difftool' can be customized to run an alternative program +'git difftool' can be customized to run an alternative program by specifying the command line to invoke in a configuration variable `difftool.<tool>.cmd`. + -When 'git-difftool' is invoked with this tool (either through the +When 'git difftool' is invoked with this tool (either through the `-t` or `--tool` option or the `diff.tool` configuration variable) the configured command line will be invoked with the following variables available: `$LOCAL` is set to the name of the temporary @@ -58,16 +58,31 @@ is set to the name of the temporary file containing the contents of the diff post-image. `$BASE` is provided for compatibility with custom merge tool commands and has the same value as `$LOCAL`. +-x <command>:: +--extcmd=<command>:: + Specify a custom command for viewing diffs. + 'git-difftool' ignores the configured defaults and runs + `$command $LOCAL $REMOTE` when this option is specified. + +-g:: +--gui:: + When 'git-difftool' is invoked with the `-g` or `--gui` option + the default diff tool will be read from the configured + `diff.guitool` variable instead of `diff.tool`. + See linkgit:git-diff[1] for the full list of supported options. CONFIG VARIABLES ---------------- -'git-difftool' falls back to 'git-mergetool' config variables when the +'git difftool' falls back to 'git mergetool' config variables when the difftool equivalents have not been defined. diff.tool:: The default diff tool to use. +diff.guitool:: + The default diff tool to use when `--gui` is specified. + difftool.<tool>.path:: Override the path for the given tool. This is useful in case your tool is not in the PATH. diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt index 75b06f33e7..98ec6b5871 100644 --- a/Documentation/git-fast-export.txt +++ b/Documentation/git-fast-export.txt @@ -13,18 +13,18 @@ SYNOPSIS DESCRIPTION ----------- This program dumps the given revisions in a form suitable to be piped -into 'git-fast-import'. +into 'git fast-import'. You can use it as a human-readable bundle replacement (see linkgit:git-bundle[1]), or as a kind of an interactive -'git-filter-branch'. +'git filter-branch'. OPTIONS ------- --progress=<n>:: Insert 'progress' statements every <n> objects, to be shown by - 'git-fast-import' during import. + 'git fast-import' during import. --signed-tags=(verbatim|warn|strip|abort):: Specify how to handle signed tags. Since any transformation @@ -37,7 +37,7 @@ unsigned, with 'verbatim', they will be silently exported and with 'warn', they will be exported, but you will see a warning. --tag-of-filtered-object=(abort|drop|rewrite):: - Specify how to handle tags whose tagged objectis filtered out. + Specify how to handle tags whose tagged object is filtered out. Since revisions and files to export can be limited by path, tagged objects may be filtered completely. + @@ -91,8 +91,8 @@ marks the same across runs. already contains the necessary objects. [git-rev-list-args...]:: - A list of arguments, acceptable to 'git-rev-parse' and - 'git-rev-list', that specifies the specific objects and references + A list of arguments, acceptable to 'git rev-parse' and + 'git rev-list', that specifies the specific objects and references to export. For example, `master\~10..master` causes the current master reference to be exported along with all objects added since its 10th ancestor commit. @@ -125,7 +125,7 @@ referenced by that revision range contains the string Limitations ----------- -Since 'git-fast-import' cannot tag trees, you will not be +Since 'git fast-import' cannot tag trees, you will not be able to export the linux-2.6.git repository completely, as it contains a tag referencing a tree instead of a commit. diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index c2f483a8d2..19082b04eb 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -15,7 +15,7 @@ DESCRIPTION This program is usually not what the end user wants to run directly. Most end users want to use one of the existing frontend programs, which parses a specific type of foreign source and feeds the contents -stored there to 'git-fast-import'. +stored there to 'git fast-import'. fast-import reads a mixed command/data stream from standard input and writes one or more packfiles directly into the current repository. @@ -24,7 +24,7 @@ updated branch and tag refs, fully updating the current repository with the newly imported data. The fast-import backend itself can import into an empty repository (one that -has already been initialized by 'git-init') or incrementally +has already been initialized by 'git init') or incrementally update an existing populated repository. Whether or not incremental imports are supported from a particular foreign source depends on the frontend program in use. @@ -44,11 +44,14 @@ OPTIONS not contain the old commit). --max-pack-size=<n>:: - Maximum size of each output packfile, expressed in MiB. - The default is 4096 (4 GiB) as that is the maximum allowed - packfile size (due to file format limitations). Some - importers may wish to lower this, such as to ensure the - resulting packfiles fit on CDs. + Maximum size of each output packfile. + The default is unlimited. + +--big-file-threshold=<n>:: + Maximum size of a blob that fast-import will attempt to + create a delta for, expressed in bytes. The default is 512m + (512 MiB). Some importers may wish to lower this on systems + with constrained memory. --depth=<n>:: Maximum delta depth, for blob and tree deltification. @@ -75,6 +78,20 @@ OPTIONS set of marks. If a mark is defined to different values, the last file wins. +--relative-marks:: + After specifying --relative-marks= the paths specified + with --import-marks= and --export-marks= are relative + to an internal directory in the current repository. + In git-fast-import this means that the paths are relative + to the .git/info/fast-import directory. However, other + importers may use a different location. + +--no-relative-marks:: + Negates a previous --relative-marks. Allows for combining + relative and non-relative marks by interweaving + --(no-)-relative-marks= with the --(import|export)-marks= + options. + --export-pack-edges=<file>:: After creating a packfile, print a line of data to <file> listing the filename of the packfile and the last @@ -82,7 +99,7 @@ OPTIONS This information may be useful after importing projects whose total object set exceeds the 4 GiB packfile limit, as these commits can be used as edge points during calls - to 'git-pack-objects'. + to 'git pack-objects'. --quiet:: Disable all non-fatal output, making fast-import silent when it @@ -124,9 +141,9 @@ an ideal situation, given that most conversion tools are throw-away Parallel Operation ------------------ -Like 'git-push' or 'git-fetch', imports handled by fast-import are safe to +Like 'git push' or 'git fetch', imports handled by fast-import are safe to run alongside parallel `git repack -a -d` or `git gc` invocations, -or any other Git operation (including 'git-prune', as loose objects +or any other Git operation (including 'git prune', as loose objects are never used by fast-import). fast-import does not lock the branch or tag refs it is actively importing. @@ -138,7 +155,7 @@ fast-forward update, fast-import will skip updating that ref and instead prints a warning message. fast-import will always attempt to update all branch refs, and does not stop on the first failure. -Branch updates can be forced with \--force, but its recommended that +Branch updates can be forced with \--force, but it's recommended that this only be used on an otherwise quiet repository. Using \--force is not necessary for an initial import into an empty repository. @@ -220,7 +237,7 @@ variation in formatting will cause fast-import to reject the value. + An example value is ``Tue Feb 6 11:22:18 2007 -0500''. The Git parser is accurate, but a little on the lenient side. It is the -same parser used by 'git-am' when applying patches +same parser used by 'git am' when applying patches received from email. + Some malformed strings may be accepted as valid dates. In some of @@ -253,10 +270,10 @@ is always copied into the identity string at the time it is being created by fast-import. There is no way to specify a different time or timezone. + -This particular format is supplied as its short to implement and +This particular format is supplied as it's short to implement and may be useful to a process that wants to create a new commit right now, without needing to use a working directory or -'git-update-index'. +'git update-index'. + If separate `author` and `committer` commands are used in a `commit` the timestamps may not match, as the system clock will be polled @@ -303,6 +320,15 @@ and control the current import process. More detailed discussion standard output. This command is optional and is not needed to perform an import. +`feature`:: + Require that fast-import supports the specified feature, or + abort if it does not. + +`option`:: + Specify any of the options listed under OPTIONS that do not + change stream semantic to suit the frontend's needs. This + command is optional and is not needed to perform an import. + `commit` ~~~~~~~~ Create or update a branch with a new commit, recording one logical @@ -311,12 +337,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 +365,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). @@ -398,7 +423,7 @@ quoting or escaping syntax is supported within `<committish>`. Here `<committish>` is any of the following: * The name of an existing branch already in fast-import's internal branch - table. If fast-import doesn't know the name, its treated as a SHA-1 + table. If fast-import doesn't know the name, it's treated as a SHA-1 expression. * A mark reference, `:<idnum>`, where `<idnum>` is the mark number. @@ -595,6 +620,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 +683,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 .... @@ -657,7 +716,7 @@ recommended, as the frontend does not (easily) have access to the complete set of bytes which normally goes into such a signature. If signing is required, create lightweight tags from within fast-import with `reset`, then create the annotated versions of those tags offline -with the standard 'git-tag' process. +with the standard 'git tag' process. `reset` ~~~~~~~ @@ -703,7 +762,7 @@ assigned mark. The mark command is optional here as some frontends have chosen to generate the Git SHA-1 for the blob on their own, and feed that -directly to `commit`. This is typically more work than its worth +directly to `commit`. This is typically more work than it's worth however, as marks are inexpensive to store and easy to use. `data` @@ -813,6 +872,62 @@ Placing a `progress` command immediately after a `checkpoint` will inform the reader when the `checkpoint` has been completed and it can safely access the refs that fast-import updated. +`feature` +~~~~~~~~~ +Require that fast-import supports the specified feature, or abort if +it does not. + +.... + 'feature' SP <feature> LF +.... + +The <feature> part of the command may be any string matching +^[a-zA-Z][a-zA-Z-]*$ and should be understood by fast-import. + +Feature work identical as their option counterparts with the +exception of the import-marks feature, see below. + +The following features are currently supported: + +* date-format +* import-marks +* export-marks +* relative-marks +* no-relative-marks +* force + +The import-marks behaves differently from when it is specified as +commandline option in that only one "feature import-marks" is allowed +per stream. Also, any --import-marks= specified on the commandline +will override those from the stream (if any). + +`option` +~~~~~~~~ +Processes the specified option so that git fast-import behaves in a +way that suits the frontend's needs. +Note that options specified by the frontend are overridden by any +options the user may specify to git fast-import itself. + +.... + 'option' SP <option> LF +.... + +The `<option>` part of the command may contain any of the options +listed in the OPTIONS section that do not change import semantics, +without the leading '--' and is treated in the same way. + +Option commands must be the first commands on the input (not counting +feature commands), to give an option command after any non-option +command is an error. + +The following commandline options change import semantics and may therefore +not be passed as option: + +* date-format +* import-marks +* export-marks +* force + Crash Reports ------------- If fast-import is supplied invalid input it will terminate with a @@ -958,7 +1073,7 @@ is not `refs/heads/TAG_FIXUP`). When committing fixups, consider using `merge` to connect the commit(s) which are supplying file revisions to the fixup branch. -Doing so will allow tools such as 'git-blame' to track +Doing so will allow tools such as 'git blame' to track through the real commit history and properly annotate the source files. @@ -987,7 +1102,7 @@ Repacking Historical Data ~~~~~~~~~~~~~~~~~~~~~~~~~ If you are repacking very old imported data (e.g. older than the last year), consider expending some extra CPU time and supplying -\--window=50 (or higher) when you run 'git-repack'. +\--window=50 (or higher) when you run 'git repack'. This will take longer, but will also produce a smaller packfile. You only need to expend the effort once, and everyone using your project will benefit from the smaller repository. diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt index 47448da22e..4a8487c154 100644 --- a/Documentation/git-fetch-pack.txt +++ b/Documentation/git-fetch-pack.txt @@ -12,13 +12,13 @@ SYNOPSIS DESCRIPTION ----------- -Usually you would want to use 'git-fetch', which is a +Usually you would want to use 'git fetch', which is a higher level wrapper of this command, instead. Invokes 'git-upload-pack' on a possibly remote repository and asks it to send objects missing from this repository, to update the named heads. The list of commits available locally -is found out by scanning local $GIT_DIR/refs/ and sent to +is found out by scanning the local refs/ hierarchy and sent to 'git-upload-pack' running on the other end. This command degenerates to download everything to complete the @@ -33,19 +33,19 @@ OPTIONS -q:: --quiet:: - Pass '-q' flag to 'git-unpack-objects'; this makes the + Pass '-q' flag to 'git unpack-objects'; this makes the cloning process less verbose. -k:: --keep:: - Do not invoke 'git-unpack-objects' on received data, but + Do not invoke 'git unpack-objects' on received data, but create a single packfile out of it instead, and store it in the object database. If provided twice then the pack is locked against repacking. --thin:: - Spend extra cycles to minimize the number of objects to be sent. - Use it on slower connection. + Fetch a "thin" pack, which records objects in deltified form based + on objects not included in the pack to reduce network traffic. --include-tag:: If the remote side supports it, annotated tags objects will diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index f2483d624e..948ea26c5a 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -10,15 +10,21 @@ 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 -operation done by 'git-merge'. +operation done by 'git merge'. When <refspec> stores the fetched result in tracking branches, the tags that point at these branches are automatically @@ -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..020028cf9a 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -116,7 +116,7 @@ OPTIONS --commit-filter <command>:: This is the filter for performing the commit. If this filter is specified, it will be called instead of the - 'git-commit-tree' command, with arguments of the form + 'git commit-tree' command, with arguments of the form "<TREE_ID> [-p <PARENT_COMMIT_ID>]..." and the log message on stdin. The commit id is expected on stdout. + @@ -127,10 +127,10 @@ have all of them as parents. You can use the 'map' convenience function in this filter, and other convenience functions, too. For example, calling 'skip_commit "$@"' will leave out the current commit (but not its changes! If you want -that, use 'git-rebase' instead). +that, use 'git rebase' instead). + -You can also use the 'git_commit_non_empty_tree "$@"' instead of -'git commit-tree "$@"' if you don't wish to keep commits with a single parent +You can also use the `git_commit_non_empty_tree "$@"` instead of +`git commit-tree "$@"` if you don't wish to keep commits with a single parent and that makes no change to the tree. --tag-name-filter <command>:: @@ -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 @@ -168,7 +179,7 @@ to other tags will be rewritten to point to the underlying commit. and only one parent, it will hence keep merges points. Also, this option is not compatible with the use of '--commit-filter'. Though you just need to use the function 'git_commit_non_empty_tree "$@"' instead - of the 'git commit-tree "$@"' idiom in your commit filter to make that + of the `git commit-tree "$@"` idiom in your commit filter to make that happen. --original <namespace>:: @@ -185,15 +196,15 @@ to other tags will be rewritten to point to the underlying commit. -f:: --force:: - 'git-filter-branch' refuses to start with an existing temporary + 'git filter-branch' refuses to start with an existing temporary directory or when there are already refs starting with 'refs/original/', unless forced. <rev-list options>...:: - Arguments for 'git-rev-list'. All positive refs included by + Arguments for 'git rev-list'. All positive refs included by these options are rewritten. You may also specify options such as '--all', but you must use '--' to separate them from - the 'git-filter-branch' options. + the 'git filter-branch' options. Examples @@ -210,7 +221,7 @@ However, if the file is absent from the tree of some commit, a simple `rm filename` will fail for that tree and commit. Thus you may instead want to use `rm -f filename` as the script. -Using `\--index-filter` with 'git-rm' yields a significantly faster +Using `\--index-filter` with 'git rm' yields a significantly faster version. Like with using `rm filename`, `git rm --cached filename` will fail if the file is absent from the tree of a commit. If you want to "completely forget" a file, it does not matter when it entered @@ -292,7 +303,7 @@ and all children of the merge will become merge commits with P1,P2 as their parents instead of the merge commit. You can rewrite the commit log messages using `--msg-filter`. For -example, 'git-svn-id' strings in a repository created by 'git-svn' can +example, 'git svn-id' strings in a repository created by 'git svn' can be removed this way: ------------------------------------------------------- @@ -303,7 +314,7 @@ git filter-branch --msg-filter ' To restrict rewriting to only part of the history, specify a revision range in addition to the new branch name. The new branch name will -point to the top-most revision that a 'git-rev-list' of this range +point to the top-most revision that a 'git rev-list' of this range will print. If you need to add 'Acked-by' lines to, say, the last 10 commits (none @@ -319,7 +330,7 @@ git filter-branch --msg-filter ' *NOTE* the changes introduced by the commits, and which are not reverted by subsequent commits, will still be in the rewritten branch. If you want to throw out _changes_ together with the commits, you should use the -interactive mode of 'git-rebase'. +interactive mode of 'git rebase'. Consider this history: @@ -347,7 +358,7 @@ To move the whole tree into a subdirectory, or remove it from there: --------------------------------------------------------------- git filter-branch --index-filter \ - 'git ls-files -s | sed "s-\t-&newsubdir/-" | + 'git ls-files -s | sed "s-\t\"*-&newsubdir/-" | GIT_INDEX_FILE=$GIT_INDEX_FILE.new \ git update-index --index-info && mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD diff --git a/Documentation/git-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt index a586950b48..a585dbe898 100644 --- a/Documentation/git-fmt-merge-msg.txt +++ b/Documentation/git-fmt-merge-msg.txt @@ -16,7 +16,7 @@ DESCRIPTION ----------- Takes the list of merged objects on stdin and produces a suitable commit message to be used for the merge commit, usually to be -passed as the '<merge-message>' argument of 'git-merge'. +passed as the '<merge-message>' argument of 'git merge'. This command is intended mostly for internal use by scripts automatically invoking 'git merge'. diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 8dc873fd44..7e83288d18 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -82,7 +82,7 @@ objecttype:: The type of the object (`blob`, `tree`, `commit`, `tag`). objectsize:: - The size of the object (the same as 'git-cat-file -s' reports). + The size of the object (the same as 'git cat-file -s' reports). objectname:: The object name (aka SHA-1). diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 687e667598..9674f9de67 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -29,7 +29,7 @@ DESCRIPTION Prepare each commit with its patch in one file per commit, formatted to resemble UNIX mailbox format. The output of this command is convenient for e-mail submission or -for use with 'git-am'. +for use with 'git am'. There are two ways to specify which commits to operate on. @@ -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 @@ -221,7 +221,7 @@ EXAMPLES -------- * Extract commits between revisions R1 and R2, and apply them on top of -the current branch using 'git-am' to cherry-pick them: +the current branch using 'git am' to cherry-pick them: + ------------ $ git format-patch -k --stdout R1..R2 | git am -3 -k diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index 287c4fc5e0..3ad48a6336 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 ----------- @@ -21,7 +21,7 @@ OPTIONS <object>:: An object to treat as the head of an unreachability trace. + -If no objects are given, 'git-fsck' defaults to using the +If no objects are given, 'git fsck' defaults to using the index file, all SHA1 references in .git/refs/*, and all reflogs (unless --no-reflogs is given) as heads. @@ -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 @@ -84,7 +85,7 @@ So for example will do quite a _lot_ of verification on the tree. There are a few extra validity tests to be added (make sure that tree objects are -sorted properly etc), but on the whole if 'git-fsck' is happy, you +sorted properly etc), but on the whole if 'git fsck' is happy, you do have a valid tree. Any corrupt objects you will have to find in backups or other archives diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 4cd9cdf905..189573a3b3 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -15,13 +15,13 @@ DESCRIPTION Runs a number of housekeeping tasks within the current repository, such as compressing file revisions (to reduce disk space and increase performance) and removing unreachable objects which may have been -created from prior invocations of 'git-add'. +created from prior invocations of 'git add'. Users are encouraged to run this task on a regular basis within each repository to maintain good disk space utilization and good operating performance. -Some git commands may automatically run 'git-gc'; see the `--auto` flag +Some git commands may automatically run 'git gc'; see the `--auto` flag below for details. If you know what you're doing and all you want is to disable this behavior permanently without further considerations, just do: @@ -33,15 +33,15 @@ OPTIONS ------- --aggressive:: - Usually 'git-gc' runs very quickly while providing good disk + Usually 'git gc' runs very quickly while providing good disk space utilization and performance. This option will cause - 'git-gc' to more aggressively optimize the repository at the expense + 'git gc' to more aggressively optimize the repository at the expense of taking much more time. The effects of this optimization are persistent, so this option only needs to be used occasionally; every few hundred changesets or so. --auto:: - With this option, 'git-gc' checks whether any housekeeping is + With this option, 'git gc' checks whether any housekeeping is required; if not, it exits without performing any work. Some git commands run `git gc --auto` after performing operations that could create many loose objects. @@ -50,13 +50,13 @@ Housekeeping is required if there are too many loose objects or too many packs in the repository. If the number of loose objects exceeds the value of the `gc.auto` configuration variable, then all loose objects are combined into a single pack using -'git-repack -d -l'. Setting the value of `gc.auto` to 0 +`git repack -d -l`. Setting the value of `gc.auto` to 0 disables automatic packing of loose objects. + If the number of packs exceeds the value of `gc.autopacklimit`, then existing packs (except those marked with a `.keep` file) are consolidated into a single pack by using the `-A` option of -'git-repack'. Setting `gc.autopacklimit` to 0 disables +'git repack'. Setting `gc.autopacklimit` to 0 disables automatic consolidation of packs. --prune=<date>:: @@ -97,7 +97,7 @@ how long records of conflicted merge you have not resolved are kept. This defaults to 15 days. The optional configuration variable 'gc.packrefs' determines if -'git-gc' runs 'git-pack-refs'. This can be set to "nobare" to enable +'git gc' runs 'git pack-refs'. This can be set to "nobare" to enable it within all non-bare repos or it can be set to a boolean value. This defaults to true. @@ -116,10 +116,10 @@ default is "2 weeks ago". Notes ----- -'git-gc' tries very hard to be safe about the garbage it collects. In +'git gc' tries very hard to be safe about the garbage it collects. In particular, it will keep not only objects referenced by your current set of branches and tags, but also objects referenced by the index, remote -tracking branches, refs saved by 'git-filter-branch' in +tracking branches, refs saved by 'git filter-branch' in refs/original/, or reflogs (which may reference commits in branches that were later amended or rewound). diff --git a/Documentation/git-get-tar-commit-id.txt b/Documentation/git-get-tar-commit-id.txt index 84f23ee525..790af9573b 100644 --- a/Documentation/git-get-tar-commit-id.txt +++ b/Documentation/git-get-tar-commit-id.txt @@ -14,12 +14,12 @@ SYNOPSIS DESCRIPTION ----------- Acts as a filter, extracting the commit ID stored in archives created by -'git-archive'. It reads only the first 1024 bytes of input, thus its +'git archive'. It reads only the first 1024 bytes of input, thus its runtime is not influenced by the size of <tarfile> very much. -If no commit ID is found, 'git-get-tar-commit-id' quietly exists with a +If no commit ID is found, 'git get-tar-commit-id' quietly exists with a return code of 1. This can happen if <tarfile> had not been created -using 'git-archive' or if the first parameter of 'git-archive' had been +using 'git archive' or if the first parameter of 'git archive' had been a tree ID instead of a commit ID or tag. diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 8c700200f5..c44724d03a 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -16,18 +16,18 @@ SYNOPSIS [-F | --fixed-strings] [-n] [-l | --files-with-matches] [-L | --files-without-match] [-z | --null] - [-c | --count] [--all-match] + [-c | --count] [--all-match] [-q | --quiet] [--max-depth <depth>] [--color | --no-color] [-A <post-context>] [-B <pre-context>] [-C <context>] [-f <file>] [-e] <pattern> [--and|--or|--not|(|)|-e <pattern>...] [<tree>...] - [--] [<path>...] + [--] [<pathspec>...] DESCRIPTION ----------- -Look for specified patterns in the working tree files, blobs -registered in the index file, or given tree objects. +Look for specified patterns in the tracked files in the work tree, blobs +registered in the index file, or blobs in given tree objects. OPTIONS @@ -49,7 +49,7 @@ OPTIONS Don't match the pattern in binary files. --max-depth <depth>:: - For each pathspec given on command line, descend at most <depth> + For each <pathspec> given on command line, descend at most <depth> levels of directories. A negative value means no limit. -w:: @@ -98,7 +98,7 @@ OPTIONS --files-without-match:: Instead of showing every matched line, show only the names of files that contain (or do not contain) matches. - For better compatibility with 'git-diff', --name-only is a + For better compatibility with 'git diff', --name-only is a synonym for --files-with-matches. -z:: @@ -158,17 +158,29 @@ OPTIONS this flag is specified to limit the match to files that have lines to match all of them. +-q:: +--quiet:: + Do not output matched lines; instead, exit with status 0 when + there is a match and with non-zero status when there isn't. + `<tree>...`:: Search blobs in the trees for specified patterns. \--:: Signals the end of options; the rest of the parameters - are <path> limiters. + are <pathspec> limiters. +<pathspec>...:: + If given, limit the search to paths matching at least one pattern. + Both leading paths match and glob(7) patterns are supported. Example ------- +git grep 'time_t' -- '*.[ch]':: + Looks for `time_t` in all tracked .c and .h files in the working + directory and its subdirectories. + git grep -e \'#define\' --and \( -e MAX_PATH -e PATH_MAX \):: Looks for a line that has `#define` and either `MAX_PATH` or `PATH_MAX`. diff --git a/Documentation/git-gui.txt b/Documentation/git-gui.txt index d0bc98b852..2563710b56 100644 --- a/Documentation/git-gui.txt +++ b/Documentation/git-gui.txt @@ -11,19 +11,19 @@ SYNOPSIS DESCRIPTION ----------- -A Tcl/Tk based graphical user interface to Git. 'git-gui' focuses +A Tcl/Tk based graphical user interface to Git. 'git gui' focuses on allowing users to make changes to their repository by making new commits, amending existing ones, creating branches, performing local merges, and fetching/pushing to remote repositories. -Unlike 'gitk', 'git-gui' focuses on commit generation +Unlike 'gitk', 'git gui' focuses on commit generation and single file annotation and does not show project history. It does however supply menu actions to start a 'gitk' session from -within 'git-gui'. +within 'git gui'. -'git-gui' is known to work on all popular UNIX systems, Mac OS X, +'git gui' is known to work on all popular UNIX systems, Mac OS X, and Windows (under both Cygwin and MSYS). To the extent possible -OS specific user interface guidelines are followed, making 'git-gui' +OS specific user interface guidelines are followed, making 'git gui' a fairly native interface for users. COMMANDS @@ -38,13 +38,13 @@ browser:: browser are opened in the blame viewer. citool:: - Start 'git-gui' and arrange to make exactly one commit before + Start 'git gui' and arrange to make exactly one commit before exiting and returning to the shell. The interface is limited to only commit actions, slightly reducing the application's startup time and simplifying the menubar. version:: - Display the currently running version of 'git-gui'. + Display the currently running version of 'git gui'. Examples @@ -103,15 +103,15 @@ SEE ALSO linkgit:gitk[1]:: The git repository browser. Shows branches, commit history and file differences. gitk is the utility started by - 'git-gui''s Repository Visualize actions. + 'git gui''s Repository Visualize actions. Other ----- -'git-gui' is actually maintained as an independent project, but stable +'git gui' is actually maintained as an independent project, but stable versions are distributed as part of the Git suite for the convenience of end users. -A 'git-gui' development repository can be obtained from: +A 'git gui' development repository can be obtained from: git clone git://repo.or.cz/git-gui.git diff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt index 0af40cfb85..479fce4693 100644 --- a/Documentation/git-hash-object.txt +++ b/Documentation/git-hash-object.txt @@ -18,7 +18,7 @@ Computes the object ID value for an object with specified type with the contents of the named file (which can be outside of the work tree), and optionally writes the resulting object into the object database. Reports its object ID to its standard output. -This is used by 'git-cvsimport' to update the index +This is used by 'git cvsimport' to update the index without modifying files in the work tree. When <type> is not specified, it defaults to "blob". diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt index d9b9c34b3a..f8df109d07 100644 --- a/Documentation/git-help.txt +++ b/Documentation/git-help.txt @@ -55,8 +55,8 @@ other display programs (see below). + The web browser can be specified using the configuration variable 'help.browser', or 'web.browser' if the former is not set. If none of -these config variables is set, the 'git-web--browse' helper script -(called by 'git-help') will pick a suitable default. See +these config variables is set, the 'git web--browse' helper script +(called by 'git help') will pick a suitable default. See linkgit:git-web--browse[1] for more information about this. CONFIGURATION VARIABLES @@ -67,7 +67,7 @@ help.format If no command line option is passed, the 'help.format' configuration variable will be checked. The following values are supported for this -variable; they make 'git-help' behave as their corresponding command +variable; they make 'git help' behave as their corresponding command line option: * "man" corresponds to '-m|--man', @@ -122,7 +122,7 @@ man.<tool>.path You can explicitly provide a full path to your preferred man viewer by setting the configuration variable 'man.<tool>.path'. For example, you can configure the absolute path to konqueror by setting -'man.konqueror.path'. Otherwise, 'git-help' assumes the tool is +'man.konqueror.path'. Otherwise, 'git help' assumes the tool is available in PATH. man.<tool>.cmd diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt new file mode 100644 index 0000000000..5238820657 --- /dev/null +++ b/Documentation/git-http-backend.txt @@ -0,0 +1,188 @@ +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 protocol +and the backwards-compatible dumb HTTP protocol, as well as clients +pushing using the smart HTTP protocol. + +It verifies that the directory has the magic file +"git-daemon-export-ok", and it will refuse to export any git directory +that hasn't explicitly been marked for export this way (unless the +GIT_HTTP_EXPORT_ALL environmental variable is set). + +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 +SetEnv GIT_HTTP_EXPORT_ALL +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 GIT_HTTP_EXPORT_ALL environmental variable may be passed to +'git-http-backend' to bypass the check for the "git-daemon-export-ok" +file in each repository before allowing export of that repository. + +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-fetch.txt b/Documentation/git-http-fetch.txt index e7c796155f..d91cb7ff85 100644 --- a/Documentation/git-http-fetch.txt +++ b/Documentation/git-http-fetch.txt @@ -35,7 +35,7 @@ commit-id:: --stdin:: Instead of a commit id on the command line (which is not expected in this - case), 'git-http-fetch' expects lines on stdin in the format + case), 'git http-fetch' expects lines on stdin in the format <commit-id>['\t'<filename-as-in--w>] 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-imap-send.txt b/Documentation/git-imap-send.txt index d016dafd49..57db955bd4 100644 --- a/Documentation/git-imap-send.txt +++ b/Documentation/git-imap-send.txt @@ -13,7 +13,7 @@ SYNOPSIS DESCRIPTION ----------- -This command uploads a mailbox generated with 'git-format-patch' +This command uploads a mailbox generated with 'git format-patch' into an IMAP drafts folder. This allows patches to be sent as other email is when using mail clients that cannot read mailbox files directly. diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index 4b5c743c1e..f3ccc72f0d 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -43,23 +43,19 @@ OPTIONS a default name determined from the pack content. If <pack-file> is not specified consider using --keep to prevent a race condition between this process and - 'git-repack'. + 'git repack'. --fix-thin:: - It is possible for 'git-pack-objects' to build - "thin" pack, which records objects in deltified form based on - objects not included in the pack to reduce network traffic. - Those objects are expected to be present on the receiving end - and they must be included in the pack for that pack to be self - contained and indexable. Without this option any attempt to - index a thin pack will fail. This option only makes sense in - conjunction with --stdin. + Fix a "thin" pack produced by `git pack-objects --thin` (see + linkgit:git-pack-objects[1] for details) by adding the + excluded objects the deltified objects are based on to the + pack. This option only makes sense in conjunction with --stdin. --keep:: Before moving the index into its final destination create an empty .keep file for the associated pack file. This option is usually necessary with --stdin to prevent a - simultaneous 'git-repack' process from deleting + simultaneous 'git repack' process from deleting the newly constructed pack and index before refs can be updated to use objects contained in the pack. @@ -86,7 +82,7 @@ Once the index has been created, the list of object names is sorted and the SHA1 hash of that list is printed to stdout. If --stdin was also used then this is prefixed by either "pack\t", or "keep\t" if a new .keep file was successfully created. This is useful to remove a -.keep file used as a lock to prevent the race with 'git-repack' +.keep file used as a lock to prevent the race with 'git repack' mentioned above. diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt index f081b24d9d..7ee102da48 100644 --- a/Documentation/git-init.txt +++ b/Documentation/git-init.txt @@ -95,11 +95,11 @@ If the object storage directory is specified via the `$GIT_OBJECT_DIRECTORY` environment variable then the sha1 directories are created underneath - otherwise the default `$GIT_DIR/objects` directory is used. -Running 'git-init' in an existing repository is safe. It will not overwrite -things that are already there. The primary reason for rerunning 'git-init' +Running 'git init' in an existing repository is safe. It will not overwrite +things that are already there. The primary reason for rerunning 'git init' is to pick up newly added templates. -Note that 'git-init' is the same as 'git-init-db'. The command +Note that 'git init' is the same as 'git init-db'. The command was primarily meant to initialize the object database, but over time it has become responsible for setting up the other aspects of the repository, such as installing the default hooks and diff --git a/Documentation/git-instaweb.txt b/Documentation/git-instaweb.txt index 0771f25443..a1f17df074 100644 --- a/Documentation/git-instaweb.txt +++ b/Documentation/git-instaweb.txt @@ -44,7 +44,7 @@ OPTIONS -b:: --browser:: The web browser that should be used to view the gitweb - page. This will be passed to the 'git-web--browse' helper + page. This will be passed to the 'git web--browse' helper script along with the URL of the gitweb instance. See linkgit:git-web--browse[1] for more information about this. If the script fails, the URL will be printed to stdout. diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 3d79de11ec..0e39bb61ee 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -14,9 +14,9 @@ DESCRIPTION ----------- Shows the commit logs. -The command takes options applicable to the 'git-rev-list' +The command takes options applicable to the 'git rev-list' command to control what is shown and how, and options applicable to -the 'git-diff-*' commands to control how the changes +the 'git diff-*' commands to control how the changes each commit introduces are shown. @@ -107,6 +107,17 @@ git log --follow builtin-rev-list.c:: those commits that occurred before the file was given its present name. +git log --branches --not --remotes=origin:: + + Shows all commits that are in any of local branches but not in + any of remote tracking branches for 'origin' (what you have that + origin doesn't). + +git log master --not --remotes=*/master:: + + Shows all commits that are in local master but not in any remote + repository master branches. + Discussion ---------- diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 625723e41f..3521637b58 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -109,6 +109,7 @@ OPTIONS Identify the file status with the following tags (followed by a space) at the start of each line: H:: cached + S:: skip-worktree M:: unmerged R:: removed/deleted C:: modified/changed @@ -140,12 +141,12 @@ OPTIONS Output ------ -show files just outputs the filename unless '--stage' is specified in +'git ls-files' just outputs the filenames unless '--stage' is specified in which case it outputs: [<tag> ]<mode> <object> <stage> <file> -'git-ls-files --unmerged' and 'git-ls-files --stage' can be used to examine +'git ls-files --unmerged' and 'git ls-files --stage' can be used to examine detailed information on unmerged paths. For an unmerged path, instead of recording a single mode/SHA1 pair, @@ -162,7 +163,7 @@ respectively. Exclude Patterns ---------------- -'git-ls-files' can use a list of "exclude patterns" when +'git ls-files' can use a list of "exclude patterns" when traversing the directory tree and finding files to show when the flags --others or --ignored are specified. linkgit:gitignore[5] specifies the format of exclude patterns. @@ -178,7 +179,7 @@ These exclude patterns come from these places, in order: in the same order they appear in the file. 3. command line flag --exclude-per-directory=<name> specifies - a name of the file in each directory 'git-ls-files' + a name of the file in each directory 'git ls-files' examines, normally `.gitignore`. Files in deeper directories take precedence. Patterns are ordered in the same order they appear in the files. diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt index c3fdccb4c2..1f89d36800 100644 --- a/Documentation/git-ls-tree.txt +++ b/Documentation/git-ls-tree.txt @@ -28,7 +28,7 @@ in the current working directory. Note that: in a directory 'sub' that has a directory 'dir', you can run 'git ls-tree -r HEAD dir' to list the contents of the tree (that is 'sub/dir' in 'HEAD'). You don't want to give a tree that is not at the - root level (e.g. 'git ls-tree -r HEAD:sub dir') in this case, as that + root level (e.g. `git ls-tree -r HEAD:sub dir`) in this case, as that would result in asking for 'sub/sub/dir' in the 'HEAD' commit. However, the current working directory can be ignored by passing --full-tree option. @@ -84,7 +84,7 @@ Output Format Unless the `-z` option is used, TAB, LF, and backslash characters in pathnames are represented as `\t`, `\n`, and `\\`, respectively. -This output format is compatible with what '--index-info --stdin' of +This output format is compatible with what `--index-info --stdin` of 'git update-index' expects. When the `-l` option is used, format changes to diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt index 996c3fcc6c..e3d58cbac3 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 @@ -16,7 +16,7 @@ DESCRIPTION Reads a single e-mail message from the standard input, and writes the commit log message in <msg> file, and the patches in <patch> file. The author name, e-mail and e-mail subject are -written out to the standard output to be used by 'git-am' +written out to the standard output to be used by 'git am' to create a commit. It is usually not necessary to use this command directly. See linkgit:git-am[1] instead. @@ -30,7 +30,12 @@ OPTIONS whitespaces, (3) '[' up to ']', typically '[PATCH]', and then prepends "[PATCH] ". This flag forbids this munging, and is most useful when used to read back - 'git-format-patch -k' output. + '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 diff --git a/Documentation/git-merge-file.txt b/Documentation/git-merge-file.txt index 303537357b..234269ae59 100644 --- a/Documentation/git-merge-file.txt +++ b/Documentation/git-merge-file.txt @@ -10,20 +10,21 @@ SYNOPSIS -------- [verse] 'git merge-file' [-L <current-name> [-L <base-name> [-L <other-name>]]] - [-p|--stdout] [-q|--quiet] <current-file> <base-file> <other-file> + [--ours|--theirs] [-p|--stdout] [-q|--quiet] + <current-file> <base-file> <other-file> DESCRIPTION ----------- -'git-merge-file' incorporates all changes that lead from the `<base-file>` +'git merge-file' incorporates all changes that lead from the `<base-file>` to `<other-file>` into `<current-file>`. The result ordinarily goes into -`<current-file>`. 'git-merge-file' is useful for combining separate changes +`<current-file>`. 'git merge-file' is useful for combining separate changes to an original. Suppose `<base-file>` is the original, and both `<current-file>` and `<other-file>` are modifications of `<base-file>`, -then 'git-merge-file' combines both changes. +then 'git merge-file' combines both changes. A conflict occurs if both `<current-file>` and `<other-file>` have changes -in a common segment of lines. If a conflict is found, 'git-merge-file' +in a common segment of lines. If a conflict is found, 'git merge-file' normally outputs a warning and brackets the conflict with lines containing <<<<<<< and >>>>>>> markers. A typical conflict will look like this: @@ -34,12 +35,14 @@ normally outputs a warning and brackets the conflict with lines containing >>>>>>> B If there are conflicts, the user should edit the result and delete one of -the alternatives. +the alternatives. When `--ours` or `--theirs` option is in effect, however, +these conflicts are resolved favouring lines from `<current-file>` or +lines from `<other-file>` respectively. The exit value of this program is negative on error, and the number of conflicts otherwise. If the merge was clean, the exit value is 0. -'git-merge-file' is designed to be a minimal clone of RCS 'merge'; that is, it +'git merge-file' is designed to be a minimal clone of RCS 'merge'; that is, it implements all of RCS 'merge''s functionality which is needed by linkgit:git[1]. @@ -62,6 +65,11 @@ OPTIONS -q:: Quiet; do not warn about conflicts. +--ours:: +--theirs:: + Instead of leaving conflicts in the file, resolve conflicts + favouring our (or their) side of the lines. + EXAMPLES -------- diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt index 123e6d024a..4d266de9cc 100644 --- a/Documentation/git-merge-index.txt +++ b/Documentation/git-merge-index.txt @@ -36,14 +36,14 @@ OPTIONS failure usually indicates conflicts during the merge). This is for porcelains which might want to emit custom messages. -If 'git-merge-index' is called with multiple <file>s (or -a) then it +If 'git merge-index' is called with multiple <file>s (or -a) then it processes them in turn only stopping if merge returns a non-zero exit code. Typically this is run with a script calling git's imitation of the 'merge' command from the RCS package. -A sample script called 'git-merge-one-file' is included in the +A sample script called 'git merge-one-file' is included in the distribution. ALERT ALERT ALERT! The git "merge object order" is different from the @@ -68,10 +68,10 @@ or This is added AA in the branch B. fatal: merge program failed -where the latter example shows how 'git-merge-index' will stop trying to +where the latter example shows how 'git merge-index' will stop trying to merge once anything has returned an error (i.e., `cat` returned an error for the AA file, because it didn't exist in the original, and thus -'git-merge-index' didn't even try to merge the MM thing). +'git merge-index' didn't even try to merge the MM thing). Author ------ diff --git a/Documentation/git-merge-one-file.txt b/Documentation/git-merge-one-file.txt index dc8a96adb0..a163cfca69 100644 --- a/Documentation/git-merge-one-file.txt +++ b/Documentation/git-merge-one-file.txt @@ -8,12 +8,12 @@ git-merge-one-file - The standard helper program to use with git-merge-index SYNOPSIS -------- -'git-merge-one-file' +'git merge-one-file' DESCRIPTION ----------- -This is the standard helper program to use with 'git-merge-index' -to resolve a merge after the trivial merge done with 'git-read-tree -m'. +This is the standard helper program to use with 'git merge-index' +to resolve a merge after the trivial merge done with 'git read-tree -m'. Author ------ diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index e886c2ef54..9c9618cead 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -10,17 +10,45 @@ SYNOPSIS -------- [verse] 'git merge' [-n] [--stat] [--no-commit] [--squash] [-s <strategy>]... - [-m <msg>] <remote>... -'git merge' <msg> HEAD <remote>... + [--[no-]rerere-autoupdate] [-m <msg>] <commit>... +'git merge' <msg> HEAD <commit>... DESCRIPTION ----------- -This is the top-level interface to the merge machinery -which drives multiple merge strategy scripts. +Incorporates changes from the named commits (since the time their +histories diverged from the current branch) into the current +branch. This command is used by 'git pull' to incorporate changes +from another repository and can be used by hand to merge changes +from one branch into another. -The second syntax (<msg> `HEAD` <remote>) is supported for +Assume the following history exists and the current branch is +"`master`": + +------------ + A---B---C topic + / + D---E---F---G master +------------ + +Then "`git merge topic`" will replay the changes made on the +`topic` branch since it diverged from `master` (i.e., `E`) until +its current commit (`C`) on top of `master`, and record the result +in a new commit along with the names of the two parent commits and +a log message from the user describing the changes. + +------------ + A---B---C topic + / \ + D---E---F---G---H master +------------ + +The second syntax (<msg> `HEAD` <commit>...) is supported for historical reasons. Do not use it from the command line or in -new scripts. It is the same as `git merge -m <msg> <remote>`. +new scripts. It is the same as `git merge -m <msg> <commit>...`. + +*Warning*: Running 'git merge' with uncommitted changes is +discouraged: while possible, it leaves you in a state that is hard to +back out of in the case of a conflict. OPTIONS @@ -33,93 +61,83 @@ include::merge-options.txt[] used to give a good default for automated 'git merge' invocations. -<remote>...:: - Other branch heads to merge into our branch. You need at - least one <remote>. Specifying more than one <remote> - obviously means you are trying an Octopus. +--rerere-autoupdate:: +--no-rerere-autoupdate:: + Allow the rerere mechanism to update the index with the + result of auto-conflict resolution if possible. -include::merge-strategies.txt[] +<commit>...:: + Commits, usually other branch heads, to merge into our branch. + You need at least one <commit>. Specifying more than one + <commit> obviously means you are trying an Octopus. -If you tried a merge which resulted in complex conflicts and -want to start over, you can recover with 'git-reset'. +PRE-MERGE CHECKS +---------------- -CONFIGURATION -------------- -include::merge-config.txt[] +Before applying outside changes, you should get your own work in +good shape and committed locally, so it will not be clobbered if +there are conflicts. See also linkgit:git-stash[1]. +'git pull' and 'git merge' will stop without doing anything when +local uncommitted changes overlap with files that 'git pull'/'git +merge' may need to update. -branch.<name>.mergeoptions:: - Sets default options for merging into branch <name>. The syntax and - supported options are the same as those of 'git merge', but option - values containing whitespace characters are currently not supported. +To avoid recording unrelated changes in the merge commit, +'git pull' and 'git merge' will also abort if there are any changes +registered in the index relative to the `HEAD` commit. (One +exception is when the changed index entries are in the state that +would result from the merge already.) -HOW MERGE WORKS ---------------- - -A merge is always between the current `HEAD` and one or more -commits (usually, branch head or tag), and the index file must -match the tree of `HEAD` commit (i.e. the contents of the last commit) -when it starts out. In other words, `git diff --cached HEAD` must -report no changes. (One exception is when the changed index -entries are already in the same state that would result from -the merge anyway.) - -Three kinds of merge can happen: - -* The merged commit is already contained in `HEAD`. This is the - simplest case, called "Already up-to-date." - -* `HEAD` is already contained in the merged commit. This is the - most common case especially when invoked from 'git pull': - you are tracking an upstream repository, have committed no local - changes and now you want to update to a newer upstream revision. - Your `HEAD` (and the index) is updated to point at the merged - commit, without creating an extra merge commit. This is - called "Fast-forward". - -* Both the merged commit and `HEAD` are independent and must be - tied together by a merge commit that has both of them as its parents. - The rest of this section describes this "True merge" case. - -The chosen merge strategy merges the two commits into a single -new source tree. -When things merge cleanly, this is what happens: - -1. The results are updated both in the index file and in your - working tree; -2. Index file is written out as a tree; -3. The tree gets committed; and -4. The `HEAD` pointer gets advanced. - -Because of 2., we require that the original state of the index -file matches exactly the current `HEAD` commit; otherwise we -will write out your local changes already registered in your -index file along with the merge result, which is not good. -Because 1. involves only those paths differing between your -branch and the remote branch you are pulling from during the -merge (which is typically a fraction of the whole tree), you can -have local modifications in your working tree as long as they do -not overlap with what the merge updates. - -When there are conflicts, the following happens: - -1. `HEAD` stays the same. - -2. Cleanly merged paths are updated both in the index file and - in your working tree. +If all named commits are already ancestors of `HEAD`, 'git merge' +will exit early with the message "Already up-to-date." + +FAST-FORWARD MERGE +------------------ + +Often the current branch head is an ancestor of the named commit. +This is the most common case especially when invoked from 'git +pull': you are tracking an upstream repository, you have committed +no local changes, and now you want to update to a newer upstream +revision. In this case, a new commit is not needed to store the +combined history; instead, the `HEAD` (along with the index) is +updated to point at the named commit, without creating an extra +merge commit. -3. For conflicting paths, the index file records up to three - versions; stage1 stores the version from the common ancestor, - stage2 from `HEAD`, and stage3 from the remote branch (you +This behavior can be suppressed with the `--no-ff` option. + +TRUE MERGE +---------- + +Except in a fast-forward merge (see above), the branches to be +merged must be tied together by a merge commit that has both of them +as its parents. + +A merged version reconciling the changes from all branches to be +merged is committed, and your `HEAD`, index, and working tree are +updated to it. It is possible to have modifications in the working +tree as long as they do not overlap; the update will preserve them. + +When it is not obvious how to reconcile the changes, the following +happens: + +1. The `HEAD` pointer stays the same. +2. The `MERGE_HEAD` ref is set to point to the other branch head. +3. Paths that merged cleanly are updated both in the index file and + in your working tree. +4. For conflicting paths, the index file records up to three + versions: stage 1 stores the version from the common ancestor, + stage 2 from `HEAD`, and stage 3 from `MERGE_HEAD` (you can inspect the stages with `git ls-files -u`). The working tree files contain the result of the "merge" program; i.e. 3-way - merge results with familiar conflict markers `<<< === >>>`. - -4. No other changes are done. In particular, the local + merge results with familiar conflict markers `<<<` `===` `>>>`. +5. No other changes are made. In particular, the local modifications you had before you started merge will stay the same and the index entries for them stay as they were, i.e. matching `HEAD`. +If you tried a merge which resulted in complex conflicts and +want to start over, you can recover with `git reset --merge`. + HOW CONFLICTS ARE PRESENTED --------------------------- @@ -189,28 +207,30 @@ After seeing a conflict, you can do two things: * Decide not to merge. The only clean-ups you need are to reset the index file to the `HEAD` commit to reverse 2. and to clean - up working tree changes made by 2. and 3.; 'git-reset --hard' can + up working tree changes made by 2. and 3.; `git-reset --hard` can be used for this. * Resolve the conflicts. Git will mark the conflicts in the working tree. Edit the files into shape and - 'git-add' them to the index. Use 'git-commit' to seal the deal. + 'git add' them to the index. Use 'git commit' to seal the deal. You can work through the conflict with a number of tools: - * Use a mergetool. 'git mergetool' to launch a graphical + * Use a mergetool. `git mergetool` to launch a graphical mergetool which will work you through the merge. - * Look at the diffs. 'git diff' will show a three-way diff, - highlighting changes from both the HEAD and remote versions. + * Look at the diffs. `git diff` will show a three-way diff, + highlighting changes from both the `HEAD` and `MERGE_HEAD` + versions. - * Look at the diffs on their own. 'git log --merge -p <path>' - will show diffs first for the HEAD version and then the - remote version. + * Look at the diffs from each branch. `git log --merge -p <path>` + will show diffs first for the `HEAD` version and then the + `MERGE_HEAD` version. - * Look at the originals. 'git show :1:filename' shows the - common ancestor, 'git show :2:filename' shows the HEAD - version and 'git show :3:filename' shows the remote version. + * Look at the originals. `git show :1:filename` shows the + common ancestor, `git show :2:filename` shows the `HEAD` + version, and `git show :3:filename` shows the `MERGE_HEAD` + version. EXAMPLES @@ -245,6 +265,17 @@ changes into a merge commit. Small fixups like bumping release/version name would be acceptable. +include::merge-strategies.txt[] + +CONFIGURATION +------------- +include::merge-config.txt[] + +branch.<name>.mergeoptions:: + Sets default options for merging into branch <name>. The syntax and + supported options are the same as those of 'git merge', but option + values containing whitespace characters are currently not supported. + SEE ALSO -------- linkgit:git-fmt-merge-msg[1], linkgit:git-pull[1], diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 68ed6c0956..55735faf7b 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -13,11 +13,11 @@ DESCRIPTION ----------- Use `git mergetool` to run one of several merge utilities to resolve -merge conflicts. It is typically run after 'git-merge'. +merge conflicts. It is typically run after 'git merge'. If one or more <file> parameters are given, the merge tool program will be run to resolve differences on each file. If no <file> names are -specified, 'git-mergetool' will run the merge tool program on every file +specified, 'git mergetool' will run the merge tool program on every file with merge conflicts. OPTIONS @@ -27,25 +27,25 @@ 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' +If a merge resolution program is not specified, 'git mergetool' will use the configuration variable `merge.tool`. If the -configuration variable `merge.tool` is not set, 'git-mergetool' +configuration variable `merge.tool` is not set, 'git mergetool' will pick a suitable default. + You can explicitly provide a full path to the tool by setting the configuration variable `mergetool.<tool>.path`. For example, you can configure the absolute path to kdiff3 by setting -`mergetool.kdiff3.path`. Otherwise, 'git-mergetool' assumes the +`mergetool.kdiff3.path`. Otherwise, 'git mergetool' assumes the tool is available in PATH. + Instead of running one of the known merge tool programs, -'git-mergetool' can be customized to run an alternative program +'git mergetool' can be customized to run an alternative program by specifying the command line to invoke in a configuration variable `mergetool.<tool>.cmd`. + -When 'git-mergetool' is invoked with this tool (either through the +When 'git mergetool' is invoked with this tool (either through the `-t` or `--tool` option or the `merge.tool` configuration variable) the configured command line will be invoked with `$BASE` set to the name of a temporary file containing the common base for @@ -59,7 +59,7 @@ merge resolution. If the custom merge tool correctly indicates the success of a merge resolution with its exit code, then the configuration variable `mergetool.<tool>.trustExitCode` can be set to `true`. -Otherwise, 'git-mergetool' will prompt the user to indicate the +Otherwise, 'git mergetool' will prompt the user to indicate the success of the resolution after the custom tool has exited. -y:: diff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt index 7ca8a7b48c..2108237c36 100644 --- a/Documentation/git-name-rev.txt +++ b/Documentation/git-name-rev.txt @@ -15,7 +15,7 @@ SYNOPSIS DESCRIPTION ----------- Finds symbolic names suitable for human digestion for revisions given in any -format parsable by 'git-rev-parse'. +format parsable by 'git rev-parse'. OPTIONS @@ -55,7 +55,7 @@ wrote you about that fantastic commit 33db5f4d9027a10e477ccf054b2c1ab94f74c85a. Of course, you look into the commit, but that only tells you what happened, but not the context. -Enter 'git-name-rev': +Enter 'git name-rev': ------------ % git name-rev 33db5f4d9027a10e477ccf054b2c1ab94f74c85a diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt new file mode 100644 index 0000000000..d4487cab52 --- /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-pack-objects.txt b/Documentation/git-pack-objects.txt index f54d433d36..034caedc39 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -21,27 +21,28 @@ DESCRIPTION Reads list of objects from the standard input, and writes a packed archive with specified base-name, or to the standard output. -A packed archive is an efficient way to transfer set of objects -between two repositories, and also is an archival format which -is efficient to access. The packed archive format (.pack) is -designed to be self contained so that it can be unpacked without -any further information, but for fast, random access to the objects -in the pack, a pack index file (.idx) will be generated. - -Placing both in the pack/ subdirectory of $GIT_OBJECT_DIRECTORY (or +A packed archive is an efficient way to transfer a set of objects +between two repositories as well as an access efficient archival +format. In a packed archive, an object is either stored as a +compressed whole or as a difference from some other object. +The latter is often called a delta. + +The packed archive format (.pack) is designed to be self-contained +so that it can be unpacked without any further information. Therefore, +each object that a delta depends upon must be present within the pack. + +A pack index file (.idx) is generated for fast, random access to the +objects in the pack. Placing both the index file (.idx) and the packed +archive (.pack) in the pack/ subdirectory of $GIT_OBJECT_DIRECTORY (or any of the directories on $GIT_ALTERNATE_OBJECT_DIRECTORIES) -enables git to read from such an archive. +enables git to read from the pack archive. -The 'git-unpack-objects' command can read the packed archive and +The 'git unpack-objects' command can read the packed archive and expand the objects contained in the pack into "one-file one-object" format; this is typically done by the smart-pull commands when a pack is created on-the-fly for efficient network transport by their peers. -In a packed archive, an object is either stored as a compressed -whole, or as a difference from some other object. The latter is -often called a delta. - OPTIONS ------- @@ -61,7 +62,7 @@ base-name:: --revs:: Read the revision arguments from the standard input, instead of individual object names. The revision arguments are processed - the same way as 'git-rev-list' with the `--objects` flag + the same way as 'git rev-list' with the `--objects` flag uses its `commit` arguments to build the list of objects it outputs. The objects on the resulting list are packed. @@ -73,7 +74,7 @@ base-name:: --all:: This implies `--revs`. In addition to the list of revision arguments read from the standard input, pretend - as if all refs under `$GIT_DIR/refs` are specified to be + as if all refs under `refs/` are specified to be included. --include-tag:: @@ -105,8 +106,9 @@ base-name:: `--window-memory=0` makes memory usage unlimited, which is the default. ---max-pack-size=<n>:: - Maximum size of each output packfile, expressed in MiB. +--max-pack-size=[N]:: + Maximum size of each output pack file. The size can be suffixed with + "k", "m", or "g". The minimum size allowed is limited to 1 MiB. If specified, multiple packfiles may be created. The default is unlimited, unless the config variable `pack.packSizeLimit` is set. @@ -178,11 +180,21 @@ base-name:: Add --no-reuse-object if you want to force a uniform compression level on all data no matter the source. +--thin:: + Create a "thin" pack by omitting the common objects between a + sender and a receiver in order to reduce network transfer. This + option only makes sense in conjunction with --stdout. ++ +Note: A thin pack violates the packed archive format by omitting +required objects and is thus unusable by git without making it +self-contained. Use `git index-pack --fix-thin` +(see linkgit:git-index-pack[1]) to restore the self-contained property. + --delta-base-offset:: A packed archive can express base object of a delta as either 20-byte object name or as an offset in the stream, but older version of git does not understand the - latter. By default, 'git-pack-objects' only uses the + latter. By default, 'git pack-objects' only uses the former format for better compatibility. This option allows the command to use the latter format for compactness. Depending on the average delta chain diff --git a/Documentation/git-pack-redundant.txt b/Documentation/git-pack-redundant.txt index 5f9435e59b..d0607879db 100644 --- a/Documentation/git-pack-redundant.txt +++ b/Documentation/git-pack-redundant.txt @@ -16,7 +16,7 @@ This program computes which packs in your repository are redundant. The output is suitable for piping to `xargs rm` if you are in the root of the repository. -'git-pack-redundant' accepts a list of objects on standard input. Any objects +'git pack-redundant' accepts a list of objects on standard input. Any objects given will be ignored when checking which packs are required. This makes the following command useful when wanting to remove packs which contain unreachable objects. diff --git a/Documentation/git-patch-id.txt b/Documentation/git-patch-id.txt index 253fc0fc25..4dae1390a5 100644 --- a/Documentation/git-patch-id.txt +++ b/Documentation/git-patch-id.txt @@ -18,7 +18,7 @@ ID" are almost guaranteed to be the same thing. IOW, you can use this thing to look for likely duplicate commits. -When dealing with 'git-diff-tree' output, it takes advantage of +When dealing with 'git diff-tree' output, it takes advantage of the fact that the patch is prefixed with the object name of the commit, and outputs two 40-byte hexadecimal strings. The first string is the patch ID, and the second string is the commit ID. diff --git a/Documentation/git-peek-remote.txt b/Documentation/git-peek-remote.txt index 8282a5e82b..87dacd797f 100644 --- a/Documentation/git-peek-remote.txt +++ b/Documentation/git-peek-remote.txt @@ -12,7 +12,7 @@ SYNOPSIS DESCRIPTION ----------- -This command is deprecated; use 'git-ls-remote' instead. +This command is deprecated; use 'git ls-remote' instead. OPTIONS ------- diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt index da6055d4b8..15cfb7a8dc 100644 --- a/Documentation/git-prune.txt +++ b/Documentation/git-prune.txt @@ -8,21 +8,21 @@ git-prune - Prune all unreachable objects from the object database SYNOPSIS -------- -'git-prune' [-n] [-v] [--expire <expire>] [--] [<head>...] +'git prune' [-n] [-v] [--expire <expire>] [--] [<head>...] DESCRIPTION ----------- -NOTE: In most cases, users should run 'git-gc', which calls -'git-prune'. See the section "NOTES", below. +NOTE: In most cases, users should run 'git gc', which calls +'git prune'. See the section "NOTES", below. -This runs 'git-fsck --unreachable' using all the refs -available in `$GIT_DIR/refs`, optionally with additional set of +This runs 'git fsck --unreachable' using all the refs +available in `refs/`, optionally with additional set of objects specified on the command line, and prunes all unpacked objects unreachable from any of these head objects from the object database. In addition, it prunes the unpacked objects that are also found in packs by -running 'git-prune-packed'. +running 'git prune-packed'. Note that unreachable, packed objects will remain. If this is not desired, see linkgit:git-repack[1]. @@ -62,12 +62,12 @@ $ git prune $(cd ../another && $(git rev-parse --all)) Notes ----- -In most cases, users will not need to call 'git-prune' directly, but -should instead call 'git-gc', which handles pruning along with +In most cases, users will not need to call 'git prune' directly, but +should instead call 'git gc', which handles pruning along with many other housekeeping tasks. For a description of which objects are considered for pruning, see -'git-fsck''s --unreachable option. +'git fsck''s --unreachable option. SEE ALSO -------- diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index b93201158f..31f42ea21a 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -13,16 +13,20 @@ SYNOPSIS DESCRIPTION ----------- -Runs 'git-fetch' with the given parameters, and calls 'git-merge' +Runs 'git fetch' with the given parameters, and calls 'git merge' to merge the retrieved head(s) into the current branch. -With `--rebase`, calls 'git-rebase' instead of 'git-merge'. +With `--rebase`, calls 'git rebase' instead of 'git merge'. Note that you can use `.` (current directory) as the <repository> to pull from the local repository -- this is useful when merging local branches into the current branch. -Also note that options meant for 'git-pull' itself and underlying -'git-merge' must be given before the options meant for 'git-fetch'. +Also note that options meant for 'git pull' itself and underlying +'git merge' must be given before the options meant for 'git fetch'. + +*Warning*: Running 'git pull' (actually, the underlying 'git merge') +with uncommitted changes is discouraged: while possible, it leaves you +in a state that is hard to back out of in the case of a conflict. OPTIONS ------- @@ -148,7 +152,7 @@ $ git merge origin/next If you tried a pull which resulted in a complex conflicts and -would want to start over, you can recover with 'git-reset'. +would want to start over, you can recover with 'git reset'. SEE ALSO diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 37c88953d1..49b6bd9d92 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>] - [--repo=<repository>] [-f | --force] [-v | --verbose] + [--repo=<repository>] [-f | --force] [-v | --verbose] [-u | --set-upstream] [<repository> <refspec>...] DESCRIPTION @@ -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 @@ -69,11 +69,11 @@ nor in any Push line of the corresponding remotes file---see below). --all:: Instead of naming each ref to push, specifies that all - refs under `$GIT_DIR/refs/heads/` be pushed. + refs under `refs/heads/` be pushed. --mirror:: Instead of naming each ref to push, specifies that all - refs under `$GIT_DIR/refs/` (which includes but is not + refs under `refs/` (which includes but is not limited to `refs/heads/`, `refs/remotes/`, and `refs/tags/`) be mirrored to the remote repository. Newly created local refs will be pushed to the remote end, locally updated refs @@ -91,8 +91,12 @@ nor in any Push line of the corresponding remotes file---see below). will be tab-separated and sent to stdout instead of stderr. The full symbolic names of the refs will be given. +--delete:: + All listed refs are deleted from the remote repository. This is + the same as prefixing all refs with a colon. + --tags:: - All refs under `$GIT_DIR/refs/tags` are pushed, in + All refs under `refs/tags` are pushed, in addition to refspecs explicitly listed on the command line. @@ -112,7 +116,7 @@ nor in any Push line of the corresponding remotes file---see below). --repo=<repository>:: This option is only relevant if no <repository> argument is - passed in the invocation. In this case, 'git-push' derives the + passed in the invocation. In this case, 'git push' derives the remote name from the current branch: If it tracks a remote branch, then that remote repository is pushed to. Otherwise, the name "origin" is used. For this latter case, this option @@ -126,13 +130,21 @@ git push --repo=public #2 + is that #1 always pushes to "public" whereas #2 pushes to "public" only if the current branch does not track a remote branch. This is -useful if you write an alias or script around 'git-push'. +useful if you write an alias or script around 'git push'. + +-u:: +--set-upstream:: + For every branch that is up to date or successfully pushed, add + upstream (tracking) reference, used by argument-less + linkgit:git-pull[1] and other commands. For more information, + see 'branch.<name>.merge' in linkgit:git-config[1]. --thin:: --no-thin:: - These options are passed to 'git-send-pack'. Thin - transfer spends extra cycles to minimize the number of - objects to be sent and meant to be used on slower connection. + These options are passed to linkgit:git-send-pack[1]. A thin transfer + significantly reduces the amount of sent data when the sender and + receiver share many of the same objects in common. The default is + \--thin. -v:: --verbose:: @@ -165,21 +177,26 @@ If --porcelain is used, then each line of the output is of the form: <flag> \t <from>:<to> \t <summary> (<reason>) ------------------------------- +The status of up-to-date refs is shown only if --porcelain or --verbose +option is used. + flag:: - A single character indicating the status of the ref. This is - blank for a successfully pushed ref, `!` for a ref that was - rejected or failed to push, and '=' for a ref that was up to - date and did not need pushing (note that the status of up to - date refs is shown only when `git push` is running verbosely). + A single character indicating the status of the ref: +(space);; for a successfully pushed fast-forward; +`{plus}`;; for a successful forced update; +`-`;; for a successfully deleted ref; +`*`;; for a successfully pushed new ref; +`!`;; for a ref that was rejected or failed to push; and +`=`;; for a ref that was up to date and did not need pushing. 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 +364,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..567671c013 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- 'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] - [--index-output=<file>] + [--index-output=<file>] [--no-sparse-checkout] <tree-ish1> [<tree-ish2> [<tree-ish3>]] @@ -25,8 +25,8 @@ fast-forward (i.e. 2-way) merge, or a 3-way merge, with the `-m` flag. When used with `-m`, the `-u` flag causes it to also update the files in the work tree with the result of the merge. -Trivial merges are done by 'git-read-tree' itself. Only conflicting paths -will be in unmerged state when 'git-read-tree' returns. +Trivial merges are done by 'git read-tree' itself. Only conflicting paths +will be in unmerged state when 'git read-tree' returns. OPTIONS ------- @@ -57,13 +57,13 @@ OPTIONS Show the progress of checking files out. --trivial:: - Restrict three-way merge by 'git-read-tree' to happen + Restrict three-way merge by 'git read-tree' to happen only if there is no file-level merging required, instead of resolving merge for trivial cases and leaving conflicting files unresolved in the index. --aggressive:: - Usually a three-way merge by 'git-read-tree' resolves + Usually a three-way merge by 'git read-tree' resolves the merge for really trivial cases and leaves other cases unresolved in the index, so that Porcelains can implement different merge policies. This flag makes the @@ -110,13 +110,17 @@ OPTIONS directories the index file and index output file are located in. +--no-sparse-checkout:: + Disable sparse checkout support even if `core.sparseCheckout` + is true. + <tree-ish#>:: The id of the tree object(s) to be read/merged. Merging ------- -If `-m` is specified, 'git-read-tree' can perform 3 kinds of +If `-m` is specified, 'git read-tree' can perform 3 kinds of merge, a single tree merge if only 1 tree is given, a fast-forward merge with 2 trees, or a 3-way merge if 3 trees are provided. @@ -124,18 +128,18 @@ provided. Single Tree Merge ~~~~~~~~~~~~~~~~~ -If only 1 tree is specified, 'git-read-tree' operates as if the user did not +If only 1 tree is specified, 'git read-tree' operates as if the user did not specify `-m`, except that if the original index has an entry for a given pathname, and the contents of the path matches with the tree being read, the stat info from the index is used. (In other words, the index's stat()s take precedence over the merged tree's). That means that if you do a `git read-tree -m <newtree>` followed by a -`git checkout-index -f -u -a`, the 'git-checkout-index' only checks out +`git checkout-index -f -u -a`, the 'git checkout-index' only checks out the stuff that really changed. -This is used to avoid unnecessary false hits when 'git-diff-files' is -run after 'git-read-tree'. +This is used to avoid unnecessary false hits when 'git diff-files' is +run after 'git read-tree'. Two Tree Merge @@ -144,9 +148,9 @@ 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' +When two trees are specified, the user is telling 'git read-tree' the following: 1. The current index and work tree is derived from $H, but @@ -199,10 +203,10 @@ Here are the "carry forward" rules: In all "keep index" cases, the index entry stays as in the original index file. If the entry were not up to date, -'git-read-tree' keeps the copy in the work tree intact when +'git read-tree' keeps the copy in the work tree intact when operating under the -u flag. -When this form of 'git-read-tree' returns successfully, you can +When this form of 'git read-tree' returns successfully, you can see what "local changes" you made are carried forward by running `git diff-index --cached $M`. Note that this does not necessarily match `git diff-index --cached $H` would have @@ -225,7 +229,7 @@ of the path is kept as long as $H and $M are the same. Each "index" entry has two bits worth of "stage" state. stage 0 is the normal one, and is the only one you'd see in any kind of normal use. -However, when you do 'git-read-tree' with three trees, the "stage" +However, when you do 'git read-tree' with three trees, the "stage" starts out at 1. This means that you can do @@ -241,7 +245,7 @@ branch into the current branch, we use the common ancestor tree as <tree1>, the current branch head as <tree2>, and the other branch head as <tree3>. -Furthermore, 'git-read-tree' has special-case logic that says: if you see +Furthermore, 'git read-tree' has special-case logic that says: if you see a file that matches in all respects in the following states, it "collapses" back to "stage0": @@ -257,7 +261,7 @@ a file that matches in all respects in the following states, it - stage 1 and stage 3 are the same and stage 2 is different take stage 2 (we did something while they did nothing) -The 'git-write-tree' command refuses to write a nonsensical tree, and it +The 'git write-tree' command refuses to write a nonsensical tree, and it will complain about unmerged entries if it sees a single entry that is not stage 0. @@ -273,7 +277,7 @@ start a 3-way merge with an index file that is already populated. Here is an outline of how the algorithm works: - if a file exists in identical format in all three trees, it will - automatically collapse to "merged" state by 'git-read-tree'. + automatically collapse to "merged" state by 'git read-tree'. - a file that has _any_ difference what-so-ever in the three trees will stay as separate entries in the index. It's up to "porcelain @@ -297,8 +301,8 @@ populated. Here is an outline of how the algorithm works: matching "stage1" entry if it exists too. .. all the normal trivial rules .. -You would normally use 'git-merge-index' with supplied -'git-merge-one-file' to do this last step. The script updates +You would normally use 'git merge-index' with supplied +'git merge-one-file' to do this last step. The script updates the files in the working tree as it merges each path and at the end of a successful merge. @@ -320,7 +324,7 @@ $ JC=`git rev-parse --verify "HEAD^0"` $ git checkout-index -f -u -a $JC ---------------- -You do random edits, without running 'git-update-index'. And then +You do random edits, without running 'git update-index'. And then you notice that the tip of your "upstream" tree has advanced since you pulled from him: @@ -346,20 +350,66 @@ your work-in-progress changes, and your work tree would be updated to the result of the merge. However, if you have local changes in the working tree that -would be overwritten by this merge, 'git-read-tree' will refuse +would be overwritten by this merge, 'git read-tree' will refuse to run to prevent your changes from being lost. In other words, there is no need to worry about what exists only in the working tree. When you have local changes in a part of the project that is not involved in the merge, your changes do not interfere with the merge, and are kept intact. When they -*do* interfere, the merge does not even start ('git-read-tree' +*do* interfere, the merge does not even start ('git read-tree' complains loudly and fails without modifying anything). In such a case, you can simply continue doing what you were in the middle of doing, and when your working tree is ready (i.e. you have finished your work-in-progress), attempt the merge again. +Sparse checkout +--------------- + +"Sparse checkout" allows to sparsely populate working directory. +It uses skip-worktree bit (see linkgit:git-update-index[1]) to tell +Git whether a file on working directory is worth looking at. + +"git read-tree" and other merge-based commands ("git merge", "git +checkout"...) can help maintaining skip-worktree bitmap and working +directory update. `$GIT_DIR/info/sparse-checkout` is used to +define the skip-worktree reference bitmap. When "git read-tree" needs +to update working directory, it will reset skip-worktree bit in index +based on this file, which uses the same syntax as .gitignore files. +If an entry matches a pattern in this file, skip-worktree will be +set on that entry. Otherwise, skip-worktree will be unset. + +Then it compares the new skip-worktree value with the previous one. If +skip-worktree turns from unset to set, it will add the corresponding +file back. If it turns from set to unset, that file will be removed. + +While `$GIT_DIR/info/sparse-checkout` is usually used to specify what +files are in. You can also specify what files are _not_ in, using +negate patterns. For example, to remove file "unwanted": + +---------------- +* +!unwanted +---------------- + +Another tricky thing is fully repopulating working directory when you +no longer want sparse checkout. You cannot just disable "sparse +checkout" because skip-worktree are still in the index and you working +directory is still sparsely populated. You should re-populate working +directory with the `$GIT_DIR/info/sparse-checkout` file content as +follows: + +---------------- +* +---------------- + +Then you can disable sparse checkout. Sparse checkout support in "git +read-tree" and similar commands is disabled by default. You need to +turn `core.sparseCheckout` on in order to have sparse checkout +support. + + SEE ALSO -------- linkgit:git-write-tree[1]; linkgit:git-ls-files[1]; diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 0aefc34d0d..823f2a4638 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -17,7 +17,7 @@ SYNOPSIS DESCRIPTION ----------- -If <branch> is specified, 'git-rebase' will perform an automatic +If <branch> is specified, 'git rebase' will perform an automatic `git checkout <branch>` before doing anything else. Otherwise it remains on the current branch. @@ -170,8 +170,8 @@ This is useful if F and G were flawed in some way, or should not be part of topicA. Note that the argument to --onto and the <upstream> parameter can be any valid commit-ish. -In case of conflict, 'git-rebase' will stop at the first problematic commit -and leave conflict markers in the tree. You can use 'git-diff' to locate +In case of conflict, 'git rebase' will stop at the first problematic commit +and leave conflict markers in the tree. You can use 'git diff' to locate the markers (<<<<<<) and make edits to resolve the conflict. For each file you edit, you need to tell git that the conflict has been resolved, typically this would be done with @@ -187,7 +187,7 @@ desired resolution, you can continue the rebasing process with git rebase --continue -Alternatively, you can undo the 'git-rebase' with +Alternatively, you can undo the 'git rebase' with git rebase --abort @@ -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:: @@ -270,13 +280,13 @@ OPTIONS --ignore-whitespace:: --whitespace=<option>:: - These flag are passed to the 'git-apply' program + These flag are passed to the 'git apply' program (see linkgit:git-apply[1]) that applies the patch. Incompatible with the --interactive option. --committer-date-is-author-date:: --ignore-date:: - These flags are passed to 'git-am' to easily change the dates + These flags are passed to 'git am' to easily change the dates of the rebased commits (see linkgit:git-am[1]). -i:: @@ -298,12 +308,22 @@ OPTIONS root commits will be rewritten to have <newbase> as parent instead. +--autosquash:: + When the commit log message begins with "squash! ..." (or + "fixup! ..."), and there is a commit whose title begins with + the same ..., automatically modify the todo list of rebase -i + so that the commit marked for squashing comes right after the + commit to be modified, and change the action of the moved + commit from `pick` to `squash` (or `fixup`). ++ +This option is only valid when '--interactive' option is used. + include::merge-strategies.txt[] NOTES ----- -You should understand the implications of using 'git-rebase' on a +You should understand the implications of using 'git rebase' on a repository that you share. See also RECOVERING FROM UPSTREAM REBASE below. @@ -359,27 +379,33 @@ pick fa1afe1 The oneline of the next commit ... ------------------------------------------- -The oneline descriptions are purely for your pleasure; 'git-rebase' will +The oneline descriptions are purely for your pleasure; 'git rebase' will not look at them but at the commit names ("deadbee" and "fa1afe1" in this example), so do not delete or edit the names. By replacing the command "pick" with the command "edit", you can tell -'git-rebase' to stop after applying that commit, so that you can edit +'git rebase' to stop after applying that commit, so that you can edit 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. +"pick" for the second and subsequent commits with "squash" or "fixup". +If the commits had different authors, the folded commit will be +attributed to the author of the first commit. The suggested commit +message for the folded commit is the concatenation of the commit +messages of the first commit and of those with the "squash" command, +but omits the commit messages of commits with the "fixup" command. -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 -'git-rebase' like this: +'git rebase' like this: ---------------------- $ git rebase -i HEAD~5 @@ -409,7 +435,7 @@ SPLITTING COMMITS ----------------- In interactive mode, you can mark commits with the action "edit". However, -this does not necessarily mean that 'git-rebase' expects the result of this +this does not necessarily mean that 'git rebase' expects the result of this edit to be exactly one commit. Indeed, you can undo the commit, or you can add other commits. This can be used to split a commit into two: @@ -425,7 +451,7 @@ add other commits. This can be used to split a commit into two: - Now add the changes to the index that you want to have in the first commit. You can use `git add` (possibly interactively) or - 'git-gui' (or both) to do that. + 'git gui' (or both) to do that. - Commit the now-current index with whatever commit message is appropriate now. @@ -436,7 +462,7 @@ add other commits. This can be used to split a commit into two: If you are not absolutely sure that the intermediate revisions are consistent (they compile, pass the testsuite, etc.) you should use -'git-stash' to stash away the not-yet-committed changes +'git stash' to stash away the not-yet-committed changes after each commit, test, and amend the commit if fixes are necessary. @@ -499,8 +525,8 @@ Easy case: The changes are literally the same.:: Hard case: The changes are not the same.:: This happens if the 'subsystem' rebase had conflicts, or used - `\--interactive` to omit, edit, or squash commits; or if the - upstream used one of `commit \--amend`, `reset`, or + `\--interactive` to omit, edit, squash, or fixup commits; or + if the upstream used one of `commit \--amend`, `reset`, or `filter-branch`. @@ -511,7 +537,7 @@ Only works if the changes (patch IDs based on the diff contents) on 'subsystem' are literally the same before and after the rebase 'subsystem' did. -In that case, the fix is easy because 'git-rebase' knows to skip +In that case, the fix is easy because 'git rebase' knows to skip changes that are already present in the new upstream. So if you say (assuming you're on 'topic') ------------ @@ -538,12 +564,12 @@ NOTE: While an "easy case recovery" sometimes appears to be successful example, a commit that was removed via `git rebase \--interactive` will be **resurrected**! -The idea is to manually tell 'git-rebase' "where the old 'subsystem' +The idea is to manually tell 'git rebase' "where the old 'subsystem' ended and your 'topic' began", that is, what the old merge-base between them was. You will have to find a way to name the last commit of the old 'subsystem', for example: -* With the 'subsystem' reflog: after 'git-fetch', the old tip of +* With the 'subsystem' reflog: after 'git fetch', the old tip of 'subsystem' is at `subsystem@\{1}`. Subsequent fetches will increase the number. (See linkgit:git-reflog[1].) diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index 514f03c979..2790eebaff 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -8,19 +8,19 @@ git-receive-pack - Receive what is pushed into the repository SYNOPSIS -------- -'git receive-pack' <directory> +'git-receive-pack' <directory> DESCRIPTION ----------- -Invoked by 'git-send-pack' and updates the repository with the +Invoked by 'git send-pack' and updates the repository with the information fed from the remote end. This command is usually not invoked directly by the end user. -The UI for the protocol is on the 'git-send-pack' side, and the +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-reflog.txt b/Documentation/git-reflog.txt index 7f7a5445c7..802bd5791c 100644 --- a/Documentation/git-reflog.txt +++ b/Documentation/git-reflog.txt @@ -60,7 +60,7 @@ OPTIONS refs. + This computation involves traversing all the reachable objects, i.e. it -has the same cost as 'git-prune'. Fortunately, once this is run, we +has the same cost as 'git prune'. Fortunately, once this is run, we should not have to ever worry about missing objects, because the current prune and pack-objects know about reflogs and protect objects referred by them. diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index 173ee232f2..1b5f61aa0b 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -25,7 +25,10 @@ Commands are given by the caller on the helper's standard input, one per line. 'capabilities':: Lists the capabilities of the helper, one per line, ending - with a blank line. + with a blank line. Each capability may be preceded with '*'. + This marks them mandatory for git version using the remote + helper to understand (unknown mandatory capability is fatal + error). 'list':: Lists the refs, one per line, in the format "<value> <name> @@ -34,15 +37,76 @@ 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. + +'connect' <service>:: + Connects to given service. Standard input and standard output + of helper are connected to specified service (git prefix is + included in service name so e.g. fetching uses 'git-upload-pack' + as service) on remote side. Valid replies to this command are + empty line (connection established), 'fallback' (no smart + transport support, fall back to dumb transports) and just + exiting with error message printed (can't connect, don't + bother trying to fall back). After line feed terminating the + positive (empty) response, the output of service starts. After + the connection ends, the remote helper exits. ++ +Supported if the helper has the "connect" 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,14 +121,79 @@ 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 "*:*" + +'connect':: + This helper supports the 'connect' command. + 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. + +'option servpath <c-style-quoted-path>':: + Set service path (--upload-pack, --receive-pack etc.) for + next connect. Remote helper MAY support this option. Remote + helper MUST NOT rely on this option being set before + connect request occurs. Documentation ------------- -Documentation by Daniel Barkalow. +Documentation by Daniel Barkalow and Ilari Liusvaara GIT --- diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index c272c92d4b..3fc599c0c7 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -14,6 +14,9 @@ SYNOPSIS 'git remote rename' <old> <new> 'git remote rm' <name> 'git remote set-head' <name> (-a | -d | <branch>) +'git remote set-url' [--push] <name> <newurl> [<oldurl>] +'git remote set-url --add' [--push] <name> <newurl> +'git remote set-url --delete' [--push] <name> <url> 'git remote' [-v | --verbose] 'show' [-n] <name> 'git remote prune' [-n | --dry-run] <name> 'git remote' [-v | --verbose] 'update' [-p | --prune] [group | remote]... @@ -101,6 +104,20 @@ remote set-head origin master" will set `$GIT_DIR/refs/remotes/origin/HEAD` to `refs/remotes/origin/master` already exists; if not it must be fetched first. + +'set-url':: + +Changes URL remote points to. Sets first URL remote points to matching +regex <oldurl> (first URL if no <oldurl> is given) to <newurl>. If +<oldurl> doesn't match any URL, error occurs and nothing is changed. ++ +With '--push', push URLs are manipulated instead of fetch URLs. ++ +With '--add', instead of changing some URL, new URL is added. ++ +With '--delete', instead of changing some URL, all URLs matching +regex <url> are deleted. Trying to delete all non-push URLs is an +error. + 'show':: Gives some information about the remote <name>. @@ -161,7 +178,7 @@ $ git checkout -b nfs linux-nfs/master ... ------------ -* Imitate 'git-clone' but track only selected branches +* Imitate 'git clone' but track only selected branches + ------------ $ mkdir project.git diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index c9257a10c9..8c67d1724f 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -49,16 +49,16 @@ other objects in that pack they already have locally. deleted by way of being left in the old pack and then removed. Instead, the loose unreachable objects will be pruned according to normal expiry rules - with the next 'git-gc' invocation. See linkgit:git-gc[1]. + with the next 'git gc' invocation. See linkgit:git-gc[1]. -d:: After packing, if the newly created packs make some existing packs redundant, remove the redundant packs. - Also run 'git-prune-packed' to remove redundant + Also run 'git prune-packed' to remove redundant loose object files. -l:: - Pass the `--local` option to 'git-pack-objects'. See + Pass the `--local` option to 'git pack-objects'. See linkgit:git-pack-objects[1]. -f:: @@ -66,12 +66,12 @@ other objects in that pack they already have locally. linkgit:git-pack-objects[1]. -q:: - Pass the `-q` option to 'git-pack-objects'. See + Pass the `-q` option to 'git pack-objects'. See linkgit:git-pack-objects[1]. -n:: Do not update the server information with - 'git-update-server-info'. This option skips + 'git update-server-info'. This option skips updating local catalog files needed to publish this repository (or a direct copy of it) over HTTP or FTP. See linkgit:git-update-server-info[1]. @@ -98,24 +98,26 @@ other objects in that pack they already have locally. `--window-memory=0` makes memory usage unlimited, which is the default. ---max-pack-size=<n>:: - Maximum size of each output packfile, expressed in MiB. +--max-pack-size=[N]:: + Maximum size of each output pack file. The size can be suffixed with + "k", "m", or "g". The minimum size allowed is limited to 1 MiB. If specified, multiple packfiles may be created. - The default is unlimited. + The default is unlimited, unless the config variable + `pack.packSizeLimit` is set. Configuration ------------- -When configuration variable `repack.UseDeltaBaseOffset` is set -for the repository, the command passes `--delta-base-offset` -option to 'git-pack-objects'; this typically results in slightly -smaller packs, but the generated packs are incompatible with -versions of git older than (and including) v1.4.3; do not set -the variable in a repository that older version of git needs to -be able to read (this includes repositories from which packs can -be copied out over http or rsync, and people who obtained packs -that way can try to use older git with it). +By default, the command passes `--delta-base-offset` option to +'git pack-objects'; this typically results in slightly smaller packs, +but the generated packs are incompatible with versions of Git older than +version 1.4.4. If you need to share your repository with such ancient Git +versions, either directly or via the dumb http or rsync protocol, then you +need to set the configuration variable `repack.UseDeltaBaseOffset` to +"false" and repack. Access from old Git versions over the native protocol +is unaffected by this option as the conversion is performed on the fly +as needed in that case. Author diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt index 915cb77b29..fde2092582 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:: @@ -41,7 +65,7 @@ OPTIONS BUGS ---- Comparing blobs or trees that have been replaced with those that -replace them will not work properly. And using 'git reset --hard' to +replace them will not work properly. And using `git reset --hard` to go back to a replaced commit will move the branch to the replacement commit instead of the replaced commit. @@ -54,6 +78,7 @@ SEE ALSO -------- linkgit:git-tag[1] linkgit:git-branch[1] +linkgit:git[1] Author ------ diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt index 7dd515b8cc..acc220a00f 100644 --- a/Documentation/git-rerere.txt +++ b/Documentation/git-rerere.txt @@ -30,14 +30,14 @@ enable this command. COMMANDS -------- -Normally, 'git-rerere' is run without arguments or user-intervention. +Normally, 'git rerere' is run without arguments or user-intervention. However, it has several commands that allow it to interact with its working state. 'clear':: This resets the metadata used by rerere if a merge resolution is to be -aborted. Calling 'git-am [--skip|--abort]' or 'git-rebase [--skip|--abort]' +aborted. Calling 'git am [--skip|--abort]' or 'git rebase [--skip|--abort]' will automatically invoke this command. 'diff':: @@ -142,32 +142,32 @@ finally ready and merged into the master branch. This merge would require you to resolve the conflict, introduced by the commits marked with `*`. However, this conflict is often the same conflict you resolved when you created the test merge you -blew away. 'git-rerere' helps you resolve this final +blew away. 'git rerere' helps you resolve this final conflicted merge using the information from your earlier hand resolve. -Running the 'git-rerere' command immediately after a conflicted +Running the 'git rerere' command immediately after a conflicted automerge records the conflicted working tree files, with the usual conflict markers `<<<<<<<`, `=======`, and `>>>>>>>` in them. Later, after you are done resolving the conflicts, -running 'git-rerere' again will record the resolved state of these +running 'git rerere' again will record the resolved state of these files. Suppose you did this when you created the test merge of master into the topic branch. Next time, after seeing the same conflicted automerge, -running 'git-rerere' will perform a three-way merge between the +running 'git rerere' will perform a three-way merge between the earlier conflicted automerge, the earlier manual resolution, and the current conflicted automerge. If this three-way merge resolves cleanly, the result is written out to your working tree file, so you do not have to manually -resolve it. Note that 'git-rerere' leaves the index file alone, +resolve it. Note that 'git rerere' leaves the index file alone, so you still need to do the final sanity checks with `git diff` -(or `git diff -c`) and 'git-add' when you are satisfied. +(or `git diff -c`) and 'git add' when you are satisfied. -As a convenience measure, 'git-merge' automatically invokes -'git-rerere' upon exiting with a failed automerge and 'git-rerere' +As a convenience measure, 'git merge' automatically invokes +'git rerere' upon exiting with a failed automerge and 'git rerere' records the hand resolve when it is a new conflict, or reuses the earlier hand -resolve when it is not. 'git-commit' also invokes 'git-rerere' +resolve when it is not. 'git commit' also invokes 'git rerere' when committing a merge result. What this means is that you do not have to do anything special yourself (besides enabling the rerere.enabled config variable). @@ -177,8 +177,8 @@ resolution is recorded, and it will be reused when you do the actual merge later with the updated master and topic branch, as long as the recorded resolution is still applicable. -The information 'git-rerere' records is also used when running -'git-rebase'. After blowing away the test merge and continuing +The information 'git rerere' records is also used when running +'git rebase'. After blowing away the test merge and continuing development on the topic branch: ------------ @@ -197,7 +197,7 @@ you could run `git rebase master topic`, to bring yourself up-to-date before your topic is ready to be sent upstream. This would result in falling back to a three-way merge, and it would conflict the same way as the test merge you resolved earlier. -'git-rerere' will be run by 'git-rebase' to help you resolve this +'git rerere' will be run by 'git rebase' to help you resolve this conflict. diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 469cf6dbac..168db08627 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -39,7 +39,7 @@ OPTIONS --soft:: Does not touch the index file nor the working tree at all, but requires them to be in a good order. This leaves all your changed - files "Changes to be committed", as 'git-status' would + files "Changes to be committed", as 'git status' would put it. --hard:: @@ -62,11 +62,101 @@ This means that `git reset -p` is the opposite of `git add -p` (see linkgit:git-add[1]). -q:: +--quiet:: Be quiet, only report errors. <commit>:: Commit to make the current HEAD. If not given defaults to HEAD. +DISCUSSION +---------- + +The tables below show what happens when running: + +---------- +git reset --option target +---------- + +to reset the HEAD to another commit (`target`) with the different +reset options depending on the state of the files. + +In these tables, A, B, C and D are some different states of a +file. For example, the first line of the first table means that if a +file is in state A in the working tree, in state B in the index, in +state C in HEAD and in state D in the target, then "git reset --soft +target" will put the file in state A in the working tree, in state B +in the index and in state D in HEAD. + + working index HEAD target working index HEAD + ---------------------------------------------------- + A B C D --soft A B D + --mixed A D D + --hard D D D + --merge (disallowed) + + working index HEAD target working index HEAD + ---------------------------------------------------- + A B C C --soft A B C + --mixed A C C + --hard C C C + --merge (disallowed) + + working index HEAD target working index HEAD + ---------------------------------------------------- + B B C D --soft B B D + --mixed B D D + --hard D D D + --merge D D D + + working index HEAD target working index HEAD + ---------------------------------------------------- + B B C C --soft B B C + --mixed B C C + --hard C C C + --merge C C C + + working index HEAD target working index HEAD + ---------------------------------------------------- + B C C D --soft B C D + --mixed B D D + --hard D D D + --merge (disallowed) + + working index HEAD target working index HEAD + ---------------------------------------------------- + B C C C --soft B C C + --mixed B C C + --hard C C C + --merge B C C + +"reset --merge" is meant to be used when resetting out of a conflicted +merge. Any mergy operation guarantees that the work tree file that is +involved in the merge does not have local change wrt the index before +it starts, and that it writes the result out to the work tree. So if +we see some difference between the index and the target and also +between the index and the work tree, then it means that we are not +resetting out from a state that a mergy operation left after failing +with a conflict. That is why we disallow --merge option in this case. + +The following tables show what happens when there are unmerged +entries: + + working index HEAD target working index HEAD + ---------------------------------------------------- + X U A B --soft (disallowed) + --mixed X B B + --hard B B B + --merge B B B + + working index HEAD target working index HEAD + ---------------------------------------------------- + X U A A --soft (disallowed) + --mixed X A A + --hard A A A + --merge A A A + +X means any state and U means an unmerged index. + Examples -------- @@ -150,7 +240,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 +251,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-rev-list.txt b/Documentation/git-rev-list.txt index 3341d1b62f..173f3fc785 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -9,7 +9,7 @@ git-rev-list - Lists commit objects in reverse chronological order SYNOPSIS -------- [verse] -'git-rev-list' [ \--max-count=number ] +'git rev-list' [ \--max-count=number ] [ \--skip=number ] [ \--max-age=timestamp ] [ \--min-age=timestamp ] @@ -21,9 +21,10 @@ SYNOPSIS [ \--full-history ] [ \--not ] [ \--all ] - [ \--branches ] - [ \--tags ] - [ \--remotes ] + [ \--branches[=pattern] ] + [ \--tags[=pattern] ] + [ \--remotes[=pattern] ] + [ \--glob=glob-pattern ] [ \--stdin ] [ \--quiet ] [ \--topo-order ] @@ -93,8 +94,8 @@ between the two operands. The following two commands are equivalent: 'rev-list' is a very essential git command, since it provides the ability to build and traverse commit ancestry graphs. For this reason, it has a lot of different options that enables it to be -used by commands as different as 'git-bisect' and -'git-repack'. +used by commands as different as 'git bisect' and +'git repack'. OPTIONS ------- diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 82045a2522..1a613aa108 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -15,16 +15,16 @@ DESCRIPTION Many git porcelainish commands take mixture of flags (i.e. parameters that begin with a dash '-') and parameters -meant for the underlying 'git-rev-list' command they use internally +meant for the underlying 'git rev-list' command they use internally and flags and parameters for the other commands they use -downstream of 'git-rev-list'. This command is used to +downstream of 'git rev-list'. This command is used to distinguish between them. OPTIONS ------- --parseopt:: - Use 'git-rev-parse' in option parsing mode (see PARSEOPT section below). + Use 'git rev-parse' in option parsing mode (see PARSEOPT section below). --keep-dashdash:: Only meaningful in `--parseopt` mode. Tells the option parser to echo @@ -33,20 +33,20 @@ OPTIONS --stop-at-non-option:: Only meaningful in `--parseopt` mode. Lets the option parser stop at the first non-option argument. This can be used to parse sub-commands - that take options themself. + that take options themselves. --sq-quote:: - Use 'git-rev-parse' in shell quoting mode (see SQ-QUOTE + Use 'git rev-parse' in shell quoting mode (see SQ-QUOTE section below). In contrast to the `--sq` option below, this mode does only quoting. Nothing else is done to command input. --revs-only:: Do not output flags and parameters not meant for - 'git-rev-list' command. + 'git rev-list' command. --no-revs:: Do not output flags and parameters meant for - 'git-rev-list' command. + 'git rev-list' command. --flags:: Do not output non-flag parameters. @@ -74,7 +74,7 @@ OPTIONS properly quoted for consumption by shell. Useful when you expect your parameter to contain whitespaces and newlines (e.g. when using pickaxe `-S` with - 'git-diff-\*'). In contrast to the `--sq-quote` option, + 'git diff-\*'). In contrast to the `--sq-quote` option, the command input is still interpreted as usual. --not:: @@ -101,16 +101,28 @@ OPTIONS abbreviation mode. --all:: - Show all refs found in `$GIT_DIR/refs`. - ---branches:: - Show branch refs found in `$GIT_DIR/refs/heads`. + Show all refs found in `refs/`. + +--branches[=pattern]:: +--tags[=pattern]:: +--remotes[=pattern]:: + Show all branches, tags, or remote-tracking branches, + respectively (i.e., refs found in `refs/heads`, + `refs/tags`, or `refs/remotes`, respectively). ++ +If a `pattern` is given, only refs matching the given shell glob are +shown. If the pattern does not contain a globbing character (`?`, +`\*`, or `[`), it is turned into a prefix match by appending `/\*`. ---tags:: - Show tag refs found in `$GIT_DIR/refs/tags`. +--glob=pattern:: + Show all refs matching the shell glob pattern `pattern`. If + the pattern does not start with `refs/`, this is automatically + prepended. If the pattern does not contain a globbing + character (`?`, `\*`, or `[`), it is turned into a prefix + match by appending `/\*`. ---remotes:: - Show tag refs found in `$GIT_DIR/refs/remotes`. +--show-toplevel:: + Show the absolute path of the top-level directory. --show-prefix:: When the command is invoked from a subdirectory, show the @@ -145,12 +157,12 @@ OPTIONS --since=datestring:: --after=datestring:: Parse the date string, and output the corresponding - --max-age= parameter for 'git-rev-list'. + --max-age= parameter for 'git rev-list'. --until=datestring:: --before=datestring:: Parse the date string, and output the corresponding - --min-age= parameter for 'git-rev-list'. + --min-age= parameter for 'git rev-list'. <args>...:: Flags and parameters to be parsed. @@ -171,12 +183,12 @@ blobs contained in a commit. name the same commit object if there are no other object in your repository whose object name starts with dae86e. -* An output from 'git-describe'; i.e. a closest tag, optionally +* An output from 'git describe'; i.e. a closest tag, optionally followed by a dash and a number of commits, followed by a dash, a `g`, and an abbreviated object name. * A symbolic ref name. E.g. 'master' typically means the commit - object referenced by $GIT_DIR/refs/heads/master. If you + object referenced by refs/heads/master. If you happen to have both heads/master and tags/master, you can explicitly say 'heads/master' to tell git which one you mean. When ambiguous, a `<name>` is disambiguated by taking the @@ -185,25 +197,28 @@ blobs contained in a commit. . if `$GIT_DIR/<name>` exists, that is what you mean (this is usually useful only for `HEAD`, `FETCH_HEAD`, `ORIG_HEAD` and `MERGE_HEAD`); - . otherwise, `$GIT_DIR/refs/<name>` if exists; + . otherwise, `refs/<name>` if exists; - . otherwise, `$GIT_DIR/refs/tags/<name>` if exists; + . otherwise, `refs/tags/<name>` if exists; - . otherwise, `$GIT_DIR/refs/heads/<name>` if exists; + . otherwise, `refs/heads/<name>` if exists; - . otherwise, `$GIT_DIR/refs/remotes/<name>` if exists; + . otherwise, `refs/remotes/<name>` if exists; - . otherwise, `$GIT_DIR/refs/remotes/<name>/HEAD` if exists. + . otherwise, `refs/remotes/<name>/HEAD` if exists. + HEAD names the commit your changes in the working tree is based on. FETCH_HEAD records the branch you fetched from a remote repository -with your last 'git-fetch' invocation. +with your last 'git fetch' invocation. ORIG_HEAD is created by commands that moves your HEAD in a drastic way, to record the position of the HEAD before their operation, so that you can change the tip of the branch back to the state before you ran them easily. MERGE_HEAD records the commit(s) you are merging into your branch -when you run 'git-merge'. +when you run 'git merge'. ++ +Note that any of the `refs/*` cases above may come either from +the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file. * A ref followed by the suffix '@' with a date specification enclosed in a brace @@ -231,6 +246,10 @@ when you run 'git-merge'. * The special construct '@\{-<n>\}' means the <n>th branch checked out before the current one. +* The suffix '@\{upstream\}' to a ref (short form 'ref@\{u\}') refers to + the branch the ref is set to build on top of. Missing ref defaults + to the current branch. + * A suffix '{caret}' to a revision parameter means the first parent of that commit object. '{caret}<n>' means the <n>th parent (i.e. 'rev{caret}' @@ -308,7 +327,7 @@ G H I J SPECIFYING RANGES ----------------- -History traversing commands such as 'git-log' operate on a set +History traversing commands such as 'git log' operate on a set of commits, not just a single commit. To these commands, specifying a single revision with the notation described in the previous section means the set of commits reachable from that @@ -349,7 +368,7 @@ Here are a handful of examples: PARSEOPT -------- -In `--parseopt` mode, 'git-rev-parse' helps massaging options to bring to shell +In `--parseopt` mode, 'git rev-parse' helps massaging options to bring to shell scripts the same facilities C builtins have. It works as an option normalizer (e.g. splits single switches aggregate values), a bit like `getopt(1)` does. @@ -361,7 +380,7 @@ usage on the standard error stream, and exits with code 129. Input Format ~~~~~~~~~~~~ -'git-rev-parse --parseopt' input format is fully text based. It has two parts, +'git rev-parse --parseopt' input format is fully text based. It has two parts, separated by a line that contains only `--`. The lines before the separator (should be more than one) are used for the usage. The lines after the separator describe the options. @@ -420,13 +439,13 @@ eval `echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?` SQ-QUOTE -------- -In `--sq-quote` mode, 'git-rev-parse' echoes on the standard output a +In `--sq-quote` mode, 'git rev-parse' echoes on the standard output a single line suitable for `sh(1)` `eval`. This line is made by normalizing the arguments following `--sq-quote`. Nothing other than quoting the arguments is done. If you want command input to still be interpreted as usual by -'git-rev-parse' before the output is shell quoted, see the `--sq` +'git rev-parse' before the output is shell quoted, see the `--sq` option. Example diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index 5e1175800a..c66bf8072e 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -20,8 +20,8 @@ effect of an earlier commit (often a faulty one). If you want to throw away all uncommitted changes in your working directory, you should see linkgit:git-reset[1], particularly the '--hard' option. If you want to extract specific files as they were in another commit, you -should see linkgit:git-checkout[1], specifically the 'git checkout -<commit> -- <filename>' syntax. Take care with these alternatives as +should see linkgit:git-checkout[1], specifically the `git checkout +<commit> -- <filename>` syntax. Take care with these alternatives as both will discard uncommitted changes in your working directory. OPTIONS @@ -33,7 +33,7 @@ OPTIONS -e:: --edit:: - With this option, 'git-revert' will let you edit the commit + With this option, 'git revert' will let you edit the commit message prior to committing the revert. This is the default if you run the command from a terminal. @@ -54,7 +54,7 @@ See the link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for more details. --no-edit:: - With this option, 'git-revert' will not start the commit + With this option, 'git revert' will not start the commit message editor. -n:: 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..deaa7d9654 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -12,7 +12,7 @@ SYNOPSIS DESCRIPTION ----------- -Usually you would want to use 'git-push', which is a +Usually you would want to use 'git push', which is a higher-level wrapper of this command, instead. See linkgit:git-push[1]. Invokes 'git-receive-pack' on a possibly remote repository, and @@ -48,8 +48,8 @@ OPTIONS Run verbosely. --thin:: - Spend extra cycles to minimize the number of objects to be sent. - Use it on slower connection. + Send a "thin" pack, which records objects in deltified form based + on objects not included in the pack to reduce network traffic. <host>:: A remote host to house the repository. When this @@ -86,7 +86,7 @@ and the destination side (after the colon). The ref to be pushed is determined by finding a match that matches the source side, and where it is pushed is determined by using the destination side. The rules used to match a ref are the same -rules used by 'git-rev-parse' to resolve a symbolic ref +rules used by 'git rev-parse' to resolve a symbolic ref name. See linkgit:git-rev-parse[1]. - It is an error if <src> does not match exactly one of the @@ -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-sh-setup.txt b/Documentation/git-sh-setup.txt index 18f14b5be8..3da241304b 100644 --- a/Documentation/git-sh-setup.txt +++ b/Documentation/git-sh-setup.txt @@ -16,7 +16,7 @@ This is not a command the end user would want to run. Ever. This documentation is meant for people who are studying the Porcelain-ish scripts and/or are writing new ones. -The 'git-sh-setup' scriptlet is designed to be sourced (using +The 'git sh-setup' scriptlet is designed to be sourced (using `.`) by other shell scripts to set up some variables pointing at the normal git directories and a few helper shell functions. diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index 42463a955d..dfd4d0c223 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -3,17 +3,17 @@ git-shortlog(1) NAME ---- -git-shortlog - Summarize 'git-log' output +git-shortlog - Summarize 'git log' output SYNOPSIS -------- [verse] git log --pretty=short | 'git shortlog' [-h] [-n] [-s] [-e] [-w] -git shortlog [-n|--numbered] [-s|--summary] [-e|--email] [-w[<width>[,<indent1>[,<indent2>]]]] [<committish>...] +'git shortlog' [-n|--numbered] [-s|--summary] [-e|--email] [-w[<width>[,<indent1>[,<indent2>]]]] [<committish>...] DESCRIPTION ----------- -Summarizes 'git-log' output in a format suitable for inclusion +Summarizes 'git log' output in a format suitable for inclusion in release announcements. Each commit will be grouped by author and the first line of the commit message will be shown. diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index 734336119c..b9c4154e73 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -20,8 +20,8 @@ DESCRIPTION ----------- Shows the commit ancestry graph starting from the commits named -with <rev>s or <globs>s (or all refs under $GIT_DIR/refs/heads -and/or $GIT_DIR/refs/tags) semi-visually. +with <rev>s or <globs>s (or all refs under refs/heads +and/or refs/tags) semi-visually. It cannot show more than 29 branches and commits at a time. @@ -37,8 +37,8 @@ OPTIONS <glob>:: A glob pattern that matches branch or tag names under - $GIT_DIR/refs. For example, if you have many topic - branches under $GIT_DIR/refs/heads/topic, giving + refs/. For example, if you have many topic + branches under refs/heads/topic, giving `topic/*` would show all of them. -r:: @@ -176,7 +176,7 @@ EXAMPLE ------- If you keep your primary branches immediately under -`$GIT_DIR/refs/heads`, and topic branches in subdirectories of +`refs/heads`, and topic branches in subdirectories of it, having the following in the configuration file may help: ------------ diff --git a/Documentation/git-show-index.txt b/Documentation/git-show-index.txt index e3285aacfd..8382fbe0ec 100644 --- a/Documentation/git-show-index.txt +++ b/Documentation/git-show-index.txt @@ -14,10 +14,10 @@ SYNOPSIS DESCRIPTION ----------- Reads given idx file for packed git archive created with -'git-pack-objects' command, and dumps its contents. +'git pack-objects' command, and dumps its contents. The information it outputs is subset of what you can get from -'git-verify-pack -v'; this command only shows the packfile +'git verify-pack -v'; this command only shows the packfile offset and SHA1 of each object. diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt index f4429bdc68..df17d49b87 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. @@ -73,7 +72,7 @@ OPTIONS --exclude-existing[=<pattern>]:: - Make 'git-show-ref' act as a filter that reads refs from stdin of the + Make 'git show-ref' act as a filter that reads refs from stdin of the form "^(?:<anything>\s)?<refname>(?:\^\{\})?$" and performs the following actions on each: (1) strip "^{}" at the end of line if any; @@ -136,7 +135,7 @@ When using the '--verify' flag, the command requires an exact path: will only match the exact branch called "master". -If nothing matches, 'git-show-ref' will return an error code of 1, +If nothing matches, 'git show-ref' will return an error code of 1, and in the case of verification, it will show an error message. For scripting, you can ask it to be quiet with the "--quiet" flag, which diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt index 48b612e2ae..55e687a7c7 100644 --- a/Documentation/git-show.txt +++ b/Documentation/git-show.txt @@ -16,16 +16,16 @@ Shows one or more objects (blobs, trees, tags and commits). For commits it shows the log message and textual diff. It also presents the merge commit in a special format as produced by -'git-diff-tree --cc'. +'git diff-tree --cc'. For tags, it shows the tag message and the referenced objects. -For trees, it shows the names (equivalent to 'git-ls-tree' +For trees, it shows the names (equivalent to 'git ls-tree' with \--name-only). For plain blobs, it shows the plain contents. -The command takes options applicable to the 'git-diff-tree' command to +The command takes options applicable to the 'git diff-tree' command to control how the changes the commit introduces are shown. This manual page describes only the most frequently used options. diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index fafe728f89..473889a660 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -20,7 +20,7 @@ SYNOPSIS DESCRIPTION ----------- -Use 'git stash' when you want to record the current state of the +Use `git stash` when you want to record the current state of the working directory and the index, but want to go back to a clean working directory. The command saves your local modifications away and reverts the working directory to match the `HEAD` commit. @@ -33,7 +33,7 @@ A stash is by default listed as "WIP on 'branchname' ...", but you can give a more descriptive message on the command line when you create one. -The latest stash you created is stored in `$GIT_DIR/refs/stash`; older +The latest stash you created is stored in `refs/stash`; older stashes are found in the reflog of this reference and can be named using the usual reflog syntax (e.g. `stash@\{0}` is the most recently created stash, `stash@\{1}` is the one before it, `stash@\{2.hours.ago}` @@ -77,16 +77,15 @@ stash@{0}: WIP on submit: 6ebd0e2... Update git-stash documentation 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]. +The command takes options applicable to the 'git log' +command to control what is shown and how. See linkgit:git-log[1]. show [<stash>]:: Show the changes recorded in the stash as a diff between the stashed state and its original parent. When no `<stash>` is given, shows the latest one. By default, the command shows the diffstat, but - it will accept any format known to 'git-diff' (e.g., `git stash show + it will accept any format known to 'git diff' (e.g., `git stash show -p stash@\{1}` to view the second most recent stash in patch form). pop [--index] [-q|--quiet] [<stash>]:: @@ -202,7 +201,7 @@ $ git reset --soft HEAD^ # ... continue hacking ... ---------------------------------------------------------------- + -You can use 'git-stash' to simplify the above, like this: +You can use 'git stash' to simplify the above, like this: + ---------------------------------------------------------------- # ... hack hack hack ... diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 84f60f3407..1cab91b534 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 ----------- @@ -17,28 +17,93 @@ current HEAD commit, paths that have differences between the working tree and the index file, and paths in the working tree that are not tracked by git (and are not ignored by linkgit:gitignore[5]). The first are what you _would_ commit by running `git commit`; the second and -third are what you _could_ commit by running 'git-add' before running +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..2502531a3d 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 ./ @@ -95,11 +99,11 @@ locate the submodule using the relative URL in .gitmodules. status:: Show the status of the submodules. This will print the SHA-1 of the currently checked out commit for each submodule, along with the - submodule path and the output of 'git-describe' for the + submodule path and the output of 'git describe' for the SHA-1. Each SHA-1 will be prefixed with `-` if the submodule is not initialized and `+` if the currently checked out submodule commit does not match the SHA-1 found in the index of the containing - repository. This command is the default command for 'git-submodule'. + repository. This command is the default command for 'git submodule'. + If '--recursive' is specified, this command will recurse into nested submodules, and show their status as well. @@ -110,8 +114,8 @@ init:: The key used in .git/config is `submodule.$name.url`. This command does not alter existing information in .git/config. You can then customize the submodule clone URLs in .git/config - for your local setup and proceed to 'git submodule update'; - you can also just use 'git submodule update --init' without + for your local setup and proceed to `git submodule update`; + you can also just use `git submodule update --init` without the explicit 'init' step if you do not intend to customize any submodule locations. @@ -214,7 +218,7 @@ OPTIONS This option is only valid for the update command. Rebase the current branch onto the commit recorded in the superproject. If this option is given, the submodule's HEAD will not - be detached. If a a merge failure prevents this process, you will have + be detached. If a merge failure prevents this process, you will have to resolve these failures with linkgit:git-rebase[1]. If the key `submodule.$name.update` is set to `rebase`, this option is implicit. diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 1812890a7e..99f3c1ea6c 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -62,6 +62,8 @@ COMMANDS Set the 'useSvnsyncProps' option in the [svn-remote] config. --rewrite-root=<URL>;; Set the 'rewriteRoot' option in the [svn-remote] config. +--rewrite-uuid=<UUID>;; + Set the 'rewriteUUID' option in the [svn-remote] config. --username=<USER>;; For transports that SVN handles authentication for (http, https, and plain svn), specify the username. For other @@ -239,6 +241,19 @@ discouraged. where <name> is the name of the SVN repository as specified by the -R option to 'init' (or "svn" by default). +--username;; + Specify the SVN username to perform the commit as. This option overrides + configuration property 'username'. + +--commit-url;; + Use the specified URL to connect to the destination Subversion + repository. This is useful in cases where the source SVN + repository is read-only. This option overrides configuration + property 'commiturl'. ++ + git config --get-all svn-remote.<name>.commiturl ++ + 'tag':: Create a tag in the SVN repository. This is a shorthand for 'branch -t'. @@ -320,6 +335,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 @@ -609,6 +631,12 @@ svn-remote.<name>.rewriteRoot:: the repository with a public http:// or svn:// URL in the metadata so users of it will see the public URL. +svn-remote.<name>.rewriteUUID:: + Similar to the useSvmProps option; this is for users who need + to remap the UUID manually. This may be useful in situations + where the original UUID is not available via either useSvmProps + or useSvnsyncProps. + svn.brokenSymlinkWorkaround:: This disables potentially expensive checks to workaround broken symlinks checked into SVN by broken clients. Set this @@ -618,13 +646,14 @@ svn.brokenSymlinkWorkaround:: revision fetched. If unset, 'git svn' assumes this option to be "true". -Since the noMetadata, rewriteRoot, useSvnsyncProps and useSvmProps +Since the noMetadata, rewriteRoot, rewriteUUID, useSvnsyncProps and useSvmProps options all affect the metadata generated and used by 'git svn'; they *must* be set in the configuration file before any history is imported and these settings should never be changed once they are set. -Additionally, only one of these four options can be used per-svn-remote -section because they affect the 'git-svn-id:' metadata line. +Additionally, only one of these options can be used per svn-remote +section because they affect the 'git-svn-id:' metadata line, except +for rewriteRoot and rewriteUUID which can be used together. BASIC EXAMPLES @@ -735,6 +764,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 @@ -799,6 +838,22 @@ independent path component (surrounded by '/' or EOL). This type of configuration is not automatically created by 'init' and should be manually entered with a text-editor or using 'git config'. +It is also possible to fetch a subset of branches or tags by using a +comma-separated list of names within braces. For example: + +------------------------------------------------------------------------ +[svn-remote "huge-project"] + url = http://server.org/svn + fetch = trunk/src:refs/remotes/trunk + branches = branches/{red,green}/src:refs/remotes/branches/* + tags = tags/{1.0,2.0}/src:refs/remotes/tags/* +------------------------------------------------------------------------ + +Note that git-svn keeps track of the highest revision in which a branch +or tag has appeared. If the subset of branches or tags is changed after +fetching, then .git/svn/.metadata must be manually edited to remove (or +reset) branches-maxRev and/or tags-maxRev as appropriate. + SEE ALSO -------- linkgit:git-rebase[1] diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt index 6392538807..33a1536294 100644 --- a/Documentation/git-symbolic-ref.txt +++ b/Documentation/git-symbolic-ref.txt @@ -49,7 +49,7 @@ cumbersome. On some platforms, `ln -sf` does not even work as advertised (horrors). Therefore symbolic links are now deprecated and symbolic refs are used by default. -'git-symbolic-ref' will exit with status 0 if the contents of the +'git symbolic-ref' will exit with status 0 if the contents of the symbolic ref were printed correctly, with status 1 if the requested name is not a symbolic ref, or 128 if another error occurs. diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 299b04f726..31c78a81e0 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -95,7 +95,7 @@ OPTIONS CONFIGURATION ------------- -By default, 'git-tag' in sign-with-default mode (-s) will use your +By default, 'git tag' in sign-with-default mode (-s) will use your committer identity (of the form "Your Name <your@email.address>") to find a key. If you want to use a different default key, you can specify it in the repository configuration as follows: @@ -131,12 +131,12 @@ and be done with it. . The insane thing. You really want to call the new version "X" too, 'even though' -others have already seen the old one. So just use 'git-tag -f' +others have already seen the old one. So just use 'git tag -f' again, as if you hadn't already published the old one. However, Git does *not* (and it should not) change tags behind users back. So if somebody already got the old tag, doing a -'git-pull' on your tree shouldn't just make them overwrite the old +'git pull' on your tree shouldn't just make them overwrite the old one. If somebody got a release tag from you, you cannot just change @@ -190,7 +190,7 @@ private anchor point tags from the other person. You would notice "please pull" messages on the mailing list says repo URL and branch name alone. This is designed to be easily -cut&pasted to a 'git-fetch' command line: +cut&pasted to a 'git fetch' command line: ------------ Linus, please pull from diff --git a/Documentation/git-tar-tree.txt b/Documentation/git-tar-tree.txt index a5d9558dd1..3c786bd283 100644 --- a/Documentation/git-tar-tree.txt +++ b/Documentation/git-tar-tree.txt @@ -12,19 +12,19 @@ SYNOPSIS DESCRIPTION ----------- -THIS COMMAND IS DEPRECATED. Use 'git-archive' with `--format=tar` +THIS COMMAND IS DEPRECATED. Use 'git archive' with `--format=tar` option instead (and move the <base> argument to `--prefix=base/`). Creates a tar archive containing the tree structure for the named tree. When <base> is specified it is added as a leading path to the files in the generated tar archive. -'git-tar-tree' behaves differently when given a tree ID versus when given +'git tar-tree' behaves differently when given a tree ID versus when given a commit ID or tag ID. In the first case the current time is used as modification time of each file in the archive. In the latter case the commit time as recorded in the referenced commit object is used instead. Additionally the commit ID is stored in a global extended pax header. -It can be extracted using 'git-get-tar-commit-id'. +It can be extracted using 'git get-tar-commit-id'. OPTIONS ------- diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 25e0bbea86..68dc1879fe 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -15,6 +15,7 @@ SYNOPSIS [--cacheinfo <mode> <object> <file>]\* [--chmod=(+|-)x] [--assume-unchanged | --no-assume-unchanged] + [--skip-worktree | --no-skip-worktree] [--ignore-submodules] [--really-refresh] [--unresolve] [--again | -g] [--info-only] [--index-info] @@ -31,7 +32,7 @@ cleared. See also linkgit:git-add[1] for a more user-friendly way to do some of the most common operations on the index. -The way 'git-update-index' handles files it is told about can be modified +The way 'git update-index' handles files it is told about can be modified using the various options: OPTIONS @@ -53,7 +54,7 @@ OPTIONS -q:: Quiet. If --refresh finds that the index needs an update, the default behavior is to error out. This option makes - 'git-update-index' continue anyway. + 'git update-index' continue anyway. --ignore-submodules:: Do not try to update submodules. This option is only respected @@ -61,7 +62,7 @@ OPTIONS --unmerged:: If --refresh finds unmerged changes in the index, the default - behavior is to error out. This option makes 'git-update-index' + behavior is to error out. This option makes 'git update-index' continue anyway. --ignore-missing:: @@ -99,9 +100,20 @@ 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. + +--skip-worktree:: +--no-skip-worktree:: + When one of these flags is specified, the object name recorded + for the paths are not updated. Instead, these options + set and unset the "skip-worktree" bit for the paths. See + section "Skip-worktree bit" below for more information. + -g:: --again:: - Runs 'git-update-index' itself on the paths whose index + Runs 'git update-index' itself on the paths whose index entries are different from those from the `HEAD` commit. --unresolve:: @@ -119,7 +131,7 @@ you will need to handle the situation manually. --replace:: By default, when a file `path` exists in the index, - 'git-update-index' refuses an attempt to add `path/file`. + 'git update-index' refuses an attempt to add `path/file`. Similarly if a file `path/file` exists, a file `path` cannot be added. With --replace flag, existing entries that conflict with the entry being added are @@ -155,7 +167,7 @@ up-to-date for mode/content changes. But what it *does* do is to can refresh the index for a file that hasn't been changed but where the stat entry is out of date. -For example, you'd want to do this after doing a 'git-read-tree', to link +For example, you'd want to do this after doing a 'git read-tree', to link up the stat index details with the proper files. Using --cacheinfo or --info-only @@ -196,13 +208,13 @@ back on 3-way merge. . mode SP type SP sha1 TAB path + -The second format is to stuff 'git-ls-tree' output +The second format is to stuff 'git ls-tree' output into the index file. . mode SP sha1 SP stage TAB path + This format is to put higher order stages into the -index file and matches 'git-ls-files --stage' output. +index file and matches 'git ls-files --stage' output. To place a higher stage entry to the index, the path should first be removed by feeding a mode=0 entry for the path, and @@ -259,8 +271,8 @@ option. To unset, use `--no-assume-unchanged`. The command looks at `core.ignorestat` configuration variable. When this is true, paths updated with `git update-index paths...` and paths updated with other git commands that update both index and -working tree (e.g. 'git-apply --index', 'git-checkout-index -u', -and 'git-read-tree -u') are automatically marked as "assume +working tree (e.g. 'git apply --index', 'git checkout-index -u', +and 'git read-tree -u') are automatically marked as "assume unchanged". Note that "assume unchanged" bit is *not* set if `git update-index --refresh` finds the working tree file matches the index (use `git update-index --really-refresh` if you want @@ -304,16 +316,37 @@ M foo.c <9> now it checks with lstat(2) and finds it has been changed. +Skip-worktree bit +----------------- + +Skip-worktree bit can be defined in one (long) sentence: When reading +an entry, if it is marked as skip-worktree, then Git pretends its +working directory version is up to date and read the index version +instead. + +To elaborate, "reading" means checking for file existence, reading +file attributes or file content. The working directory version may be +present or absent. If present, its content may match against the index +version or not. Writing is not affected by this bit, content safety +is still first priority. Note that Git _can_ update working directory +file, that is marked skip-worktree, if it is safe to do so (i.e. +working directory version matches index version) + +Although this bit looks similar to assume-unchanged bit, its goal is +different from assume-unchanged bit's. Skip-worktree also takes +precedence over assume-unchanged bit when both are set. + + 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 executable bit. On such an unfortunate filesystem, you may -need to use 'git-update-index --chmod='. +need to use 'git update-index --chmod='. Quite similarly, if `core.symlinks` configuration variable is set to 'false' (see linkgit:git-config[1]), symbolic links are checked out diff --git a/Documentation/git-upload-archive.txt b/Documentation/git-upload-archive.txt index bbd7617587..f5f2b3908b 100644 --- a/Documentation/git-upload-archive.txt +++ b/Documentation/git-upload-archive.txt @@ -12,11 +12,11 @@ SYNOPSIS DESCRIPTION ----------- -Invoked by 'git-archive --remote' and sends a generated archive to the +Invoked by 'git archive --remote' and sends a generated archive to the other end over the git protocol. This command is usually not invoked directly by the end user. The UI -for the protocol is on the 'git-archive' side, and the program pair +for the protocol is on the 'git archive' side, and the program pair is meant to be used to get an archive from a remote repository. OPTIONS diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt index b8e49dce4a..71ca4ef442 100644 --- a/Documentation/git-upload-pack.txt +++ b/Documentation/git-upload-pack.txt @@ -8,17 +8,17 @@ git-upload-pack - Send objects packed back to git-fetch-pack SYNOPSIS -------- -'git upload-pack' [--strict] [--timeout=<n>] <directory> +'git-upload-pack' [--strict] [--timeout=<n>] <directory> DESCRIPTION ----------- -Invoked by 'git-fetch-pack', learns what +Invoked by 'git fetch-pack', learns what objects the other side is missing, and sends them after packing. This command is usually not invoked directly by the end user. -The UI for the protocol is on the 'git-fetch-pack' side, and the +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'. +repository. For push operations, see 'git send-pack'. OPTIONS diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt index e2f4c0901b..bb981822a4 100644 --- a/Documentation/git-var.txt +++ b/Documentation/git-var.txt @@ -20,7 +20,7 @@ OPTIONS Cause the logical variables to be listed. In addition, all the variables of the git configuration file .git/config are listed as well. (However, the configuration variables listing functionality - is deprecated in favor of 'git config -l'.) + is deprecated in favor of `git config -l`.) EXAMPLE -------- @@ -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-verify-pack.txt b/Documentation/git-verify-pack.txt index 97f7f9165e..916a38aa99 100644 --- a/Documentation/git-verify-pack.txt +++ b/Documentation/git-verify-pack.txt @@ -14,7 +14,7 @@ SYNOPSIS DESCRIPTION ----------- Reads given idx file for packed git archive created with the -'git-pack-objects' command and verifies idx file and the +'git pack-objects' command and verifies idx file and the corresponding pack file. OPTIONS diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt index 84e70a0234..dada21242c 100644 --- a/Documentation/git-verify-tag.txt +++ b/Documentation/git-verify-tag.txt @@ -11,7 +11,7 @@ SYNOPSIS DESCRIPTION ----------- -Validates the gpg signature created by 'git-tag'. +Validates the gpg signature created by 'git tag'. OPTIONS ------- diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt index 278cf73527..75720491b2 100644 --- a/Documentation/git-web--browse.txt +++ b/Documentation/git-web--browse.txt @@ -62,7 +62,7 @@ browser.<tool>.path You can explicitly provide a full path to your preferred browser by setting the configuration variable 'browser.<tool>.path'. For example, you can configure the absolute path to firefox by setting -'browser.firefox.path'. Otherwise, 'git-web--browse' assumes the tool +'browser.firefox.path'. Otherwise, 'git web--browse' assumes the tool is available in PATH. browser.<tool>.cmd @@ -71,7 +71,7 @@ browser.<tool>.cmd When the browser, specified by options or configuration variables, is not among the supported ones, then the corresponding 'browser.<tool>.cmd' configuration variable will be looked up. If this -variable exists then 'git-web--browse' will treat the specified tool +variable exists then 'git web--browse' will treat the specified tool as a custom command and will use a shell eval to run the command with the URLs passed as arguments. @@ -113,7 +113,7 @@ See linkgit:git-config[1] for more information about this. Author ------ Written by Christian Couder <chriscool@tuxfamily.org> and the git-list -<git@vger.kernel.org>, based on 'git-mergetool' by Theodore Y. Ts'o. +<git@vger.kernel.org>, based on 'git mergetool' by Theodore Y. Ts'o. Documentation ------------- diff --git a/Documentation/git-whatchanged.txt b/Documentation/git-whatchanged.txt index cadfbd9040..ea753cdafc 100644 --- a/Documentation/git-whatchanged.txt +++ b/Documentation/git-whatchanged.txt @@ -13,8 +13,8 @@ SYNOPSIS DESCRIPTION ----------- Shows commit logs and diff output each commit introduces. The -command internally invokes 'git-rev-list' piped to -'git-diff-tree', and takes command line options for both of +command internally invokes 'git rev-list' piped to +'git diff-tree', and takes command line options for both of these commands. This manual page describes only the most frequently used options. diff --git a/Documentation/git-write-tree.txt b/Documentation/git-write-tree.txt index c8899d528a..bfceacacb3 100644 --- a/Documentation/git-write-tree.txt +++ b/Documentation/git-write-tree.txt @@ -17,17 +17,17 @@ tree object is printed to standard output. The index must be in a fully merged state. -Conceptually, 'git-write-tree' sync()s the current index contents +Conceptually, 'git write-tree' sync()s the current index contents into a set of tree files. In order to have that match what is actually in your directory right -now, you need to have done a 'git-update-index' phase before you did the -'git-write-tree'. +now, you need to have done a 'git update-index' phase before you did the +'git write-tree'. OPTIONS ------- --missing-ok:: - Normally 'git-write-tree' ensures that the objects referenced by the + Normally 'git write-tree' ensures that the objects referenced by the directory exist in the object database. This option disables this check. diff --git a/Documentation/git.txt b/Documentation/git.txt index 46558c8a49..cc32ce18d2 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,6 +43,19 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: +* link:v1.7.0.1/git.html[documentation for release 1.7.0.1] + +* release notes for + link:RelNotes-1.7.0.1.txt[1.7.0.1], + link:RelNotes-1.7.0.txt[1.7.0]. + +* link:v1.6.6.2/git.html[documentation for release 1.6.6.2] + +* release notes for + link:RelNotes-1.6.6.2.txt[1.6.6.2], + link:RelNotes-1.6.6.1.txt[1.6.6.1], + link:RelNotes-1.6.6.txt[1.6.6]. + * link:v1.6.5.8/git.html[documentation for release 1.6.5.8] * release notes for @@ -245,6 +258,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 --------------------- @@ -565,8 +582,8 @@ other linkgit:git-config[1]. 'GIT_SSH':: - If this environment variable is set then 'git-fetch' - and 'git-push' will use this command instead + If this environment variable is set then 'git fetch' + and 'git push' will use this command instead of 'ssh' when they need to connect to a remote system. The '$GIT_SSH' command will be given exactly two arguments: the 'username@host' (or just 'host') from the URL and the @@ -582,8 +599,8 @@ for further details. 'GIT_FLUSH':: If this environment variable is set to "1", then commands such - as 'git-blame' (in incremental mode), 'git-rev-list', 'git-log', - and 'git-whatchanged' will force a flush of the output stream + as 'git blame' (in incremental mode), 'git rev-list', 'git log', + and 'git whatchanged' will force a flush of the output stream after each commit-oriented record have been flushed. If this variable is set to "0", the output of these commands will be done using completely buffered I/O. If this environment variable is diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 5a45e51890..d892e642ed 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -88,9 +88,9 @@ Checking-out and checking-in These attributes affect how the contents stored in the repository are copied to the working tree files when commands -such as 'git-checkout' and 'git-merge' run. They also affect how +such as 'git checkout' and 'git merge' run. They also affect how git stores the contents you prepare in the working tree in the -repository upon 'git-add' and 'git-commit'. +repository upon 'git add' and 'git commit'. `crlf` ^^^^^^ @@ -148,16 +148,16 @@ an irreversible conversion. The safety triggers to prevent such a conversion done to the files in the work tree, but there are a few exceptions. Even though... -- 'git-add' itself does not touch the files in the work tree, the +- 'git add' itself does not touch the files in the work tree, the next checkout would, so the safety triggers; -- 'git-apply' to update a text file with a patch does touch the files +- 'git apply' to update a text file with a patch does touch the files in the work tree, but the operation is about text files and CRLF conversion is about fixing the line ending inconsistencies, so the safety does not trigger; -- 'git-diff' itself does not touch the files in the work tree, it is - often run to inspect the changes you intend to next 'git-add'. To +- 'git diff' itself does not touch the files in the work tree, it is + often run to inspect the changes you intend to next 'git add'. To catch potential problems early, safety triggers. @@ -511,7 +511,8 @@ command to run to merge ancestor's version (`%O`), current version (`%A`) and the other branches' version (`%B`). These three tokens are replaced with the names of temporary files that hold the contents of these versions when the command line is -built. +built. Additionally, %L will be replaced with the conflict marker +size (see below). The merge driver is expected to leave the result of the merge in the file named with `%A` by overwriting it, and exit with zero @@ -525,6 +526,23 @@ When left unspecified, the driver itself is used for both internal merge and the final merge. +`conflict-marker-size` +^^^^^^^^^^^^^^^^^^^^^^ + +This attribute controls the length of conflict markers left in +the work tree file during a conflicted merge. Only setting to +the value to a positive integer has any meaningful effect. + +For example, this line in `.gitattributes` can be used to tell the merge +machinery to leave much longer (instead of the usual 7-character-long) +conflict markers when merging the file `Documentation/git-merge.txt` +results in a conflict. + +------------------------ +Documentation/git-merge.txt conflict-marker-size=32 +------------------------ + + Checking whitespace errors ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index 0382d2c0ad..f7815e96a2 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -27,6 +27,14 @@ interfaces on top of it called "porcelain". You may not want to use the plumbing directly very often, but it can be good to know what the plumbing does for when the porcelain isn't flushing. +Back when this document was originally written, many porcelain +commands were shell scripts. For simplicity, it still uses them as +examples to illustrate how plumbing is fit together to form the +porcelain commands. The source tree includes some of these scripts in +contrib/examples/ for reference. Although these are not implemented as +shell scripts anymore, the description of what the plumbing layer +commands do is still valid. + [NOTE] Deeper technical details are often marked as Notes, which you can skip on your first reading. @@ -44,7 +52,7 @@ to import into git. For our first example, we're going to start a totally new repository from scratch, with no pre-existing files, and we'll call it 'git-tutorial'. To start up, create a subdirectory for it, change into that -subdirectory, and initialize the git infrastructure with 'git-init': +subdirectory, and initialize the git infrastructure with 'git init': ------------------------------------------------ $ mkdir git-tutorial @@ -139,7 +147,7 @@ but to actually check in your hard work, you will have to go through two steps: - commit that index file as an object. The first step is trivial: when you want to tell git about any changes -to your working tree, you use the 'git-update-index' program. That +to your working tree, you use the 'git update-index' program. That program normally just takes a list of filenames you want to update, but to avoid trivial mistakes, it refuses to add new entries to the index (or remove existing ones) unless you explicitly tell it that you're @@ -173,19 +181,19 @@ and see two files: which correspond with the objects with names of `557db...` and `f24c7...` respectively. -If you want to, you can use 'git-cat-file' to look at those objects, but +If you want to, you can use 'git cat-file' to look at those objects, but you'll have to use the object name, not the filename of the object: ---------------- $ git cat-file -t 557db03de997c86a4a028e1ebd3a1ceb225be238 ---------------- -where the `-t` tells 'git-cat-file' to tell you what the "type" of the +where the `-t` tells 'git cat-file' to tell you what the "type" of the 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 @@ -205,7 +213,7 @@ hexadecimal digits in most places. Anyway, as we mentioned previously, you normally never actually take a look at the objects themselves, and typing long 40-character hex names is not something you'd normally want to do. The above digression -was just to show that 'git-update-index' did something magical, and +was just to show that 'git update-index' did something magical, and actually saved away the contents of your files into the git object database. @@ -228,7 +236,7 @@ $ echo "It's a new day for git" >>hello and you can now, since you told git about the previous state of `hello`, ask git what has changed in the tree compared to your old index, using the -'git-diff-files' command: +'git diff-files' command: ------------ $ git diff-files @@ -239,7 +247,7 @@ version of a 'diff', but that internal version really just tells you that it has noticed that "hello" has been modified, and that the old object contents it had have been replaced with something else. -To make it readable, we can tell 'git-diff-files' to output the +To make it readable, we can tell 'git diff-files' to output the differences as a patch, using the `-p` flag: ------------ @@ -255,7 +263,7 @@ index 557db03..263414f 100644 i.e. the diff of the change we caused by adding another line to `hello`. -In other words, 'git-diff-files' always shows us the difference between +In other words, 'git diff-files' always shows us the difference between what is recorded in the index, and what is currently in the working tree. That's very useful. @@ -283,7 +291,7 @@ that in two phases: creating a 'tree' object, and committing that 'tree' object as a 'commit' object together with an explanation of what the tree was all about, along with information of how we came to that state. -Creating a tree object is trivial, and is done with 'git-write-tree'. +Creating a tree object is trivial, and is done with 'git write-tree'. There are no options or other input: `git write-tree` will take the current index state, and write an object that describes that whole index. In other words, we're now tying together all the different @@ -307,23 +315,23 @@ is not a "blob" object, but a "tree" object (you can also use `git cat-file` to actually output the raw object contents, but you'll see mainly a binary mess, so that's less interesting). -However -- normally you'd never use 'git-write-tree' on its own, because +However -- normally you'd never use 'git write-tree' on its own, because normally you always commit a tree into a commit object using the -'git-commit-tree' command. In fact, it's easier to not actually use -'git-write-tree' on its own at all, but to just pass its result in as an -argument to 'git-commit-tree'. +'git commit-tree' command. In fact, it's easier to not actually use +'git write-tree' on its own at all, but to just pass its result in as an +argument to 'git commit-tree'. -'git-commit-tree' normally takes several arguments -- it wants to know +'git commit-tree' normally takes several arguments -- it wants to know what the 'parent' of a commit was, but since this is the first commit ever in this new repository, and it has no parents, we only need to pass in -the object name of the tree. However, 'git-commit-tree' also wants to get a +the object name of the tree. However, 'git commit-tree' also wants to get a commit message on its standard input, and it will write out the resulting object name for the commit to its standard output. And this is where we create the `.git/refs/heads/master` file which is pointed at by `HEAD`. This file is supposed to contain the reference to the top-of-tree of the master branch, and since -that's exactly what 'git-commit-tree' spits out, we can do this +that's exactly what 'git commit-tree' spits out, we can do this all with a sequence of simple shell commands: ------------------------------------------------ @@ -345,11 +353,11 @@ instead, and it would have done the above magic scripting for you. Making a change --------------- -Remember how we did the 'git-update-index' on file `hello` and then we +Remember how we did the 'git update-index' on file `hello` and then we changed `hello` afterward, and could compare the new state of `hello` with the state we saved in the index file? -Further, remember how I said that 'git-write-tree' writes the contents +Further, remember how I said that 'git write-tree' writes the contents of the *index* file to the tree, and thus what we just committed was in fact the *original* contents of the file `hello`, not the new ones. We did that on purpose, to show the difference between the index state, and the @@ -360,12 +368,12 @@ As before, if we do `git diff-files -p` in our git-tutorial project, we'll still see the same difference we saw last time: the index file hasn't changed by the act of committing anything. However, now that we have committed something, we can also learn to use a new command: -'git-diff-index'. +'git diff-index'. -Unlike 'git-diff-files', which showed the difference between the index -file and the working tree, 'git-diff-index' shows the differences +Unlike 'git diff-files', which showed the difference between the index +file and the working tree, 'git diff-index' shows the differences between a committed *tree* and either the index file or the working -tree. In other words, 'git-diff-index' wants a tree to be diffed +tree. In other words, 'git diff-index' wants a tree to be diffed against, and before we did the commit, we couldn't do that, because we didn't have anything to diff against. @@ -375,7 +383,7 @@ But now we can do $ git diff-index -p HEAD ---------------- -(where `-p` has the same meaning as it did in 'git-diff-files'), and it +(where `-p` has the same meaning as it did in 'git diff-files'), and it will show us the same difference, but for a totally different reason. Now we're comparing the working tree not against the index file, but against the tree we just wrote. It just so happens that those two @@ -390,7 +398,7 @@ $ git diff HEAD which ends up doing the above for you. -In other words, 'git-diff-index' normally compares a tree against the +In other words, 'git diff-index' normally compares a tree against the working tree, but when given the `\--cached` flag, it is told to instead compare against just the index cache contents, and ignore the current working tree state entirely. Since we just wrote the index @@ -399,7 +407,7 @@ an empty set of differences, and that's exactly what it does. [NOTE] ================ -'git-diff-index' really always uses the index for its +'git diff-index' really always uses the index for its comparisons, and saying that it compares a tree against the working tree is thus not strictly accurate. In particular, the list of files to compare (the "meta-data") *always* comes from the index file, @@ -428,11 +436,11 @@ $ git update-index hello (note how we didn't need the `\--add` flag this time, since git knew about the file already). -Note what happens to the different 'git-diff-\*' versions here. After +Note what happens to the different 'git diff-\*' versions here. After we've updated `hello` in the index, `git diff-files -p` now shows no differences, but `git diff-index -p HEAD` still *does* show that the current state is different from the state we committed. In fact, now -'git-diff-index' shows the same difference whether we use the `--cached` +'git diff-index' shows the same difference whether we use the `--cached` flag or not, since now the index is coherent with the working tree. Now, since we've updated `hello` in the index, we can commit the new @@ -460,7 +468,7 @@ You've now made your first real git commit. And if you're interested in looking at what `git commit` really does, feel free to investigate: it's a few very simple shell scripts to generate the helpful (?) commit message headers, and a few one-liners that actually do the -commit itself ('git-commit'). +commit itself ('git commit'). Inspecting Changes @@ -468,9 +476,9 @@ Inspecting Changes While creating changes is useful, it's even more useful if you can tell later what changed. The most useful command for this is another of the -'diff' family, namely 'git-diff-tree'. +'diff' family, namely 'git diff-tree'. -'git-diff-tree' can be given two arbitrary trees, and it will tell you the +'git diff-tree' can be given two arbitrary trees, and it will tell you the differences between them. Perhaps even more commonly, though, you can give it just a single commit object, and it will figure out the parent of that commit itself, and show the difference directly. Thus, to get @@ -518,15 +526,15 @@ various diff-\* commands compare things. +-----------+ ============ -More interestingly, you can also give 'git-diff-tree' the `--pretty` flag, +More interestingly, you can also give 'git diff-tree' the `--pretty` flag, which tells it to also show the commit message and author and date of the commit, and you can tell it to show a whole series of diffs. Alternatively, you can tell it to be "silent", and not show the diffs at all, but just show the actual commit message. -In fact, together with the 'git-rev-list' program (which generates a -list of revisions), 'git-diff-tree' ends up being a veritable fount of -changes. A trivial (but very useful) script called 'git-whatchanged' is +In fact, together with the 'git rev-list' program (which generates a +list of revisions), 'git diff-tree' ends up being a veritable fount of +changes. A trivial (but very useful) script called 'git whatchanged' is included with git which does exactly this, and shows a log of recent activities. @@ -553,14 +561,14 @@ When using the above two commands, the initial commit will be shown. If this is a problem because it is huge, you can hide it by setting the log.showroot configuration variable to false. Having this, you can still show it for each command just adding the `\--root` option, -which is a flag for 'git-diff-tree' accepted by both commands. +which is a flag for 'git diff-tree' accepted by both commands. With that, you should now be having some inkling of what git does, and can explore on your own. [NOTE] Most likely, you are not directly using the core -git Plumbing commands, but using Porcelain such as 'git-add', `git-rm' +git Plumbing commands, but using Porcelain such as 'git add', `git-rm' and `git-commit'. @@ -595,7 +603,7 @@ pointer to the state you want to tag, but also a small tag name and message, along with optionally a PGP signature that says that yes, you really did that tag. You create these annotated tags with either the `-a` or -`-s` flag to 'git-tag': +`-s` flag to 'git tag': ---------------- $ git tag -s <tagname> @@ -642,7 +650,7 @@ and it will be gone. There's no external repository, and there's no history outside the project you created. - if you want to move or duplicate a git repository, you can do so. There - is 'git-clone' command, but if all you want to do is just to + is 'git clone' command, but if all you want to do is just to create a copy of your repository (with all the full history that went along with it), you can do so with a regular `cp -a git-tutorial new-git-tutorial`. @@ -666,7 +674,7 @@ When copying a remote repository, you'll want to at a minimum update the index cache when you do this, and especially with other peoples' repositories you often want to make sure that the index cache is in some known state (you don't know *what* they've done and not yet checked in), -so usually you'll precede the 'git-update-index' with a +so usually you'll precede the 'git update-index' with a ---------------- $ git read-tree --reset HEAD @@ -674,7 +682,7 @@ $ git update-index --refresh ---------------- which will force a total index re-build from the tree pointed to by `HEAD`. -It resets the index contents to `HEAD`, and then the 'git-update-index' +It resets the index contents to `HEAD`, and then the 'git update-index' makes sure to match up all index entries with the checked-out files. If the original repository had uncommitted changes in its working tree, `git update-index --refresh` notices them and @@ -689,8 +697,8 @@ $ git reset and in fact a lot of the common git command combinations can be scripted with the `git xyz` interfaces. You can learn things by just looking at what the various git scripts do. For example, `git reset` used to be -the above two lines implemented in 'git-reset', but some things like -'git-status' and 'git-commit' are slightly more complex scripts around +the above two lines implemented in 'git reset', but some things like +'git status' and 'git commit' are slightly more complex scripts around the basic git commands. Many (most?) public remote repositories will not contain any of @@ -729,7 +737,7 @@ where the `-u` flag means that you want the checkout to keep the index up-to-date (so that you don't have to refresh it afterward), and the `-a` flag means "check out all files" (if you have a stale copy or an older version of a checked out tree you may also need to add the `-f` -flag first, to tell 'git-checkout-index' to *force* overwriting of any old +flag first, to tell 'git checkout-index' to *force* overwriting of any old files). Again, this can all be simplified with @@ -776,7 +784,7 @@ to it. ================================================ If you make the decision to start your new branch at some other point in the history than the current `HEAD`, you can do so by -just telling 'git-checkout' what the base of the checkout would be. +just telling 'git checkout' what the base of the checkout would be. In other words, if you have an earlier tag or branch, you'd just do ------------ @@ -819,7 +827,7 @@ $ git branch <branchname> [startingpoint] which will simply _create_ the branch, but will not do anything further. You can then later -- once you decide that you want to actually develop -on that branch -- switch to that branch with a regular 'git-checkout' +on that branch -- switch to that branch with a regular 'git checkout' with the branchname as the argument. @@ -881,7 +889,7 @@ source. Anyway, let's exit 'gitk' (`^Q` or the File menu), and decide that we want to merge the work we did on the `mybranch` branch into the `master` branch (which is currently our `HEAD` too). To do that, there's a nice -script called 'git-merge', which wants to know which branches you want +script called 'git merge', which wants to know which branches you want to resolve and what the merge is all about: ------------ @@ -925,7 +933,7 @@ $ git commit -i hello which will very loudly warn you that you're now committing a merge (which is correct, so never mind), and you can write a small merge -message about your adventures in 'git-merge'-land. +message about your adventures in 'git merge'-land. After you're done, start up `gitk \--all` to see graphically what the history looks like. Notice that `mybranch` still exists, and you can @@ -967,21 +975,21 @@ branch head. Please see linkgit:git-rev-parse[1] if you want to see more complex cases. [NOTE] -Without the '--more=1' option, 'git-show-branch' would not output the +Without the '--more=1' option, 'git show-branch' would not output the '[master^]' commit, as '[mybranch]' commit is a common ancestor of both 'master' and 'mybranch' tips. Please see linkgit:git-show-branch[1] for details. [NOTE] If there were more commits on the 'master' branch after the merge, the -merge commit itself would not be shown by 'git-show-branch' by +merge commit itself would not be shown by 'git show-branch' by default. You would need to provide '--sparse' option to make the merge commit visible in this case. Now, let's pretend you are the one who did all the work in `mybranch`, and the fruit of your hard work has finally been merged to the `master` branch. Let's go back to `mybranch`, and run -'git-merge' to get the "upstream changes" back to your branch. +'git merge' to get the "upstream changes" back to your branch. ------------ $ git checkout mybranch @@ -993,7 +1001,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 +1011,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. @@ -1023,12 +1031,12 @@ Merging external work It's usually much more common that you merge with somebody else than merging with your own branches, so it's worth pointing out that git makes that very easy too, and in fact, it's not that different from -doing a 'git-merge'. In fact, a remote merge ends up being nothing +doing a 'git merge'. In fact, a remote merge ends up being nothing more than "fetch the work from a remote repository into a temporary tag" -followed by a 'git-merge'. +followed by a 'git merge'. Fetching from a remote repository is done by, unsurprisingly, -'git-fetch': +'git fetch': ---------------- $ git fetch <remote-repository> @@ -1095,7 +1103,7 @@ The 'commit walkers' are sometimes also called 'dumb transports', because they do not require any git aware smart server like git Native transport does. Any stock HTTP server that does not even support directory index would suffice. But -you must prepare your repository with 'git-update-server-info' +you must prepare your repository with 'git update-server-info' to help dumb transport downloaders. Once you fetch from the remote repository, you `merge` that @@ -1115,7 +1123,7 @@ argument. [NOTE] You could do without using any branches at all, by keeping as many local repositories as you would like to have -branches, and merging between them with 'git-pull', just like +branches, and merging between them with 'git pull', just like you merge between branches. The advantage of this approach is that it lets you keep a set of files for each `branch` checked out and you may find it easier to switch back and forth if you @@ -1132,7 +1140,7 @@ like this: $ git config remote.linus.url http://www.kernel.org/pub/scm/git/git.git/ ------------------------------------------------ -and use the "linus" keyword with 'git-pull' instead of the full URL. +and use the "linus" keyword with 'git pull' instead of the full URL. Examples. @@ -1168,7 +1176,7 @@ $ git show-branch --more=2 master mybranch +* [master^] Some fun. ------------ -Remember, before running 'git-merge', our `master` head was at +Remember, before running 'git merge', our `master` head was at "Some fun." commit, while our `mybranch` head was at "Some work." commit. @@ -1186,16 +1194,16 @@ $ 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. `git merge` command, when merging two branches, uses 3-way merge algorithm. First, it finds the common ancestor between them. -The command it uses is 'git-merge-base': +The command it uses is 'git merge-base': ------------ $ mb=$(git merge-base HEAD mybranch) @@ -1204,11 +1212,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 ------------ @@ -1219,7 +1227,7 @@ this: $ git read-tree -m -u $mb HEAD mybranch ------------ -This is the same 'git-read-tree' command we have already seen, +This is the same 'git read-tree' command we have already seen, but it takes three trees, unlike previous examples. This reads the contents of each tree into different 'stage' in the index file (the first tree goes to stage 1, the second to stage 2, @@ -1237,8 +1245,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,15 +1261,15 @@ 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 ------------ The next step of merging is to merge these three versions of the file, using 3-way merge. This is done by giving -'git-merge-one-file' command as one of the arguments to -'git-merge-index' command: +'git merge-one-file' command as one of the arguments to +'git merge-index' command: ------------ $ git merge-index git-merge-one-file hello @@ -1270,7 +1278,7 @@ ERROR: Merge conflict in hello fatal: merge program failed ------------ -'git-merge-one-file' script is called with parameters to +'git merge-one-file' script is called with parameters to describe those three versions, and is responsible to leave the merge results in the working tree. It is a fairly straightforward shell script, and @@ -1283,15 +1291,15 @@ 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 ------------ This is the state of the index file and the working file after -'git-merge' returns control back to you, leaving the conflicting +'git merge' returns control back to you, leaving the conflicting merge for you to resolve. Notice that the path `hello` is still -unmerged, and what you see with 'git-diff' at this point is +unmerged, and what you see with 'git diff' at this point is differences since stage 2 (i.e. your version). @@ -1328,8 +1336,8 @@ into it later. Obviously, this repository creation needs to be done only once. [NOTE] -'git-push' uses a pair of commands, -'git-send-pack' on your local machine, and 'git-receive-pack' +'git push' uses a pair of commands, +'git send-pack' on your local machine, and 'git-receive-pack' on the remote machine. The communication between the two over the network internally uses an SSH connection. @@ -1344,7 +1352,7 @@ $ mkdir my-git.git ------------ Then, make that directory into a git repository by running -'git-init', but this time, since its name is not the usual +'git init', but this time, since its name is not the usual `.git`, we do things slightly differently: ------------ @@ -1407,7 +1415,7 @@ $ git repack will do it for you. If you followed the tutorial examples, you would have accumulated about 17 objects in `.git/objects/??/` -directories by now. 'git-repack' tells you how many objects it +directories by now. 'git repack' tells you how many objects it packed, and stores the packed file in `.git/objects/pack` directory. @@ -1420,7 +1428,7 @@ them together. The former holds all the data from the objects in the pack, and the latter holds the index for random access. -If you are paranoid, running 'git-verify-pack' command would +If you are paranoid, running 'git verify-pack' command would detect if you have a corrupt pack, but do not worry too much. Our programs are always perfect ;-). @@ -1487,17 +1495,17 @@ If other people are pulling from your repository over dumb transport protocols (HTTP), you need to keep this repository 'dumb transport friendly'. After `git init`, `$GIT_DIR/hooks/post-update.sample` copied from the standard templates -would contain a call to 'git-update-server-info' +would contain a call to 'git update-server-info' but you need to manually enable the hook with `mv post-update.sample post-update`. This makes sure -'git-update-server-info' keeps the necessary files up-to-date. +'git update-server-info' keeps the necessary files up-to-date. 3. Push into the public repository from your primary repository. -4. 'git-repack' the public repository. This establishes a big +4. 'git repack' the public repository. This establishes a big pack that contains the initial set of objects as the - baseline, and possibly 'git-prune' if the transport + baseline, and possibly 'git prune' if the transport used for pulling from your repository supports packed repositories. @@ -1511,14 +1519,14 @@ You can repack this private repository whenever you feel like. 6. Push your changes to the public repository, and announce it to the public. -7. Every once in a while, 'git-repack' the public repository. +7. Every once in a while, 'git repack' the public repository. Go back to step 5. and continue working. A recommended work cycle for a "subsystem maintainer" who works on that project and has an own "public repository" goes like this: -1. Prepare your work repository, by 'git-clone' the public +1. Prepare your work repository, by 'git clone' the public repository of the "project lead". The URL used for the initial cloning is stored in the remote.origin.url configuration variable. @@ -1533,7 +1541,7 @@ on that project and has an own "public repository" goes like this: point at the repository you are borrowing from. 4. Push into the public repository from your primary - repository. Run 'git-repack', and possibly 'git-prune' if the + repository. Run 'git repack', and possibly 'git prune' if the transport used for pulling from your repository supports packed repositories. @@ -1550,7 +1558,7 @@ like. "project lead" and possibly your "sub-subsystem maintainers" to pull from it. -7. Every once in a while, 'git-repack' the public repository. +7. Every once in a while, 'git repack' the public repository. Go back to step 5. and continue working. @@ -1558,7 +1566,7 @@ A recommended work cycle for an "individual developer" who does not have a "public" repository is somewhat different. It goes like this: -1. Prepare your work repository, by 'git-clone' the public +1. Prepare your work repository, by 'git clone' the public repository of the "project lead" (or a "subsystem maintainer", if you work on a subsystem). The URL used for the initial cloning is stored in the remote.origin.url @@ -1656,8 +1664,8 @@ $ git reset --hard master~2 ------------ You can make sure `git show-branch` matches the state before -those two 'git-merge' you just did. Then, instead of running -two 'git-merge' commands in a row, you would merge these two +those two 'git merge' you just did. Then, instead of running +two 'git merge' commands in a row, you would merge these two branch heads (this is known as 'making an Octopus'): ------------ diff --git a/Documentation/gitcvs-migration.txt b/Documentation/gitcvs-migration.txt index 0e49c1c037..d861ec452f 100644 --- a/Documentation/gitcvs-migration.txt +++ b/Documentation/gitcvs-migration.txt @@ -47,25 +47,25 @@ them first before running git pull. [NOTE] ================================ The 'pull' command knows where to get updates from because of certain -configuration variables that were set by the first 'git-clone' +configuration variables that were set by the first 'git clone' command; see `git config -l` and the linkgit:git-config[1] man page for details. ================================ You can update the shared repository with your changes by first committing -your changes, and then using the 'git-push' command: +your changes, and then using the 'git push' command: ------------------------------------------------ $ git push origin master ------------------------------------------------ to "push" those commits to the shared repository. If someone else has -updated the repository more recently, 'git-push', like 'cvs commit', will +updated the repository more recently, 'git push', like 'cvs commit', will complain, in which case you must pull any changes before attempting the push again. -In the 'git-push' command above we specify the name of the remote branch -to update (`master`). If we leave that out, 'git-push' tries to update +In the 'git push' command above we specify the name of the remote branch +to update (`master`). If we leave that out, 'git push' tries to update any branches in the remote repository that have the same name as a branch in the local repository. So the last 'push' can be done with either of: diff --git a/Documentation/gitdiffcore.txt b/Documentation/gitdiffcore.txt index e8041bc08f..dcdea54df3 100644 --- a/Documentation/gitdiffcore.txt +++ b/Documentation/gitdiffcore.txt @@ -12,7 +12,7 @@ SYNOPSIS DESCRIPTION ----------- -The diff commands 'git-diff-index', 'git-diff-files', and 'git-diff-tree' +The diff commands 'git diff-index', 'git diff-files', and 'git diff-tree' can be told to manipulate differences they find in unconventional ways before showing 'diff' output. The manipulation is collectively called "diffcore transformation". This short note @@ -23,18 +23,18 @@ that is easier to understand than the conventional kind. The chain of operation ---------------------- -The 'git-diff-{asterisk}' family works by first comparing two sets of +The 'git diff-{asterisk}' family works by first comparing two sets of files: - - 'git-diff-index' compares contents of a "tree" object and the + - 'git diff-index' compares contents of a "tree" object and the working directory (when '\--cached' flag is not used) or a "tree" object and the index file (when '\--cached' flag is used); - - 'git-diff-files' compares contents of the index file and the + - 'git diff-files' compares contents of the index file and the working directory; - - 'git-diff-tree' compares contents of two "tree" objects; + - 'git diff-tree' compares contents of two "tree" objects; In all of these cases, the commands themselves first optionally limit the two sets of files by any pathspecs given on their command-lines, @@ -74,12 +74,12 @@ into another list. There are currently 5 such transformations: - diffcore-pickaxe - diffcore-order -These are applied in sequence. The set of filepairs 'git-diff-{asterisk}' +These are applied in sequence. The set of filepairs 'git diff-{asterisk}' commands find are used as the input to diffcore-break, and the output from diffcore-break is used as the input to the next transformation. The final result is then passed to the output routine and generates either diff-raw format (see Output -format sections of the manual for 'git-diff-{asterisk}' commands) or +format sections of the manual for 'git diff-{asterisk}' commands) or diff-patch format. @@ -87,7 +87,7 @@ diffcore-break: For Splitting Up "Complete Rewrites" ---------------------------------------------------- The second transformation in the chain is diffcore-break, and is -controlled by the -B option to the 'git-diff-{asterisk}' commands. This is +controlled by the -B option to the 'git diff-{asterisk}' commands. This is used to detect a filepair that represents "complete rewrite" and break such filepair into two filepairs that represent delete and create. E.g. If the input contained this filepair: @@ -123,7 +123,7 @@ diffcore-rename: For Detection Renames and Copies This transformation is used to detect renames and copies, and is controlled by the -M option (to detect renames) and the -C option -(to detect copies as well) to the 'git-diff-{asterisk}' commands. If the +(to detect copies as well) to the 'git diff-{asterisk}' commands. If the input contained these filepairs: ------------------------------------------------ @@ -168,11 +168,11 @@ number after the "-M" or "-C" option (e.g. "-M8" to tell it to use 8/10 = 80%). Note. When the "-C" option is used with `\--find-copies-harder` -option, 'git-diff-{asterisk}' commands feed unmodified filepairs to +option, 'git diff-{asterisk}' commands feed unmodified filepairs to diffcore mechanism as well as modified ones. This lets the copy detector consider unmodified files as copy source candidates at the expense of making it slower. Without `\--find-copies-harder`, -'git-diff-{asterisk}' commands can detect copies only if the file that was +'git diff-{asterisk}' commands can detect copies only if the file that was copied happened to have been modified in the same changeset. @@ -223,7 +223,7 @@ diffcore-pickaxe: For Detecting Addition/Deletion of Specified String This transformation is used to find filepairs that represent changes that touch a specified string, and is controlled by the --S option and the `\--pickaxe-all` option to the 'git-diff-{asterisk}' +-S option and the `\--pickaxe-all` option to the 'git diff-{asterisk}' commands. When diffcore-pickaxe is in use, it checks if there are @@ -246,7 +246,7 @@ diffcore-order: For Sorting the Output Based on Filenames This is used to reorder the filepairs according to the user's (or project's) taste, and is controlled by the -O option to the -'git-diff-{asterisk}' commands. +'git diff-{asterisk}' commands. This takes a text file each of whose lines is a shell glob pattern. Filepairs that match a glob pattern on an earlier line diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 3ab4f4d42c..87e2c035a7 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -15,7 +15,7 @@ DESCRIPTION Hooks are little scripts you can place in `$GIT_DIR/hooks` directory to trigger action at certain points. When -'git-init' is run, a handful of example hooks are copied into the +'git init' is run, a handful of example hooks are copied into the `hooks` directory of the new repository, but by default they are all disabled. To enable a hook, rename it by removing its `.sample` suffix. @@ -32,10 +32,10 @@ HOOKS applypatch-msg ~~~~~~~~~~~~~~ -This hook is invoked by 'git-am' script. It takes a single +This hook is invoked by 'git am' script. It takes a single parameter, the name of the file that holds the proposed commit log message. Exiting with non-zero status causes -'git-am' to abort before applying the patch. +'git am' to abort before applying the patch. The hook is allowed to edit the message file in place, and can be used to normalize the message into some project standard @@ -48,7 +48,7 @@ The default 'applypatch-msg' hook, when enabled, runs the pre-applypatch ~~~~~~~~~~~~~~ -This hook is invoked by 'git-am'. It takes no parameter, and is +This hook is invoked by 'git am'. It takes no parameter, and is invoked after the patch is applied, but before a commit is made. If it exits with non-zero status, then the working tree will not be @@ -63,33 +63,33 @@ The default 'pre-applypatch' hook, when enabled, runs the post-applypatch ~~~~~~~~~~~~~~~ -This hook is invoked by 'git-am'. It takes no parameter, +This hook is invoked by 'git am'. It takes no parameter, and is invoked after the patch is applied and a commit is made. This hook is meant primarily for notification, and cannot affect -the outcome of 'git-am'. +the outcome of 'git am'. pre-commit ~~~~~~~~~~ -This hook is invoked by 'git-commit', and can be bypassed +This hook is invoked by 'git commit', and can be bypassed with `\--no-verify` option. It takes no parameter, and is invoked before obtaining the proposed commit log message and making a commit. Exiting with non-zero status from this script -causes the 'git-commit' to abort. +causes the 'git commit' to abort. The default 'pre-commit' hook, when enabled, catches introduction of lines with trailing whitespaces and aborts the commit when such a line is found. -All the 'git-commit' hooks are invoked with the environment +All the 'git commit' hooks are invoked with the environment variable `GIT_EDITOR=:` if the command will not bring up an editor to modify the commit message. prepare-commit-msg ~~~~~~~~~~~~~~~~~~ -This hook is invoked by 'git-commit' right after preparing the +This hook is invoked by 'git commit' right after preparing the default log message, and before the editor is started. It takes one to three parameters. The first is the name of the file @@ -101,7 +101,7 @@ commit is a merge or a `.git/MERGE_MSG` file exists); `squash` (if a `.git/SQUASH_MSG` file exists); or `commit`, followed by a commit SHA1 (if a `-c`, `-C` or `\--amend` option was given). -If the exit status is non-zero, 'git-commit' will abort. +If the exit status is non-zero, 'git commit' will abort. The purpose of the hook is to edit the message file in place, and it is not suppressed by the `\--no-verify` option. A non-zero exit @@ -114,10 +114,10 @@ out the `Conflicts:` part of a merge's commit message. commit-msg ~~~~~~~~~~ -This hook is invoked by 'git-commit', and can be bypassed +This hook is invoked by 'git commit', and can be bypassed with `\--no-verify` option. It takes a single parameter, the name of the file that holds the proposed commit log message. -Exiting with non-zero status causes the 'git-commit' to +Exiting with non-zero status causes the 'git commit' to abort. The hook is allowed to edit the message file in place, and can @@ -131,30 +131,30 @@ The default 'commit-msg' hook, when enabled, detects duplicate post-commit ~~~~~~~~~~~ -This hook is invoked by 'git-commit'. It takes no +This hook is invoked by 'git commit'. It takes no parameter, and is invoked after a commit is made. This hook is meant primarily for notification, and cannot affect -the outcome of 'git-commit'. +the outcome of 'git commit'. pre-rebase ~~~~~~~~~~ -This hook is called by 'git-rebase' and can be used to prevent a branch +This hook is called by 'git rebase' and can be used to prevent a branch from getting rebased. post-checkout ~~~~~~~~~~~~~ -This hook is invoked when a 'git-checkout' is run after having updated the +This hook is invoked when a 'git checkout' is run after having updated the worktree. The hook is given three parameters: the ref of the previous HEAD, the ref of the new HEAD (which may or may not have changed), and a flag indicating whether the checkout was a branch checkout (changing branches, flag=1) or a file checkout (retrieving a file from the index, flag=0). -This hook cannot affect the outcome of 'git-checkout'. +This hook cannot affect the outcome of 'git checkout'. -It is also run after 'git-clone', unless the --no-checkout (-n) option is +It is also run after 'git clone', unless the --no-checkout (-n) option is used. The first parameter given to the hook is the null-ref, the second the ref of the new HEAD and the flag is always 1. @@ -165,10 +165,10 @@ properties. post-merge ~~~~~~~~~~ -This hook is invoked by 'git-merge', which happens when a 'git-pull' +This hook is invoked by 'git merge', which happens when a 'git pull' is done on a local repository. The hook takes a single parameter, a status flag specifying whether or not the merge being done was a squash merge. -This hook cannot affect the outcome of 'git-merge' and is not executed, +This hook cannot affect the outcome of 'git merge' and is not executed, if the merge failed due to conflicts. This hook can be used in conjunction with a corresponding pre-commit hook to @@ -181,7 +181,7 @@ pre-receive ~~~~~~~~~~~ This hook is invoked by 'git-receive-pack' on the remote repository, -which happens when a 'git-push' is done on a local repository. +which happens when a 'git push' is done on a local repository. Just before starting to update refs on the remote repository, the pre-receive hook is invoked. Its exit status determines the success or failure of the update. @@ -202,7 +202,7 @@ updated. If the hook exits with zero, updating of individual refs can still be prevented by the <<update,'update'>> hook. Both standard output and standard error output are forwarded to -'git-send-pack' on the other end, so you can simply `echo` messages +'git send-pack' on the other end, so you can simply `echo` messages for the user. [[update]] @@ -210,7 +210,7 @@ update ~~~~~~ This hook is invoked by 'git-receive-pack' on the remote repository, -which happens when a 'git-push' is done on a local repository. +which happens when a 'git push' is done on a local repository. Just before updating the ref on the remote repository, the update hook is invoked. Its exit status determines the success or failure of the ref update. @@ -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 @@ -241,7 +241,7 @@ implement access control which is finer grained than the one based on filesystem group. Both standard output and standard error output are forwarded to -'git-send-pack' on the other end, so you can simply `echo` messages +'git send-pack' on the other end, so you can simply `echo` messages for the user. The default 'update' hook, when enabled--and with @@ -253,7 +253,7 @@ post-receive ~~~~~~~~~~~~ This hook is invoked by 'git-receive-pack' on the remote repository, -which happens when a 'git-push' is done on a local repository. +which happens when a 'git push' is done on a local repository. It executes on the remote repository once after all the refs have been updated. @@ -270,7 +270,7 @@ both old and new values of all the refs in addition to their names. Both standard output and standard error output are forwarded to -'git-send-pack' on the other end, so you can simply `echo` messages +'git send-pack' on the other end, so you can simply `echo` messages for the user. The default 'post-receive' hook is empty, but there is @@ -283,7 +283,7 @@ post-update ~~~~~~~~~~~ This hook is invoked by 'git-receive-pack' on the remote repository, -which happens when a 'git-push' is done on a local repository. +which happens when a 'git push' is done on a local repository. It executes on the remote repository once after all the refs have been updated. @@ -301,20 +301,20 @@ updated values of the refs. You might consider it instead if you need them. When enabled, the default 'post-update' hook runs -'git-update-server-info' to keep the information used by dumb +'git update-server-info' to keep the information used by dumb transports (e.g., HTTP) up-to-date. If you are publishing a git repository that is accessible via HTTP, you should probably enable this hook. Both standard output and standard error output are forwarded to -'git-send-pack' on the other end, so you can simply `echo` messages +'git send-pack' on the other end, so you can simply `echo` messages for the user. pre-auto-gc ~~~~~~~~~~~ -This hook is invoked by 'git-gc --auto'. It takes no parameter, and -exiting with non-zero status from this script causes the 'git-gc --auto' +This hook is invoked by 'git gc --auto'. It takes no parameter, and +exiting with non-zero status from this script causes the 'git gc --auto' to abort. GIT diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index 7df3cef46f..98c459dc82 100644 --- a/Documentation/gitignore.txt +++ b/Documentation/gitignore.txt @@ -56,10 +56,10 @@ the user's editor of choice) generally go into a file specified by `core.excludesfile` in the user's `~/.gitconfig`. The underlying git plumbing tools, such as -'git-ls-files' and 'git-read-tree', read +'git ls-files' and 'git read-tree', read `gitignore` patterns specified by command-line options, or from files specified by command-line options. Higher-level git -tools, such as 'git-status' and 'git-add', +tools, such as 'git status' and 'git add', use patterns from the sources specified above. Patterns have the following format: diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index cf465cb47e..99baa24a2d 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -22,7 +22,7 @@ git repository. OPTIONS ------- To control which revisions to show, the command takes options applicable to -the 'git-rev-list' command (see linkgit:git-rev-list[1]). +the 'git rev-list' command (see linkgit:git-rev-list[1]). This manual page describes only the most frequently used options. diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt index 1befca98d4..3cd32d6803 100644 --- a/Documentation/gitrepository-layout.txt +++ b/Documentation/gitrepository-layout.txt @@ -64,7 +64,7 @@ objects/info/packs:: are available in this object store. Whenever a pack is added or removed, `git update-server-info` should be run to keep this file up-to-date if the repository is - published for dumb transports. 'git-repack' does this + published for dumb transports. 'git repack' does this by default. objects/info/alternates:: @@ -85,7 +85,7 @@ objects/info/http-alternates:: refs:: References are stored in subdirectories of this - directory. The 'git-prune' command knows to keep + directory. The 'git prune' command knows to keep objects reachable from refs found in this directory and its subdirectories. @@ -125,7 +125,7 @@ details. branches:: A slightly deprecated way to store shorthands to be used - to specify URL to 'git-fetch', 'git-pull' and 'git-push' + to specify URL to 'git fetch', 'git pull' and 'git push' commands is to store a file in `branches/<name>` and give 'name' to these commands in place of 'repository' argument. @@ -133,7 +133,7 @@ branches:: hooks:: Hooks are customization scripts used by various git commands. A handful of sample hooks are installed when - 'git-init' is run, but all of them are disabled by + 'git init' is run, but all of them are disabled by default. To enable, the `.sample` suffix has to be removed from the filename by renaming. Read linkgit:githooks[5] for more details about @@ -151,10 +151,10 @@ info/refs:: This file helps dumb transports discover what refs are available in this repository. If the repository is published for dumb transports, this file should be - regenerated by 'git-update-server-info' every time a tag + regenerated by 'git update-server-info' every time a tag or branch is created or modified. This is normally done from the `hooks/update` hook, which is run by the - 'git-receive-pack' command when you 'git-push' into the + 'git-receive-pack' command when you 'git push' into the repository. info/grafts:: @@ -168,14 +168,14 @@ info/grafts:: info/exclude:: This file, by convention among Porcelains, stores the exclude pattern list. `.gitignore` is the per-directory - ignore file. 'git-status', 'git-add', 'git-rm' and - 'git-clean' look at it but the core git commands do not look + ignore file. 'git status', 'git add', 'git rm' and + 'git clean' look at it but the core git commands do not look at it. See also: linkgit:gitignore[5]. remotes:: Stores shorthands to be used to give URL and default refnames to interact with remote repository to - 'git-fetch', 'git-pull' and 'git-push' commands. + 'git fetch', 'git pull' and 'git push' commands. logs:: Records of changes made to refs are stored in this diff --git a/Documentation/gittutorial-2.txt b/Documentation/gittutorial-2.txt index dc8fc3a18a..ecab0c09d0 100644 --- a/Documentation/gittutorial-2.txt +++ b/Documentation/gittutorial-2.txt @@ -260,7 +260,7 @@ index a042389..513feba 100644 +hello world, again ------------------------------------------------ -So 'git-diff' is comparing against something other than the head. +So 'git diff' is comparing against something other than the head. The thing that it's comparing against is actually the index file, which is stored in .git/index in a binary format, but whose contents we can examine with ls-files: @@ -275,9 +275,9 @@ hello world! hello world, again ------------------------------------------------ -So what our 'git-add' did was store a new blob and then put +So what our 'git add' did was store a new blob and then put a reference to it in the index file. If we modify the file again, -we'll see that the new modifications are reflected in the 'git-diff' +we'll see that the new modifications are reflected in the 'git diff' output: ------------------------------------------------ @@ -292,7 +292,7 @@ index 513feba..ba3da7b 100644 +again? ------------------------------------------------ -With the right arguments, 'git-diff' can also show us the difference +With the right arguments, 'git diff' can also show us the difference between the working directory and the last commit, or between the index and the last commit: @@ -316,7 +316,7 @@ index a042389..513feba 100644 +hello world, again ------------------------------------------------ -At any time, we can create a new commit using 'git-commit' (without +At any time, we can create a new commit using 'git commit' (without the "-a" option), and verify that the state committed only includes the changes stored in the index file, not the additional change that is still only in our working tree: @@ -334,11 +334,11 @@ index 513feba..ba3da7b 100644 +again? ------------------------------------------------ -So by default 'git-commit' uses the index to create the commit, not +So by default 'git commit' uses the index to create the commit, not the working tree; the "-a" option to commit tells it to first update the index with all changes in the working tree. -Finally, it's worth looking at the effect of 'git-add' on the index +Finally, it's worth looking at the effect of 'git add' on the index file: ------------------------------------------------ @@ -346,7 +346,7 @@ $ echo "goodbye, world" >closing.txt $ git add closing.txt ------------------------------------------------ -The effect of the 'git-add' was to add one entry to the index file: +The effect of the 'git add' was to add one entry to the index file: ------------------------------------------------ $ git ls-files --stage diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt index cf0689cfeb..1c1606696e 100644 --- a/Documentation/gittutorial.txt +++ b/Documentation/gittutorial.txt @@ -67,7 +67,7 @@ You've now initialized the working directory--you may notice a new directory created, named ".git". Next, tell git to take a snapshot of the contents of all files under the -current directory (note the '.'), with 'git-add': +current directory (note the '.'), with 'git add': ------------------------------------------------ $ git add . @@ -75,7 +75,7 @@ $ git add . This snapshot is now stored in a temporary staging area which git calls the "index". You can permanently store the contents of the index in the -repository with 'git-commit': +repository with 'git commit': ------------------------------------------------ $ git commit @@ -94,15 +94,15 @@ $ git add file1 file2 file3 ------------------------------------------------ You are now ready to commit. You can see what is about to be committed -using 'git-diff' with the --cached option: +using 'git diff' with the --cached option: ------------------------------------------------ $ git diff --cached ------------------------------------------------ -(Without --cached, 'git-diff' will show you any changes that +(Without --cached, 'git diff' will show you any changes that you've made but not yet added to the index.) You can also get a brief -summary of the situation with 'git-status': +summary of the situation with 'git status': ------------------------------------------------ $ git status @@ -126,7 +126,7 @@ $ git commit This will again prompt you for a message describing the change, and then record a new version of the project. -Alternatively, instead of running 'git-add' beforehand, you can use +Alternatively, instead of running 'git add' beforehand, you can use ------------------------------------------------ $ git commit -a @@ -147,7 +147,7 @@ Git tracks content not files Many revision control systems provide an `add` command that tells the system to start tracking changes to a new file. Git's `add` command -does something simpler and more powerful: 'git-add' is used both for new +does something simpler and more powerful: 'git add' is used both for new and newly modified files, and in both cases it takes a snapshot of the given files and stages that content in the index, ready for inclusion in the next commit. @@ -376,7 +376,7 @@ alice$ git remote add bob /home/bob/myrepo ------------------------------------------------ With this, Alice can perform the first part of the "pull" operation -alone using the 'git-fetch' command without merging them with her own +alone using the 'git fetch' command without merging them with her own branch, using: ------------------------------------- @@ -384,7 +384,7 @@ alice$ git fetch bob ------------------------------------- Unlike the longhand form, when Alice fetches from Bob using a -remote repository shorthand set up with 'git-remote', what was +remote repository shorthand set up with 'git remote', what was fetched is stored in a remote tracking branch, in this case `bob/master`. So after this: @@ -428,7 +428,7 @@ bob$ git config --get remote.origin.url /home/alice/project ------------------------------------- -(The complete configuration created by 'git-clone' is visible using +(The complete configuration created by 'git clone' is visible using `git config -l`, and the linkgit:git-config[1] man page explains the meaning of each option.) @@ -458,7 +458,7 @@ Exploring history ----------------- Git history is represented as a series of interrelated commits. We -have already seen that the 'git-log' command can list those commits. +have already seen that the 'git log' command can list those commits. Note that first line of each git log entry also gives a name for the commit: @@ -471,7 +471,7 @@ Date: Tue May 16 17:18:22 2006 -0700 merge-base: Clarify the comments on post processing. ------------------------------------- -We can give this name to 'git-show' to see the details about this +We can give this name to 'git show' to see the details about this commit. ------------------------------------- @@ -529,13 +529,13 @@ $ git reset --hard HEAD^ # reset your current branch and working Be careful with that last command: in addition to losing any changes in the working directory, it will also remove all later commits from this branch. If this branch is the only branch containing those -commits, they will be lost. Also, don't use 'git-reset' on a +commits, they will be lost. Also, don't use 'git reset' on a publicly-visible branch that other developers pull from, as it will force needless merges on other developers to clean up the history. -If you need to undo changes that you have pushed, use 'git-revert' +If you need to undo changes that you have pushed, use 'git revert' instead. -The 'git-grep' command can search for strings in any version of your +The 'git grep' command can search for strings in any version of your project, so ------------------------------------- @@ -544,7 +544,7 @@ $ git grep "hello" v2.5 searches for all occurrences of "hello" in v2.5. -If you leave out the commit name, 'git-grep' will search any of the +If you leave out the commit name, 'git grep' will search any of the files it manages in your current directory. So ------------------------------------- @@ -554,7 +554,7 @@ $ git grep "hello" is a quick way to search just the files that are tracked by git. Many git commands also take sets of commits, which can be specified -in a number of ways. Here are some examples with 'git-log': +in a number of ways. Here are some examples with 'git log': ------------------------------------- $ git log v2.5..v2.6 # commits between v2.5 and v2.6 @@ -564,7 +564,7 @@ $ git log v2.5.. Makefile # commits since v2.5 which modify # Makefile ------------------------------------- -You can also give 'git-log' a "range" of commits where the first is not +You can also give 'git log' a "range" of commits where the first is not necessarily an ancestor of the second; for example, if the tips of the branches "stable" and "master" diverged from a common commit some time ago, then @@ -583,9 +583,9 @@ $ git log master..stable will show the list of commits made on the stable branch but not the master branch. -The 'git-log' command has a weakness: it must present commits in a +The 'git log' command has a weakness: it must present commits in a list. When the history has lines of development that diverged and -then merged back together, the order in which 'git-log' presents +then merged back together, the order in which 'git log' presents those commits is meaningless. Most projects with multiple contributors (such as the Linux kernel, @@ -609,7 +609,7 @@ of the file: $ git diff v2.5:Makefile HEAD:Makefile.in ------------------------------------- -You can also use 'git-show' to see any such file: +You can also use 'git show' to see any such file: ------------------------------------- $ git show v2.5:Makefile diff --git a/Documentation/gitworkflows.txt b/Documentation/gitworkflows.txt index 91c0eea890..1ef55fffcf 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 @@ -360,7 +360,7 @@ There are three main tools that can be used for this: * linkgit:git-pull[1] that does fetch and merge in one go. -Note the last point. Do 'not' use 'git-pull' unless you actually want +Note the last point. Do 'not' use 'git pull' unless you actually want to merge the remote branch. Getting changes out is easy: @@ -397,7 +397,7 @@ Please pull from <url> <branch> ------------------------------------- -In that case, 'git-pull' can do the fetch and merge in one go, as +In that case, 'git pull' can do the fetch and merge in one go, as follows. .Push/pull: Merging remote topics @@ -449,7 +449,7 @@ problem. If you receive such a patch series (as maintainer, or perhaps as a reader of the mailing list it was sent to), save the mails to files, -create a new topic branch and use 'git-am' to import the commits: +create a new topic branch and use 'git am' to import the commits: .format-patch/am: Importing patches [caption="Recipe: "] 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/i18n.txt b/Documentation/i18n.txt index 708da6ca31..625d3154ea 100644 --- a/Documentation/i18n.txt +++ b/Documentation/i18n.txt @@ -21,7 +21,7 @@ project find it more convenient to use legacy encodings, git does not forbid it. However, there are a few things to keep in mind. -. 'git-commit' and 'git-commit-tree' issues +. 'git commit' and 'git commit-tree' issues a warning if the commit log message given to it does not look like a valid UTF-8 string, unless you explicitly say your project uses a legacy encoding. The way to say this is to @@ -37,7 +37,7 @@ of `i18n.commitencoding` in its `encoding` header. This is to help other people who look at them later. Lack of this header implies that the commit log message is encoded in UTF-8. -. 'git-log', 'git-show', 'git-blame' and friends look at the +. 'git log', 'git show', 'git blame' and friends look at the `encoding` header of a commit object, and try to re-code the log message into UTF-8 unless otherwise specified. You can specify the desired output encoding with 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..3b83dba1a0 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -49,13 +49,18 @@ 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 once to specify them in the order they should be tried. 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). + is used instead ('git merge-recursive' when merging a single + head, 'git merge-octopus' otherwise). --summary:: --no-summary:: @@ -69,3 +74,8 @@ option can be used to override --squash. -v:: --verbose:: Be verbose. + +-X <option>:: +--strategy-option=<option>:: + Pass merge strategy specific option through to the merge + strategy. diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt index 4365b7e842..a5bc1dbb95 100644 --- a/Documentation/merge-strategies.txt +++ b/Documentation/merge-strategies.txt @@ -1,6 +1,11 @@ MERGE STRATEGIES ---------------- +The merge mechanism ('git-merge' and 'git-pull' commands) allows the +backend 'merge strategies' to be chosen with `-s` option. Some strategies +can also take their own options, which can be passed by giving `-X<option>` +arguments to 'git-merge' and/or 'git-pull'. + resolve:: This can only resolve two heads (i.e. the current branch and another branch you pulled from) using a 3-way merge @@ -20,6 +25,27 @@ recursive:: Additionally this can detect and handle merges involving renames. This is the default merge strategy when pulling or merging one branch. ++ +The 'recursive' strategy can take the following options: + +ours;; + This option forces conflicting hunks to be auto-resolved cleanly by + favoring 'our' version. Changes from the other tree that do not + conflict with our side are reflected to the merge result. ++ +This should not be confused with the 'ours' merge strategy, which does not +even look at what the other tree contains at all. It discards everything +the other tree did, declaring 'our' history contains all that happened in it. + +theirs;; + This is opposite of 'ours'. + +subtree[=path];; + This option is a more advanced form of 'subtree' strategy, where + the strategy makes a guess on how two trees must be shifted to + match with each other when merging. Instead, the specified path + is prefixed (or stripped from the beginning) to make the shape of + two trees to match. octopus:: This resolves cases with more than two heads, but refuses to do @@ -29,10 +55,12 @@ 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. + branches. Note that this is different from the -Xours option to + the 'recursive' merge strategy. subtree:: This is a modified recursive strategy. When merging trees A and diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 2a845b1e57..1686a54d22 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 @@ -130,7 +134,24 @@ The placeholders are: - '%C(...)': color specification, as described in color.branch.* config option - '%m': left, right or boundary mark - '%n': newline +- '%%': a raw '%' - '%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/pretty-options.txt b/Documentation/pretty-options.txt index bff94991b6..aa96caeab2 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -28,3 +28,11 @@ people using 80-column terminals. command to re-code the commit log message in the encoding preferred by the user. For non plumbing commands this defaults to UTF-8. + +--no-notes:: +--show-notes:: + Show the notes (see linkgit:git-notes[1]) that annotate the + commit, when showing the commit log message. This is the default + for `git log`, `git show` and `git whatchanged` commands when + there is no `--pretty`, `--format` nor `--oneline` option is + given on the command line. 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..5dd6e5a0c7 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] @@ -31,7 +38,7 @@ must know this is the expected usage pattern for a branch. [NOTE] You never do your own development on branches that appear on the right hand side of a <refspec> colon on `Pull:` lines; -they are to be updated by 'git-fetch'. If you intend to do +they are to be updated by 'git fetch'. If you intend to do development derived from a remote branch `B`, have a `Pull:` line to track it (i.e. `Pull: B:remote-B`), and have a separate branch `my-B` to do your development on top of it. The latter @@ -43,13 +50,13 @@ on the remote branch, merge it into your development branch with + [NOTE] There is a difference between listing multiple <refspec> -directly on 'git-pull' command line and having multiple +directly on 'git pull' command line and having multiple `Pull:` <refspec> lines for a <repository> and running -'git-pull' command without any explicit <refspec> parameters. +'git pull' command without any explicit <refspec> parameters. <refspec> listed explicitly on the command line are always merged into the current branch after fetching. In other words, if you list more than one remote refs, you would be making -an Octopus. While 'git-pull' run without any explicit <refspec> +an Octopus. While 'git pull' run without any explicit <refspec> parameter takes default <refspec>s from `Pull:` lines, it merges only the first <refspec> found into the current branch, after fetching all the remote refs. This is because making an diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index bf66116d61..81c0e6f184 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -225,30 +225,54 @@ endif::git-rev-list[] --all:: - Pretend as if all the refs in `$GIT_DIR/refs/` are listed on the + Pretend as if all the refs in `refs/` are listed on the command line as '<commit>'. ---branches:: +--branches[=pattern]:: - Pretend as if all the refs in `$GIT_DIR/refs/heads` are listed - on the command line as '<commit>'. + Pretend as if all the refs in `refs/heads` are listed + on the command line as '<commit>'. If `pattern` is given, limit + branches to ones matching given shell glob. If pattern lacks '?', + '*', or '[', '/*' at the end is implied. ---tags:: +--tags[=pattern]:: - Pretend as if all the refs in `$GIT_DIR/refs/tags` are listed - on the command line as '<commit>'. + Pretend as if all the refs in `refs/tags` are listed + on the command line as '<commit>'. If `pattern` is given, limit + tags to ones matching given shell glob. If pattern lacks '?', '*', + or '[', '/*' at the end is implied. ---remotes:: +--remotes[=pattern]:: - Pretend as if all the refs in `$GIT_DIR/refs/remotes` are listed - on the command line as '<commit>'. + Pretend as if all the refs in `refs/remotes` are listed + on the command line as '<commit>'. If `pattern`is given, limit + remote tracking branches to ones matching given shell glob. + If pattern lacks '?', '*', or '[', '/*' at the end is implied. + +--glob=glob-pattern:: + Pretend as if all the refs matching shell glob `glob-pattern` + are listed on the command line as '<commit>'. Leading 'refs/', + is automatically prepended if missing. If pattern lacks '?', '*', + or '[', '/*' at the end is implied. + + +ifndef::git-rev-list[] +--bisect:: + + Pretend as if the bad bisection ref `refs/bisect/bad` + was listed and as if it was followed by `--not` and the good + bisection refs `refs/bisect/good-*` on the command + line. +endif::git-rev-list[] -ifdef::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 +560,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 +`refs/bisect/bad` is added to the included commits (if it +exists) and the good bisection refs `refs/bisect/good-*` are +added to the excluded commits (if they exist). Thus, supposing there +are no refs in `refs/bisect/`, if ----------------------------------------------------------------------- $ git rev-list --bisect foo ^bar ^baz @@ -556,22 +584,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 +`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 `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-directory-listing.txt b/Documentation/technical/api-directory-listing.txt index 5bbd18f020..add6f435b5 100644 --- a/Documentation/technical/api-directory-listing.txt +++ b/Documentation/technical/api-directory-listing.txt @@ -58,6 +58,9 @@ The result of the enumeration is left in these fields:: Calling sequence ---------------- +Note: index may be looked at for .gitignore files that are CE_SKIP_WORKTREE +marked. If you to exclude files, make sure you have loaded index first. + * Prepare `struct dir_struct dir` and clear it with `memset(&dir, 0, sizeof(dir))`. diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt index b26c28133c..44876fa703 100644 --- a/Documentation/technical/api-run-command.txt +++ b/Documentation/technical/api-run-command.txt @@ -51,7 +51,7 @@ The functions above do the following: ENOENT; a diagnostic is printed only if .silent_exec_failure is 0. . Otherwise, the program is run. If it terminates regularly, its exit - code is returned. No diagnistic is printed, even if the exit code is + code is returned. No diagnostic is printed, even if the exit code is non-zero. . If the program terminated due to a signal, then the return value is the @@ -64,8 +64,8 @@ The functions above do the following: `start_async`:: Run a function asynchronously. Takes a pointer to a `struct - async` that specifies the details and returns a pipe FD - from which the caller reads. See below for details. + async` that specifies the details and returns a set of pipe FDs + for communication with the function. See below for details. `finish_async`:: @@ -135,7 +135,7 @@ stderr as follows: .in: The FD must be readable; it becomes child's stdin. .out: The FD must be writable; it becomes child's stdout. - .err > 0 is not supported. + .err: The FD must be writable; it becomes child's stderr. The specified FD is closed by start_command(), even if it fails to run the sub-process! @@ -180,17 +180,47 @@ The caller: struct async variable; 2. initializes .proc and .data; 3. calls start_async(); -4. processes the data by reading from the fd in .out; -5. closes .out; +4. processes communicates with proc through .in and .out; +5. closes .in and .out; 6. calls finish_async(). +The members .in, .out are used to provide a set of fd's for +communication between the caller and the callee as follows: + +. Specify 0 to have no file descriptor passed. The callee will + receive -1 in the corresponding argument. + +. Specify < 0 to have a pipe allocated; start_async() replaces + with the pipe FD in the following way: + + .in: Returns the writable pipe end into which the caller + writes; the readable end of the pipe becomes the function's + in argument. + + .out: Returns the readable pipe end from which the caller + reads; the writable end of the pipe becomes the function's + out argument. + + The caller of start_async() must close the returned FDs after it + has completed reading from/writing from them. + +. Specify a file descriptor > 0 to be used by the function: + + .in: The FD must be readable; it becomes the function's in. + .out: The FD must be writable; it becomes the function's out. + + The specified FD is closed by start_async(), even if it fails to + run the function. + The function pointer in .proc has the following signature: - int proc(int fd, void *data); + int proc(int in, int out, void *data); -. fd specifies a writable file descriptor to which the function must - write the data that it produces. The function *must* close this - descriptor before it returns. +. in, out specifies a set of file descriptors to which the function + must read/write the data that it needs/produces. The function + *must* close these descriptors before it returns. A descriptor + may be -1 if the caller did not configure a descriptor for that + direction. . data is the value that the caller has specified in the .data member of struct async. @@ -205,8 +235,8 @@ because this facility is implemented by a pipe to a forked process on UNIX, but by a thread in the same address space on Windows: . It cannot change the program's state (global variables, environment, - etc.) in a way that the caller notices; in other words, .out is the - only communication channel to the caller. + etc.) in a way that the caller notices; in other words, .in and .out + are the only communication channels to the caller. . It must not change the program's state that the caller of the facility also uses. diff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt index a0e0f850f8..afe2759951 100644 --- a/Documentation/technical/api-strbuf.txt +++ b/Documentation/technical/api-strbuf.txt @@ -199,6 +199,10 @@ character if the letter `n` appears after a `%`. The function returns the length of the placeholder recognized and `strbuf_expand()` skips over it. + +The format `%%` is automatically expanded to a single `%` as a quoting +mechanism; callers do not need to handle the `%` placeholder themselves, +and the callback function will not be invoked for this placeholder. ++ All other characters (non-percent and not skipped ones) are copied verbatim to the strbuf. If the callback returned zero, meaning that the placeholder is unknown, then the percent sign is copied, too. @@ -214,6 +218,13 @@ which can be used by the programmer of the callback as she sees fit. placeholder and replacement string. The array needs to be terminated by an entry with placeholder set to NULL. +`strbuf_addbuf_percentquote`:: + + Append the contents of one strbuf to another, quoting any + percent signs ("%") into double-percents ("%%") in the + destination. This is useful for literal data to be fed to either + strbuf_expand or to the *printf family of functions. + `strbuf_addf`:: Add a formatted string to the buffer. diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt index 9cd48b4859..9a5cdafa9c 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 it's 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, it's 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 of its objects), then the client will send +a 'done' command. The 'done' command signals to the server that the client +is ready to receive its 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 its 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..fd1a593149 --- /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 it's 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 a 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/urls-remotes.txt b/Documentation/urls-remotes.txt index 2a0e7b8944..00f7e79c44 100644 --- a/Documentation/urls-remotes.txt +++ b/Documentation/urls-remotes.txt @@ -52,8 +52,8 @@ following format: ------------ -`Push:` lines are used by 'git-push' and -`Pull:` lines are used by 'git-pull' and 'git-fetch'. +`Push:` lines are used by 'git push' and +`Pull:` lines are used by 'git pull' and 'git fetch'. Multiple `Push:` and `Pull:` lines may be specified for additional branch mappings. diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index c32dd87c8b..fe6fb722da 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -1196,7 +1196,7 @@ the time, you will want to commit your changes before you can merge, and if you don't, then linkgit:git-stash[1] can take these changes away while you're doing the merge, and reapply them afterwards. -If the changes are independant enough, Git will automatically complete +If the changes are independent enough, Git will automatically complete the merge and commit the result (or reuse an existing commit in case of <<fast-forwards,fast-forward>>, see below). On the other hand, if there are conflicts--for example, if the same file is @@ -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 @@ -3640,6 +3640,26 @@ Did you forget to 'git add'? Unable to checkout '261dfac35cb99d380eb966e102c1197139f7fa24' in submodule path 'a' ------------------------------------------------- +In older git versions it could be easily forgotten to commit new or modified +files in a submodule, which silently leads to similar problems as not pushing +the submodule changes. Starting with git 1.7.0 both "git status" and "git diff" +in the superproject show submodules as modified when they contain new or +modified files to protect against accidentally committing such a state. "git +diff" will also add a "-dirty" to the work tree side when generating patch +output or used with the --submodule option: + +------------------------------------------------- +$ git diff +diff --git a/sub b/sub +--- a/sub ++++ b/sub +@@ -1 +1 @@ +-Subproject commit 3f356705649b5d566d97ff843cf193359229a453 ++Subproject commit 3f356705649b5d566d97ff843cf193359229a453-dirty +$ git diff --submodule +Submodule sub 3f35670..3f35670-dirty: +------------------------------------------------- + You also should not rewind branches in a submodule beyond commits that were ever recorded in any superproject. @@ -4291,7 +4311,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 52b058a586..7f894fdd46 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.5.8 +DEF_VER=v1.7.0.1 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 @@ -178,17 +180,10 @@ all:: # If not set it defaults to the bare 'wish'. If it is set to the empty # string then NO_TCLTK will be forced (this is used by configure script). # -# Define THREADED_DELTA_SEARCH if you have pthreads and wish to exploit -# parallel delta searching when packing objects. -# # Define INTERNAL_QSORT to use Git's implementation of qsort(), which # is a simplified version of the merge sort used in glibc. This is # recommended if Git triggers O(n^2) behavior in your platform's qsort(). # -# Define NO_EXTERNAL_GREP if you don't want "git grep" to ever call -# your external grep (e.g., if your system lacks grep, if its grep is -# broken, or spawning external process is slower than built-in grep git has). -# # Define UNRELIABLE_FSTAT if your system's fstat does not return the same # information on a not yet closed file that lstat would return for the same # file after it was closed. @@ -204,8 +199,23 @@ 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 +GIT-VERSION-FILE: FORCE @$(SHELL_PATH) ./GIT-VERSION-GEN -include GIT-VERSION-FILE @@ -216,6 +226,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,23 +272,8 @@ lib = lib # DESTDIR= pathsep = : -# default configuration for gitweb -GITWEB_CONFIG = gitweb_config.perl -GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf -GITWEB_HOME_LINK_STR = projects -GITWEB_SITENAME = -GITWEB_PROJECTROOT = /pub/git -GITWEB_PROJECT_MAXDEPTH = 2007 -GITWEB_EXPORT_OK = -GITWEB_STRICT_EXPORT = -GITWEB_BASE_URL = -GITWEB_LIST = -GITWEB_HOMETEXT = indextext.html -GITWEB_CSS = gitweb.css -GITWEB_LOGO = git-logo.png -GITWEB_FAVICON = git-favicon.png -GITWEB_SITE_HEADER = -GITWEB_SITE_FOOTER = +# JavaScript minifier invocation that can function as filter +JSMIN = export prefix bindir sharedir sysconfdir @@ -312,7 +313,9 @@ LIB_H = LIB_OBJS = PROGRAMS = SCRIPT_PERL = +SCRIPT_PYTHON = SCRIPT_SH = +SCRIPT_LIB = TEST_PROGRAMS = SCRIPT_SH += git-am.sh @@ -324,19 +327,21 @@ SCRIPT_SH += git-merge-octopus.sh 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-parse-remote.sh +SCRIPT_SH += git-notes.sh SCRIPT_SH += git-pull.sh SCRIPT_SH += git-quiltimport.sh SCRIPT_SH += git-rebase--interactive.sh SCRIPT_SH += git-rebase.sh SCRIPT_SH += git-repack.sh SCRIPT_SH += git-request-pull.sh -SCRIPT_SH += git-sh-setup.sh SCRIPT_SH += git-stash.sh SCRIPT_SH += git-submodule.sh SCRIPT_SH += git-web--browse.sh +SCRIPT_LIB += git-mergetool--lib +SCRIPT_LIB += git-parse-remote +SCRIPT_LIB += git-sh-setup + SCRIPT_PERL += git-add--interactive.perl SCRIPT_PERL += git-difftool.perl SCRIPT_PERL += git-archimport.perl @@ -349,6 +354,7 @@ SCRIPT_PERL += git-svn.perl SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ + $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ git-instaweb # Empty... @@ -357,18 +363,11 @@ EXTRA_PROGRAMS = # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS += $(EXTRA_PROGRAMS) PROGRAMS += git-fast-import$X -PROGRAMS += git-hash-object$X -PROGRAMS += git-index-pack$X -PROGRAMS += git-merge-index$X -PROGRAMS += git-merge-tree$X -PROGRAMS += git-mktag$X -PROGRAMS += git-pack-redundant$X -PROGRAMS += git-patch-id$X +PROGRAMS += git-imap-send$X PROGRAMS += git-shell$X 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 +394,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 +410,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 @@ -419,6 +431,7 @@ LIB_H += commit.h LIB_H += compat/bswap.h LIB_H += compat/cygwin.h LIB_H += compat/mingw.h +LIB_H += compat/win32/pthread.h LIB_H += csum-file.h LIB_H += decorate.h LIB_H += delta.h @@ -437,6 +450,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 @@ -450,6 +464,7 @@ LIB_H += reflog-walk.h LIB_H += refs.h LIB_H += remote.h LIB_H += rerere.h +LIB_H += resolve-undo.h LIB_H += revision.h LIB_H += run-command.h LIB_H += sha1-lookup.h @@ -457,6 +472,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 @@ -510,6 +526,7 @@ LIB_OBJS += graph.o LIB_OBJS += grep.o LIB_OBJS += hash.o LIB_OBJS += help.o +LIB_OBJS += hex.o LIB_OBJS += ident.o LIB_OBJS += levenshtein.o LIB_OBJS += list-objects.o @@ -521,6 +538,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 @@ -543,6 +561,7 @@ LIB_OBJS += refs.o LIB_OBJS += remote.o LIB_OBJS += replace_object.o LIB_OBJS += rerere.o +LIB_OBJS += resolve-undo.o LIB_OBJS += revision.o LIB_OBJS += run-command.o LIB_OBJS += server-info.o @@ -555,6 +574,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 +619,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 @@ -607,7 +626,9 @@ BUILTIN_OBJS += builtin-for-each-ref.o BUILTIN_OBJS += builtin-fsck.o BUILTIN_OBJS += builtin-gc.o BUILTIN_OBJS += builtin-grep.o +BUILTIN_OBJS += builtin-hash-object.o BUILTIN_OBJS += builtin-help.o +BUILTIN_OBJS += builtin-index-pack.o BUILTIN_OBJS += builtin-init-db.o BUILTIN_OBJS += builtin-log.o BUILTIN_OBJS += builtin-ls-files.o @@ -618,13 +639,18 @@ BUILTIN_OBJS += builtin-mailsplit.o BUILTIN_OBJS += builtin-merge.o BUILTIN_OBJS += builtin-merge-base.o BUILTIN_OBJS += builtin-merge-file.o +BUILTIN_OBJS += builtin-merge-index.o BUILTIN_OBJS += builtin-merge-ours.o BUILTIN_OBJS += builtin-merge-recursive.o +BUILTIN_OBJS += builtin-merge-tree.o +BUILTIN_OBJS += builtin-mktag.o BUILTIN_OBJS += builtin-mktree.o BUILTIN_OBJS += builtin-mv.o BUILTIN_OBJS += builtin-name-rev.o BUILTIN_OBJS += builtin-pack-objects.o +BUILTIN_OBJS += builtin-pack-redundant.o BUILTIN_OBJS += builtin-pack-refs.o +BUILTIN_OBJS += builtin-patch-id.o BUILTIN_OBJS += builtin-prune-packed.o BUILTIN_OBJS += builtin-prune.o BUILTIN_OBJS += builtin-push.o @@ -647,11 +673,13 @@ BUILTIN_OBJS += builtin-stripspace.o BUILTIN_OBJS += builtin-symbolic-ref.o BUILTIN_OBJS += builtin-tag.o BUILTIN_OBJS += builtin-tar-tree.o +BUILTIN_OBJS += builtin-unpack-file.o BUILTIN_OBJS += builtin-unpack-objects.o BUILTIN_OBJS += builtin-update-index.o BUILTIN_OBJS += builtin-update-ref.o BUILTIN_OBJS += builtin-update-server-info.o BUILTIN_OBJS += builtin-upload-archive.o +BUILTIN_OBJS += builtin-var.o BUILTIN_OBJS += builtin-verify-pack.o BUILTIN_OBJS += builtin-verify-tag.o BUILTIN_OBJS += builtin-write-tree.o @@ -670,12 +698,10 @@ EXTLIBS = ifeq ($(uname_S),Linux) NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease - THREADED_DELTA_SEARCH = YesPlease endif ifeq ($(uname_S),GNU/kFreeBSD) NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease - THREADED_DELTA_SEARCH = YesPlease endif ifeq ($(uname_S),UnixWare) CC = cc @@ -729,7 +755,6 @@ ifeq ($(uname_S),Darwin) NO_STRLCPY = YesPlease endif NO_MEMMEM = YesPlease - THREADED_DELTA_SEARCH = YesPlease USE_ST_TIMESPEC = YesPlease endif ifeq ($(uname_S),SunOS) @@ -742,8 +767,6 @@ ifeq ($(uname_S),SunOS) NO_MKDTEMP = YesPlease NO_MKSTEMPS = YesPlease NO_REGEX = YesPlease - NO_EXTERNAL_GREP = YesPlease - THREADED_DELTA_SEARCH = YesPlease ifeq ($(uname_R),5.7) NEEDS_RESOLV = YesPlease NO_IPV6 = YesPlease @@ -793,12 +816,12 @@ 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 DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease USE_ST_TIMESPEC = YesPlease - THREADED_DELTA_SEARCH = YesPlease ifeq ($(shell expr "$(uname_R)" : '4\.'),2) PTHREAD_LIBS = -pthread NO_UINTMAX_T = YesPlease @@ -812,7 +835,6 @@ ifeq ($(uname_S),OpenBSD) NEEDS_LIBICONV = YesPlease BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib - THREADED_DELTA_SEARCH = YesPlease endif ifeq ($(uname_S),NetBSD) ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2) @@ -820,7 +842,6 @@ ifeq ($(uname_S),NetBSD) endif BASIC_CFLAGS += -I/usr/pkg/include BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib - THREADED_DELTA_SEARCH = YesPlease USE_ST_TIMESPEC = YesPlease NO_MKSTEMPS = YesPlease endif @@ -835,9 +856,7 @@ ifeq ($(uname_S),AIX) INTERNAL_QSORT = UnfortunatelyYes NEEDS_LIBICONV=YesPlease BASIC_CFLAGS += -D_LARGE_FILES - ifneq ($(shell expr "$(uname_V)" : '[1234]'),1) - THREADED_DELTA_SEARCH = YesPlease - else + ifeq ($(shell expr "$(uname_V)" : '[1234]'),1) NO_PTHREADS = YesPlease endif endif @@ -860,11 +879,9 @@ ifeq ($(uname_S),IRIX) # NO_MMAP. If you suspect that your compiler is not affected by this # issue, comment out the NO_MMAP statement. NO_MMAP = YesPlease - NO_EXTERNAL_GREP = UnfortunatelyYes SNPRINTF_RETURNS_BOGUS = YesPlease SHELL_PATH = /usr/gnu/bin/bash NEEDS_LIBGEN = YesPlease - THREADED_DELTA_SEARCH = YesPlease endif ifeq ($(uname_S),IRIX64) NO_SETENV=YesPlease @@ -880,11 +897,9 @@ ifeq ($(uname_S),IRIX64) # NO_MMAP. If you suspect that your compiler is not affected by this # issue, comment out the NO_MMAP statement. NO_MMAP = YesPlease - NO_EXTERNAL_GREP = UnfortunatelyYes SNPRINTF_RETURNS_BOGUS = YesPlease SHELL_PATH=/usr/gnu/bin/bash NEEDS_LIBGEN = YesPlease - THREADED_DELTA_SEARCH = YesPlease endif ifeq ($(uname_S),HP-UX) NO_IPV6=YesPlease @@ -899,11 +914,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 @@ -932,16 +947,18 @@ ifdef MSVC OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo NO_REGEX = YesPlease NO_CURL = YesPlease - NO_PTHREADS = YesPlease + NO_PYTHON = YesPlease + BLK_SHA1 = YesPlease CC = compat/vcbuild/scripts/clink.pl AR = compat/vcbuild/scripts/lib.pl CFLAGS = BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE - COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o - COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -DSTRIP_EXTENSION=\".exe\" + COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o compat/win32/pthread.o + COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib EXTLIBS = advapi32.lib shell32.lib wininet.lib ws2_32.lib + PTHREAD_LIBS = lib = ifndef DEBUG BASIC_CFLAGS += -GL -Os -MT @@ -951,14 +968,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,10 +998,14 @@ ifneq (,$(findstring MINGW,$(uname_S))) UNRELIABLE_FSTAT = UnfortunatelyYes OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo NO_REGEX = YesPlease - COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch + NO_PYTHON = YesPlease + BLK_SHA1 = YesPlease + COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch -Icompat/win32 COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" - COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o + COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o \ + compat/win32/pthread.o EXTLIBS += -lws2_32 + PTHREAD_LIBS = X = .exe ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) htmldir=doc/git/html/ @@ -994,11 +1014,8 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) EXTLIBS += /mingw/lib/libz.a NO_R_TO_GCC_LINKER = YesPlease INTERNAL_QSORT = YesPlease - THREADED_DELTA_SEARCH = YesPlease else NO_CURL = YesPlease - NO_PTHREADS = YesPlease -endif endif endif @@ -1046,6 +1063,9 @@ endif ifdef NO_CURL BASIC_CFLAGS += -DNO_CURL + REMOTE_CURL_PRIMARY = + REMOTE_CURL_ALIASES = + REMOTE_CURL_NAMES = else ifdef CURLDIR # Try "-Wl,-rpath=$(CURLDIR)/$(lib)" in such a case. @@ -1054,7 +1074,10 @@ else else CURL_LIBCURL = -lcurl endif - PROGRAMS += git-remote-curl$X git-http-fetch$X + REMOTE_CURL_PRIMARY = git-remote-http$X + REMOTE_CURL_ALIASES = git-remote-https$X git-remote-ftp$X git-remote-ftps$X + REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES) + PROGRAMS += $(REMOTE_CURL_NAMES) git-http-fetch$X curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p) ifeq "$(curl_check)" "070908" ifndef NO_EXPAT @@ -1079,7 +1102,6 @@ EXTLIBS += -lz ifndef NO_POSIX_ONLY_PROGRAMS PROGRAMS += git-daemon$X - PROGRAMS += git-imap-send$X endif ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl @@ -1275,22 +1297,15 @@ ifdef RUNTIME_PREFIX endif ifdef NO_PTHREADS - THREADED_DELTA_SEARCH = BASIC_CFLAGS += -DNO_PTHREADS else EXTLIBS += $(PTHREAD_LIBS) -endif - -ifdef THREADED_DELTA_SEARCH - BASIC_CFLAGS += -DTHREADED_DELTA_SEARCH LIB_OBJS += thread-utils.o endif + ifdef DIR_HAS_BSD_GROUP_SEMANTICS COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS endif -ifdef NO_EXTERNAL_GREP - BASIC_CFLAGS += -DNO_EXTERNAL_GREP -endif ifdef UNRELIABLE_FSTAT BASIC_CFLAGS += -DUNRELIABLE_FSTAT endif @@ -1312,6 +1327,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 +1378,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 +1387,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) @@ -1377,7 +1413,7 @@ export TAR INSTALL DESTDIR SHELL_PATH SHELL = $(SHELL_PATH) -all:: shell_compatibility_test $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS +all:: shell_compatibility_test $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS ifneq (,$X) $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';) endif @@ -1390,6 +1426,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: @@ -1400,20 +1439,19 @@ shell_compatibility_test: please_set_SHELL_PATH_to_a_more_modern_shell strip: $(PROGRAMS) git$X $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X -git.o: git.c common-cmds.h GIT-CFLAGS - $(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \ - '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ - $(ALL_CFLAGS) -o $@ -c $(filter %.c,$^) +git.o: common-cmds.h +git.s git.o: ALL_CFLAGS += -DGIT_VERSION='"$(GIT_VERSION)"' \ + '-DGIT_HTML_PATH="$(htmldir_SQ)"' git$X: git.o $(BUILTIN_OBJS) $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \ $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) -builtin-help.o: builtin-help.c common-cmds.h GIT-CFLAGS - $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \ - '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ - '-DGIT_MAN_PATH="$(mandir_SQ)"' \ - '-DGIT_INFO_PATH="$(infodir_SQ)"' $< +builtin-help.o: common-cmds.h +builtin-help.s builtin-help.o: ALL_CFLAGS += \ + '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ + '-DGIT_MAN_PATH="$(mandir_SQ)"' \ + '-DGIT_INFO_PATH="$(infodir_SQ)"' $(BUILT_INS): git$X $(QUIET_BUILT_IN)$(RM) $@ && \ @@ -1426,17 +1464,25 @@ common-cmds.h: ./generate-cmdlist.sh command-list.txt common-cmds.h: $(wildcard Documentation/git-*.txt) $(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@ +define cmd_munge_script +$(RM) $@ $@+ && \ +sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \ + -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ + -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ + -e $(BROKEN_PATH_FIX) \ + $@.sh >$@+ +endef + $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh - $(QUIET_GEN)$(RM) $@ $@+ && \ - sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ - -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \ - -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ - -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ - -e $(BROKEN_PATH_FIX) \ - $@.sh >$@+ && \ + $(QUIET_GEN)$(cmd_munge_script) && \ chmod +x $@+ && \ mv $@+ $@ +$(SCRIPT_LIB) : % : %.sh + $(QUIET_GEN)$(cmd_munge_script) && \ + mv $@+ $@ + ifndef NO_PERL $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak @@ -1459,33 +1505,27 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl chmod +x $@+ && \ mv $@+ $@ + +.PHONY: gitweb +gitweb: + $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) all + +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 - $(QUIET_GEN)$(RM) $@ $@+ && \ - sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ - -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ - -e 's|++GIT_BINDIR++|$(bindir)|g' \ - -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \ - -e 's|++GITWEB_CONFIG_SYSTEM++|$(GITWEB_CONFIG_SYSTEM)|g' \ - -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \ - -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ - -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ - -e 's|"++GITWEB_PROJECT_MAXDEPTH++"|$(GITWEB_PROJECT_MAXDEPTH)|g' \ - -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \ - -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \ - -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \ - -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \ - -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \ - -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ - -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \ - -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \ - -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \ - -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \ - $< >$@+ && \ - chmod +x $@+ && \ - mv $@+ $@ +endif + $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@) -git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css +ifdef JSMIN +gitweb/gitweb.min.js: gitweb/gitweb.js + $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@) +endif # JSMIN + + +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 +1534,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 +1550,35 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh mv $@+ $@ endif # NO_PERL +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' \ @@ -1523,30 +1594,26 @@ git.o git.spec \ %.o: %.c GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $< -%.s: %.c GIT-CFLAGS +%.s: %.c GIT-CFLAGS FORCE $(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 - $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \ - '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \ - '-DBINDIR="$(bindir_relative_SQ)"' \ - '-DPREFIX="$(prefix_SQ)"' \ - $< +exec_cmd.s exec_cmd.o: ALL_CFLAGS += \ + '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \ + '-DBINDIR="$(bindir_relative_SQ)"' \ + '-DPREFIX="$(prefix_SQ)"' -builtin-init-db.o: builtin-init-db.c GIT-CFLAGS - $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $< +builtin-init-db.s builtin-init-db.o: ALL_CFLAGS += \ + -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' -config.o: config.c GIT-CFLAGS - $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $< +config.s config.o: ALL_CFLAGS += -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' -http.o: http.c GIT-CFLAGS - $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $< +http.s http.o: ALL_CFLAGS += -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' ifdef NO_EXPAT -http-walker.o: http-walker.c http.h GIT-CFLAGS - $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DNO_EXPAT $< +http-walker.o: http.h +http-walker.s http-walker.o: ALL_CFLAGS += -DNO_EXPAT endif git-%$X: %.o $(GITLIBS) @@ -1567,7 +1634,13 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) -git-remote-curl$X: remote-curl.o http.o http-walker.o $(GITLIBS) +$(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY) + $(QUIET_LNCP)$(RM) $@ && \ + ln $< $@ 2>/dev/null || \ + ln -s $< $@ 2>/dev/null || \ + cp $< $@ + +$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) @@ -1618,7 +1691,7 @@ cscope: TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\ $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ) -GIT-CFLAGS: .FORCE-GIT-CFLAGS +GIT-CFLAGS: FORCE @FLAGS='$(TRACK_CFLAGS)'; \ if test x"$$FLAGS" != x"`cat GIT-CFLAGS 2>/dev/null`" ; then \ echo 1>&2 " * new build flags or prefix"; \ @@ -1628,42 +1701,54 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS # We need to apply sq twice, once to protect from the shell # that runs GIT-BUILD-OPTIONS, and then again to protect it # and the first level quoting from the shell that runs "echo". -GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS +GIT-BUILD-OPTIONS: FORCE @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@ @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@ @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 TRACK_VARS = $(subst ','\'',-DTCLTK_PATH='$(TCLTK_PATH_SQ)') -GIT-GUI-VARS: .FORCE-GIT-GUI-VARS +GIT-GUI-VARS: FORCE @VARS='$(TRACK_VARS)'; \ if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \ echo 1>&2 " * new Tcl/Tk interpreter location"; \ echo "$$VARS" >$@; \ fi - -.PHONY: .FORCE-GIT-GUI-VARS 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 - -all:: $(TEST_PROGRAMS) +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-run-command +TEST_PROGRAMS_NEED_X += test-sha1 +TEST_PROGRAMS_NEED_X += test-sigchain +TEST_PROGRAMS_NEED_X += test-index-version + +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 +1809,21 @@ 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) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_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 @@ -1740,6 +1831,7 @@ endif ifneq (,$X) $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p' -ef '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p$X' || $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';) endif + bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \ execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \ { test "$$bindir/" = "$$execdir/" || \ @@ -1753,6 +1845,12 @@ endif ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \ cp "$$execdir/git$X" "$$execdir/$$p" || exit; \ done; } && \ + { for p in $(REMOTE_CURL_ALIASES); do \ + $(RM) "$$execdir/$$p" && \ + ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \ + ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \ + cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; \ + done; } && \ ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X" install-doc: @@ -1835,10 +1933,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) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(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 +1949,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 @@ -1860,13 +1962,12 @@ endif .PHONY: all install clean strip .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell -.PHONY: .FORCE-GIT-VERSION-FILE TAGS tags cscope .FORCE-GIT-CFLAGS -.PHONY: .FORCE-GIT-BUILD-OPTIONS +.PHONY: FORCE TAGS tags cscope ### Check documentation # check-docs:: - @(for v in $(ALL_PROGRAMS) $(BUILT_INS) git gitk; \ + @(for v in $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git gitk; \ do \ case "$$v" in \ git-merge-octopus | git-merge-ours | git-merge-recursive | \ @@ -1909,9 +2010,12 @@ check-docs:: documented,gitrepository-layout | \ documented,gittutorial | \ documented,gittutorial-2 | \ + documented,git-bisect-lk2009 | \ + documented.git-remote-helpers | \ + documented,gitworkflows | \ sentinel,not,matching,is,ok ) continue ;; \ esac; \ - case " $(ALL_PROGRAMS) $(BUILT_INS) git gitk " in \ + case " $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git gitk " in \ *" $$cmd "*) ;; \ *) echo "removed but $$how: $$cmd" ;; \ esac; \ @@ -1 +1 @@ -Documentation/RelNotes-1.6.5.8.txt
\ No newline at end of file +Documentation/RelNotes-1.7.1.txt
\ No newline at end of file @@ -3,6 +3,9 @@ int advice_push_nonfastforward = 1; int advice_status_hints = 1; int advice_commit_before_merge = 1; +int advice_resolve_conflict = 1; +int advice_implicit_identity = 1; +int advice_detached_head = 1; static struct { const char *name; @@ -11,6 +14,9 @@ static struct { { "pushnonfastforward", &advice_push_nonfastforward }, { "statushints", &advice_status_hints }, { "commitbeforemerge", &advice_commit_before_merge }, + { "resolveconflict", &advice_resolve_conflict }, + { "implicitidentity", &advice_implicit_identity }, + { "detachedhead", &advice_detached_head }, }; int git_default_advice_config(const char *var, const char *value) @@ -27,3 +33,17 @@ int git_default_advice_config(const char *var, const char *value) return 0; } + +void NORETURN die_resolve_conflict(const char *me) +{ + if (advice_resolve_conflict) + /* + * Message used both when 'git commit' fails and when + * other commands doing a merge do. + */ + die("'%s' is not possible because you have unmerged files.\n" + "Please, fix them up in the work tree, and then use 'git add/rm <file>' as\n" + "appropriate to mark resolution and make a commit, or use 'git commit -a'.", me); + else + die("'%s' is not possible because you have unmerged files.", me); +} @@ -1,10 +1,17 @@ #ifndef ADVICE_H #define ADVICE_H +#include "git-compat-util.h" + extern int advice_push_nonfastforward; extern int advice_status_hints; extern int advice_commit_before_merge; +extern int advice_resolve_conflict; +extern int advice_implicit_identity; +extern int advice_detached_head; int git_default_advice_config(const char *var, const char *value); +extern void NORETURN die_resolve_conflict(const char *me); + #endif /* ADVICE_H */ @@ -31,6 +31,8 @@ static void format_subst(const struct commit *commit, { char *to_free = NULL; struct strbuf fmt = STRBUF_INIT; + struct pretty_print_context ctx = {0}; + ctx.date_mode = DATE_NORMAL; if (src == buf->buf) to_free = strbuf_detach(buf, NULL); @@ -48,7 +50,7 @@ static void format_subst(const struct commit *commit, strbuf_add(&fmt, b + 8, c - b - 8); strbuf_add(buf, src, b - src); - format_commit_message(commit, fmt.buf, buf, DATE_NORMAL); + format_commit_message(commit, fmt.buf, buf, &ctx); len -= c + 1 - src; src = c + 1; } @@ -85,8 +87,8 @@ static void setup_archive_check(struct git_attr_check *check) static struct git_attr *attr_export_subst; if (!attr_export_ignore) { - attr_export_ignore = git_attr("export-ignore", 13); - attr_export_subst = git_attr("export-subst", 12); + attr_export_ignore = git_attr("export-ignore"); + attr_export_subst = git_attr("export-subst"); } check[0].attr = attr_export_ignore; check[1].attr = attr_export_subst; @@ -209,10 +211,33 @@ static const struct archiver *lookup_archiver(const char *name) return NULL; } +static int reject_entry(const unsigned char *sha1, const char *base, + int baselen, const char *filename, unsigned mode, + int stage, void *context) +{ + return -1; +} + +static int path_exists(struct tree *tree, const char *path) +{ + const char *pathspec[] = { path, NULL }; + + if (read_tree_recursive(tree, "", 0, 0, pathspec, reject_entry, NULL)) + return 1; + return 0; +} + static void parse_pathspec_arg(const char **pathspec, struct archiver_args *ar_args) { - ar_args->pathspec = get_pathspec("", pathspec); + ar_args->pathspec = pathspec = get_pathspec("", pathspec); + if (pathspec) { + while (*pathspec) { + if (!path_exists(ar_args->tree, *pathspec)) + die("path not found: %s", *pathspec); + pathspec++; + } + } } static void parse_treeish_arg(const char **argv, @@ -65,7 +65,7 @@ static int invalid_attr_name(const char *name, int namelen) return 0; } -struct git_attr *git_attr(const char *name, int len) +static struct git_attr *git_attr_internal(const char *name, int len) { unsigned hval = hash_name(name, len); unsigned pos = hval % HASHSIZE; @@ -95,6 +95,11 @@ struct git_attr *git_attr(const char *name, int len) return a; } +struct git_attr *git_attr(const char *name) +{ + return git_attr_internal(name, strlen(name)); +} + /* * .gitattributes file is one line per record, each of which is * @@ -162,7 +167,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp, else { e->setto = xmemdupz(equals + 1, ep - equals - 1); } - e->attr = git_attr(cp, len); + e->attr = git_attr_internal(cp, len); } (*num_attr)++; return ep + strspn(ep, blank); @@ -221,7 +226,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, sizeof(struct attr_state) * num_attr + (is_macro ? 0 : namelen + 1)); if (is_macro) - res->u.attr = git_attr(name, namelen); + res->u.attr = git_attr_internal(name, namelen); else { res->u.pattern = (char *)&(res->state[num_attr]); memcpy(res->u.pattern, name, namelen); @@ -8,7 +8,7 @@ struct git_attr; * Given a string, return the gitattribute object that * corresponds to it. */ -struct git_attr *git_attr(const char *, int); +struct git_attr *git_attr(const char *); /* Internal use */ extern const char git_attr__true[]; @@ -593,7 +593,7 @@ struct commit_list *filter_skipped(struct commit_list *list, * is increased by one between each call, but that should not matter * for this application. */ -int get_prn(int count) { +static int get_prn(int count) { count = count * 1103515245 + 12345; return ((unsigned)(count/65536) % PRN_MODULO); } @@ -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); } @@ -956,7 +956,7 @@ int bisect_next_all(const char *prefix) { struct rev_info revs; struct commit_list *tried; - int reaches = 0, all = 0, nr; + int reaches = 0, all = 0, nr, steps; const unsigned char *bisect_rev; char bisect_rev_hex[41]; @@ -1004,8 +1004,10 @@ int bisect_next_all(const char *prefix) } nr = all - reaches - 1; - printf("Bisecting: %d revisions left to test after this " - "(roughly %d steps)\n", nr, estimate_bisect_steps(all)); + steps = estimate_bisect_steps(all); + printf("Bisecting: %d revision%s left to test after this " + "(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"), + steps, (steps == 1 ? "" : "s")); return bisect_checkout(bisect_rev_hex); } @@ -27,8 +27,6 @@ struct rev_list_info { const char *header_prefix; }; -extern int show_bisect_vars(struct rev_list_info *info, int reaches, int all); - extern int bisect_next_all(const char *prefix); extern int estimate_bisect_steps(int all); @@ -23,24 +23,3 @@ int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size) item->object.parsed = 1; return 0; } - -int parse_blob(struct blob *item) -{ - enum object_type type; - void *buffer; - unsigned long size; - int ret; - - if (item->object.parsed) - return 0; - buffer = read_sha1_file(item->object.sha1, &type, &size); - if (!buffer) - return error("Could not read %s", - sha1_to_hex(item->object.sha1)); - if (type != OBJ_BLOB) - return error("Object %s not a blob", - sha1_to_hex(item->object.sha1)); - ret = parse_blob_buffer(item, buffer, size); - free(buffer); - return ret; -} @@ -13,6 +13,13 @@ struct blob *lookup_blob(const unsigned char *sha1); int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size); -int parse_blob(struct blob *item); +/** + * Blobs do not contain references to other objects and do not have + * structured data that needs parsing. However, code may use the + * "parsed" bit in the struct object for a blob to determine whether + * its content has been found to actually be available, so + * parse_blob_buffer() is used (by object.c) to flag that the object + * has been read successfully from the database. + **/ #endif /* BLOB_H */ @@ -49,9 +49,19 @@ static int should_setup_rebase(const char *origin) void install_branch_config(int flag, const char *local, const char *origin, const char *remote) { + const char *shortname = remote + 11; + int remote_is_branch = !prefixcmp(remote, "refs/heads/"); struct strbuf key = STRBUF_INIT; int rebasing = should_setup_rebase(origin); + if (remote_is_branch + && !strcmp(local, shortname) + && !origin) { + warning("Not setting branch %s as its own upstream.", + local); + return; + } + strbuf_addf(&key, "branch.%s.remote", local); git_config_set(key.buf, origin ? origin : "."); @@ -71,8 +81,8 @@ void install_branch_config(int flag, const char *local, const char *origin, cons strbuf_addstr(&key, origin ? "remote" : "local"); /* Are we tracking a proper "branch"? */ - if (!prefixcmp(remote, "refs/heads/")) { - strbuf_addf(&key, " branch %s", remote + 11); + if (remote_is_branch) { + strbuf_addf(&key, " branch %s", shortname); if (origin) strbuf_addf(&key, " from %s", origin); } @@ -108,6 +118,7 @@ static int setup_tracking(const char *new_ref, const char *orig_ref, switch (track) { case BRANCH_TRACK_ALWAYS: case BRANCH_TRACK_EXPLICIT: + case BRANCH_TRACK_OVERRIDE: break; default: return 1; @@ -128,18 +139,25 @@ void create_branch(const char *head, const char *name, const char *start_name, int force, int reflog, enum branch_track track) { - struct ref_lock *lock; + struct ref_lock *lock = NULL; struct commit *commit; unsigned char sha1[20]; char *real_ref, msg[PATH_MAX + 20]; struct strbuf ref = STRBUF_INIT; int forcing = 0; + int dont_change_ref = 0; + int explicit_tracking = 0; + + if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE) + explicit_tracking = 1; if (strbuf_check_branch_ref(&ref, name)) die("'%s' is not a valid branch name.", name); if (resolve_ref(ref.buf, sha1, 1, NULL)) { - if (!force) + if (!force && track == BRANCH_TRACK_OVERRIDE) + dont_change_ref = 1; + else if (!force) die("A branch named '%s' already exists.", name); else if (!is_bare_repository() && !strcmp(head, name)) die("Cannot force update the current branch."); @@ -153,12 +171,12 @@ void create_branch(const char *head, switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) { case 0: /* Not branching from any existing branch */ - if (track == BRANCH_TRACK_EXPLICIT) + if (explicit_tracking) die("Cannot setup tracking information; starting point is not a branch."); break; case 1: /* Unique completion -- good, only if it is a real ref */ - if (track == BRANCH_TRACK_EXPLICIT && !strcmp(real_ref, "HEAD")) + if (explicit_tracking && !strcmp(real_ref, "HEAD")) die("Cannot setup tracking information; starting point is not a branch."); break; default: @@ -170,9 +188,11 @@ void create_branch(const char *head, die("Not a valid branch point: '%s'.", start_name); hashcpy(sha1, commit->object.sha1); - lock = lock_any_ref_for_update(ref.buf, NULL, 0); - if (!lock) - die_errno("Failed to lock ref for update"); + if (!dont_change_ref) { + lock = lock_any_ref_for_update(ref.buf, NULL, 0); + if (!lock) + die_errno("Failed to lock ref for update"); + } if (reflog) log_all_ref_updates = 1; @@ -180,15 +200,16 @@ void create_branch(const char *head, if (forcing) snprintf(msg, sizeof msg, "branch: Reset from %s", start_name); - else + else if (!dont_change_ref) snprintf(msg, sizeof msg, "branch: Created from %s", start_name); if (real_ref && track) setup_tracking(name, real_ref, track); - if (write_ref_sha1(lock, sha1, msg) < 0) - die_errno("Failed to write ref"); + if (!dont_change_ref) + if (write_ref_sha1(lock, sha1, msg) < 0) + die_errno("Failed to write ref"); strbuf_release(&ref); free(real_ref); diff --git a/builtin-add.c b/builtin-add.c index cb6e5906fb..2705f8d057 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -11,6 +11,7 @@ #include "run-command.h" #include "parse-options.h" #include "diff.h" +#include "diffcore.h" #include "revision.h" static const char * const builtin_add_usage[] = { @@ -20,6 +21,81 @@ static const char * const builtin_add_usage[] = { static int patch_interactive, add_interactive, edit_interactive; static int take_worktree_changes; +struct update_callback_data +{ + int flags; + int add_errors; +}; + +static void update_callback(struct diff_queue_struct *q, + struct diff_options *opt, void *cbdata) +{ + int i; + struct update_callback_data *data = cbdata; + + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + const char *path = p->one->path; + switch (p->status) { + default: + die("unexpected diff status %c", p->status); + case DIFF_STATUS_UNMERGED: + /* + * ADD_CACHE_IGNORE_REMOVAL is unset if "git + * add -u" is calling us, In such a case, a + * missing work tree file needs to be removed + * if there is an unmerged entry at stage #2, + * but such a diff record is followed by + * another with DIFF_STATUS_DELETED (and if + * there is no stage #2, we won't see DELETED + * nor MODIFIED). We can simply continue + * either way. + */ + if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL)) + continue; + /* + * Otherwise, it is "git add path" is asking + * to explicitly add it; we fall through. A + * missing work tree file is an error and is + * caught by add_file_to_index() in such a + * case. + */ + case DIFF_STATUS_MODIFIED: + case DIFF_STATUS_TYPE_CHANGED: + if (add_file_to_index(&the_index, path, data->flags)) { + if (!(data->flags & ADD_CACHE_IGNORE_ERRORS)) + die("updating files failed"); + data->add_errors++; + } + break; + case DIFF_STATUS_DELETED: + if (data->flags & ADD_CACHE_IGNORE_REMOVAL) + break; + if (!(data->flags & ADD_CACHE_PRETEND)) + remove_file_from_index(&the_index, path); + if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE)) + printf("remove '%s'\n", path); + break; + } + } +} + +int add_files_to_cache(const char *prefix, const char **pathspec, int flags) +{ + struct update_callback_data data; + struct rev_info rev; + init_revisions(&rev, prefix); + setup_revisions(0, NULL, &rev, NULL); + rev.prune_data = pathspec; + rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = update_callback; + data.flags = flags; + data.add_errors = 0; + rev.diffopt.format_callback_data = &data; + run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); + return !!data.add_errors; +} + static void fill_pathspec_matches(const char **pathspec, char *seen, int specs) { int num_unmatched = 0, i; diff --git a/builtin-apply.c b/builtin-apply.c index 36e2f9dda5..3af4ae0c26 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -404,6 +404,9 @@ static char *squash_slash(char *name) { int i = 0, j = 0; + if (!name) + return NULL; + while (name[i]) { if ((name[j++] = name[i++]) == '/') while (name[i] == '/') @@ -416,7 +419,10 @@ static char *squash_slash(char *name) static char *find_name(const char *line, char *def, int p_value, int terminate) { int len; - const char *start = line; + const char *start = NULL; + + if (p_value == 0) + start = line; if (*line == '"') { struct strbuf name = STRBUF_INIT; @@ -686,7 +692,7 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, if (isnull) die("git apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr); another = find_name(line, NULL, p_value, TERM_TAB); - if (!another || memcmp(another, name, len)) + if (!another || memcmp(another, name, len + 1)) die("git apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr); free(another); return orig_name; @@ -1199,7 +1205,8 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc continue; if (!patch->old_name && !patch->new_name) { if (!patch->def_name) - die("git diff header lacks filename information (line %d)", linenr); + die("git diff header lacks filename information when removing " + "%d leading pathname components (line %d)" , p_value, linenr); patch->old_name = patch->new_name = patch->def_name; } patch->is_toplevel_relative = 1; @@ -1999,7 +2006,7 @@ static int find_pos(struct image *img, return -1; /* - * If match_begining or match_end is specified, there is no + * If match_beginning or match_end is specified, there is no * point starting from a wrong line that will never match and * wander around and wait for a match at the specified end. */ @@ -2666,7 +2673,7 @@ static int verify_index_match(struct cache_entry *ce, struct stat *st) return -1; return 0; } - return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID); + return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); } static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st) diff --git a/builtin-archive.c b/builtin-archive.c index 446d6bff30..6a887f5a9d 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -5,6 +5,7 @@ #include "cache.h" #include "builtin.h" #include "archive.h" +#include "transport.h" #include "parse-options.h" #include "pkt-line.h" #include "sideband.h" @@ -25,12 +26,16 @@ static void create_output_file(const char *output_file) static int run_remote_archiver(int argc, const char **argv, const char *remote, const char *exec) { - char *url, buf[LARGE_PACKET_MAX]; + char buf[LARGE_PACKET_MAX]; int fd[2], i, len, rv; - struct child_process *conn; + struct transport *transport; + struct remote *_remote; - url = xstrdup(remote); - conn = git_connect(fd, url, exec, 0); + _remote = remote_get(remote); + if (!_remote->url[0]) + die("git archive: Remote with no URL"); + transport = transport_get(_remote, _remote->url[0]); + transport_connect(transport, "git-upload-archive", exec, fd); for (i = 1; i < argc; i++) packet_write(fd[1], "argument %s\n", argv[i]); @@ -53,9 +58,7 @@ static int run_remote_archiver(int argc, const char **argv, /* Now, start reading from fd[0] and spit it out to stdout */ rv = recv_sideband("archive", fd[0], 1); - close(fd[0]); - close(fd[1]); - rv |= finish_connect(conn); + rv |= transport_disconnect(transport); return !!rv; } @@ -67,7 +70,7 @@ static const char *format_from_name(const char *filename) return NULL; ext++; if (!strcasecmp(ext, "zip")) - return "zip"; + return "--format=zip"; return NULL; } @@ -81,7 +84,7 @@ int cmd_archive(int argc, const char **argv, const char *prefix) const char *exec = "git-upload-archive"; const char *output = NULL; const char *remote = NULL; - const char *format = NULL; + const char *format_option = NULL; struct option local_opts[] = { OPT_STRING('o', "output", &output, "file", "write the archive to this file"), @@ -89,33 +92,31 @@ int cmd_archive(int argc, const char **argv, const char *prefix) "retrieve the archive from remote repository <repo>"), OPT_STRING(0, "exec", &exec, "cmd", "path to the remote git-upload-archive command"), - OPT_STRING(0, "format", &format, "fmt", "archive format"), OPT_END() }; - char fmt_opt[32]; argc = parse_options(argc, argv, prefix, local_opts, NULL, PARSE_OPT_KEEP_ALL); if (output) { create_output_file(output); - if (!format) - format = format_from_name(output); + format_option = format_from_name(output); } - if (format) { - sprintf(fmt_opt, "--format=%s", format); - /* - * 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. - */ + /* + * We have enough room in argv[] to muck it in place, because + * --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. + * + * We add a fake --format option at the beginning, with the + * format inferred from our output filename. This way explicit + * --format options can override it, and the fake option is + * inserted before any "--" that might have been given. + */ + if (format_option) { memmove(argv + 2, argv + 1, sizeof(*argv) * argc); - argv[1] = fmt_opt; + argv[1] = format_option; argv[++argc] = NULL; } diff --git a/builtin-blame.c b/builtin-blame.c index 4094f3c061..10f7eacf6e 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -2365,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 0c84f9f9eb..a28a13986d 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -93,9 +93,60 @@ static const char *branch_get_color(enum color_branch ix) return ""; } +static int branch_merged(int kind, const char *name, + struct commit *rev, struct commit *head_rev) +{ + /* + * This checks whether the merge bases of branch and HEAD (or + * the other branch this branch builds upon) contains the + * branch, which means that the branch has already been merged + * safely to HEAD (or the other branch). + */ + struct commit *reference_rev = NULL; + const char *reference_name = NULL; + int merged; + + if (kind == REF_LOCAL_BRANCH) { + struct branch *branch = branch_get(name); + unsigned char sha1[20]; + + if (branch && + branch->merge && + branch->merge[0] && + branch->merge[0]->dst && + (reference_name = + resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL) + reference_rev = lookup_commit_reference(sha1); + } + if (!reference_rev) + reference_rev = head_rev; + + merged = in_merge_bases(rev, &reference_rev, 1); + + /* + * After the safety valve is fully redefined to "check with + * upstream, if any, otherwise with HEAD", we should just + * return the result of the in_merge_bases() above without + * any of the following code, but during the transition period, + * a gentle reminder is in order. + */ + if ((head_rev != reference_rev) && + in_merge_bases(rev, &head_rev, 1) != merged) { + if (merged) + warning("deleting branch '%s' that has been merged to\n" + " '%s', but it is not yet merged to HEAD.", + name, reference_name); + else + warning("not deleting branch '%s' that is not yet merged to\n" + " '%s', even though it is merged to HEAD.", + name, reference_name); + } + return merged; +} + static int delete_branches(int argc, const char **argv, int force, int kinds) { - struct commit *rev, *head_rev = head_rev; + struct commit *rev, *head_rev = NULL; unsigned char sha1[20]; char *name = NULL; const char *fmt, *remote; @@ -148,15 +199,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) continue; } - /* This checks whether the merge bases of branch and - * HEAD contains branch -- which means that the HEAD - * contains everything in both. - */ - - if (!force && - !in_merge_bases(rev, &head_rev, 1)) { - error("The branch '%s' is not an ancestor of " - "your current HEAD.\n" + if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) { + error("The branch '%s' is not fully merged.\n" "If you are sure you want to delete it, " "run 'git branch -D %s'.", bname.buf, bname.buf); ret = 1; @@ -389,8 +433,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; } @@ -563,6 +608,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT__VERBOSE(&verbose), OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))", BRANCH_TRACK_EXPLICIT), + OPT_SET_INT( 0, "set-upstream", &track, "change upstream info", + BRANCH_TRACK_OVERRIDE), OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"), OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", REF_REMOTE_BRANCH), diff --git a/builtin-cat-file.c b/builtin-cat-file.c index 5906842008..a933eaa043 100644 --- a/builtin-cat-file.c +++ b/builtin-cat-file.c @@ -219,9 +219,10 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) "exit with zero when there's no error", 'e'), OPT_SET_INT('p', NULL, &opt, "pretty-print object's content", 'p'), OPT_SET_INT(0, "batch", &batch, - "show info and content of objects feeded on stdin", BATCH), + "show info and content of objects fed from the standard input", + BATCH), OPT_SET_INT(0, "batch-check", &batch, - "show info about objects feeded on stdin", + "show info about objects fed from the standard input", BATCH_CHECK), OPT_END() }; diff --git a/builtin-check-attr.c b/builtin-check-attr.c index 8bd0430098..3016d29caa 100644 --- a/builtin-check-attr.c +++ b/builtin-check-attr.c @@ -106,7 +106,7 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix) const char *name; struct git_attr *a; name = argv[i]; - a = git_attr(name, strlen(name)); + a = git_attr(name); if (!a) return error("%s: not a valid attribute name", name); check[i].attr = a; 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 f2786fe439..c5ab7835e1 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -17,6 +17,7 @@ #include "blob.h" #include "xdiff-interface.h" #include "ll-merge.h" +#include "resolve-undo.h" static const char * const checkout_usage[] = { "git checkout [options] <branch>", @@ -167,7 +168,7 @@ static int checkout_merged(int pos, struct checkout *state) fill_mm(active_cache[pos+2]->sha1, &theirs); status = ll_merge(&result_buf, path, &ancestor, - &ours, "ours", &theirs, "theirs", 1); + &ours, "ours", &theirs, "theirs", 0); free(ancestor.ptr); free(ours.ptr); free(theirs.ptr); @@ -234,6 +235,10 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, if (report_path_error(ps_matched, pathspec, 0)) return 1; + /* "checkout -m path" to recreate conflicted state */ + if (opts->merge) + unmerge_cache(pathspec); + /* Any unmerged paths? */ for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; @@ -302,8 +307,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); @@ -369,6 +375,7 @@ static int merge_working_tree(struct checkout_opts *opts, if (read_cache_preload(NULL) < 0) return error("corrupt index file"); + resolve_undo_clear(); if (opts->force) { ret = reset_tree(new->commit->tree, opts, 1); if (ret) @@ -481,6 +488,20 @@ static void report_tracking(struct branch_info *new) strbuf_release(&sb); } +static void detach_advice(const char *old_path, const char *new_name) +{ + const char fmt[] = + "Note: checking out '%s'.\n\n" + "You are in 'detached HEAD' state. You can look around, make experimental\n" + "changes and commit them, and you can discard any commits you make in this\n" + "state without impacting any branches by performing another checkout.\n\n" + "If you want to create a new branch to retain commits you create, you may\n" + "do so (now or later) by using -b with the checkout command again. Example:\n\n" + " git checkout -b new_branch_name\n\n"; + + fprintf(stderr, fmt, new_name); +} + static void update_refs_for_switch(struct checkout_opts *opts, struct branch_info *old, struct branch_info *new) @@ -515,8 +536,8 @@ static void update_refs_for_switch(struct checkout_opts *opts, update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, REF_NODEREF, DIE_ON_ERR); if (!opts->quiet) { - if (old->path) - fprintf(stderr, "Note: moving to '%s' which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n git checkout -b <new_branch_name>\n", new->name); + if (old->path && advice_detached_head) + detach_advice(old->path, new->name); describe_detached_head("HEAD is now at", new->commit); } } @@ -578,6 +599,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) { @@ -588,6 +643,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"), @@ -603,6 +659,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; @@ -636,8 +695,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); @@ -659,7 +716,15 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) * case 3: git checkout <something> [<paths>] * * With no paths, if <something> is a commit, that is to - * switch to the branch or detach HEAD at it. + * switch to the branch or detach HEAD at it. As a special case, + * if <something> is A...B (missing A or B means HEAD but you can + * omit at most one side), and if there is a unique merge base + * between A and B, A...B names that merge base. + * + * 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). @@ -680,10 +745,24 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "-")) arg = "@{-1}"; - if (get_sha1(arg, rev)) { + if (get_sha1_mb(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 */ @@ -693,8 +772,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) new.name = arg; if ((new.commit = lookup_commit_reference_gently(rev, 1))) { setup_branch_path(&new); - if (resolve_ref(new.path, rev, 1, NULL)) - new.commit = lookup_commit_reference(rev); + + if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) && + resolve_ref(new.path, rev, 1, NULL)) + ; else new.path = NULL; parse_commit(new.commit); @@ -721,6 +802,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-clean.c b/builtin-clean.c index 28cdcd0274..fac64e6cd3 100644 --- a/builtin-clean.c +++ b/builtin-clean.c @@ -67,19 +67,21 @@ int cmd_clean(int argc, const char **argv, const char *prefix) die("-x and -X cannot be used together"); if (!show_only && !force) - die("clean.requireForce%s set and -n or -f not given; " - "refusing to clean", config_set ? "" : " not"); + die("clean.requireForce %s to true and neither -n nor -f given; " + "refusing to clean", config_set ? "set" : "defaults"); if (force > 1) rm_flags = 0; dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; + if (read_cache() < 0) + die("index file corrupt"); + if (!ignored) setup_standard_excludes(&dir); pathspec = get_pathspec(prefix, argv); - read_cache(); fill_directory(&dir, pathspec); diff --git a/builtin-clone.c b/builtin-clone.c index caf3025031..58bacbd552 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -44,10 +44,13 @@ static char *option_origin = NULL; static char *option_branch = NULL; static char *option_upload_pack = "git-upload-pack"; static int option_verbose; +static int option_progress; static struct option builtin_clone_options[] = { OPT__QUIET(&option_quiet), OPT__VERBOSE(&option_verbose), + OPT_BOOLEAN(0, "progress", &option_progress, + "force progress reporting"), OPT_BOOLEAN('n', "no-checkout", &option_no_checkout, "don't create a checkout"), OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"), @@ -362,9 +365,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; @@ -525,6 +529,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_quiet) transport->verbose = -1; else if (option_verbose) + transport->verbose = 1; + + if (option_progress) transport->progress = 1; if (option_upload_pack) diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index 6467077731..90dac349a3 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -9,8 +9,6 @@ #include "builtin.h" #include "utf8.h" -#define BLOCKING (1ul << 14) - /* * FIXME! Share the code with "write-tree.c" */ @@ -105,7 +103,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 c2ab85e1aa..46513bf90b 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,20 @@ static const char * const builtin_status_usage[] = { NULL }; -static unsigned char head_sha1[20], merge_head_sha1[20]; +static const char implicit_ident_advice[] = +"Your name and email address were configured automatically based\n" +"on your username and hostname. Please check that they are accurate.\n" +"You can suppress this message by setting them explicitly:\n" +"\n" +" git config --global user.name \"Your Name\"\n" +" git config --global user.email you@example.com\n" +"\n" +"If the identity used for this commit is wrong, you can fix it with:\n" +"\n" +" git commit --amend --author='Your Name <you@example.com>'\n"; + +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 +65,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 @@ -67,10 +81,17 @@ static enum { } cleanup_mode; static char *cleanup_arg; -static int use_editor = 1, initial_commit, in_merge; +static int use_editor = 1, initial_commit, in_merge, include_status = 1; 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; @@ -90,13 +111,16 @@ static struct option builtin_commit_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"), + OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"), /* end commit message options */ OPT_GROUP("Commit contents options"), @@ -106,6 +130,12 @@ 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"), @@ -167,11 +197,15 @@ static int list_paths(struct string_list *list, const char *with_tree, for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; + struct string_list_item *item; + if (ce->ce_flags & CE_UPDATE) continue; if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m)) continue; - string_list_insert(ce->name, list); + item = string_list_insert(ce->name, list); + if (ce_skip_worktree(ce)) + item->util = item; /* better a valid pointer than a fake one */ } return report_path_error(m, pattern, prefix ? strlen(prefix) : 0); @@ -184,6 +218,10 @@ static void add_remove_files(struct string_list *list) struct stat st; struct string_list_item *p = &(list->items[i]); + /* p->util is skip-worktree */ + if (p->util) + continue; + if (!lstat(p->string, &st)) { if (add_to_cache(p->string, &st, 0)) die("updating files failed"); @@ -220,6 +258,16 @@ static void create_base_index(void) exit(128); /* We've already reported the error, finish dying */ } +static void refresh_cache_or_die(int refresh_flags) +{ + /* + * refresh_flags contains REFRESH_QUIET, so the only errors + * are for unmerged entries. + */ + if (refresh_cache(refresh_flags | REFRESH_IN_PORCELAIN)) + die_resolve_conflict("commit"); +} + static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status) { int fd; @@ -259,7 +307,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int if (all || (also && pathspec && *pathspec)) { int fd = hold_locked_index(&index_lock, 1); add_files_to_cache(also ? prefix : NULL, pathspec, 0); - refresh_cache(refresh_flags); + refresh_cache_or_die(refresh_flags); if (write_cache(fd, active_cache, active_nr) || close_lock_file(&index_lock)) die("unable to write new_index file"); @@ -278,7 +326,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int */ if (!pathspec || !*pathspec) { fd = hold_locked_index(&index_lock, 1); - refresh_cache(refresh_flags); + refresh_cache_or_die(refresh_flags); if (write_cache(fd, active_cache, active_nr) || commit_locked_index(&index_lock)) die("unable to write new_index file"); @@ -307,7 +355,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)); @@ -348,6 +396,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; @@ -359,8 +409,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_collect(s); - wt_status_print(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; } @@ -383,7 +446,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 "); @@ -411,11 +474,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) { @@ -491,7 +598,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); } @@ -507,7 +614,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, /* This checks if committer ident is explicitly given */ git_committer_info(0); - if (use_editor) { + if (use_editor && include_status) { char *author_ident; const char *committer_ident; @@ -549,7 +656,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, author_ident); free(author_ident); - if (!user_ident_explicitly_given) + if (!user_ident_sufficiently_given()) fprintf(fp, "%s" "# Committer: %s\n", @@ -686,13 +793,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, @@ -706,6 +830,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) @@ -716,9 +843,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."); @@ -739,6 +863,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"; @@ -796,22 +922,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; } @@ -893,26 +1015,75 @@ 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_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL); + 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) { struct rev_info rev; struct commit *commit; - static const char *format = "format:%h] %s"; + struct strbuf format = STRBUF_INIT; unsigned char junk_sha1[20]; const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL); + struct pretty_print_context pctx = {0}; + struct strbuf author_ident = STRBUF_INIT; + struct strbuf committer_ident = STRBUF_INIT; commit = lookup_commit(sha1); if (!commit) @@ -920,6 +1091,25 @@ static void print_summary(const char *prefix, const unsigned char *sha1) if (!commit || parse_commit(commit)) die("could not parse newly created commit"); + strbuf_addstr(&format, "format:%h] %s"); + + format_commit_message(commit, "%an <%ae>", &author_ident, &pctx); + format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx); + if (strbuf_cmp(&author_ident, &committer_ident)) { + strbuf_addstr(&format, "\n Author: "); + strbuf_addbuf_percentquote(&format, &author_ident); + } + if (!user_ident_sufficiently_given()) { + strbuf_addstr(&format, "\n Committer: "); + strbuf_addbuf_percentquote(&format, &committer_ident); + if (advice_implicit_identity) { + strbuf_addch(&format, '\n'); + strbuf_addstr(&format, implicit_ident_advice); + } + } + strbuf_release(&author_ident); + strbuf_release(&committer_ident); + init_revisions(&rev, prefix); setup_revisions(0, NULL, &rev, NULL); @@ -930,7 +1120,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) rev.verbose_header = 1; rev.show_root_diff = 1; - get_commit_format(format, &rev); + get_commit_format(format.buf, &rev); rev.always_show_header = 0; rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = 100; @@ -946,11 +1136,14 @@ 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.buf + 7, &buf, &ctx); printf("%s\n", buf.buf); strbuf_release(&buf); } + strbuf_release(&format); } static int git_commit_config(const char *k, const char *v, void *cb) @@ -959,6 +1152,10 @@ static int git_commit_config(const char *k, const char *v, void *cb) if (!strcmp(k, "commit.template")) return git_config_pathname(&template_file, k, v); + if (!strcmp(k, "commit.status")) { + include_status = git_config_bool(k, v); + return 0; + } return git_status_config(k, v, s); } @@ -977,10 +1174,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) { @@ -1103,7 +1301,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) "new_index file. Check that disk is not full or quota is\n" "not exceeded, and then \"git reset HEAD\" to recover."); - rerere(); + rerere(0); run_hook(get_index_file(), "post-commit", NULL); if (!quiet) print_summary(prefix, commit_sha1); diff --git a/builtin-config.c b/builtin-config.c index a2d656edb3..4bc46b15fd 100644 --- a/builtin-config.c +++ b/builtin-config.c @@ -45,6 +45,7 @@ static int end_null; #define TYPE_BOOL (1<<0) #define TYPE_INT (1<<1) #define TYPE_BOOL_OR_INT (1<<2) +#define TYPE_PATH (1<<3) static struct option builtin_config_options[] = { OPT_GROUP("Config file location"), @@ -69,6 +70,7 @@ static struct option builtin_config_options[] = { OPT_BIT(0, "bool", &types, "value is \"true\" or \"false\"", TYPE_BOOL), OPT_BIT(0, "int", &types, "value is decimal number", TYPE_INT), OPT_BIT(0, "bool-or-int", &types, "value is --bool or --int", TYPE_BOOL_OR_INT), + OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH), OPT_GROUP("Other"), OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"), OPT_END(), @@ -94,6 +96,7 @@ static int show_config(const char *key_, const char *value_, void *cb) { char value[256]; const char *vptr = value; + int must_free_vptr = 0; int dup_error = 0; if (!use_key_regexp && strcmp(key_, key)) @@ -123,6 +126,9 @@ static int show_config(const char *key_, const char *value_, void *cb) vptr = v ? "true" : "false"; else sprintf(value, "%d", v); + } else if (types == TYPE_PATH) { + git_config_pathname(&vptr, key_, value_); + must_free_vptr = 1; } else vptr = value_?value_:""; @@ -133,6 +139,12 @@ static int show_config(const char *key_, const char *value_, void *cb) } else printf("%s%c", vptr, term); + if (must_free_vptr) + /* If vptr must be freed, it's a pointer to a + * dynamically allocated buffer, it's safe to cast to + * const. + */ + free((char *)vptr); return 0; } @@ -215,7 +227,13 @@ static char *normalize_value(const char *key, const char *value) if (!value) return NULL; - if (types == 0) + if (types == 0 || types == TYPE_PATH) + /* + * We don't do normalization for TYPE_PATH here: If + * the path is like ~/foobar/, we prefer to store + * "~/foobar/" in the config file, and to expand the ~ + * when retrieving the value. + */ normalized = xstrdup(value); else { normalized = xmalloc(64); @@ -340,7 +358,7 @@ int cmd_config(int argc, const char **argv, const char *unused_prefix) if (!is_absolute_path(given_config_file) && prefix) config_exclusive_filename = prefix_filename(prefix, strlen(prefix), - argv[2]); + given_config_file); else config_exclusive_filename = given_config_file; } 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..dbd8b7bcc8 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; @@ -479,12 +586,12 @@ static int everything_local(struct ref **refs, int nr_match, char **match) return retval; } -static int sideband_demux(int fd, void *data) +static int sideband_demux(int in, int out, void *data) { int *xd = data; - int ret = recv_sideband("fetch-pack", xd[0], fd); - close(fd); + int ret = recv_sideband("fetch-pack", xd[0], out); + close(out); return ret; } @@ -506,6 +613,7 @@ static int get_pack(int xd[2], char **pack_lockfile) */ demux.proc = sideband_demux; demux.data = xd; + demux.out = -1; if (start_async(&demux)) die("fetch-pack: unable to fork off sideband" " demultiplexer"); @@ -584,7 +692,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 +728,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 +800,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 +851,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 +870,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 +943,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 +961,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..0929c7f245 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; @@ -576,7 +578,7 @@ static struct option fsck_opts[] = { OPT_BOOLEAN(0, "root", &show_root, "report root nodes"), OPT_BOOLEAN(0, "cache", &keep_cache_objects, "make index objects head nodes"), OPT_BOOLEAN(0, "reflogs", &include_reflogs, "make reflogs head nodes (default)"), - OPT_BOOLEAN(0, "full", &check_full, "also consider alternate objects"), + OPT_BOOLEAN(0, "full", &check_full, "also consider packs and alternate objects"), OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"), OPT_BOOLEAN(0, "lost-found", &write_lost_and_found, "write dangling objects in .git/lost-found"), diff --git a/builtin-gc.c b/builtin-gc.c index 093517e390..c304638b78 100644 --- a/builtin-gc.c +++ b/builtin-gc.c @@ -180,12 +180,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix) char buf[80]; struct option builtin_gc_options[] = { + OPT__QUIET(&quiet), { OPTION_STRING, 0, "prune", &prune_expire, "date", "prune unreferenced objects", PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire }, OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"), OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"), - OPT_BOOLEAN('q', "quiet", &quiet, "suppress progress reports"), OPT_END() }; diff --git a/builtin-grep.c b/builtin-grep.c index 63dc31c45c..552ef1face 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -14,13 +14,11 @@ #include "userdiff.h" #include "grep.h" #include "quote.h" +#include "dir.h" -#ifndef NO_EXTERNAL_GREP -#ifdef __unix__ -#define NO_EXTERNAL_GREP 0 -#else -#define NO_EXTERNAL_GREP 1 -#endif +#ifndef NO_PTHREADS +#include "thread-utils.h" +#include <pthread.h> #endif static char const * const grep_usage[] = { @@ -28,6 +26,266 @@ static char const * const grep_usage[] = { NULL }; +static int use_threads = 1; + +#ifndef NO_PTHREADS +#define THREADS 8 +static pthread_t threads[THREADS]; + +static void *load_sha1(const unsigned char *sha1, unsigned long *size, + const char *name); +static void *load_file(const char *filename, size_t *sz); + +enum work_type {WORK_SHA1, WORK_FILE}; + +/* We use one producer thread and THREADS consumer + * threads. The producer adds struct work_items to 'todo' and the + * consumers pick work items from the same array. + */ +struct work_item +{ + enum work_type type; + char *name; + + /* if type == WORK_SHA1, then 'identifier' is a SHA1, + * otherwise type == WORK_FILE, and 'identifier' is a NUL + * terminated filename. + */ + void *identifier; + char done; + struct strbuf out; +}; + +/* In the range [todo_done, todo_start) in 'todo' we have work_items + * that have been or are processed by a consumer thread. We haven't + * written the result for these to stdout yet. + * + * The work_items in [todo_start, todo_end) are waiting to be picked + * up by a consumer thread. + * + * The ranges are modulo TODO_SIZE. + */ +#define TODO_SIZE 128 +static struct work_item todo[TODO_SIZE]; +static int todo_start; +static int todo_end; +static int todo_done; + +/* Has all work items been added? */ +static int all_work_added; + +/* This lock protects all the variables above. */ +static pthread_mutex_t grep_mutex; + +/* Used to serialize calls to read_sha1_file. */ +static pthread_mutex_t read_sha1_mutex; + +#define grep_lock() pthread_mutex_lock(&grep_mutex) +#define grep_unlock() pthread_mutex_unlock(&grep_mutex) +#define read_sha1_lock() pthread_mutex_lock(&read_sha1_mutex) +#define read_sha1_unlock() pthread_mutex_unlock(&read_sha1_mutex) + +/* Signalled when a new work_item is added to todo. */ +static pthread_cond_t cond_add; + +/* Signalled when the result from one work_item is written to + * stdout. + */ +static pthread_cond_t cond_write; + +/* Signalled when we are finished with everything. */ +static pthread_cond_t cond_result; + +static void add_work(enum work_type type, char *name, void *id) +{ + grep_lock(); + + while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) { + pthread_cond_wait(&cond_write, &grep_mutex); + } + + todo[todo_end].type = type; + todo[todo_end].name = name; + todo[todo_end].identifier = id; + todo[todo_end].done = 0; + strbuf_reset(&todo[todo_end].out); + todo_end = (todo_end + 1) % ARRAY_SIZE(todo); + + pthread_cond_signal(&cond_add); + grep_unlock(); +} + +static struct work_item *get_work(void) +{ + struct work_item *ret; + + grep_lock(); + while (todo_start == todo_end && !all_work_added) { + pthread_cond_wait(&cond_add, &grep_mutex); + } + + if (todo_start == todo_end && all_work_added) { + ret = NULL; + } else { + ret = &todo[todo_start]; + todo_start = (todo_start + 1) % ARRAY_SIZE(todo); + } + grep_unlock(); + return ret; +} + +static void grep_sha1_async(struct grep_opt *opt, char *name, + const unsigned char *sha1) +{ + unsigned char *s; + s = xmalloc(20); + memcpy(s, sha1, 20); + add_work(WORK_SHA1, name, s); +} + +static void grep_file_async(struct grep_opt *opt, char *name, + const char *filename) +{ + add_work(WORK_FILE, name, xstrdup(filename)); +} + +static void work_done(struct work_item *w) +{ + int old_done; + + grep_lock(); + w->done = 1; + old_done = todo_done; + for(; todo[todo_done].done && todo_done != todo_start; + todo_done = (todo_done+1) % ARRAY_SIZE(todo)) { + w = &todo[todo_done]; + write_or_die(1, w->out.buf, w->out.len); + free(w->name); + free(w->identifier); + } + + if (old_done != todo_done) + pthread_cond_signal(&cond_write); + + if (all_work_added && todo_done == todo_end) + pthread_cond_signal(&cond_result); + + grep_unlock(); +} + +static void *run(void *arg) +{ + int hit = 0; + struct grep_opt *opt = arg; + + while (1) { + struct work_item *w = get_work(); + if (!w) + break; + + opt->output_priv = w; + if (w->type == WORK_SHA1) { + unsigned long sz; + void* data = load_sha1(w->identifier, &sz, w->name); + + if (data) { + hit |= grep_buffer(opt, w->name, data, sz); + free(data); + } + } else if (w->type == WORK_FILE) { + size_t sz; + void* data = load_file(w->identifier, &sz); + if (data) { + hit |= grep_buffer(opt, w->name, data, sz); + free(data); + } + } else { + assert(0); + } + + work_done(w); + } + free_grep_patterns(arg); + free(arg); + + return (void*) (intptr_t) hit; +} + +static void strbuf_out(struct grep_opt *opt, const void *buf, size_t size) +{ + struct work_item *w = opt->output_priv; + strbuf_add(&w->out, buf, size); +} + +static void start_threads(struct grep_opt *opt) +{ + int i; + + pthread_mutex_init(&grep_mutex, NULL); + pthread_mutex_init(&read_sha1_mutex, NULL); + pthread_cond_init(&cond_add, NULL); + pthread_cond_init(&cond_write, NULL); + pthread_cond_init(&cond_result, NULL); + + for (i = 0; i < ARRAY_SIZE(todo); i++) { + strbuf_init(&todo[i].out, 0); + } + + for (i = 0; i < ARRAY_SIZE(threads); i++) { + int err; + struct grep_opt *o = grep_opt_dup(opt); + o->output = strbuf_out; + compile_grep_patterns(o); + err = pthread_create(&threads[i], NULL, run, o); + + if (err) + die("grep: failed to create thread: %s", + strerror(err)); + } +} + +static int wait_all(void) +{ + int hit = 0; + int i; + + grep_lock(); + all_work_added = 1; + + /* Wait until all work is done. */ + while (todo_done != todo_end) + pthread_cond_wait(&cond_result, &grep_mutex); + + /* Wake up all the consumer threads so they can see that there + * is no more work to do. + */ + pthread_cond_broadcast(&cond_add); + grep_unlock(); + + for (i = 0; i < ARRAY_SIZE(threads); i++) { + void *h; + pthread_join(threads[i], &h); + hit |= (int) (intptr_t) h; + } + + pthread_mutex_destroy(&grep_mutex); + pthread_mutex_destroy(&read_sha1_mutex); + pthread_cond_destroy(&cond_add); + pthread_cond_destroy(&cond_write); + pthread_cond_destroy(&cond_result); + + return hit; +} +#else /* !NO_PTHREADS */ +#define read_sha1_lock() +#define read_sha1_unlock() + +static int wait_all(void) +{ + return 0; +} +#endif + static int grep_config(const char *var, const char *value, void *cb) { struct grep_opt *opt = cb; @@ -42,8 +300,6 @@ static int grep_config(const char *var, const char *value, void *cb) opt->color = git_config_colorbool(var, value, -1); return 0; } - if (!strcmp(var, "color.grep.external")) - return git_config_string(&(opt->color_external), var, value); if (!strcmp(var, "color.grep.match")) { if (!value) return config_error_nonbool(var); @@ -153,37 +409,74 @@ static int pathspec_matches(const char **paths, const char *name, int max_depth) return 0; } -static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name, int tree_name_len) +static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size) +{ + void *data; + + if (use_threads) { + read_sha1_lock(); + data = read_sha1_file(sha1, type, size); + read_sha1_unlock(); + } else { + data = read_sha1_file(sha1, type, size); + } + return data; +} + +static void *load_sha1(const unsigned char *sha1, unsigned long *size, + const char *name) { - unsigned long size; - char *data; enum object_type type; - int hit; - struct strbuf pathbuf = STRBUF_INIT; + void *data = lock_and_read_sha1_file(sha1, &type, size); - data = read_sha1_file(sha1, &type, &size); - if (!data) { + if (!data) error("'%s': unable to read %s", name, sha1_to_hex(sha1)); - return 0; - } + + return data; +} + +static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, + const char *filename, int tree_name_len) +{ + struct strbuf pathbuf = STRBUF_INIT; + char *name; + if (opt->relative && opt->prefix_length) { - quote_path_relative(name + tree_name_len, -1, &pathbuf, opt->prefix); - strbuf_insert(&pathbuf, 0, name, tree_name_len); - name = pathbuf.buf; + quote_path_relative(filename + tree_name_len, -1, &pathbuf, + opt->prefix); + strbuf_insert(&pathbuf, 0, filename, tree_name_len); + } else { + strbuf_addstr(&pathbuf, filename); + } + + name = strbuf_detach(&pathbuf, NULL); + +#ifndef NO_PTHREADS + if (use_threads) { + grep_sha1_async(opt, name, sha1); + return 0; + } else +#endif + { + int hit; + unsigned long sz; + void *data = load_sha1(sha1, &sz, name); + if (!data) + hit = 0; + else + hit = grep_buffer(opt, name, data, sz); + + free(data); + free(name); + return hit; } - hit = grep_buffer(opt, name, data, size); - strbuf_release(&pathbuf); - free(data); - return hit; } -static int grep_file(struct grep_opt *opt, const char *filename) +static void *load_file(const char *filename, size_t *sz) { struct stat st; - int i; char *data; - size_t sz; - struct strbuf buf = STRBUF_INIT; + int i; if (lstat(filename, &st) < 0) { err_ret: @@ -191,313 +484,62 @@ static int grep_file(struct grep_opt *opt, const char *filename) error("'%s': %s", filename, strerror(errno)); return 0; } - if (!st.st_size) - return 0; /* empty file -- no grep hit */ if (!S_ISREG(st.st_mode)) return 0; - sz = xsize_t(st.st_size); + *sz = xsize_t(st.st_size); i = open(filename, O_RDONLY); if (i < 0) goto err_ret; - data = xmalloc(sz + 1); - if (st.st_size != read_in_full(i, data, sz)) { + data = xmalloc(*sz + 1); + if (st.st_size != read_in_full(i, data, *sz)) { error("'%s': short read %s", filename, strerror(errno)); close(i); free(data); return 0; } close(i); - data[sz] = 0; - if (opt->relative && opt->prefix_length) - filename = quote_path_relative(filename, -1, &buf, opt->prefix); - i = grep_buffer(opt, filename, data, sz); - strbuf_release(&buf); - free(data); - return i; + data[*sz] = 0; + return data; } -#if !NO_EXTERNAL_GREP -static int exec_grep(int argc, const char **argv) +static int grep_file(struct grep_opt *opt, const char *filename) { - pid_t pid; - int status; + struct strbuf buf = STRBUF_INIT; + char *name; - argv[argc] = NULL; - pid = fork(); - if (pid < 0) - return pid; - if (!pid) { - execvp("grep", (char **) argv); - exit(255); - } - while (waitpid(pid, &status, 0) < 0) { - if (errno == EINTR) - continue; - return -1; - } - if (WIFEXITED(status)) { - if (!WEXITSTATUS(status)) - return 1; + if (opt->relative && opt->prefix_length) + quote_path_relative(filename, -1, &buf, opt->prefix); + else + strbuf_addstr(&buf, filename); + name = strbuf_detach(&buf, NULL); + +#ifndef NO_PTHREADS + if (use_threads) { + grep_file_async(opt, name, filename); return 0; - } - return -1; -} - -#define MAXARGS 1000 -#define ARGBUF 4096 -#define push_arg(a) do { \ - if (nr < MAXARGS) argv[nr++] = (a); \ - else die("maximum number of args exceeded"); \ - } while (0) - -/* - * If you send a singleton filename to grep, it does not give - * the name of the file. GNU grep has "-H" but we would want - * that behaviour in a portable way. - * - * So we keep two pathnames in argv buffer unsent to grep in - * the main loop if we need to do more than one grep. - */ -static int flush_grep(struct grep_opt *opt, - int argc, int arg0, const char **argv, int *kept) -{ - int status; - int count = argc - arg0; - const char *kept_0 = NULL; - - if (count <= 2) { - /* - * Because we keep at least 2 paths in the call from - * the main loop (i.e. kept != NULL), and MAXARGS is - * far greater than 2, this usually is a call to - * conclude the grep. However, the user could attempt - * to overflow the argv buffer by giving too many - * options to leave very small number of real - * arguments even for the call in the main loop. - */ - if (kept) - die("insanely many options to grep"); - - /* - * If we have two or more paths, we do not have to do - * anything special, but we need to push /dev/null to - * get "-H" behaviour of GNU grep portably but when we - * are not doing "-l" nor "-L" nor "-c". - */ - if (count == 1 && - !opt->name_only && - !opt->unmatch_name_only && - !opt->count) { - argv[argc++] = "/dev/null"; - argv[argc] = NULL; - } - } - - else if (kept) { - /* - * Called because we found many paths and haven't finished - * iterating over the cache yet. We keep two paths - * for the concluding call. argv[argc-2] and argv[argc-1] - * has the last two paths, so save the first one away, - * replace it with NULL while sending the list to grep, - * and recover them after we are done. - */ - *kept = 2; - kept_0 = argv[argc-2]; - argv[argc-2] = NULL; - argc -= 2; - } - - if (opt->pre_context || opt->post_context) { - /* - * grep handles hunk marks between files, but we need to - * do that ourselves between multiple calls. - */ - if (opt->show_hunk_mark) - write_or_die(1, "--\n", 3); + } else +#endif + { + int hit; + size_t sz; + void *data = load_file(filename, &sz); + if (!data) + hit = 0; else - opt->show_hunk_mark = 1; - } - - status = exec_grep(argc, argv); - - if (kept_0) { - /* - * Then recover them. Now the last arg is beyond the - * terminating NULL which is at argc, and the second - * from the last is what we saved away in kept_0 - */ - argv[arg0++] = kept_0; - argv[arg0] = argv[argc+1]; - } - return status; -} - -static void grep_add_color(struct strbuf *sb, const char *escape_seq) -{ - size_t orig_len = sb->len; - - while (*escape_seq) { - if (*escape_seq == 'm') - strbuf_addch(sb, ';'); - else if (*escape_seq != '\033' && *escape_seq != '[') - strbuf_addch(sb, *escape_seq); - escape_seq++; - } - if (sb->len > orig_len && sb->buf[sb->len - 1] == ';') - strbuf_setlen(sb, sb->len - 1); -} - -static int external_grep(struct grep_opt *opt, const char **paths, int cached) -{ - int i, nr, argc, hit, len, status; - const char *argv[MAXARGS+1]; - char randarg[ARGBUF]; - char *argptr = randarg; - struct grep_pat *p; - - if (opt->extended || (opt->relative && opt->prefix_length)) - return -1; - len = nr = 0; - push_arg("grep"); - if (opt->fixed) - push_arg("-F"); - if (opt->linenum) - push_arg("-n"); - if (!opt->pathname) - push_arg("-h"); - if (opt->regflags & REG_EXTENDED) - push_arg("-E"); - if (opt->ignore_case) - push_arg("-i"); - if (opt->binary == GREP_BINARY_NOMATCH) - push_arg("-I"); - if (opt->word_regexp) - push_arg("-w"); - if (opt->name_only) - push_arg("-l"); - if (opt->unmatch_name_only) - push_arg("-L"); - if (opt->null_following_name) - /* in GNU grep git's "-z" translates to "-Z" */ - push_arg("-Z"); - if (opt->count) - push_arg("-c"); - if (opt->post_context || opt->pre_context) { - if (opt->post_context != opt->pre_context) { - if (opt->pre_context) { - push_arg("-B"); - len += snprintf(argptr, sizeof(randarg)-len, - "%u", opt->pre_context) + 1; - if (sizeof(randarg) <= len) - die("maximum length of args exceeded"); - push_arg(argptr); - argptr += len; - } - if (opt->post_context) { - push_arg("-A"); - len += snprintf(argptr, sizeof(randarg)-len, - "%u", opt->post_context) + 1; - if (sizeof(randarg) <= len) - die("maximum length of args exceeded"); - push_arg(argptr); - argptr += len; - } - } - else { - push_arg("-C"); - len += snprintf(argptr, sizeof(randarg)-len, - "%u", opt->post_context) + 1; - if (sizeof(randarg) <= len) - die("maximum length of args exceeded"); - push_arg(argptr); - argptr += len; - } - } - for (p = opt->pattern_list; p; p = p->next) { - push_arg("-e"); - push_arg(p->pattern); - } - if (opt->color) { - struct strbuf sb = STRBUF_INIT; - - grep_add_color(&sb, opt->color_match); - setenv("GREP_COLOR", sb.buf, 1); + hit = grep_buffer(opt, name, data, sz); - strbuf_reset(&sb); - strbuf_addstr(&sb, "mt="); - grep_add_color(&sb, opt->color_match); - strbuf_addstr(&sb, ":sl=:cx=:fn=:ln=:bn=:se="); - setenv("GREP_COLORS", sb.buf, 1); - - strbuf_release(&sb); - - if (opt->color_external && strlen(opt->color_external) > 0) - push_arg(opt->color_external); - } - - hit = 0; - argc = nr; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - char *name; - int kept; - if (!S_ISREG(ce->ce_mode)) - continue; - if (!pathspec_matches(paths, ce->name, opt->max_depth)) - continue; - name = ce->name; - if (name[0] == '-') { - int len = ce_namelen(ce); - name = xmalloc(len + 3); - memcpy(name, "./", 2); - memcpy(name + 2, ce->name, len + 1); - } - argv[argc++] = name; - if (MAXARGS <= argc) { - status = flush_grep(opt, argc, nr, argv, &kept); - if (0 < status) - hit = 1; - argc = nr + kept; - } - if (ce_stage(ce)) { - do { - i++; - } while (i < active_nr && - !strcmp(ce->name, active_cache[i]->name)); - i--; /* compensate for loop control */ - } - } - if (argc > nr) { - status = flush_grep(opt, argc, nr, argv, NULL); - if (0 < status) - hit = 1; + free(data); + free(name); + return hit; } - return hit; } -#endif -static int grep_cache(struct grep_opt *opt, const char **paths, int cached, - int external_grep_allowed) +static int grep_cache(struct grep_opt *opt, const char **paths, int cached) { int hit = 0; int nr; read_cache(); -#if !NO_EXTERNAL_GREP - /* - * Use the external "grep" command for the case where - * we grep through the checked-out files. It tends to - * be a lot more optimized - */ - if (!cached && external_grep_allowed) { - hit = external_grep(opt, paths, cached); - if (hit >= 0) - return hit; - hit = 0; - } -#endif - for (nr = 0; nr < active_nr; nr++) { struct cache_entry *ce = active_cache[nr]; if (!S_ISREG(ce->ce_mode)) @@ -509,7 +551,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached, * are identical, even if worktree file has been modified, so use * cache version instead */ - if (cached || (ce->ce_flags & CE_VALID)) { + if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) { if (ce_stage(ce)) continue; hit |= grep_sha1(opt, ce->sha1, ce->name, 0); @@ -523,6 +565,8 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached, !strcmp(ce->name, active_cache[nr]->name)); nr--; /* compensate for loop control */ } + if (hit && opt->status_only) + break; } free_grep_patterns(opt); return hit; @@ -572,7 +616,7 @@ static int grep_tree(struct grep_opt *opt, const char **paths, void *data; unsigned long size; - data = read_sha1_file(entry.sha1, &type, &size); + data = lock_and_read_sha1_file(entry.sha1, &type, &size); if (!data) die("unable to read tree (%s)", sha1_to_hex(entry.sha1)); @@ -580,6 +624,8 @@ static int grep_tree(struct grep_opt *opt, const char **paths, hit |= grep_tree(opt, paths, &sub, tree_name, down); free(data); } + if (hit && opt->status_only) + break; } strbuf_release(&pathbuf); return hit; @@ -607,6 +653,24 @@ static int grep_object(struct grep_opt *opt, const char **paths, die("unable to grep from object of type %s", typename(obj->type)); } +static int grep_directory(struct grep_opt *opt, const char **paths) +{ + struct dir_struct dir; + int i, hit = 0; + + memset(&dir, 0, sizeof(dir)); + setup_standard_excludes(&dir); + + fill_directory(&dir, paths); + for (i = 0; i < dir.nr; i++) { + hit |= grep_file(opt, dir.entries[i]->name); + if (hit && opt->status_only) + break; + } + free_grep_patterns(opt); + return hit; +} + static int context_callback(const struct option *opt, const char *arg, int unset) { @@ -694,16 +758,19 @@ int cmd_grep(int argc, const char **argv, const char *prefix) { int hit = 0; int cached = 0; - int external_grep_allowed = 1; int seen_dashdash = 0; + int external_grep_allowed__ignored; struct grep_opt opt; struct object_array list = { 0, 0, NULL }; const char **paths = NULL; int i; int dummy; + int nongit = 0, use_index = 1; struct option options[] = { OPT_BOOLEAN(0, "cached", &cached, "search in index instead of in the work tree"), + OPT_BOOLEAN(0, "index", &use_index, + "--no-index finds in contents not managed by git"), OPT_GROUP(""), OPT_BOOLEAN('v', "invert-match", &opt.invert, "show non-matching lines"), @@ -774,21 +841,27 @@ int cmd_grep(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, ')', NULL, &opt, NULL, "", PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, close_callback }, + OPT_BOOLEAN('q', "quiet", &opt.status_only, + "indicate hit with exit status without output"), OPT_BOOLEAN(0, "all-match", &opt.all_match, "show only matches from files that match all patterns"), OPT_GROUP(""), -#if NO_EXTERNAL_GREP - OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed, - "allow calling of grep(1) (ignored by this build)"), -#else - OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed, - "allow calling of grep(1) (default)"), -#endif + OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored, + "allow calling of grep(1) (ignored by this build)"), { OPTION_CALLBACK, 0, "help-all", &options, NULL, "show usage", PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback }, OPT_END() }; + prefix = setup_git_directory_gently(&nongit); + + /* + * '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; @@ -819,6 +892,20 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_STOP_AT_NON_OPTION | PARSE_OPT_NO_INTERNAL_HELP); + if (use_index && nongit) + /* die the same way as if we did it at the beginning */ + setup_git_directory(); + + /* + * skip a -- separator; we know it cannot be + * separating revisions from pathnames if + * we haven't even had any patterns yet + */ + if (argc > 0 && !opt.pattern_list && !strcmp(argv[0], "--")) { + argv++; + argc--; + } + /* First unrecognized non-option token */ if (argc > 0 && !opt.pattern_list) { append_grep_pattern(&opt, argv[0], "command line", 0, @@ -827,14 +914,23 @@ int cmd_grep(int argc, const char **argv, const char *prefix) argc--; } - if ((opt.color && !opt.color_external) || opt.funcname) - external_grep_allowed = 0; if (!opt.pattern_list) die("no pattern given."); if (!opt.fixed && opt.ignore_case) opt.regflags |= REG_ICASE; if ((opt.regflags != REG_NEWLINE) && opt.fixed) die("cannot mix --fixed-strings and regexp"); + +#ifndef NO_PTHREADS + if (online_cpus() == 1 || !grep_threads_ok(&opt)) + use_threads = 0; + + if (use_threads) + start_threads(&opt); +#else + use_threads = 0; +#endif + compile_grep_patterns(&opt); /* Check revs and then paths */ @@ -871,10 +967,27 @@ int cmd_grep(int argc, const char **argv, const char *prefix) paths[1] = NULL; } + if (!use_index) { + int hit; + if (cached) + die("--cached cannot be used with --no-index."); + if (list.nr) + die("--no-index cannot be used with revs."); + hit = grep_directory(&opt, paths); + if (use_threads) + hit |= wait_all(); + return !hit; + } + if (!list.nr) { + int hit; if (!cached) setup_work_tree(); - return !grep_cache(&opt, paths, cached, external_grep_allowed); + + hit = grep_cache(&opt, paths, cached); + if (use_threads) + hit |= wait_all(); + return !hit; } if (cached) @@ -883,9 +996,15 @@ int cmd_grep(int argc, const char **argv, const char *prefix) for (i = 0; i < list.nr; i++) { struct object *real_obj; real_obj = deref_tag(list.objects[i].item, NULL, 0); - if (grep_object(&opt, paths, real_obj, list.objects[i].name)) + if (grep_object(&opt, paths, real_obj, list.objects[i].name)) { hit = 1; + if (opt.status_only) + break; + } } + + if (use_threads) + hit |= wait_all(); free_grep_patterns(&opt); return !hit; } diff --git a/hash-object.c b/builtin-hash-object.c index 9455dd0709..6a5f5b5f0e 100644 --- a/hash-object.c +++ b/builtin-hash-object.c @@ -73,17 +73,14 @@ static const struct option hash_object_options[] = { OPT_END() }; -int main(int argc, const char **argv) +int cmd_hash_object(int argc, const char **argv, const char *prefix) { int i; - const char *prefix = NULL; int prefix_length = -1; const char *errstr = NULL; type = blob_type; - git_extract_argv0_path(argv[0]); - argc = parse_options(argc, argv, NULL, hash_object_options, hash_object_usage, 0); diff --git a/builtin-help.c b/builtin-help.c index 09ad4b04f9..3182a2bec4 100644 --- a/builtin-help.c +++ b/builtin-help.c @@ -23,13 +23,14 @@ static struct man_viewer_info_list { } *man_viewer_info_list; enum help_format { + HELP_FORMAT_NONE, HELP_FORMAT_MAN, HELP_FORMAT_INFO, HELP_FORMAT_WEB, }; static int show_all = 0; -static enum help_format help_format = HELP_FORMAT_MAN; +static enum help_format help_format = HELP_FORMAT_NONE; static struct option builtin_help_options[] = { OPT_BOOLEAN('a', "all", &show_all, "print all available commands"), OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN), @@ -415,10 +416,12 @@ int cmd_help(int argc, const char **argv, const char *prefix) { int nongit; const char *alias; + enum help_format parsed_help_format; load_command_list("git-", &main_cmds, &other_cmds); argc = parse_options(argc, argv, prefix, builtin_help_options, builtin_help_usage, 0); + parsed_help_format = help_format; if (show_all) { printf("usage: %s\n\n", git_usage_string); @@ -437,6 +440,9 @@ int cmd_help(int argc, const char **argv, const char *prefix) setup_git_directory_gently(&nongit); git_config(git_help_config, NULL); + if (parsed_help_format != HELP_FORMAT_NONE) + help_format = parsed_help_format; + alias = alias_lookup(argv[0]); if (alias && !is_git_command(argv[0])) { printf("`git %s' is aliased to `%s'\n", argv[0], alias); @@ -444,6 +450,7 @@ int cmd_help(int argc, const char **argv, const char *prefix) } switch (help_format) { + case HELP_FORMAT_NONE: case HELP_FORMAT_MAN: show_man_page(argv[0]); break; diff --git a/index-pack.c b/builtin-index-pack.c index b4f8278659..b4cf8c53e0 100644 --- a/index-pack.c +++ b/builtin-index-pack.c @@ -166,7 +166,7 @@ static void use(int bytes) consumed_bytes += bytes; } -static char *open_pack_file(char *pack_name) +static const char *open_pack_file(const char *pack_name) { if (from_stdin) { input_fd = 0; @@ -870,17 +870,18 @@ static int git_index_pack_config(const char *k, const char *v, void *cb) return git_default_config(k, v, cb); } -int main(int argc, char **argv) +int cmd_index_pack(int argc, const char **argv, const char *prefix) { int i, fix_thin_pack = 0; - char *curr_pack, *pack_name = NULL; - char *curr_index, *index_name = NULL; + const char *curr_pack, *curr_index; + const char *index_name = NULL, *pack_name = NULL; const char *keep_name = NULL, *keep_msg = NULL; char *index_name_buf = NULL, *keep_name_buf = NULL; struct pack_idx_entry **idx_objects; unsigned char pack_sha1[20]; - 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 @@ -903,7 +904,7 @@ int main(int argc, char **argv) } for (i = 1; i < argc; i++) { - char *arg = argv[i]; + const char *arg = argv[i]; if (*arg == '-') { if (!strcmp(arg, "--stdin")) { @@ -1036,9 +1037,9 @@ int main(int argc, char **argv) free(index_name_buf); free(keep_name_buf); if (pack_name == NULL) - free(curr_pack); + free((void *) curr_pack); if (index_name == NULL) - free(curr_index); + free((void *) curr_index); return 0; } diff --git a/builtin-log.c b/builtin-log.c index 0cf978e094..e0d5caa61b 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -50,8 +50,17 @@ 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->show_notes_given && !rev->pretty_given) + rev->show_notes = 1; + if (rev->diffopt.pickaxe || rev->diffopt.filter) rev->always_show_header = 0; if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) { @@ -561,7 +570,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 +930,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 +1041,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); @@ -1070,7 +1089,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) /* * We cannot move this anywhere earlier because we do want to - * know if --root was given explicitly from the comand line. + * know if --root was given explicitly from the command line. */ rev.show_root_diff = 1; @@ -1243,6 +1262,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 +1332,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) if (verbose) { struct strbuf buf = STRBUF_INIT; + struct pretty_print_context ctx = {0}; pretty_print_commit(CMIT_FMT_ONELINE, commit, - &buf, 0, NULL, NULL, 0, 0); + &buf, &ctx); printf("%c %s %s\n", sign, sha1_to_hex(commit->object.sha1), buf.buf); strbuf_release(&buf); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index c9a03e5427..b065061392 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -11,6 +11,8 @@ #include "builtin.h" #include "tree.h" #include "parse-options.h" +#include "resolve-undo.h" +#include "string-list.h" static int abbrev; static int show_deleted; @@ -18,6 +20,7 @@ static int show_cached; static int show_others; static int show_stage; static int show_unmerged; +static int show_resolve_undo; static int show_modified; static int show_killed; static int show_valid_bit; @@ -37,6 +40,8 @@ static const char *tag_removed = ""; static const char *tag_other = ""; static const char *tag_killed = ""; static const char *tag_modified = ""; +static const char *tag_skip_worktree = ""; +static const char *tag_resolve_undo = ""; static void show_dir_entry(const char *tag, struct dir_entry *ent) { @@ -155,6 +160,38 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) write_name_quoted(ce->name + offset, stdout, line_terminator); } +static int show_one_ru(struct string_list_item *item, void *cbdata) +{ + int offset = prefix_offset; + const char *path = item->string; + struct resolve_undo_info *ui = item->util; + int i, len; + + len = strlen(path); + if (len < prefix_len) + return 0; /* outside of the prefix */ + if (!match_pathspec(pathspec, path, len, prefix_len, ps_matched)) + return 0; /* uninterested */ + for (i = 0; i < 3; i++) { + if (!ui->mode[i]) + continue; + printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i], + abbrev + ? find_unique_abbrev(ui->sha1[i], abbrev) + : sha1_to_hex(ui->sha1[i]), + i + 1); + write_name_quoted(path + offset, stdout, line_terminator); + } + return 0; +} + +static void show_ru_info(const char *prefix) +{ + if (!the_index.resolve_undo) + return; + for_each_string_list(show_one_ru, the_index.resolve_undo, NULL); +} + static void show_files(struct dir_struct *dir, const char *prefix) { int i; @@ -178,7 +215,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) continue; if (ce->ce_flags & CE_UPDATE) continue; - show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce); + show_ce_entry(ce_stage(ce) ? tag_unmerged : + (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce); } } if (show_deleted | show_modified) { @@ -192,6 +230,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) continue; if (ce->ce_flags & CE_UPDATE) continue; + if (ce_skip_worktree(ce)) + continue; err = lstat(ce->name, &st); if (show_deleted && err) show_ce_entry(tag_removed, ce); @@ -454,6 +494,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) DIR_HIDE_EMPTY_DIRECTORIES), OPT_BOOLEAN('u', "unmerged", &show_unmerged, "show unmerged files in the output"), + OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo, + "show resolve-undo information"), { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern", "skip files matching pattern", 0, option_parse_exclude }, @@ -481,6 +523,9 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) prefix_offset = strlen(prefix); git_config(git_default_config, NULL); + if (read_cache() < 0) + die("index file corrupt"); + argc = parse_options(argc, argv, prefix, builtin_ls_files_options, ls_files_usage, 0); if (show_tag || show_valid_bit) { @@ -490,6 +535,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) tag_modified = "C "; tag_other = "? "; tag_killed = "K "; + tag_skip_worktree = "S "; + tag_resolve_undo = "U "; } if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed) require_work_tree = 1; @@ -508,7 +555,6 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) pathspec = get_pathspec(prefix, argv); /* be nice with submodule paths ending in a slash */ - read_cache(); if (pathspec) strip_trailing_slash_from_submodules(); @@ -529,7 +575,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) /* With no flags, we default to showing the cached files */ if (!(show_stage | show_deleted | show_others | show_unmerged | - show_killed | show_modified)) + show_killed | show_modified | show_resolve_undo)) show_cached = 1; if (prefix) @@ -544,6 +590,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) overlay_tree_on_cache(with_tree, prefix); } show_files(&dir, prefix); + if (show_resolve_undo) + show_ru_info(prefix); if (ps_matched) { int bad; 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-file.c b/builtin-merge-file.c index afd2ea7a73..1e70073a7e 100644 --- a/builtin-merge-file.c +++ b/builtin-merge-file.c @@ -25,15 +25,20 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) const char *names[3] = { NULL, NULL, NULL }; mmfile_t mmfs[3]; mmbuffer_t result = {NULL, 0}; - xpparam_t xpp = {XDF_NEED_MINIMAL}; + xmparam_t xmp = {{XDF_NEED_MINIMAL}}; int ret = 0, i = 0, to_stdout = 0; - int merge_level = XDL_MERGE_ZEALOUS_ALNUM; - int merge_style = 0, quiet = 0; + int level = XDL_MERGE_ZEALOUS_ALNUM; + int style = 0, quiet = 0; + int favor = 0; int nongit; struct option options[] = { OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"), - OPT_SET_INT(0, "diff3", &merge_style, "use a diff3 based merge", XDL_MERGE_DIFF3), + OPT_SET_INT(0, "diff3", &style, "use a diff3 based merge", XDL_MERGE_DIFF3), + OPT_SET_INT(0, "ours", &favor, "for conflicts, use our version", + XDL_MERGE_FAVOR_OURS), + OPT_SET_INT(0, "theirs", &favor, "for conflicts, use their version", + XDL_MERGE_FAVOR_THEIRS), OPT__QUIET(&quiet), OPT_CALLBACK('L', NULL, names, "name", "set labels for file1/orig_file/file2", &label_cb), @@ -45,7 +50,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) /* Read the configuration file */ git_config(git_xmerge_config, NULL); if (0 <= git_xmerge_style) - merge_style = git_xmerge_style; + style = git_xmerge_style; } argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0); @@ -68,7 +73,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) } ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2], - &xpp, merge_level | merge_style, &result); + &xmp, XDL_MERGE_FLAGS(level, style, favor), &result); for (i = 0; i < 3; i++) free(mmfs[i].ptr); diff --git a/merge-index.c b/builtin-merge-index.c index 19ddd03e0b..2c4cf5e559 100644 --- a/merge-index.c +++ b/builtin-merge-index.c @@ -66,7 +66,7 @@ static void merge_all(void) } } -int main(int argc, char **argv) +int cmd_merge_index(int argc, const char **argv, const char *prefix) { int i, force_file = 0; @@ -78,9 +78,6 @@ int main(int argc, char **argv) if (argc < 3) usage("git merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)"); - git_extract_argv0_path(argv[0]); - - setup_git_directory(); read_cache(); i = 1; @@ -94,7 +91,7 @@ int main(int argc, char **argv) } pgm = argv[i++]; for (; i < argc; i++) { - char *arg = argv[i]; + const char *arg = argv[i]; if (!force_file && *arg == '-') { if (!strcmp(arg, "--")) { force_file = 1; 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..d8875d5892 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -25,19 +25,30 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) struct commit *result; init_merge_options(&o); - if (argv[0]) { - int namelen = strlen(argv[0]); - if (8 < namelen && - !strcmp(argv[0] + namelen - 8, "-subtree")) - o.subtree_merge = 1; - } + if (argv[0] && !suffixcmp(argv[0], "-subtree")) + o.subtree_shift = ""; 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], "--")) - break; + const char *arg = argv[i]; + + if (!prefixcmp(arg, "--")) { + if (!arg[2]) + break; + if (!strcmp(arg+2, "ours")) + o.recursive_variant = MERGE_RECURSIVE_OURS; + else if (!strcmp(arg+2, "theirs")) + o.recursive_variant = MERGE_RECURSIVE_THEIRS; + else if (!strcmp(arg+2, "subtree")) + o.subtree_shift = ""; + else if (!prefixcmp(arg+2, "subtree=")) + o.subtree_shift = arg + 10; + else + die("Unknown option %s", arg); + continue; + } if (bases_count < ARRAY_SIZE(bases)-1) { unsigned char *sha = xmalloc(20); if (get_sha1(argv[i], sha)) diff --git a/merge-tree.c b/builtin-merge-tree.c index f01e7c81ae..a4a4f2ce4c 100644 --- a/merge-tree.c +++ b/builtin-merge-tree.c @@ -54,7 +54,7 @@ static const char *explanation(struct merge_list *entry) return "removed in remote"; } -extern void *merge_file(struct blob *, struct blob *, struct blob *, unsigned long *); +extern void *merge_file(const char *, struct blob *, struct blob *, struct blob *, unsigned long *); static void *result(struct merge_list *entry, unsigned long *size) { @@ -76,7 +76,7 @@ static void *result(struct merge_list *entry, unsigned long *size) their = NULL; if (entry) their = entry->blob; - return merge_file(base, our, their, size); + return merge_file(entry->path, base, our, their, size); } static void *origin(struct merge_list *entry, unsigned long *size) @@ -337,7 +337,7 @@ static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) return buf; } -int main(int argc, char **argv) +int cmd_merge_tree(int argc, const char **argv, const char *prefix) { struct tree_desc t[3]; void *buf1, *buf2, *buf3; @@ -345,10 +345,6 @@ int main(int argc, char **argv) if (argc != 4) usage(merge_tree_usage); - git_extract_argv0_path(argv[0]); - - setup_git_directory(); - buf1 = get_tree_descriptor(t+0, argv[1]); buf2 = get_tree_descriptor(t+1, argv[2]); buf3 = get_tree_descriptor(t+2, argv[3]); diff --git a/builtin-merge.c b/builtin-merge.c index 921453949a..3aaec7bed7 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -24,6 +24,7 @@ #include "rerere.h" #include "help.h" #include "merge-recursive.h" +#include "resolve-undo.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -43,14 +44,18 @@ 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; static unsigned char head[20], stash[20]; static struct strategy **use_strategies; static size_t use_strategies_nr, use_strategies_alloc; +static const char **xopts; +static size_t xopts_nr, xopts_alloc; static const char *branch; static int verbosity; +static int allow_rerere_auto; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@ -145,6 +150,17 @@ static int option_parse_strategy(const struct option *opt, return 0; } +static int option_parse_x(const struct option *opt, + const char *arg, int unset) +{ + if (unset) + return 0; + + ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc); + xopts[xopts_nr++] = xstrdup(arg); + return 0; +} + static int option_parse_n(const struct option *opt, const char *arg, int unset) { @@ -166,9 +182,14 @@ 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_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_CALLBACK('s', "strategy", &use_strategies, "strategy", "merge strategy to use", option_parse_strategy), + OPT_CALLBACK('X', "strategy-option", &xopts, "option=value", + "option for selected merge strategy", option_parse_x), OPT_CALLBACK('m', "message", &merge_msg, "message", "message to be used for the merge commit (if any)", option_parse_message), @@ -264,6 +285,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 +307,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"); @@ -528,7 +552,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, const char *head_arg) { const char **args; - int i = 0, ret; + int i = 0, x = 0, ret; struct commit_list *j; struct strbuf buf = STRBUF_INIT; int index_fd; @@ -557,7 +581,20 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, init_merge_options(&o); if (!strcmp(strategy, "subtree")) - o.subtree_merge = 1; + o.subtree_shift = ""; + + for (x = 0; x < xopts_nr; x++) { + if (!strcmp(xopts[x], "ours")) + o.recursive_variant = MERGE_RECURSIVE_OURS; + else if (!strcmp(xopts[x], "theirs")) + o.recursive_variant = MERGE_RECURSIVE_THEIRS; + else if (!strcmp(xopts[x], "subtree")) + o.subtree_shift = ""; + else if (!prefixcmp(xopts[x], "subtree=")) + o.subtree_shift = xopts[x]+8; + else + die("Unknown option for merge-recursive: -X%s", xopts[x]); + } o.branch1 = head_arg; o.branch2 = remoteheads->item->util; @@ -575,10 +612,16 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, rollback_lock_file(lock); return clean ? 0 : 1; } else { - args = xmalloc((4 + commit_list_count(common) + + args = xmalloc((4 + xopts_nr + commit_list_count(common) + commit_list_count(remoteheads)) * sizeof(char *)); strbuf_addf(&buf, "merge-%s", strategy); args[i++] = buf.buf; + for (x = 0; x < xopts_nr; x++) { + char *s = xmalloc(strlen(xopts[x])+2+1); + strcpy(s, "--"); + strcpy(s+2, xopts[x]); + args[i++] = s; + } for (j = common; j; j = j->next) args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); args[i++] = "--"; @@ -589,6 +632,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, ret = run_command_v_opt(args, RUN_GIT_CMD); strbuf_release(&buf); i = 1; + for (x = 0; x < xopts_nr; x++) + free((void *)args[i++]); for (j = common; j; j = j->next) free((void *)args[i++]); i += 2; @@ -598,6 +643,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, discard_cache(); if (read_cache() < 0) die("failed to read the cache"); + resolve_undo_clear(); return ret; } } @@ -612,11 +658,10 @@ static void count_diff_files(struct diff_queue_struct *q, static int count_unmerged_entries(void) { - const struct index_state *state = &the_index; int i, ret = 0; - for (i = 0; i < state->cache_nr; i++) - if (ce_stage(state->cache[i])) + for (i = 0; i < active_nr; i++) + if (ce_stage(active_cache[i])) ret++; return ret; @@ -784,7 +829,7 @@ static int suggest_conflicts(void) } } fclose(fp); - rerere(); + rerere(allow_rerere_auto); printf("Automatic merge failed; " "fix conflicts and then commit the result.\n"); return 1; @@ -841,12 +886,22 @@ int cmd_merge(int argc, const char **argv, const char *prefix) const char *best_strategy = NULL, *wt_strategy = NULL; struct commit_list **remotes = &remoteheads; - if (file_exists(git_path("MERGE_HEAD"))) - die("You have not concluded your merge. (MERGE_HEAD exists)"); - if (read_cache_unmerged()) - die("You are in the middle of a conflicted merge." - " (index unmerged)"); + if (read_cache_unmerged()) { + die_resolve_conflict("merge"); + } + if (file_exists(git_path("MERGE_HEAD"))) { + /* + * There is no unmerged entry, don't advise 'git + * add/rm <file>', just 'git commit'. + */ + if (advice_resolve_conflict) + die("You have not concluded your merge (MERGE_HEAD exists).\n" + "Please, commit your changes before you can merge."); + else + die("You have not concluded your merge (MERGE_HEAD exists)."); + } + resolve_undo_clear(); /* * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. @@ -874,6 +929,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); @@ -1015,7 +1073,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)"); @@ -1033,16 +1091,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"); @@ -1081,6 +1139,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/mktag.c b/builtin-mktag.c index a3b4270c18..1cb0f3f2a7 100644 --- a/mktag.c +++ b/builtin-mktag.c @@ -153,7 +153,7 @@ static int verify_tag(char *buffer, unsigned long size) #undef PD_FMT -int main(int argc, char **argv) +int cmd_mktag(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; unsigned char result_sha1[20]; @@ -161,10 +161,6 @@ int main(int argc, char **argv) if (argc != 1) usage("git mktag < signaturefile"); - git_extract_argv0_path(argv[0]); - - setup_git_directory(); - if (strbuf_read(&buf, 0, 4096) < 0) { die_errno("could not read from stdin"); } diff --git a/builtin-mv.c b/builtin-mv.c index 1b20028c67..c07f53b343 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -24,10 +24,13 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec, result[count] = NULL; for (i = 0; i < count; i++) { int length = strlen(result[i]); - if (length > 0 && is_dir_sep(result[i][length - 1])) - result[i] = xmemdupz(result[i], length - 1); - if (base_name) - result[i] = basename((char *)result[i]); + int to_copy = length; + while (to_copy > 0 && is_dir_sep(result[i][to_copy - 1])) + to_copy--; + if (to_copy != length || base_name) { + char *it = xmemdupz(result[i], to_copy); + result[i] = base_name ? strdup(basename(it)) : it; + } } return get_pathspec(prefix, result); } @@ -64,15 +67,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 +172,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..e1d3adf405 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -17,7 +17,7 @@ #include "progress.h" #include "refs.h" -#ifdef THREADED_DELTA_SEARCH +#ifndef NO_PTHREADS #include "thread-utils.h" #include <pthread.h> #endif @@ -77,7 +77,7 @@ static int allow_ofs_delta; static const char *base_name; static int progress = 1; static int window = 10; -static uint32_t pack_size_limit, pack_size_limit_cfg; +static unsigned long pack_size_limit, pack_size_limit_cfg; static int depth = 50; static int delta_search_threads; static int pack_to_stdout; @@ -246,7 +246,7 @@ static unsigned long write_object(struct sha1file *f, type = entry->type; - /* write limit if limited packsize and not first object */ + /* apply size limit if limited packsize and not first object */ if (!pack_size_limit || !nr_written) limit = 0; else if (pack_size_limit <= write_offset) @@ -443,7 +443,7 @@ static int write_one(struct sha1file *f, /* offset is non zero if object is written already. */ if (e->idx.offset || e->preferred_base) - return 1; + return -1; /* if we are deltified, write out base object first. */ if (e->delta && !write_one(f, e->delta, offset)) @@ -525,7 +525,8 @@ static void write_pack_file(void) if (!pack_to_stdout) { mode_t mode = umask(0); struct stat st; - char *idx_tmp_name, tmpname[PATH_MAX]; + const char *idx_tmp_name; + char tmpname[PATH_MAX]; umask(mode); mode = 0444 & ~mode; @@ -569,7 +570,7 @@ static void write_pack_file(void) if (rename(idx_tmp_name, tmpname)) die_errno("unable to rename temporary index file"); - free(idx_tmp_name); + free((void *) idx_tmp_name); free(pack_tmp_name); puts(sha1_to_hex(sha1)); } @@ -586,19 +587,6 @@ static void write_pack_file(void) if (written != nr_result) die("wrote %"PRIu32" objects while expecting %"PRIu32, written, nr_result); - /* - * We have scanned through [0 ... i). Since we have written - * the correct number of objects, the remaining [i ... nr_objects) - * items must be either already written (due to out-of-order delta base) - * or a preferred base. Count those which are neither and complain if any. - */ - for (j = 0; i < nr_objects; i++) { - struct object_entry *e = objects + i; - j += !e->idx.offset && !e->preferred_base; - } - if (j) - die("wrote %"PRIu32" objects as expected but %"PRIu32 - " unwritten", written, j); } static int locate_object_entry_hash(const unsigned char *sha1) @@ -673,7 +661,7 @@ static void setup_delta_attr_check(struct git_attr_check *check) static struct git_attr *attr_delta; if (!attr_delta) - attr_delta = git_attr("delta", 5); + attr_delta = git_attr("delta"); check[0].attr = attr_delta; } @@ -1254,17 +1242,17 @@ static int delta_cacheable(unsigned long src_size, unsigned long trg_size, return 0; } -#ifdef THREADED_DELTA_SEARCH +#ifndef NO_PTHREADS -static pthread_mutex_t read_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t read_mutex; #define read_lock() pthread_mutex_lock(&read_mutex) #define read_unlock() pthread_mutex_unlock(&read_mutex) -static pthread_mutex_t cache_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t cache_mutex; #define cache_lock() pthread_mutex_lock(&cache_mutex) #define cache_unlock() pthread_mutex_unlock(&cache_mutex) -static pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t progress_mutex; #define progress_lock() pthread_mutex_lock(&progress_mutex) #define progress_unlock() pthread_mutex_unlock(&progress_mutex) @@ -1379,7 +1367,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, /* * Handle memory allocation outside of the cache * accounting lock. Compiler will optimize the strangeness - * away when THREADED_DELTA_SEARCH is not defined. + * away when NO_PTHREADS is defined. */ free(trg_entry->delta_data); cache_lock(); @@ -1566,7 +1554,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, free(array); } -#ifdef THREADED_DELTA_SEARCH +#ifndef NO_PTHREADS /* * The main thread waits on the condition that (at least) one of the workers @@ -1591,7 +1579,26 @@ struct thread_params { unsigned *processed; }; -static pthread_cond_t progress_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t progress_cond; + +/* + * Mutex and conditional variable can't be statically-initialized on Windows. + */ +static void init_threaded_search(void) +{ + pthread_mutex_init(&read_mutex, NULL); + pthread_mutex_init(&cache_mutex, NULL); + pthread_mutex_init(&progress_mutex, NULL); + pthread_cond_init(&progress_cond, NULL); +} + +static void cleanup_threaded_search(void) +{ + pthread_cond_destroy(&progress_cond); + pthread_mutex_destroy(&read_mutex); + pthread_mutex_destroy(&cache_mutex); + pthread_mutex_destroy(&progress_mutex); +} static void *threaded_find_deltas(void *arg) { @@ -1630,8 +1637,13 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, struct thread_params *p; int i, ret, active_threads = 0; + init_threaded_search(); + + 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); + cleanup_threaded_search(); return; } if (progress > pack_to_stdout) @@ -1746,6 +1758,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, active_threads--; } } + cleanup_threaded_search(); free(p); } @@ -1873,7 +1886,7 @@ static int git_pack_config(const char *k, const char *v, void *cb) if (delta_search_threads < 0) die("invalid number of threads specified (%d)", delta_search_threads); -#ifndef THREADED_DELTA_SEARCH +#ifdef NO_PTHREADS if (delta_search_threads != 1) warning("no threads support, ignoring %s", k); #endif @@ -2177,10 +2190,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) continue; } if (!prefixcmp(arg, "--max-pack-size=")) { - char *end; pack_size_limit_cfg = 0; - pack_size_limit = strtoul(arg+16, &end, 0) * 1024 * 1024; - if (!arg[16] || *end) + if (!git_parse_ulong(arg+16, &pack_size_limit)) usage(pack_usage); continue; } @@ -2201,7 +2212,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) delta_search_threads = strtoul(arg+10, &end, 0); if (!arg[10] || *end || delta_search_threads < 0) usage(pack_usage); -#ifndef THREADED_DELTA_SEARCH +#ifdef NO_PTHREADS if (delta_search_threads != 1) warning("no threads support, " "ignoring %s", arg); @@ -2320,9 +2331,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (!pack_to_stdout && !pack_size_limit) pack_size_limit = pack_size_limit_cfg; - if (pack_to_stdout && pack_size_limit) die("--max-pack-size cannot be used to build a pack for transfer."); + if (pack_size_limit && pack_size_limit < 1024*1024) { + warning("minimum pack size limit is 1 MiB"); + pack_size_limit = 1024*1024; + } if (!pack_to_stdout && thin) die("--thin cannot be used to build an indexable pack."); @@ -2330,11 +2344,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/pack-redundant.c b/builtin-pack-redundant.c index 69a7ab2e27..41e1615a28 100644 --- a/pack-redundant.c +++ b/builtin-pack-redundant.c @@ -568,7 +568,7 @@ static struct pack_list * add_pack(struct packed_git *p) return pack_list_insert(&altodb_packs, &l); } -static struct pack_list * add_pack_file(char *filename) +static struct pack_list * add_pack_file(const char *filename) { struct packed_git *p = packed_git; @@ -593,7 +593,7 @@ static void load_all(void) } } -int main(int argc, char **argv) +int cmd_pack_redundant(int argc, const char **argv, const char *prefix) { int i; struct pack_list *min, *red, *pl; @@ -601,9 +601,8 @@ int main(int argc, char **argv) unsigned char *sha1; char buf[42]; /* 40 byte sha1 + \n + \0 */ - git_extract_argv0_path(argv[0]); - - setup_git_directory(); + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(pack_redundant_usage); for (i = 1; i < argc; i++) { const char *arg = argv[i]; diff --git a/patch-id.c b/builtin-patch-id.c index 0df4cb086b..af0911e4bd 100644 --- a/patch-id.c +++ b/builtin-patch-id.c @@ -75,13 +75,11 @@ static void generate_id_list(void) static const char patch_id_usage[] = "git patch-id < patch"; -int main(int argc, char **argv) +int cmd_patch_id(int argc, const char **argv, const char *prefix) { if (argc != 1) usage(patch_id_usage); - git_extract_argv0_path(argv[0]); - generate_id_list(); return 0; } diff --git a/builtin-prune.c b/builtin-prune.c index 8459aec8e8..4675f6054f 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -106,7 +106,7 @@ static void prune_object_dir(const char *path) /* * Write errors (particularly out of space) can result in * failed temporary packs (and more rarely indexes and other - * files begining with "tmp_") accumulating in the object + * files beginning with "tmp_") accumulating in the object * and the pack directories. */ static void remove_temporary_files(const char *path) diff --git a/builtin-push.c b/builtin-push.c index 752121f247..5633f0ade4 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -15,6 +15,7 @@ static const char * const push_usage[] = { }; static int thin; +static int deleterefs; static const char *receivepack; static const char **refspec; @@ -39,11 +40,24 @@ static void set_refspecs(const char **refs, int nr) if (nr <= ++i) die("tag shorthand without <tag>"); len = strlen(refs[i]) + 11; - tag = xmalloc(len); - strcpy(tag, "refs/tags/"); + if (deleterefs) { + tag = xmalloc(len+1); + strcpy(tag, ":refs/tags/"); + } else { + tag = xmalloc(len); + strcpy(tag, "refs/tags/"); + } strcat(tag, refs[i]); ref = tag; - } + } else if (deleterefs && !strchr(ref, ':')) { + char *delref; + int len = strlen(ref)+1; + delref = xmalloc(len+1); + strcpy(delref, ":"); + strcat(delref, ref); + ref = delref; + } else if (deleterefs) + die("--delete only accepts plain target ref names"); add_refspec(ref); } } @@ -87,6 +101,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 'Note about\n" + "fast-forwards' section of 'git push --help' for details.\n"); + } + + return 1; +} + static int do_push(const char *repo, int flags) { int i, errs; @@ -135,33 +180,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; } @@ -179,6 +210,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL), OPT_BIT( 0 , "mirror", &flags, "mirror all refs", (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)), + OPT_BOOLEAN( 0, "delete", &deleterefs, "delete refs"), OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"), OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN), @@ -186,12 +218,19 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"), OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"), OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"), + OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status", + TRANSPORT_PUSH_SET_UPSTREAM), OPT_END() }; git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, push_usage, 0); + if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR)))) + die("--delete is incompatible with --all, --mirror and --tags"); + if (deleterefs && argc < 2) + die("--delete doesn't make sense without any refs"); + if (tags) add_refspec("refs/tags/*"); diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 14c836b169..8bdcab1138 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -13,6 +13,7 @@ #include "dir.h" #include "builtin.h" #include "parse-options.h" +#include "resolve-undo.h" static int nr_trees; static struct tree *trees[MAX_UNPACK_TREES]; @@ -31,7 +32,7 @@ static int list_tree(unsigned char *sha1) } static const char * const read_tree_usage[] = { - "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]", + "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]", NULL }; @@ -64,6 +65,34 @@ static int exclude_per_directory_cb(const struct option *opt, const char *arg, return 0; } +static void debug_stage(const char *label, struct cache_entry *ce, + struct unpack_trees_options *o) +{ + printf("%s ", label); + if (!ce) + printf("(missing)\n"); + else if (ce == o->df_conflict_entry) + printf("(conflict)\n"); + else + printf("%06o #%d %s %.8s\n", + ce->ce_mode, ce_stage(ce), ce->name, + sha1_to_hex(ce->sha1)); +} + +static int debug_merge(struct cache_entry **stages, struct unpack_trees_options *o) +{ + int i; + + printf("* %d-way merge\n", o->merge_size); + debug_stage("index", stages[0], o); + for (i = 1; i <= o->merge_size; i++) { + char buf[24]; + sprintf(buf, "ent#%d", i); + debug_stage(buf, stages[i], o); + } + return 0; +} + static struct lock_file lock_file; int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) @@ -98,6 +127,10 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) PARSE_OPT_NONEG, exclude_per_directory_cb }, OPT_SET_INT('i', NULL, &opts.index_only, "don't check the working tree after merging", 1), + OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout, + "skip applying sparse checkout filter", 1), + OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack, + "debug unpack-trees", 1), OPT_END() }; @@ -108,11 +141,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?"); @@ -122,6 +155,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) die("You need to resolve your current index first"); stage = opts.merge = 1; } + resolve_undo_clear(); for (i = 0; i < argc; i++) { const char *arg = argv[i]; @@ -165,6 +199,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) opts.head_idx = 1; } + if (opts.debug_unpack) + opts.fn = debug_merge; + cache_tree_free(&active_cache_tree); for (i = 0; i < nr_trees; i++) { struct tree *tree = trees[i]; @@ -174,6 +211,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) if (unpack_trees(nr_trees, t, &opts)) return 128; + if (opts.debug_unpack) + return 0; /* do not write the index out */ + /* * When reading only one tree (either the most basic form, * "-m ent" or "--reset ent" form), we can obtain a fully diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index e8bde02c27..0559fcc871 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -2,6 +2,7 @@ #include "pack.h" #include "refs.h" #include "pkt-line.h" +#include "sideband.h" #include "run-command.h" #include "exec_cmd.h" #include "commit.h" @@ -27,11 +28,12 @@ static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; static int unpack_limit = 100; static int report_status; +static int use_sideband; static int prefer_ofs_delta = 1; static int auto_update_server_info; static int auto_gc = 1; static const char *head_name; -static char *capabilities_to_send; +static int sent_capabilities; static enum deny_action parse_deny_action(const char *var, const char *value) { @@ -105,19 +107,21 @@ static int receive_pack_config(const char *var, const char *value, void *cb) static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { - if (!capabilities_to_send) + if (sent_capabilities) packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); else - packet_write(1, "%s %s%c%s\n", - sha1_to_hex(sha1), path, 0, capabilities_to_send); - capabilities_to_send = NULL; + packet_write(1, "%s %s%c%s%s\n", + sha1_to_hex(sha1), path, 0, + " report-status delete-refs side-band-64k", + prefer_ofs_delta ? " ofs-delta" : ""); + sent_capabilities = 1; return 0; } static void write_head_info(void) { for_each_ref(show_ref, NULL); - if (capabilities_to_send) + if (!sent_capabilities) show_ref("capabilities^{}", null_sha1, 0, NULL); } @@ -135,11 +139,61 @@ static struct command *commands; static const char pre_receive_hook[] = "hooks/pre-receive"; static const char post_receive_hook[] = "hooks/post-receive"; +static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2))); +static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2))); + +static void report_message(const char *prefix, const char *err, va_list params) +{ + int sz = strlen(prefix); + char msg[4096]; + + strncpy(msg, prefix, sz); + sz += vsnprintf(msg + sz, sizeof(msg) - sz, err, params); + if (sz > (sizeof(msg) - 1)) + sz = sizeof(msg) - 1; + msg[sz++] = '\n'; + + if (use_sideband) + send_sideband(1, 2, msg, sz, use_sideband); + else + xwrite(2, msg, sz); +} + +static void rp_warning(const char *err, ...) +{ + va_list params; + va_start(params, err); + report_message("warning: ", err, params); + va_end(params); +} + +static void rp_error(const char *err, ...) +{ + va_list params; + va_start(params, err); + report_message("error: ", err, params); + va_end(params); +} + +static int copy_to_sideband(int in, int out, void *arg) +{ + char data[128]; + while (1) { + ssize_t sz = xread(in, data, sizeof(data)); + if (sz <= 0) + break; + send_sideband(1, 2, data, sz, use_sideband); + } + close(in); + return 0; +} + static int run_receive_hook(const char *hook_name) { static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4]; struct command *cmd; struct child_process proc; + struct async muxer; const char *argv[2]; int have_input = 0, code; @@ -159,9 +213,23 @@ static int run_receive_hook(const char *hook_name) proc.in = -1; proc.stdout_to_stderr = 1; + if (use_sideband) { + memset(&muxer, 0, sizeof(muxer)); + muxer.proc = copy_to_sideband; + muxer.in = -1; + code = start_async(&muxer); + if (code) + return code; + proc.err = muxer.in; + } + code = start_command(&proc); - if (code) + if (code) { + if (use_sideband) + finish_async(&muxer); return code; + } + for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) { size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n", @@ -173,6 +241,8 @@ static int run_receive_hook(const char *hook_name) } } close(proc.in); + if (use_sideband) + finish_async(&muxer); return finish_command(&proc); } @@ -180,6 +250,8 @@ static int run_update_hook(struct command *cmd) { static const char update_hook[] = "hooks/update"; const char *argv[5]; + struct child_process proc; + int code; if (access(update_hook, X_OK) < 0) return 0; @@ -190,8 +262,18 @@ static int run_update_hook(struct command *cmd) argv[3] = sha1_to_hex(cmd->new_sha1); argv[4] = NULL; - return run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | - RUN_COMMAND_STDOUT_TO_STDERR); + memset(&proc, 0, sizeof(proc)); + proc.no_stdin = 1; + proc.stdout_to_stderr = 1; + proc.err = use_sideband ? -1 : 0; + proc.argv = argv; + + code = start_command(&proc); + if (code) + return code; + if (use_sideband) + copy_to_sideband(proc.err, -1, NULL); + return finish_command(&proc); } static int is_ref_checked_out(const char *ref) @@ -204,59 +286,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." - "", - "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'.", + "'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.", "", - "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++) + rp_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.", + "'warn' or 'ignore' in the remote repository to allow deleting the", + "current branch, with or without a warning message.", "", - "You can set it to 'ignore' to allow such a delete without a warning.", - "", - "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]); + rp_error("%s", refuse_unconfigured_deny_delete_current_msg[i]); } static const char *update(struct command *cmd) @@ -268,7 +338,7 @@ static const char *update(struct command *cmd) /* only refs/... are allowed */ if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) { - error("refusing to create funny ref '%s' remotely", name); + rp_error("refusing to create funny ref '%s' remotely", name); return "funny refname"; } @@ -276,14 +346,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(); + rp_warning("updating the current branch"); break; case DENY_REFUSE: - error("refusing to update checked out branch: %s", name); + case DENY_UNCONFIGURED: + rp_error("refusing to update checked out branch: %s", name); + if (deny_current_branch == DENY_UNCONFIGURED) + refuse_unconfigured_deny(); return "branch is currently checked out"; } } @@ -296,7 +366,7 @@ static const char *update(struct command *cmd) if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) { if (deny_deletes && !prefixcmp(name, "refs/heads/")) { - error("denying ref deletion for %s", name); + rp_error("denying ref deletion for %s", name); return "deletion prohibited"; } @@ -305,13 +375,13 @@ 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"); + rp_warning("deleting the current branch"); break; case DENY_REFUSE: - error("refusing to delete the current branch: %s", name); + case DENY_UNCONFIGURED: + if (deny_delete_current == DENY_UNCONFIGURED) + refuse_unconfigured_deny_delete_current(); + rp_error("refusing to delete the current branch: %s", name); return "deletion of the current branch prohibited"; } } @@ -341,23 +411,23 @@ static const char *update(struct command *cmd) break; free_commit_list(bases); if (!ent) { - error("denying non-fast forward %s" - " (you should pull first)", name); - return "non-fast forward"; + rp_error("denying non-fast-forward %s" + " (you should pull first)", name); + return "non-fast-forward"; } } if (run_update_hook(cmd)) { - error("hook declined to update %s", name); + rp_error("hook declined to update %s", name); return "hook declined"; } if (is_null_sha1(new_sha1)) { if (!parse_object(old_sha1)) { - warning ("Allowing deletion of corrupt ref."); + rp_warning("Allowing deletion of corrupt ref."); old_sha1 = NULL; } if (delete_ref(name, old_sha1, 0)) { - error("failed to delete %s", name); + rp_error("failed to delete %s", name); return "failed to delete"; } return NULL; /* good */ @@ -365,7 +435,7 @@ static const char *update(struct command *cmd) else { lock = lock_any_ref_for_update(name, old_sha1, 0); if (!lock) { - error("failed to lock %s", name); + rp_error("failed to lock %s", name); return "failed to lock"; } if (write_ref_sha1(lock, new_sha1, "push")) { @@ -380,8 +450,9 @@ static char update_post_hook[] = "hooks/post-update"; static void run_update_post_hook(struct command *cmd) { struct command *cmd_p; - int argc, status; + int argc; const char **argv; + struct child_process proc; for (argc = 0, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) { if (cmd_p->error_string) @@ -403,8 +474,18 @@ static void run_update_post_hook(struct command *cmd) argc++; } argv[argc] = NULL; - status = run_command_v_opt(argv, RUN_COMMAND_NO_STDIN - | RUN_COMMAND_STDOUT_TO_STDERR); + + memset(&proc, 0, sizeof(proc)); + proc.no_stdin = 1; + proc.stdout_to_stderr = 1; + proc.err = use_sideband ? -1 : 0; + proc.argv = argv; + + if (!start_command(&proc)) { + if (use_sideband) + copy_to_sideband(proc.err, -1, NULL); + finish_command(&proc); + } } static void execute_commands(const char *unpacker_error) @@ -464,6 +545,8 @@ static void read_head_info(void) if (reflen + 82 < len) { if (strstr(refname + reflen + 1, "report-status")) report_status = 1; + if (strstr(refname + reflen + 1, "side-band-64k")) + use_sideband = LARGE_PACKET_MAX; } cmd = xmalloc(sizeof(struct command) + len - 80); hashcpy(cmd->old_sha1, old_sha1); @@ -563,17 +646,25 @@ static const char *unpack(void) static void report(const char *unpack_status) { struct command *cmd; - packet_write(1, "unpack %s\n", - unpack_status ? unpack_status : "ok"); + struct strbuf buf = STRBUF_INIT; + + packet_buf_write(&buf, "unpack %s\n", + unpack_status ? unpack_status : "ok"); for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) - packet_write(1, "ok %s\n", - cmd->ref_name); + packet_buf_write(&buf, "ok %s\n", + cmd->ref_name); else - packet_write(1, "ng %s %s\n", - cmd->ref_name, cmd->error_string); + packet_buf_write(&buf, "ng %s %s\n", + cmd->ref_name, cmd->error_string); } - packet_flush(1); + packet_buf_flush(&buf); + + if (use_sideband) + send_sideband(1, 1, buf.buf, buf.len, use_sideband); + else + safe_write(1, buf.buf, buf.len); + strbuf_release(&buf); } static int delete_only(struct command *cmd) @@ -627,6 +718,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 +728,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) @@ -660,16 +761,16 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) else if (0 <= receive_unpack_limit) unpack_limit = receive_unpack_limit; - capabilities_to_send = (prefer_ofs_delta) ? - " report-status delete-refs ofs-delta " : - " report-status delete-refs "; + if (advertise_refs || !stateless_rpc) { + add_alternate_refs(); + write_head_info(); + clear_extra_refs(); - 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) { @@ -693,5 +794,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) if (auto_update_server_info) update_server_info(0); } + if (use_sideband) + packet_flush(1); return 0; } 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..277765b864 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -16,6 +16,9 @@ static const char * const builtin_remote_usage[] = { "git remote [-v | --verbose] show [-n] <name>", "git remote prune [-n | --dry-run] <name>", "git remote [-v | --verbose] update [-p | --prune] [group | remote]", + "git remote set-url <name> <newurl> [<oldurl>]", + "git remote set-url --add <name> <newurl>", + "git remote set-url --delete <name> <url>", NULL }; @@ -54,6 +57,13 @@ static const char * const builtin_remote_update_usage[] = { NULL }; +static const char * const builtin_remote_seturl_usage[] = { + "git remote set-url [--push] <name> <newurl> [<oldurl>]", + "git remote set-url --add <name> <newurl>", + "git remote set-url --delete <name> <url>", + NULL +}; + #define GET_REF_STATES (1<<0) #define GET_HEAD_NAMES (1<<1) #define GET_PUSH_REF_STATES (1<<2) @@ -261,32 +271,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 +282,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 +292,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 +780,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 +983,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 +1209,146 @@ 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) +static int get_remote_default(const char *key, const char *value, 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) -{ - 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 (prune) + fetch_argv[fetch_argc++] = "--prune"; + if (verbose) + fetch_argv[fetch_argc++] = "-v"; + fetch_argv[fetch_argc++] = "--multiple"; + if (argc < 2) + fetch_argv[fetch_argc++] = "default"; + for (i = 1; i < argc; i++) + fetch_argv[fetch_argc++] = argv[i]; + + 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"; } - if (!result && !list.nr && argc == 2 && !strcmp(argv[1], "default")) - result = for_each_remote(get_one_remote_for_update, &list); + fetch_argv[fetch_argc] = NULL; + + return run_command_v_opt(fetch_argv, RUN_GIT_CMD); +} + +static int set_url(int argc, const char **argv) +{ + int i, push_mode = 0, add_mode = 0, delete_mode = 0; + int matches = 0, negative_matches = 0; + const char *remotename = NULL; + const char *newurl = NULL; + const char *oldurl = NULL; + struct remote *remote; + regex_t old_regex; + const char **urlset; + int urlset_nr; + struct strbuf name_buf = STRBUF_INIT; + struct option options[] = { + OPT_BOOLEAN('\0', "push", &push_mode, + "manipulate push URLs"), + OPT_BOOLEAN('\0', "add", &add_mode, + "add URL"), + OPT_BOOLEAN('\0', "delete", &delete_mode, + "delete URLs"), + OPT_END() + }; + argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage, + PARSE_OPT_KEEP_ARGV0); + + if (add_mode && delete_mode) + die("--add --delete doesn't make sense"); + + if (argc < 3 || argc > 4 || ((add_mode || delete_mode) && argc != 3)) + usage_with_options(builtin_remote_seturl_usage, options); + + remotename = argv[1]; + newurl = argv[2]; + if (argc > 3) + oldurl = argv[3]; - 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 (delete_mode) + oldurl = newurl; + + if (!remote_is_configured(remotename)) + die("No such remote '%s'", remotename); + remote = remote_get(remotename); + + if (push_mode) { + strbuf_addf(&name_buf, "remote.%s.pushurl", remotename); + urlset = remote->pushurl; + urlset_nr = remote->pushurl_nr; + } else { + strbuf_addf(&name_buf, "remote.%s.url", remotename); + urlset = remote->url; + urlset_nr = remote->url_nr; } - /* all names were strdup()ed or strndup()ed */ - list.strdup_strings = 1; - string_list_clear(&list, 0); + /* Special cases that add new entry. */ + if ((!oldurl && !delete_mode) || add_mode) { + if (add_mode) + git_config_set_multivar(name_buf.buf, newurl, + "^$", 0); + else + git_config_set(name_buf.buf, newurl); + strbuf_release(&name_buf); + return 0; + } - return result; + /* Old URL specified. Demand that one matches. */ + if (regcomp(&old_regex, oldurl, REG_EXTENDED)) + die("Invalid old URL pattern: %s", oldurl); + + for (i = 0; i < urlset_nr; i++) + if (!regexec(&old_regex, urlset[i], 0, NULL, 0)) + matches++; + else + negative_matches++; + if (!delete_mode && !matches) + die("No such URL found: %s", oldurl); + if (delete_mode && !negative_matches && !push_mode) + die("Will not delete all non-push URLs"); + + regfree(&old_regex); + + if (!delete_mode) + git_config_set_multivar(name_buf.buf, newurl, oldurl, 0); + else + git_config_set_multivar(name_buf.buf, NULL, oldurl, 1); + return 0; } static int get_one_entry(struct remote *remote, void *priv) @@ -1382,6 +1430,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix) result = rm(argc, argv); else if (!strcmp(argv[0], "set-head")) result = set_head(argc, argv); + else if (!strcmp(argv[0], "set-url")) + result = set_url(argc, argv); else if (!strcmp(argv[0], "show")) result = show(argc, argv); else if (!strcmp(argv[0], "prune")) diff --git a/builtin-rerere.c b/builtin-rerere.c index 31fda73ae7..34f9acee91 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -103,12 +103,29 @@ static int diff_two(const char *file1, const char *label1, int cmd_rerere(int argc, const char **argv, const char *prefix) { struct string_list merge_rr = { NULL, 0, 0, 1 }; - int i, fd; - + int i, fd, flags = 0; + + if (2 < argc) { + if (!strcmp(argv[1], "-h")) + usage(git_rerere_usage); + if (!strcmp(argv[1], "--rerere-autoupdate")) + flags = RERERE_AUTOUPDATE; + else if (!strcmp(argv[1], "--no-rerere-autoupdate")) + flags = RERERE_NOAUTOUPDATE; + if (flags) { + argc--; + argv++; + } + } if (argc < 2) - return rerere(); + return rerere(flags); + + if (!strcmp(argv[1], "forget")) { + const char **pathspec = get_pathspec(prefix, argv + 2); + return rerere_forget(pathspec); + } - fd = setup_rerere(&merge_rr); + fd = setup_rerere(&merge_rr, flags); if (fd < 0) return 0; diff --git a/builtin-reset.c b/builtin-reset.c index e4418bced2..0f5022eed2 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -18,6 +18,8 @@ #include "tree.h" #include "branch.h" #include "parse-options.h" +#include "unpack-trees.h" +#include "cache-tree.h" static const char * const git_reset_usage[] = { "git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]", @@ -54,27 +56,44 @@ static inline int is_merge(void) static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet) { - int i = 0; - const char *args[6]; + int nr = 1; + int newfd; + struct tree_desc desc[2]; + struct unpack_trees_options opts; + struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - args[i++] = "read-tree"; + memset(&opts, 0, sizeof(opts)); + opts.head_idx = 1; + opts.src_index = &the_index; + opts.dst_index = &the_index; + opts.fn = oneway_merge; + opts.merge = 1; if (!quiet) - args[i++] = "-v"; + opts.verbose_update = 1; switch (reset_type) { case MERGE: - args[i++] = "-u"; - args[i++] = "-m"; + opts.update = 1; break; case HARD: - args[i++] = "-u"; + opts.update = 1; /* fallthrough */ default: - args[i++] = "--reset"; + opts.reset = 1; } - args[i++] = sha1_to_hex(sha1); - args[i] = NULL; - return run_command_v_opt(args, RUN_GIT_CMD); + newfd = hold_locked_index(lock, 1); + + read_cache_unmerged(); + + if (!fill_tree_descriptor(desc + nr - 1, sha1)) + return error("Failed to find tree of %s.", sha1_to_hex(sha1)); + if (unpack_trees(nr, desc, &opts)) + return -1; + if (write_cache(newfd, active_cache, active_nr) || + commit_locked_index(lock)) + return error("Could not write new index file."); + + return 0; } static void print_new_head_line(struct commit *commit) @@ -202,6 +221,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) struct commit *commit; char *reflog_action, msg[1024]; const struct option options[] = { + OPT__QUIET(&quiet), OPT_SET_INT(0, "mixed", &reset_type, "reset HEAD and index", MIXED), OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT), @@ -209,8 +229,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix) "reset HEAD, index and working tree", HARD), OPT_SET_INT(0, "merge", &reset_type, "reset HEAD, index and working tree", MERGE), - OPT_BOOLEAN('q', NULL, &quiet, - "disable showing new HEAD in hard reset and progress message"), OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), OPT_END() }; @@ -289,6 +307,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (reset_type == HARD || reset_type == MERGE) setup_work_tree(); + if (reset_type == MIXED && is_bare_repository()) + die("%s reset is not allowed in a bare repository", + reset_type_names[reset_type]); + /* Soft reset does not touch the index file nor the working tree * at all, but requires them in a good order. Other resets reset * the index file to the tree object we are switching to. */ diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 4ba1c12e0b..c924b3a2c7 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) @@ -252,7 +253,7 @@ static void print_var_int(const char *var, int val) printf("%s=%d\n", var, val); } -int show_bisect_vars(struct rev_list_info *info, int reaches, int all) +static int show_bisect_vars(struct rev_list_info *info, int reaches, int all) { int cnt, flags = info->bisect_show_flags; char hex[41] = ""; @@ -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..a8c5043ded 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -41,6 +41,7 @@ static int is_rev_argument(const char *arg) "--all", "--bisect", "--dense", + "--branches=", "--branches", "--header", "--max-age=", @@ -51,8 +52,11 @@ static int is_rev_argument(const char *arg) "--objects-edge", "--parents", "--pretty", + "--remotes=", "--remotes", + "--glob=", "--sparse", + "--tags=", "--tags", "--topo-order", "--date-order", @@ -180,6 +184,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 +436,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 +455,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,18 +568,48 @@ 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 (!prefixcmp(arg, "--branches=")) { + for_each_glob_ref_in(show_reference, arg + 11, + "refs/heads/", NULL); + continue; + } if (!strcmp(arg, "--branches")) { for_each_branch_ref(show_reference, NULL); continue; } + if (!prefixcmp(arg, "--tags=")) { + for_each_glob_ref_in(show_reference, arg + 7, + "refs/tags/", NULL); + continue; + } if (!strcmp(arg, "--tags")) { for_each_tag_ref(show_reference, NULL); continue; } + if (!prefixcmp(arg, "--glob=")) { + for_each_glob_ref(show_reference, arg + 7, NULL); + continue; + } + if (!prefixcmp(arg, "--remotes=")) { + for_each_glob_ref_in(show_reference, arg + 10, + "refs/remotes/", NULL); + continue; + } if (!strcmp(arg, "--remotes")) { for_each_remote_ref(show_reference, NULL); continue; } + if (!strcmp(arg, "--show-toplevel")) { + const char *work_tree = get_git_work_tree(); + if (work_tree) + puts(work_tree); + continue; + } if (!strcmp(arg, "--show-prefix")) { if (prefix) puts(prefix); diff --git a/builtin-revert.c b/builtin-revert.c index 151aa6a981..eff52687a8 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -38,6 +38,8 @@ static const char * const cherry_pick_usage[] = { static int edit, no_replay, no_commit, mainline, signoff; static enum { REVERT, CHERRY_PICK } action; static struct commit *commit; +static const char *commit_name; +static int allow_rerere_auto; static const char *me; @@ -48,7 +50,6 @@ static void parse_args(int argc, const char **argv) const char * const * usage_str = action == REVERT ? revert_usage : cherry_pick_usage; unsigned char sha1[20]; - const char *arg; int noop; struct option options[] = { OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"), @@ -57,24 +58,19 @@ static void parse_args(int argc, const char **argv) OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"), OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_INTEGER('m', "mainline", &mainline, "parent number"), + OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_END(), }; if (parse_options(argc, argv, NULL, options, usage_str, 0) != 1) usage_with_options(usage_str, options); - arg = argv[0]; - if (get_sha1(arg, sha1)) - die ("Cannot find '%s'", arg); - commit = (struct commit *)parse_object(sha1); + commit_name = argv[0]; + if (get_sha1(commit_name, sha1)) + die ("Cannot find '%s'", commit_name); + commit = lookup_commit_reference(sha1); if (!commit) - die ("Could not find %s", sha1_to_hex(sha1)); - if (commit->object.type == OBJ_TAG) { - commit = (struct commit *) - deref_tag((struct object *)commit, arg, strlen(arg)); - } - if (commit->object.type != OBJ_COMMIT) - die ("'%s' does not point to a commit", arg); + exit(1); } static char *get_oneline(const char *message) @@ -202,25 +198,27 @@ static void set_author_ident_env(const char *message) sha1_to_hex(commit->object.sha1)); } -static char *help_msg(const unsigned char *sha1) +static char *help_msg(const char *name) { - static char helpbuf[1024]; + struct strbuf helpbuf = STRBUF_INIT; char *msg = getenv("GIT_CHERRY_PICK_HELP"); if (msg) return msg; - strcpy(helpbuf, " After resolving the conflicts,\n" - "mark the corrected paths with 'git add <paths>' " - "or 'git rm <paths>' and commit the result."); + strbuf_addstr(&helpbuf, " After resolving the conflicts,\n" + "mark the corrected paths with 'git add <paths>' or 'git rm <paths>'\n" + "and commit the result"); if (action == CHERRY_PICK) { - sprintf(helpbuf + strlen(helpbuf), - "\nWhen commiting, use the option " - "'-c %s' to retain authorship and message.", - find_unique_abbrev(sha1, DEFAULT_ABBREV)); + strbuf_addf(&helpbuf, " with: \n" + "\n" + " git commit -c %s\n", + name); } - return helpbuf; + else + strbuf_addch(&helpbuf, '.'); + return strbuf_detach(&helpbuf, NULL); } static struct tree *empty_tree(void) @@ -233,6 +231,19 @@ static struct tree *empty_tree(void) return tree; } +static NORETURN void die_dirty_index(const char *me) +{ + if (read_cache_unmerged()) { + die_resolve_conflict(me); + } else { + if (advice_commit_before_merge) + die("Your local changes would be overwritten by %s.\n" + "Please, commit your changes or stash them to proceed.", me); + else + die("Your local changes would be overwritten by %s.\n", me); + } +} + static int revert_or_cherry_pick(int argc, const char **argv) { unsigned char head[20]; @@ -269,7 +280,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) if (get_sha1("HEAD", head)) die ("You do not have a valid HEAD"); if (index_differs_from("HEAD", 0)) - die ("Dirty index: cannot %s", me); + die_dirty_index(me); } discard_cache(); @@ -394,8 +405,8 @@ static int revert_or_cherry_pick(int argc, const char **argv) if (commit_lock_file(&msg_file) < 0) die ("Error wrapping up %s", defmsg); fprintf(stderr, "Automatic %s failed.%s\n", - me, help_msg(commit->object.sha1)); - rerere(); + me, help_msg(commit_name)); + rerere(allow_rerere_auto); exit(1); } if (commit_lock_file(&msg_file) < 0) diff --git a/builtin-rm.c b/builtin-rm.c index 57975dbcfd..f3772c84de 100644 --- a/builtin-rm.c +++ b/builtin-rm.c @@ -169,9 +169,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die("index file corrupt"); - refresh_cache(REFRESH_QUIET); pathspec = get_pathspec(prefix, argv); + refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL); + seen = NULL; for (i = 0; pathspec[i] ; i++) /* nothing */; diff --git a/builtin-send-pack.c b/builtin-send-pack.c index 2c4eaae684..2183a47052 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,67 @@ 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); +} + +static int sideband_demux(int in, int out, void *data) +{ + int *fd = data; + int ret = recv_sideband("send-pack", fd[0], out); + close(out); + return ret; +} + int send_pack(struct send_pack_args *args, int fd[], struct child_process *conn, struct ref *remote_refs, @@ -310,20 +387,25 @@ 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; int allow_deleting_refs = 0; - int expect_status_report = 0; + int status_report = 0; + int use_sideband = 0; + unsigned cmds_sent = 0; int ret; + struct async demux; /* Does the other end support the reporting? */ if (server_supports("report-status")) - ask_for_status_report = 1; + status_report = 1; if (server_supports("delete-refs")) allow_deleting_refs = 1; if (server_supports("ofs-delta")) args->use_ofs_delta = 1; + if (server_supports("side-band-64k")) + use_sideband = 1; if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n" @@ -336,89 +418,95 @@ int send_pack(struct send_pack_args *args, */ new_refs = 0; for (ref = remote_refs; ref; ref = ref->next) { - - if (ref->peer_ref) - hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); - else if (!args->send_mirror) + if (!ref->peer_ref && !args->send_mirror) continue; - ref->deletion = is_null_sha1(ref->new_sha1); - if (ref->deletion && !allow_deleting_refs) { - ref->status = REF_STATUS_REJECT_NODELETE; - continue; - } - if (!ref->deletion && - !hashcmp(ref->old_sha1, ref->new_sha1)) { - ref->status = REF_STATUS_UPTODATE; + /* Check for statuses set by set_ref_status_for_push() */ + switch (ref->status) { + case REF_STATUS_REJECT_NONFASTFORWARD: + case REF_STATUS_UPTODATE: continue; + default: + ; /* do nothing */ } - /* This part determines what can overwrite what. - * The rules are: - * - * (0) you can always use --force or +A:B notation to - * selectively force individual ref pairs. - * - * (1) if the old thing does not exist, it is OK. - * - * (2) if you do not have the old thing, you are not allowed - * to overwrite it; you would not know what you are losing - * otherwise. - * - * (3) if both new and old are commit-ish, and new is a - * descendant of old, it is OK. - * - * (4) regardless of all of the above, removing :B is - * always allowed. - */ - - ref->nonfastforward = - !ref->deletion && - !is_null_sha1(ref->old_sha1) && - (!has_sha1_file(ref->old_sha1) - || !ref_newer(ref->new_sha1, ref->old_sha1)); - - if (ref->nonfastforward && !ref->force && !args->force_update) { - ref->status = REF_STATUS_REJECT_NONFASTFORWARD; + if (ref->deletion && !allow_deleting_refs) { + ref->status = REF_STATUS_REJECT_NODELETE; continue; } if (!ref->deletion) new_refs++; - if (!args->dry_run) { + if (args->dry_run) { + ref->status = REF_STATUS_OK; + } else { char *old_hex = sha1_to_hex(ref->old_sha1); char *new_hex = sha1_to_hex(ref->new_sha1); - if (ask_for_status_report) { - packet_write(out, "%s %s %s%c%s", + if (!cmds_sent && (status_report || use_sideband)) { + packet_buf_write(&req_buf, "%s %s %s%c%s%s", old_hex, new_hex, ref->name, 0, - "report-status"); - ask_for_status_report = 0; - expect_status_report = 1; + status_report ? " report-status" : "", + use_sideband ? " side-band-64k" : ""); } else - packet_write(out, "%s %s %s", + packet_buf_write(&req_buf, "%s %s %s", old_hex, new_hex, ref->name); + ref->status = status_report ? + REF_STATUS_EXPECTING_REPORT : + REF_STATUS_OK; + cmds_sent++; } - ref->status = expect_status_report ? - REF_STATUS_EXPECTING_REPORT : - REF_STATUS_OK; } - packet_flush(out); - if (new_refs && !args->dry_run) { + if (args->stateless_rpc) { + if (!args->dry_run && cmds_sent) { + 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 (use_sideband && cmds_sent) { + memset(&demux, 0, sizeof(demux)); + demux.proc = sideband_demux; + demux.data = fd; + demux.out = -1; + if (start_async(&demux)) + die("receive-pack: unable to fork off sideband demultiplexer"); + in = demux.out; + } + + if (new_refs && cmds_sent) { if (pack_objects(out, remote_refs, extra_have, args) < 0) { for (ref = remote_refs; ref; ref = ref->next) ref->status = REF_STATUS_NONE; + if (use_sideband) + finish_async(&demux); return -1; } } + if (args->stateless_rpc && cmds_sent) + packet_flush(out); - if (expect_status_report) + if (status_report && cmds_sent) ret = receive_status(in, remote_refs); else ret = 0; + if (args->stateless_rpc) + packet_flush(out); + + if (use_sideband && cmds_sent) { + if (finish_async(&demux)) { + error("error in sideband demultiplexer"); + ret = -1; + } + close(demux.out); + } if (ret < 0) return ret; @@ -478,6 +566,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 +612,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 +648,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)); @@ -573,14 +677,21 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags)) return -1; + set_ref_status_for_push(remote_refs, args.send_mirror, + args.force_update); + 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..35a709e630 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] ")) @@ -567,7 +567,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb) return config_error_nonbool(var); /* * default_arg is now passed to parse_options(), so we need to - * mimick the real argv a bit better. + * mimic the real argv a bit better. */ if (!default_num) { default_alloc = 20; 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-tag.c b/builtin-tag.c index c4790185eb..4ef1c4f508 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -140,7 +140,7 @@ static int delete_tag(const char *name, const char *ref, { if (delete_ref(ref, sha1, 0)) return 1; - printf("Deleted tag '%s'\n", name); + printf("Deleted tag '%s' (was %s)\n", name, find_unique_abbrev(sha1, DEFAULT_ABBREV)); return 0; } @@ -479,6 +479,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) die("%s: cannot lock the ref", ref); if (write_ref_sha1(lock, object, NULL) < 0) die("%s: cannot update the ref", ref); + if (force && hashcmp(prev, object)) + printf("Updated tag '%s' (was %s)\n", tag, find_unique_abbrev(prev, DEFAULT_ABBREV)); strbuf_release(&buf); return 0; 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/unpack-file.c b/builtin-unpack-file.c index ac9cbf7cd8..608590ada8 100644 --- a/unpack-file.c +++ b/builtin-unpack-file.c @@ -22,18 +22,15 @@ static char *create_temp_file(unsigned char *sha1) return path; } -int main(int argc, char **argv) +int cmd_unpack_file(int argc, const char **argv, const char *prefix) { unsigned char sha1[20]; - 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]); - setup_git_directory(); git_config(git_default_config, NULL); puts(create_temp_file(sha1)); diff --git a/builtin-update-index.c b/builtin-update-index.c index 92beaaf4b3..3ab214d24e 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -9,6 +9,7 @@ #include "tree-walk.h" #include "builtin.h" #include "refs.h" +#include "resolve-undo.h" /* * Default to not allowing changes to the list of files. The @@ -24,9 +25,11 @@ static int info_only; static int force_remove; static int verbose; static int mark_valid_only; -#define MARK_VALID 1 -#define UNMARK_VALID 2 +static int mark_skip_worktree_only; +#define MARK_FLAG 1 +#define UNMARK_FLAG 2 +__attribute__((format (printf, 1, 2))) static void report(const char *fmt, ...) { va_list vp; @@ -40,19 +43,15 @@ static void report(const char *fmt, ...) va_end(vp); } -static int mark_valid(const char *path) +static int mark_ce_flags(const char *path, int flag, int mark) { int namelen = strlen(path); int pos = cache_name_pos(path, namelen); if (0 <= pos) { - switch (mark_valid_only) { - case MARK_VALID: - active_cache[pos]->ce_flags |= CE_VALID; - break; - case UNMARK_VALID: - active_cache[pos]->ce_flags &= ~CE_VALID; - break; - } + if (mark) + active_cache[pos]->ce_flags |= flag; + else + active_cache[pos]->ce_flags &= ~flag; cache_tree_invalidate_path(active_cache_tree, path); active_cache_changed = 1; return 0; @@ -175,29 +174,29 @@ static int process_directory(const char *path, int len, struct stat *st) return error("%s: is a directory - add files inside instead", path); } -/* - * Process a regular file - */ -static int process_file(const char *path, int len, struct stat *st) -{ - int pos = cache_name_pos(path, len); - struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos]; - - if (ce && S_ISGITLINK(ce->ce_mode)) - return error("%s is already a gitlink, not replacing", path); - - return add_one_path(ce, path, len, st); -} - static int process_path(const char *path) { - int len; + int pos, len; struct stat st; + struct cache_entry *ce; len = strlen(path); if (has_symlink_leading_path(path, len)) return error("'%s' is beyond a symbolic link", path); + pos = cache_name_pos(path, len); + ce = pos < 0 ? NULL : active_cache[pos]; + if (ce && ce_skip_worktree(ce)) { + /* + * working directory version is assumed "good" + * so updating it does not make sense. + * On the other hand, removing it from index should work + */ + if (allow_remove && remove_file_from_cache(path)) + return error("%s: cannot remove from the index", path); + return 0; + } + /* * First things first: get the stat information, to decide * what to do about the pathname! @@ -208,7 +207,13 @@ static int process_path(const char *path) if (S_ISDIR(st.st_mode)) return process_directory(path, len, &st); - return process_file(path, len, &st); + /* + * Process a regular file + */ + if (ce && S_ISGITLINK(ce->ce_mode)) + return error("%s is already a gitlink, not replacing", path); + + return add_one_path(ce, path, len, &st); } static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, @@ -276,7 +281,12 @@ static void update_one(const char *path, const char *prefix, int prefix_length) goto free_return; } if (mark_valid_only) { - if (mark_valid(p)) + if (mark_ce_flags(p, CE_VALID, mark_valid_only == MARK_FLAG)) + die("Unable to mark file %s", path); + goto free_return; + } + if (mark_skip_worktree_only) { + if (mark_ce_flags(p, CE_SKIP_WORKTREE, mark_skip_worktree_only == MARK_FLAG)) die("Unable to mark file %s", path); goto free_return; } @@ -388,7 +398,7 @@ static void read_index_info(int line_termination) } static const char update_index_usage[] = -"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>..."; +"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>..."; static unsigned char head_sha1[20]; static unsigned char merge_head_sha1[20]; @@ -431,7 +441,18 @@ static int unresolve_one(const char *path) /* See if there is such entry in the index. */ pos = cache_name_pos(path, namelen); - if (pos < 0) { + if (0 <= pos) { + /* already merged */ + pos = unmerge_cache_entry_at(pos); + if (pos < active_nr) { + struct cache_entry *ce = active_cache[pos]; + if (ce_stage(ce) && + ce_namelen(ce) == namelen && + !memcmp(ce->name, path, namelen)) + return 0; + } + /* no resolve-undo information; fall back */ + } else { /* If there isn't, either it is unmerged, or * resolved as "removed" by mistake. We do not * want to do anything in the former case. @@ -647,11 +668,19 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(path, "--assume-unchanged")) { - mark_valid_only = MARK_VALID; + mark_valid_only = MARK_FLAG; continue; } if (!strcmp(path, "--no-assume-unchanged")) { - mark_valid_only = UNMARK_VALID; + mark_valid_only = UNMARK_FLAG; + continue; + } + if (!strcmp(path, "--no-skip-worktree")) { + mark_skip_worktree_only = UNMARK_FLAG; + continue; + } + if (!strcmp(path, "--skip-worktree")) { + mark_skip_worktree_only = MARK_FLAG; continue; } if (!strcmp(path, "--info-only")) { @@ -702,6 +731,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) verbose = 1; continue; } + if (!strcmp(path, "--clear-resolve-undo")) { + resolve_undo_clear(); + continue; + } if (!strcmp(path, "-h") || !strcmp(path, "--help")) usage(update_index_usage); die("unknown option %s", path); 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]; @@ -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) @@ -48,7 +72,7 @@ static int show_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } -int main(int argc, char **argv) +int cmd_var(int argc, const char **argv, const char *prefix) { const char *val; int nongit; @@ -56,8 +80,6 @@ int main(int argc, char **argv) usage(var_usage); } - git_extract_argv0_path(argv[0]); - setup_git_directory_gently(&nongit); val = NULL; @@ -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); @@ -56,8 +55,10 @@ extern int cmd_fsck(int argc, const char **argv, const char *prefix); extern int cmd_gc(int argc, const char **argv, const char *prefix); extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix); extern int cmd_grep(int argc, const char **argv, const char *prefix); +extern int cmd_hash_object(int argc, const char **argv, const char *prefix); extern int cmd_help(int argc, const char **argv, const char *prefix); extern int cmd_http_fetch(int argc, const char **argv, const char *prefix); +extern int cmd_index_pack(int argc, const char **argv, const char *prefix); extern int cmd_init_db(int argc, const char **argv, const char *prefix); extern int cmd_log(int argc, const char **argv, const char *prefix); extern int cmd_log_reflog(int argc, const char **argv, const char *prefix); @@ -68,13 +69,18 @@ extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); extern int cmd_merge(int argc, const char **argv, const char *prefix); extern int cmd_merge_base(int argc, const char **argv, const char *prefix); +extern int cmd_merge_index(int argc, const char **argv, const char *prefix); extern int cmd_merge_ours(int argc, const char **argv, const char *prefix); extern int cmd_merge_file(int argc, const char **argv, const char *prefix); extern int cmd_merge_recursive(int argc, const char **argv, const char *prefix); +extern int cmd_merge_tree(int argc, const char **argv, const char *prefix); +extern int cmd_mktag(int argc, const char **argv, const char *prefix); extern int cmd_mktree(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); +extern int cmd_pack_redundant(int argc, const char **argv, const char *prefix); +extern int cmd_patch_id(int argc, const char **argv, const char *prefix); extern int cmd_pickaxe(int argc, const char **argv, const char *prefix); extern int cmd_prune(int argc, const char **argv, const char *prefix); extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); @@ -99,12 +105,14 @@ extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); extern int cmd_tag(int argc, const char **argv, const char *prefix); extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); +extern int cmd_unpack_file(int argc, const char **argv, const char *prefix); extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix); extern int cmd_update_index(int argc, const char **argv, const char *prefix); extern int cmd_update_ref(int argc, const char **argv, const char *prefix); extern int cmd_update_server_info(int argc, const char **argv, const char *prefix); extern int cmd_upload_archive(int argc, const char **argv, const char *prefix); extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); +extern int cmd_var(int argc, const char **argv, const char *prefix); extern int cmd_verify_tag(int argc, const char **argv, const char *prefix); extern int cmd_version(int argc, const char **argv, const char *prefix); extern int cmd_whatchanged(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); @@ -177,15 +177,22 @@ struct cache_entry { #define CE_HASHED (0x100000) #define CE_UNHASHED (0x200000) +#define CE_CONFLICTED (0x800000) + +/* Only remove in work directory, not index */ +#define CE_WT_REMOVE (0x400000) + +#define CE_UNPACKED (0x1000000) /* * Extended on-disk flags */ #define CE_INTENT_TO_ADD 0x20000000 +#define CE_SKIP_WORKTREE 0x40000000 /* CE_EXTENDED2 is for future extension */ #define CE_EXTENDED2 0x80000000 -#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD) +#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD | CE_SKIP_WORKTREE) /* * Safeguard to avoid saving wrong flags: @@ -234,6 +241,7 @@ static inline size_t ce_namelen(const struct cache_entry *ce) ondisk_cache_entry_size(ce_namelen(ce))) #define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT) #define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE) +#define ce_skip_worktree(ce) ((ce)->ce_flags & CE_SKIP_WORKTREE) #define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE) #define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644) @@ -282,6 +290,7 @@ static inline int ce_to_dtype(const struct cache_entry *ce) struct index_state { struct cache_entry **cache; unsigned int cache_nr, cache_alloc, cache_changed; + struct string_list *resolve_undo; struct cache_tree *cache_tree; struct cache_time timestamp; void *alloc; @@ -336,6 +345,9 @@ static inline void remove_name_hash(struct cache_entry *ce) #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options)) #define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase)) #define cache_name_is_other(name, namelen) index_name_is_other(&the_index, (name), (namelen)) +#define resolve_undo_clear() resolve_undo_clear_index(&the_index) +#define unmerge_cache_entry_at(at) unmerge_index_entry_at(&the_index, at) +#define unmerge_cache(pathspec) unmerge_index(&the_index, pathspec) #endif enum object_type { @@ -369,9 +381,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 +411,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); @@ -441,7 +457,6 @@ extern int index_name_pos(const struct index_state *, const char *name, int name #define ADD_CACHE_JUST_APPEND 8 /* Append only; tree.c::read_tree() */ #define ADD_CACHE_NEW_ONLY 16 /* Do not replace existing ones */ extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option); -extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really); extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name); extern int remove_index_entry_at(struct index_state *, int pos); extern void remove_marked_cache_entries(struct index_state *istate); @@ -460,7 +475,9 @@ extern int index_name_is_other(const struct index_state *, const char *, int); /* do stat comparison even if CE_VALID is true */ #define CE_MATCH_IGNORE_VALID 01 /* do not check the contents but report dirty on racily-clean entries */ -#define CE_MATCH_RACY_IS_DIRTY 02 +#define CE_MATCH_RACY_IS_DIRTY 02 +/* do stat comparison even if CE_SKIP_WORKTREE is true */ +#define CE_MATCH_IGNORE_SKIP_WORKTREE 04 extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); @@ -469,9 +486,6 @@ extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_obje extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object); extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); -/* "careful lstat()" */ -extern int check_path(const char *path, int len, struct stat *st, int skiplen); - #define REFRESH_REALLY 0x0001 /* ignore_valid */ #define REFRESH_UNMERGED 0x0002 /* allow unmerged */ #define REFRESH_QUIET 0x0004 /* be quiet about it */ @@ -525,6 +539,7 @@ extern int auto_crlf; extern int read_replace_refs; extern int fsync_object_files; extern int core_preload_index; +extern int core_apply_sparse_checkout; enum safe_crlf { SAFE_CRLF_FALSE = 0, @@ -540,6 +555,7 @@ enum branch_track { BRANCH_TRACK_REMOTE, BRANCH_TRACK_ALWAYS, BRANCH_TRACK_EXPLICIT, + BRANCH_TRACK_OVERRIDE, }; enum rebase_setup_type { @@ -567,6 +583,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 @@ -612,7 +630,6 @@ static inline void hashclr(unsigned char *hash) { memset(hash, 0, 20); } -extern int is_empty_blob_sha1(const unsigned char *sha1); #define EMPTY_TREE_SHA1_HEX \ "4b825dc642cb6eb9a060e54bf8d69288fbee4904" @@ -657,6 +674,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 *); @@ -681,7 +699,6 @@ extern int has_sha1_pack(const unsigned char *sha1); extern int has_sha1_file(const unsigned char *sha1); extern int has_loose_object_nonlocal(const unsigned char *sha1); -extern int has_pack_file(const unsigned char *sha1); extern int has_pack_index(const unsigned char *sha1); extern const signed char hexval_table[256]; @@ -695,7 +712,11 @@ static inline unsigned int hexval(unsigned char c) #define DEFAULT_ABBREV 7 extern int get_sha1(const char *str, unsigned char *sha1); -extern int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode); +extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int gently, const char *prefix); +static inline int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode) +{ + return get_sha1_with_mode_1(str, sha1, mode, 1, NULL); +} extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ extern int read_ref(const char *filename, unsigned char *sha1); @@ -703,6 +724,7 @@ extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int * extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); extern int interpret_branch_name(const char *str, struct strbuf *); +extern int get_sha1_mb(const char *str, unsigned char *sha1); extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules); extern const char *ref_rev_parse_rules[]; @@ -740,7 +762,8 @@ const char *show_date_relative(unsigned long time, int tz, size_t timebuf_size); int parse_date(const char *date, char *buf, int bufsize); void datestamp(char *buf, int bufsize); -unsigned long approxidate(const char *); +#define approxidate(s) approxidate_careful((s), NULL) +unsigned long approxidate_careful(const char *, int *); unsigned long approxidate_relative(const char *date, const struct timeval *now); enum date_mode parse_date_format(const char *format); @@ -751,6 +774,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; @@ -775,8 +800,6 @@ extern int has_symlink_leading_path(const char *name, int len); extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int); extern int has_symlink_or_noent_leading_path(const char *name, int len); extern int has_dirs_only_path(const char *name, int len, int prefix_len); -extern void invalidate_lstat_cache(const char *name, int len); -extern void clear_lstat_cache(void); extern void schedule_dir_for_removal(const char *name, int len); extern void remove_scheduled_dirs(void); @@ -857,7 +880,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]; @@ -917,7 +939,11 @@ extern const char *config_exclusive_filename; #define MAX_GITNAME (1000) extern char git_default_email[MAX_GITNAME]; extern char git_default_name[MAX_GITNAME]; +#define IDENT_NAME_GIVEN 01 +#define IDENT_MAIL_GIVEN 02 +#define IDENT_ALL_GIVEN (IDENT_NAME_GIVEN|IDENT_MAIL_GIVEN) extern int user_ident_explicitly_given; +extern int user_ident_sufficiently_given(void); extern const char *git_commit_encoding; extern const char *git_log_output_encoding; @@ -962,7 +988,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 */ @@ -983,6 +1011,7 @@ extern int diff_auto_refresh_index; /* match-trees.c */ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int); +void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char *, const char *); /* * whitespace rules. @@ -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,20 +63,28 @@ enum cmit_fmt { CMIT_FMT_UNSPECIFIED, }; -extern int non_ascii(int); +struct pretty_print_context +{ + int abbrev; + const char *subject; + const char *after_subject; + enum date_mode date_mode; + int need_8bit_cte; + int show_notes; + struct reflog_walk_info *reflog_info; +}; + extern int has_non_ascii(const char *text); struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ 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..c5bfb39b39 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -3,9 +3,7 @@ #include <conio.h> #include "../strbuf.h" -#include <shellapi.h> - -static int err_win_to_posix(DWORD winerr) +int err_win_to_posix(DWORD winerr) { int error = ENOSYS; switch(winerr) { @@ -142,12 +140,36 @@ int mingw_open (const char *filename, int oflags, ...) return fd; } -static inline time_t filetime_to_time_t(const FILETIME *ft) +#undef fopen +FILE *mingw_fopen (const char *filename, const char *otype) +{ + if (!strcmp(filename, "/dev/null")) + filename = "nul"; + return fopen(filename, otype); +} + +#undef freopen +FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream) +{ + if (filename && !strcmp(filename, "/dev/null")) + filename = "nul"; + return freopen(filename, otype, stream); +} + +/* + * The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC. + * Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch. + */ +static inline long long filetime_to_hnsec(const FILETIME *ft) { long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime; - winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */ - winTime /= 10000000; /* Nano to seconds resolution */ - return (time_t)winTime; + /* Windows to Unix Epoch conversion */ + return winTime - 116444736000000000LL; +} + +static inline time_t filetime_to_time_t(const FILETIME *ft) +{ + return (time_t)(filetime_to_hnsec(ft) / 10000000); } /* We keep the do_lstat code in a separate function to avoid recursion. @@ -283,64 +305,37 @@ int mkstemp(char *template) int gettimeofday(struct timeval *tv, void *tz) { - SYSTEMTIME st; - struct tm tm; - GetSystemTime(&st); - tm.tm_year = st.wYear-1900; - tm.tm_mon = st.wMonth-1; - tm.tm_mday = st.wDay; - tm.tm_hour = st.wHour; - tm.tm_min = st.wMinute; - tm.tm_sec = st.wSecond; - tv->tv_sec = tm_to_time_t(&tm); - if (tv->tv_sec < 0) - return -1; - tv->tv_usec = st.wMilliseconds*1000; + FILETIME ft; + long long hnsec; + + GetSystemTimeAsFileTime(&ft); + hnsec = filetime_to_hnsec(&ft); + tv->tv_sec = hnsec / 10000000; + tv->tv_usec = (hnsec % 10000000) / 10; return 0; } int pipe(int filedes[2]) { - int fd; - HANDLE h[2], parent; - - if (_pipe(filedes, 8192, 0) < 0) - return -1; + HANDLE h[2]; - parent = GetCurrentProcess(); - - if (!DuplicateHandle (parent, (HANDLE)_get_osfhandle(filedes[0]), - parent, &h[0], 0, FALSE, DUPLICATE_SAME_ACCESS)) { - close(filedes[0]); - close(filedes[1]); - return -1; - } - if (!DuplicateHandle (parent, (HANDLE)_get_osfhandle(filedes[1]), - parent, &h[1], 0, FALSE, DUPLICATE_SAME_ACCESS)) { - close(filedes[0]); - close(filedes[1]); - CloseHandle(h[0]); + /* this creates non-inheritable handles */ + if (!CreatePipe(&h[0], &h[1], NULL, 8192)) { + errno = err_win_to_posix(GetLastError()); return -1; } - fd = _open_osfhandle((int)h[0], O_NOINHERIT); - if (fd < 0) { - close(filedes[0]); - close(filedes[1]); + filedes[0] = _open_osfhandle((int)h[0], O_NOINHERIT); + if (filedes[0] < 0) { CloseHandle(h[0]); CloseHandle(h[1]); return -1; } - close(filedes[0]); - filedes[0] = fd; - fd = _open_osfhandle((int)h[1], O_NOINHERIT); - if (fd < 0) { + filedes[1] = _open_osfhandle((int)h[1], O_NOINHERIT); + if (filedes[0] < 0) { close(filedes[0]); - close(filedes[1]); CloseHandle(h[1]); return -1; } - close(filedes[1]); - filedes[1] = fd; return 0; } @@ -638,8 +633,8 @@ static int env_compare(const void *a, const void *b) return strcasecmp(*ea, *eb); } -static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, - int prepend_cmd) +static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, + int prepend_cmd, int fhin, int fhout, int fherr) { STARTUPINFO si; PROCESS_INFORMATION pi; @@ -675,9 +670,9 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, memset(&si, 0, sizeof(si)); si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES; - si.hStdInput = (HANDLE) _get_osfhandle(0); - si.hStdOutput = (HANDLE) _get_osfhandle(1); - si.hStdError = (HANDLE) _get_osfhandle(2); + si.hStdInput = (HANDLE) _get_osfhandle(fhin); + si.hStdOutput = (HANDLE) _get_osfhandle(fhout); + si.hStdError = (HANDLE) _get_osfhandle(fherr); /* concatenate argv, quoting args as we go */ strbuf_init(&args, 0); @@ -732,7 +727,14 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, return (pid_t)pi.hProcess; } -pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env) +static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, + int prepend_cmd) +{ + return mingw_spawnve_fd(cmd, argv, env, prepend_cmd, 0, 1, 2); +} + +pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, + int fhin, int fhout, int fherr) { pid_t pid; char **path = get_path_split(); @@ -754,13 +756,15 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env) pid = -1; } else { - pid = mingw_spawnve(iprog, argv, env, 1); + pid = mingw_spawnve_fd(iprog, argv, env, 1, + fhin, fhout, fherr); free(iprog); } argv[0] = argv0; } else - pid = mingw_spawnve(prog, argv, env, 0); + pid = mingw_spawnve_fd(prog, argv, env, 0, + fhin, fhout, fherr); free(prog); } free_path_split(path); @@ -903,19 +907,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 +1180,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]; @@ -1150,8 +1342,22 @@ static const char *make_backslash_path(const char *path) void mingw_open_html(const char *unixpath) { const char *htmlpath = make_backslash_path(unixpath); + typedef HINSTANCE (WINAPI *T)(HWND, const char *, + const char *, const char *, const char *, INT); + T ShellExecute; + HMODULE shell32; + + shell32 = LoadLibrary("shell32.dll"); + if (!shell32) + die("cannot load shell32.dll"); + ShellExecute = (T)GetProcAddress(shell32, "ShellExecuteA"); + if (!ShellExecute) + die("cannot run browser"); + printf("Launching default browser to display HTML ...\n"); ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0); + + FreeLibrary(shell32); } int link(const char *oldpath, const char *newpath) diff --git a/compat/mingw.h b/compat/mingw.h index 5b5258bceb..e81e752ed2 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 */ @@ -148,6 +170,12 @@ int link(const char *oldpath, const char *newpath); int mingw_open (const char *filename, int oflags, ...); #define open mingw_open +FILE *mingw_fopen (const char *filename, const char *otype); +#define fopen mingw_fopen + +FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream); +#define freopen mingw_freopen + char *mingw_getcwd(char *pointer, int len); #define getcwd mingw_getcwd @@ -157,6 +185,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 +206,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 @@ -175,18 +215,21 @@ int mingw_getpagesize(void); * mingw_fstat() instead of fstat() on Windows. */ #define off_t off64_t -#define stat _stati64 #define lseek _lseeki64 +#ifndef ALREADY_DECLARED_STAT_FUNCS +#define stat _stati64 int mingw_lstat(const char *file_name, struct stat *buf); int mingw_fstat(int fd, struct stat *buf); #define fstat mingw_fstat #define lstat mingw_lstat #define _stati64(x,y) mingw_lstat(x,y) +#endif int mingw_utime(const char *file_name, const struct utimbuf *times); #define utime mingw_utime -pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env); +pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, + int fhin, int fhout, int fherr); void mingw_execvp(const char *cmd, char *const *argv); #define execvp mingw_execvp @@ -273,3 +316,8 @@ struct mingw_dirent #define readdir(x) mingw_readdir(x) struct dirent *mingw_readdir(DIR *dir); #endif // !NO_MINGW_REPLACE_READDIR + +/* + * Used by Pthread API implementation for Windows + */ +extern int err_win_to_posix(DWORD winerr); diff --git a/compat/msvc.h b/compat/msvc.h index 9c753a560f..023aba0238 100644 --- a/compat/msvc.h +++ b/compat/msvc.h @@ -21,30 +21,22 @@ static __inline int strcasecmp (const char *s1, const char *s2) } #undef ERROR -#undef stat -#undef _stati64 -#include "compat/mingw.h" -#undef stat -#define stat _stati64 + +/* Use mingw_lstat() instead of lstat()/stat() and mingw_fstat() instead + * of fstat(). We add the declaration of these functions here, suppressing + * the corresponding declarations in mingw.h, so that we can use the + * appropriate structure type (and function) names from the msvc headers. + */ +#define stat _stat64 +int mingw_lstat(const char *file_name, struct stat *buf); +int mingw_fstat(int fd, struct stat *buf); +#define fstat mingw_fstat +#define lstat mingw_lstat #define _stat64(x,y) mingw_lstat(x,y) +#define ALREADY_DECLARED_STAT_FUNCS + +#include "compat/mingw.h" + +#undef ALREADY_DECLARED_STAT_FUNCS -/* - Even though _stati64 is normally just defined at _stat64 - on Windows, we specify it here as a proper struct to avoid - compiler warnings about macro redefinition due to magic in - mingw.h. Struct taken from ReactOS (GNU GPL license). -*/ -struct _stati64 { - _dev_t st_dev; - _ino_t st_ino; - unsigned short st_mode; - short st_nlink; - short st_uid; - short st_gid; - _dev_t st_rdev; - __int64 st_size; - time_t st_atime; - time_t st_mtime; - time_t st_ctime; -}; #endif diff --git a/compat/regex/regex.c b/compat/regex/regex.c index 67d5c370a0..556d8ab11f 100644 --- a/compat/regex/regex.c +++ b/compat/regex/regex.c @@ -2808,7 +2808,7 @@ re_set_registers (bufp, regs, num_regs, starts, ends) { bufp->regs_allocated = REGS_UNALLOCATED; regs->num_regs = 0; - regs->start = regs->end = (regoff_t) 0; + regs->start = regs->end = (regoff_t *) 0; } } diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl index f9528c0ea1..4374771df2 100644 --- a/compat/vcbuild/scripts/clink.pl +++ b/compat/vcbuild/scripts/clink.pl @@ -29,6 +29,10 @@ while (@ARGV) { push(@args, "zlib.lib"); } elsif ("$arg" eq "-liconv") { push(@args, "iconv.lib"); + } elsif ("$arg" eq "-lcrypto") { + push(@args, "libeay32.lib"); + } elsif ("$arg" eq "-lssl") { + push(@args, "ssleay32.lib"); } elsif ("$arg" =~ /^-L/ && "$arg" ne "-LTCG") { $arg =~ s/^-L/-LIBPATH:/; push(@args, $arg); diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c new file mode 100644 index 0000000000..0f949fc425 --- /dev/null +++ b/compat/win32/pthread.c @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2009 Andrzej K. Haczewski <ahaczewski@gmail.com> + * + * DISCLAIMER: The implementation is Git-specific, it is subset of original + * Pthreads API, without lots of other features that Git doesn't use. + * Git also makes sure that the passed arguments are valid, so there's + * no need for double-checking. + */ + +#include "../../git-compat-util.h" +#include "pthread.h" + +#include <errno.h> +#include <limits.h> + +static unsigned __stdcall win32_start_routine(void *arg) +{ + pthread_t *thread = arg; + thread->arg = thread->start_routine(thread->arg); + return 0; +} + +int pthread_create(pthread_t *thread, const void *unused, + void *(*start_routine)(void*), void *arg) +{ + thread->arg = arg; + thread->start_routine = start_routine; + thread->handle = (HANDLE) + _beginthreadex(NULL, 0, win32_start_routine, thread, 0, NULL); + + if (!thread->handle) + return errno; + else + return 0; +} + +int win32_pthread_join(pthread_t *thread, void **value_ptr) +{ + DWORD result = WaitForSingleObject(thread->handle, INFINITE); + switch (result) { + case WAIT_OBJECT_0: + if (value_ptr) + *value_ptr = thread->arg; + return 0; + case WAIT_ABANDONED: + return EINVAL; + default: + return err_win_to_posix(GetLastError()); + } +} + +int pthread_cond_init(pthread_cond_t *cond, const void *unused) +{ + cond->waiters = 0; + cond->was_broadcast = 0; + InitializeCriticalSection(&cond->waiters_lock); + + cond->sema = CreateSemaphore(NULL, 0, LONG_MAX, NULL); + if (!cond->sema) + die("CreateSemaphore() failed"); + + cond->continue_broadcast = CreateEvent(NULL, /* security */ + FALSE, /* auto-reset */ + FALSE, /* not signaled */ + NULL); /* name */ + if (!cond->continue_broadcast) + die("CreateEvent() failed"); + + return 0; +} + +int pthread_cond_destroy(pthread_cond_t *cond) +{ + CloseHandle(cond->sema); + CloseHandle(cond->continue_broadcast); + DeleteCriticalSection(&cond->waiters_lock); + return 0; +} + +int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex) +{ + int last_waiter; + + EnterCriticalSection(&cond->waiters_lock); + cond->waiters++; + LeaveCriticalSection(&cond->waiters_lock); + + /* + * Unlock external mutex and wait for signal. + * NOTE: we've held mutex locked long enough to increment + * waiters count above, so there's no problem with + * leaving mutex unlocked before we wait on semaphore. + */ + LeaveCriticalSection(mutex); + + /* let's wait - ignore return value */ + WaitForSingleObject(cond->sema, INFINITE); + + /* + * Decrease waiters count. If we are the last waiter, then we must + * notify the broadcasting thread that it can continue. + * But if we continued due to cond_signal, we do not have to do that + * because the signaling thread knows that only one waiter continued. + */ + EnterCriticalSection(&cond->waiters_lock); + cond->waiters--; + last_waiter = cond->was_broadcast && cond->waiters == 0; + LeaveCriticalSection(&cond->waiters_lock); + + if (last_waiter) { + /* + * cond_broadcast was issued while mutex was held. This means + * that all other waiters have continued, but are contending + * for the mutex at the end of this function because the + * broadcasting thread did not leave cond_broadcast, yet. + * (This is so that it can be sure that each waiter has + * consumed exactly one slice of the semaphor.) + * The last waiter must tell the broadcasting thread that it + * can go on. + */ + SetEvent(cond->continue_broadcast); + /* + * Now we go on to contend with all other waiters for + * the mutex. Auf in den Kampf! + */ + } + /* lock external mutex again */ + EnterCriticalSection(mutex); + + return 0; +} + +/* + * IMPORTANT: This implementation requires that pthread_cond_signal + * is called while the mutex is held that is used in the corresponding + * pthread_cond_wait calls! + */ +int pthread_cond_signal(pthread_cond_t *cond) +{ + int have_waiters; + + EnterCriticalSection(&cond->waiters_lock); + have_waiters = cond->waiters > 0; + LeaveCriticalSection(&cond->waiters_lock); + + /* + * Signal only when there are waiters + */ + if (have_waiters) + return ReleaseSemaphore(cond->sema, 1, NULL) ? + 0 : err_win_to_posix(GetLastError()); + else + return 0; +} + +/* + * DOUBLY IMPORTANT: This implementation requires that pthread_cond_broadcast + * is called while the mutex is held that is used in the corresponding + * pthread_cond_wait calls! + */ +int pthread_cond_broadcast(pthread_cond_t *cond) +{ + EnterCriticalSection(&cond->waiters_lock); + + if ((cond->was_broadcast = cond->waiters > 0)) { + /* wake up all waiters */ + ReleaseSemaphore(cond->sema, cond->waiters, NULL); + LeaveCriticalSection(&cond->waiters_lock); + /* + * At this point all waiters continue. Each one takes its + * slice of the semaphor. Now it's our turn to wait: Since + * the external mutex is held, no thread can leave cond_wait, + * yet. For this reason, we can be sure that no thread gets + * a chance to eat *more* than one slice. OTOH, it means + * that the last waiter must send us a wake-up. + */ + WaitForSingleObject(cond->continue_broadcast, INFINITE); + /* + * Since the external mutex is held, no thread can enter + * cond_wait, and, hence, it is safe to reset this flag + * without cond->waiters_lock held. + */ + cond->was_broadcast = 0; + } else { + LeaveCriticalSection(&cond->waiters_lock); + } + return 0; +} diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h new file mode 100644 index 0000000000..c72f100f40 --- /dev/null +++ b/compat/win32/pthread.h @@ -0,0 +1,68 @@ +/* + * Header used to adapt pthread-based POSIX code to Windows API threads. + * + * Copyright (C) 2009 Andrzej K. Haczewski <ahaczewski@gmail.com> + */ + +#ifndef PTHREAD_H +#define PTHREAD_H + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include <windows.h> + +/* + * Defines that adapt Windows API threads to pthreads API + */ +#define pthread_mutex_t CRITICAL_SECTION + +#define pthread_mutex_init(a,b) InitializeCriticalSection((a)) +#define pthread_mutex_destroy(a) DeleteCriticalSection((a)) +#define pthread_mutex_lock EnterCriticalSection +#define pthread_mutex_unlock LeaveCriticalSection + +/* + * Implement simple condition variable for Windows threads, based on ACE + * implementation. + * + * See original implementation: http://bit.ly/1vkDjo + * ACE homepage: http://www.cse.wustl.edu/~schmidt/ACE.html + * See also: http://www.cse.wustl.edu/~schmidt/win32-cv-1.html + */ +typedef struct { + LONG waiters; + int was_broadcast; + CRITICAL_SECTION waiters_lock; + HANDLE sema; + HANDLE continue_broadcast; +} pthread_cond_t; + +extern int pthread_cond_init(pthread_cond_t *cond, const void *unused); +extern int pthread_cond_destroy(pthread_cond_t *cond); +extern int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex); +extern int pthread_cond_signal(pthread_cond_t *cond); +extern int pthread_cond_broadcast(pthread_cond_t *cond); + +/* + * Simple thread creation implementation using pthread API + */ +typedef struct { + HANDLE handle; + void *(*start_routine)(void*); + void *arg; +} pthread_t; + +extern int pthread_create(pthread_t *thread, const void *unused, + void *(*start_routine)(void*), void *arg); + +/* + * To avoid the need of copying a struct, we use small macro wrapper to pass + * pointer to win32_pthread_join instead. + */ +#define pthread_join(a, b) win32_pthread_join(&(a), (b)) + +extern int win32_pthread_join(pthread_t *thread, void **value_ptr); + +#endif /* PTHREAD_H */ 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); @@ -513,6 +518,11 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.sparsecheckout")) { + core_apply_sparse_checkout = git_config_bool(var, value); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } @@ -523,8 +533,7 @@ static int git_default_user_config(const char *var, const char *value) if (!value) return config_error_nonbool(var); strlcpy(git_default_name, value, sizeof(git_default_name)); - if (git_default_email[0]) - user_ident_explicitly_given = 1; + user_ident_explicitly_given |= IDENT_NAME_GIVEN; return 0; } @@ -532,8 +541,7 @@ static int git_default_user_config(const char *var, const char *value) if (!value) return config_error_nonbool(var); strlcpy(git_default_email, value, sizeof(git_default_email)); - if (git_default_name[0]) - user_ident_explicitly_given = 1; + user_ident_explicitly_given |= IDENT_MAIL_GIVEN; return 0; } diff --git a/config.mak.in b/config.mak.in index 67b12f73a1..6008ac9f1b 100644 --- a/config.mak.in +++ b/config.mak.in @@ -56,5 +56,4 @@ NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@ FREAD_READS_DIRECTORIES=@FREAD_READS_DIRECTORIES@ SNPRINTF_RETURNS_BOGUS=@SNPRINTF_RETURNS_BOGUS@ NO_PTHREADS=@NO_PTHREADS@ -THREADED_DELTA_SEARCH=@THREADED_DELTA_SEARCH@ PTHREAD_LIBS=@PTHREAD_LIBS@ diff --git a/configure.ac b/configure.ac index b09b8e446f..914ae5759f 100644 --- a/configure.ac +++ b/configure.ac @@ -23,21 +23,32 @@ AC_DEFUN([GIT_CONF_APPEND_LINE], # GIT_ARG_SET_PATH(PROGRAM) # ------------------------- # Provide --with-PROGRAM=PATH option to set PATH to PROGRAM +# Optional second argument allows setting NO_PROGRAM=YesPlease if +# --without-PROGRAM version used. AC_DEFUN([GIT_ARG_SET_PATH], [AC_ARG_WITH([$1], [AS_HELP_STRING([--with-$1=PATH], [provide PATH to $1])], - [GIT_CONF_APPEND_PATH($1)],[]) + [GIT_CONF_APPEND_PATH($1,$2)],[]) ])# GIT_ARG_SET_PATH # # GIT_CONF_APPEND_PATH(PROGRAM) # ------------------------------ # Parse --with-PROGRAM=PATH option to set PROGRAM_PATH=PATH # Used by GIT_ARG_SET_PATH(PROGRAM) +# Optional second argument allows setting NO_PROGRAM=YesPlease if +# --without-PROGRAM is used. AC_DEFUN([GIT_CONF_APPEND_PATH], [PROGRAM=m4_toupper($1); \ if test "$withval" = "no"; then \ - AC_MSG_ERROR([You cannot use git without $1]); \ + if test -n "$2"; then \ + m4_toupper($1)_PATH=$withval; \ + AC_MSG_NOTICE([Disabling use of ${PROGRAM}]); \ + GIT_CONF_APPEND_LINE(NO_${PROGRAM}=YesPlease); \ + GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=); \ + else \ + AC_MSG_ERROR([You cannot use git without $1]); \ + fi; \ else \ if test "$withval" = "yes"; then \ AC_MSG_WARN([You should provide path for --with-$1=PATH]); \ @@ -68,6 +79,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 +258,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, allow-without) +# # Define ZLIB_PATH to provide path to zlib. GIT_ARG_SET_PATH(zlib) # @@ -716,10 +773,9 @@ AC_SUBST(NO_MKSTEMPS) # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link. # Enable it on Windows. By default, symrefs are still used. # -# Define NO_PTHREADS if we do not have pthreads +# Define NO_PTHREADS if we do not have pthreads. # -# Define PTHREAD_LIBS to the linker flag used for Pthread support and define -# THREADED_DELTA_SEARCH if Pthreads are available. +# Define PTHREAD_LIBS to the linker flag used for Pthread support. AC_DEFUN([PTHREADTEST_SRC], [ #include <pthread.h> @@ -736,7 +792,6 @@ dnl [[pthread_mutex_t test_mutex;]] dnl )]) NO_PTHREADS=UnfortunatelyYes -THREADED_DELTA_SEARCH= PTHREAD_LIBS= if test -n "$USER_NOPTHREAD"; then @@ -752,7 +807,6 @@ elif test -z "$PTHREAD_CFLAGS"; then [AC_MSG_RESULT([yes]) NO_PTHREADS= PTHREAD_LIBS="$opt" - THREADED_DELTA_SEARCH=YesPlease break ], [AC_MSG_RESULT([no])]) @@ -766,7 +820,6 @@ else [AC_MSG_RESULT([yes]) NO_PTHREADS= PTHREAD_LIBS="$PTHREAD_CFLAGS" - THREADED_DELTA_SEARCH=YesPlease ], [AC_MSG_RESULT([no])]) @@ -777,7 +830,6 @@ CFLAGS="$old_CFLAGS" AC_SUBST(PTHREAD_LIBS) AC_SUBST(NO_PTHREADS) -AC_SUBST(THREADED_DELTA_SEARCH) ## Output files AC_CONFIG_FILES(["${config_file}":"${config_in}":"${config_append}"]) @@ -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; @@ -523,12 +502,18 @@ struct child_process *git_connect(int fd[2], const char *url_orig, c = ':'; } + /* + * Don't do destructive transforms with git:// as that + * protocol code does '[]' unwrapping of its own. + */ if (host[0] == '[') { end = strchr(host + 1, ']'); if (end) { - *end = 0; + if (protocol != PROTO_GIT) { + *end = 0; + host++; + } end++; - host++; } else end = host; } else @@ -630,11 +615,11 @@ 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; - *arg++ = "sh"; - *arg++ = "-c"; + conn->use_shell = 1; } *arg++ = cmd.buf; *arg = NULL; 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..23da787dc5 100644 --- a/contrib/buildsystems/engine.pl +++ b/contrib/buildsystems/engine.pl @@ -135,6 +135,11 @@ sub parseMakeOutput } } while($ate_next); + if ($text =~ /^test /) { + # options to test (eg -o) may be mistaken for linker options + next; + } + if($text =~ / -c /) { # compilation handleCompileLine($text, $line); @@ -180,9 +185,6 @@ sub parseMakeOutput # } elsif ($text =~ /generate-cmdlist\.sh/) { # # command for generating list of commands # -# } elsif ($text =~ /^test / && $text =~ /|| rm -f /) { -# # commands removing executables, if they exist -# # } elsif ($text =~ /new locations or Tcl/) { # # command for detecting Tcl/Tk changes # @@ -315,6 +317,10 @@ sub handleLinkLine $appout = shift @parts; } elsif ("$part" eq "-lz") { push(@libs, "zlib.lib"); + } elsif ("$part" eq "-lcrypto") { + push(@libs, "libeay32.lib"); + } elsif ("$part" eq "-lssl") { + 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 4cb89a1256..fe93747c93 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 @@ -167,11 +161,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 } @@ -322,12 +313,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/\.$// @@ -337,8 +324,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 () { @@ -416,7 +412,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 @@ -472,27 +478,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 @@ -502,17 +505,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;; @@ -564,6 +568,7 @@ __git_porcelain_commands () read-tree) : plumbing;; receive-pack) : plumbing;; reflog) : plumbing;; + remote-*) : transport;; repo-config) : deprecated;; rerere) : plumbing;; rev-list) : plumbing;; @@ -593,8 +598,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 () { @@ -657,7 +667,7 @@ _git_am () { local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)" if [ -d "$dir"/rebase-apply ]; then - __gitcomp "--skip --resolved --abort" + __gitcomp "--skip --continue --resolved --abort" return fi case "$cur" in @@ -892,11 +902,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 @@ -951,11 +981,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=*) @@ -963,16 +995,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 () @@ -1067,7 +1103,8 @@ _git_grep () return ;; esac - COMPREPLY=() + + __gitcomp "$(__git_refs)" } _git_help () @@ -1079,7 +1116,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 @@ -1215,7 +1253,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 () @@ -1269,6 +1307,24 @@ _git_name_rev () __gitcomp "--tags --all --stdin" } +_git_notes () +{ + local subcommands="edit show" + if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then + __gitcomp "$subcommands" + return + fi + + case "${COMP_WORDS[COMP_CWORD-1]}" in + -m|-F) + COMPREPLY=() + ;; + *) + __gitcomp "$(__git_refs)" + ;; + esac +} + _git_pull () { __git_complete_strategy && return @@ -1320,8 +1376,19 @@ _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= + --autosquash + " + return esac __gitcomp "$(__git_refs)" @@ -1425,7 +1492,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|\ @@ -1526,7 +1594,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.*.*) @@ -1968,7 +2037,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 @@ -2015,7 +2084,7 @@ _git_svn () __gitcomp "--stdin $cmt_opts $fc_opts" ;; create-ignore,--*|propget,--*|proplist,--*|show-ignore,--*|\ - show-externals,--*) + show-externals,--*|mkdirs,--*) __gitcomp "--revision=" ;; log,--*) @@ -2052,6 +2121,9 @@ _git_svn () --no-auth-cache --username= " ;; + reset,--*) + __gitcomp "--revision= --parent" + ;; *) COMPREPLY=() ;; @@ -2123,7 +2195,8 @@ _git () --help " ;; - *) __gitcomp "$(__git_porcelain_commands) $(__git_aliases)" ;; + *) __git_compute_porcelain_commands + __gitcomp "$__git_porcelain_commands $(__git_aliases)" ;; esac return fi @@ -2165,6 +2238,7 @@ _git () merge-base) _git_merge_base ;; mv) _git_mv ;; name-rev) _git_name_rev ;; + notes) _git_notes ;; pull) _git_pull ;; push) _git_push ;; rebase) _git_rebase ;; 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..8f617fcb70 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) " @@ -31,10 +31,11 @@ LF=' ' all_strategies='recur recursive octopus resolve stupid ours subtree' +all_strategies="$all_strategies recursive-ours recursive-theirs" default_twohead_strategies='recursive' default_octopus_strategies='octopus' no_fast_forward_strategies='subtree ours' -no_trivial_strategies='recursive recur subtree ours' +no_trivial_strategies='recursive recur subtree ours recursive-ours recursive-theirs' use_strategies= allow_fast_forward=t @@ -353,7 +354,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 +366,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..cd96c6f81f 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").strip() system(editor + " " + fileName) response = "y" @@ -970,9 +967,8 @@ class P4Sync(Command): elif file["type"] == "symlink": mode = "120000" # p4 print on a symlink contains "target\n", so strip it off - last = contents.pop() - last = last[:-1] - contents.append(last) + data = ''.join(contents) + contents = [data[:-1]] if self.isWindows and file["type"].endswith("text"): mangled = [] @@ -1040,7 +1036,7 @@ class P4Sync(Command): if includeFile: filesForCommit.append(f) - if f['action'] not in ('delete', 'purge'): + if f['action'] not in ('delete', 'move/delete', 'purge'): filesToRead.append(f) else: filesToDelete.append(f) diff --git a/contrib/fast-import/import-directories.perl b/contrib/fast-import/import-directories.perl index 5782d80e26..3a5da4ab00 100755 --- a/contrib/fast-import/import-directories.perl +++ b/contrib/fast-import/import-directories.perl @@ -344,7 +344,7 @@ sub parsekeyvaluepair Key and value strings may be enclosed in quotes, in which case whitespace inside the quotes is preserved. Additionally, an equal -sign may be included in the key by preceeding it with a backslash. +sign may be included in the key by preceding it with a backslash. For example: "key1 "=value1 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/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py index 2a6839d81e..854cd94ba5 100755 --- a/contrib/hg-to-git/hg-to-git.py +++ b/contrib/hg-to-git/hg-to-git.py @@ -59,14 +59,14 @@ def getgitenv(user, date): elems = re.compile('(.*?)\s+<(.*)>').match(user) if elems: env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1) - env += 'export GIT_COMMITER_NAME="%s" ;' % elems.group(1) + env += 'export GIT_COMMITTER_NAME="%s" ;' % elems.group(1) env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2) - env += 'export GIT_COMMITER_EMAIL="%s" ;' % elems.group(2) + env += 'export GIT_COMMITTER_EMAIL="%s" ;' % elems.group(2) else: env += 'export GIT_AUTHOR_NAME="%s" ;' % user - env += 'export GIT_COMMITER_NAME="%s" ;' % user + env += 'export GIT_COMMITTER_NAME="%s" ;' % user env += 'export GIT_AUTHOR_EMAIL= ;' - env += 'export GIT_COMMITER_EMAIL= ;' + env += 'export GIT_COMMITTER_EMAIL= ;' env += 'export GIT_AUTHOR_DATE="%s" ;' % date env += 'export GIT_COMMITTER_DATE="%s" ;' % date 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 @@ -241,7 +241,7 @@ struct filter_params { const char *cmd; }; -static int filter_buffer(int fd, void *data) +static int filter_buffer(int in, int out, void *data) { /* * Spawn cmd and feed the buffer contents through its stdin. @@ -249,12 +249,13 @@ static int filter_buffer(int fd, void *data) struct child_process child_process; struct filter_params *params = (struct filter_params *)data; int write_err, status; - const char *argv[] = { "sh", "-c", params->cmd, NULL }; + const char *argv[] = { params->cmd, NULL }; memset(&child_process, 0, sizeof(child_process)); child_process.argv = argv; + child_process.use_shell = 1; child_process.in = -1; - child_process.out = fd; + child_process.out = out; if (start_command(&child_process)) return error("cannot fork to run external filter %s", params->cmd); @@ -291,6 +292,7 @@ static int apply_filter(const char *path, const char *src, size_t len, memset(&async, 0, sizeof(async)); async.proc = filter_buffer; async.data = ¶ms; + async.out = -1; params.src = src; params.size = len; params.cmd = cmd; @@ -377,9 +379,9 @@ static void setup_convert_check(struct git_attr_check *check) static struct git_attr *attr_filter; if (!attr_crlf) { - attr_crlf = git_attr("crlf", 4); - attr_ident = git_attr("ident", 5); - attr_filter = git_attr("filter", 6); + attr_crlf = git_attr("crlf"); + attr_ident = git_attr("ident"); + attr_filter = git_attr("filter"); user_convert_tail = &user_convert; git_config(read_convert_config, NULL); } @@ -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; } @@ -192,7 +147,6 @@ static char *path_ok(char *directory) { "IP", ip_address }, { "P", tcp_port }, { "D", directory }, - { "%", "%" }, { NULL } }; @@ -445,6 +399,33 @@ static char *xstrdup_tolower(const char *str) return dup; } +static void parse_host_and_port(char *hostport, char **host, + char **port) +{ + if (*hostport == '[') { + char *end; + + end = strchr(hostport, ']'); + if (!end) + die("Invalid request ('[' without ']')"); + *end = '\0'; + *host = hostport + 1; + if (!end[1]) + *port = NULL; + else if (end[1] == ':') + *port = end + 2; + else + die("Garbage after end of host part"); + } else { + *host = hostport; + *port = strrchr(hostport, ':'); + if (*port) { + *port = '\0'; + ++*port; + } + } +} + /* * Read the host as supplied by the client connection. */ @@ -461,11 +442,10 @@ static void parse_host_arg(char *extra_args, int buflen) vallen = strlen(val) + 1; if (*val) { /* Split <host>:<port> at colon. */ - char *host = val; - char *port = strrchr(host, ':'); + char *host; + char *port; + parse_host_and_port(val, &host, &port); if (port) { - *port = 0; - port++; free(tcp_port); tcp_port = xstrdup(port); } @@ -607,6 +587,24 @@ static int execute(struct sockaddr *addr) return -1; } +static int addrcmp(const struct sockaddr_storage *s1, + const struct sockaddr_storage *s2) +{ + if (s1->ss_family != s2->ss_family) + return s1->ss_family - s2->ss_family; + if (s1->ss_family == AF_INET) + return memcmp(&((struct sockaddr_in *)s1)->sin_addr, + &((struct sockaddr_in *)s2)->sin_addr, + sizeof(struct in_addr)); +#ifndef NO_IPV6 + if (s1->ss_family == AF_INET6) + return memcmp(&((struct sockaddr_in6 *)s1)->sin6_addr, + &((struct sockaddr_in6 *)s2)->sin6_addr, + sizeof(struct in6_addr)); +#endif + return 0; +} + static int max_connections = 32; static unsigned int live_children; @@ -621,17 +619,12 @@ static void add_child(pid_t pid, struct sockaddr *addr, int addrlen) { struct child *newborn, **cradle; - /* - * This must be xcalloc() -- we'll compare the whole sockaddr_storage - * but individual address may be shorter. - */ newborn = xcalloc(1, sizeof(*newborn)); live_children++; newborn->pid = pid; memcpy(&newborn->address, addr, addrlen); for (cradle = &firstborn; *cradle; cradle = &(*cradle)->next) - if (!memcmp(&(*cradle)->address, &newborn->address, - sizeof(newborn->address))) + if (!addrcmp(&(*cradle)->address, &newborn->address)) break; newborn->next = *cradle; *cradle = newborn; @@ -664,8 +657,7 @@ static void kill_some_child(void) return; for (; (next = blanket->next); blanket = next) - if (!memcmp(&blanket->address, &next->address, - sizeof(next->address))) { + if (!addrcmp(&blanket->address, &next->address)) { kill(blanket->pid, SIGTERM); break; } @@ -9,7 +9,7 @@ /* * This is like mktime, but without normalization of tm_wday and tm_yday. */ -time_t tm_to_time_t(const struct tm *tm) +static time_t tm_to_time_t(const struct tm *tm) { static const int mdays[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 @@ -696,6 +696,11 @@ static unsigned long update_tm(struct tm *tm, struct tm *now, unsigned long sec) return n; } +static void date_now(struct tm *tm, struct tm *now, int *num) +{ + update_tm(tm, now, 0); +} + static void date_yesterday(struct tm *tm, struct tm *now, int *num) { update_tm(tm, now, 24*60*60); @@ -770,6 +775,7 @@ static const struct special { { "PM", date_pm }, { "AM", date_am }, { "never", date_never }, + { "now", date_now }, { NULL } }; @@ -790,7 +796,7 @@ static const struct typelen { { NULL } }; -static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num) +static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched) { const struct typelen *tl; const struct special *s; @@ -804,6 +810,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm int match = match_string(date, month_names[i]); if (match >= 3) { tm->tm_mon = i; + *touched = 1; return end; } } @@ -812,6 +819,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm int len = strlen(s->name); if (match_string(date, s->name) == len) { s->fn(tm, now, num); + *touched = 1; return end; } } @@ -821,11 +829,14 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm int len = strlen(number_name[i]); if (match_string(date, number_name[i]) == len) { *num = i; + *touched = 1; return end; } } - if (match_string(date, "last") == 4) + if (match_string(date, "last") == 4) { *num = 1; + *touched = 1; + } return end; } @@ -835,6 +846,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm if (match_string(date, tl->type) >= len-1) { update_tm(tm, now, tl->length * *num); *num = 0; + *touched = 1; return end; } tl++; @@ -852,6 +864,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm diff += 7*n; update_tm(tm, now, diff * 24 * 60 * 60); + *touched = 1; return end; } } @@ -866,6 +879,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm tm->tm_year--; } tm->tm_mon = n; + *touched = 1; return end; } @@ -873,6 +887,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm update_tm(tm, now, 0); /* fill in date fields if needed */ tm->tm_year -= *num; *num = 0; + *touched = 1; return end; } @@ -929,9 +944,12 @@ static void pending_number(struct tm *tm, int *num) } } -static unsigned long approxidate_str(const char *date, const struct timeval *tv) +static unsigned long approxidate_str(const char *date, + const struct timeval *tv, + int *error_ret) { int number = 0; + int touched = 0; struct tm tm, now; time_t time_sec; @@ -951,33 +969,42 @@ static unsigned long approxidate_str(const char *date, const struct timeval *tv) if (isdigit(c)) { pending_number(&tm, &number); date = approxidate_digit(date-1, &tm, &number); + touched = 1; continue; } if (isalpha(c)) - date = approxidate_alpha(date-1, &tm, &now, &number); + date = approxidate_alpha(date-1, &tm, &now, &number, &touched); } pending_number(&tm, &number); + if (!touched) + *error_ret = 1; return update_tm(&tm, &now, 0); } unsigned long approxidate_relative(const char *date, const struct timeval *tv) { char buffer[50]; + int errors = 0; if (parse_date(date, buffer, sizeof(buffer)) > 0) return strtoul(buffer, NULL, 0); - return approxidate_str(date, tv); + return approxidate_str(date, tv, &errors); } -unsigned long approxidate(const char *date) +unsigned long approxidate_careful(const char *date, int *error_ret) { struct timeval tv; char buffer[50]; + int dummy = 0; + if (!error_ret) + error_ret = &dummy; - if (parse_date(date, buffer, sizeof(buffer)) > 0) + if (parse_date(date, buffer, sizeof(buffer)) > 0) { + *error_ret = 0; return strtoul(buffer, NULL, 0); + } gettimeofday(&tv, NULL); - return approxidate_str(date, &tv); + return approxidate_str(date, &tv, error_ret); } diff --git a/diff-lib.c b/diff-lib.c index 0c74ef5cbe..d7e13cb177 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -10,6 +10,7 @@ #include "cache-tree.h" #include "unpack-trees.h" #include "refs.h" +#include "submodule.h" /* * diff-files @@ -72,8 +73,9 @@ int run_diff_files(struct rev_info *revs, unsigned int option) unsigned int oldmode, newmode; struct cache_entry *ce = active_cache[i]; int changed; + unsigned dirty_submodule = 0; - if (DIFF_OPT_TST(&revs->diffopt, QUIET) && + if (DIFF_OPT_TST(&revs->diffopt, QUICK) && DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) break; @@ -159,7 +161,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } - if (ce_uptodate(ce)) + if (ce_uptodate(ce) || ce_skip_worktree(ce)) continue; /* If CE_VALID is set, don't look at workdir for file removal */ @@ -172,10 +174,17 @@ int run_diff_files(struct rev_info *revs, unsigned int option) if (silent_on_removed) continue; diff_addremove(&revs->diffopt, '-', ce->ce_mode, - ce->sha1, ce->name); + ce->sha1, ce->name, 0); continue; } changed = ce_match_stat(ce, &st, ce_option); + if (S_ISGITLINK(ce->ce_mode) + && !DIFF_OPT_TST(&revs->diffopt, IGNORE_SUBMODULES) + && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) + && is_submodule_modified(ce->name)) { + changed = 1; + dirty_submodule = 1; + } if (!changed) { ce_mark_uptodate(ce); if (!DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) @@ -185,7 +194,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) newmode = ce_mode_from_stat(ce, st.st_mode); diff_change(&revs->diffopt, oldmode, newmode, ce->sha1, (changed ? null_sha1 : ce->sha1), - ce->name); + ce->name, 0, dirty_submodule); } diffcore_std(&revs->diffopt); @@ -201,16 +210,18 @@ int run_diff_files(struct rev_info *revs, unsigned int option) static void diff_index_show_file(struct rev_info *revs, const char *prefix, struct cache_entry *ce, - const unsigned char *sha1, unsigned int mode) + const unsigned char *sha1, unsigned int mode, + unsigned dirty_submodule) { diff_addremove(&revs->diffopt, prefix[0], mode, - sha1, ce->name); + sha1, ce->name, dirty_submodule); } static int get_stat_data(struct cache_entry *ce, const unsigned char **sha1p, unsigned int *modep, - int cached, int match_missing) + int cached, int match_missing, + unsigned *dirty_submodule, struct diff_options *diffopt) { const unsigned char *sha1 = ce->sha1; unsigned int mode = ce->ce_mode; @@ -230,6 +241,13 @@ static int get_stat_data(struct cache_entry *ce, return -1; } changed = ce_match_stat(ce, &st, 0); + if (S_ISGITLINK(ce->ce_mode) + && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) + && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH)) + && is_submodule_modified(ce->name)) { + changed = 1; + *dirty_submodule = 1; + } if (changed) { mode = ce_mode_from_stat(ce, st.st_mode); sha1 = null_sha1; @@ -247,15 +265,17 @@ static void show_new_file(struct rev_info *revs, { const unsigned char *sha1; unsigned int mode; + unsigned dirty_submodule = 0; /* * New file in the index: it might actually be different in * the working copy. */ - if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) + if (get_stat_data(new, &sha1, &mode, cached, match_missing, + &dirty_submodule, &revs->diffopt) < 0) return; - diff_index_show_file(revs, "+", new, sha1, mode); + diff_index_show_file(revs, "+", new, sha1, mode, dirty_submodule); } static int show_modified(struct rev_info *revs, @@ -266,11 +286,13 @@ static int show_modified(struct rev_info *revs, { unsigned int mode, oldmode; const unsigned char *sha1; + unsigned dirty_submodule = 0; - if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) { + if (get_stat_data(new, &sha1, &mode, cached, match_missing, + &dirty_submodule, &revs->diffopt) < 0) { if (report_missing) diff_index_show_file(revs, "-", old, - old->sha1, old->ce_mode); + old->sha1, old->ce_mode, 0); return -1; } @@ -305,7 +327,7 @@ static int show_modified(struct rev_info *revs, return 0; diff_change(&revs->diffopt, oldmode, mode, - old->sha1, sha1, old->name); + old->sha1, sha1, old->name, 0, dirty_submodule); return 0; } @@ -323,7 +345,8 @@ static void do_oneway_diff(struct unpack_trees_options *o, int match_missing, cached; /* if the entry is not checked out, don't examine work tree */ - cached = o->index_only || (idx && (idx->ce_flags & CE_VALID)); + cached = o->index_only || + (idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx))); /* * Backward compatibility wart - "diff-index -m" does * not mean "do not ignore merges", but "match_missing". @@ -351,7 +374,7 @@ static void do_oneway_diff(struct unpack_trees_options *o, * Something removed from the tree? */ if (!idx) { - diff_index_show_file(revs, "-", tree, tree->sha1, tree->ce_mode); + diff_index_show_file(revs, "-", tree, tree->sha1, tree->ce_mode, 0); return; } @@ -359,21 +382,6 @@ static void do_oneway_diff(struct unpack_trees_options *o, show_modified(revs, tree, idx, 1, cached, match_missing); } -static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_options *o) -{ - int len = ce_namelen(ce); - const struct index_state *index = o->src_index; - - while (o->pos < index->cache_nr) { - struct cache_entry *next = index->cache[o->pos]; - if (len != ce_namelen(next)) - break; - if (memcmp(ce->name, next->name, len)) - break; - o->pos++; - } -} - /* * The unpack_trees() interface is designed for merging, so * the different source entries are designed primarily for @@ -383,7 +391,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 @@ -395,9 +403,6 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o) struct cache_entry *tree = src[1]; struct rev_info *revs = o->unpack_data; - if (idx && ce_stage(idx)) - skip_same_name(idx, o); - /* * Unpack-trees generates a DF/conflict entry if * there was a directory in the index and a tree @@ -443,6 +448,7 @@ int run_diff_index(struct rev_info *revs, int cached) exit(128); diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/"); + diffcore_fix_diff_index(&revs->diffopt); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); return 0; @@ -507,7 +513,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,6 +61,8 @@ static int parse_diff_color_slot(const char *var, int ofs) return DIFF_COMMIT; if (!strcasecmp(var+ofs, "whitespace")) return DIFF_WHITESPACE; + if (!strcasecmp(var+ofs, "func")) + return DIFF_FUNCINFO; return -1; } @@ -190,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) @@ -296,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) @@ -345,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++) @@ -756,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]) { @@ -782,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; @@ -1116,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; @@ -1562,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, two->dirty_submodule, + del, add, reset); + return; + } if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) { textconv_one = get_textconv(one); @@ -1585,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. @@ -1613,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; @@ -1630,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 @@ -1646,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); @@ -1675,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; @@ -1719,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); @@ -1928,7 +1997,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int * If ce is marked as "assume unchanged", there is no * guarantee that work tree matches what we are looking for. */ - if (ce->ce_flags & CE_VALID) + if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) return 0; /* @@ -1960,9 +2029,14 @@ static int populate_from_stdin(struct diff_filespec *s) static int diff_populate_gitlink(struct diff_filespec *s, int size_only) { int len; - char *data = xmalloc(100); + char *data = xmalloc(100), *dirty = ""; + + /* Are we looking at the work tree? */ + if (!s->sha1_valid && s->dirty_submodule) + dirty = "-dirty"; + len = snprintf(data, 100, - "Subproject commit %s\n", sha1_to_hex(s->sha1)); + "Subproject commit %s%s\n", sha1_to_hex(s->sha1), dirty); s->data = data; s->size = len; s->should_free = 1; @@ -2225,7 +2299,7 @@ static void run_external_diff(const char *pgm, } *arg = NULL; fflush(NULL); - retval = run_command_v_opt(spawn_arg, 0); + retval = run_command_v_opt(spawn_arg, RUN_USING_SHELL); remove_tempfile(); if (retval) { fprintf(stderr, "external diff died, stopping at %s.\n", name); @@ -2501,6 +2575,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; @@ -2561,7 +2649,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); } @@ -2752,7 +2840,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")) @@ -2763,6 +2851,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")) @@ -3455,6 +3549,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) @@ -3538,7 +3644,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) struct diff_filepair *p = q->queue[i]; /* - * 1. Entries that come from stat info dirtyness + * 1. Entries that come from stat info dirtiness * always have both sides (iow, not create/delete), * one side of the object name is unknown, with * the same mode and size. Keep the ones that @@ -3574,6 +3680,23 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) *q = outq; } +static int diffnamecmp(const void *a_, const void *b_) +{ + const struct diff_filepair *a = *((const struct diff_filepair **)a_); + const struct diff_filepair *b = *((const struct diff_filepair **)b_); + const char *name_a, *name_b; + + name_a = a->one ? a->one->path : a->two->path; + name_b = b->one ? b->one->path : b->two->path; + return strcmp(name_a, name_b); +} + +void diffcore_fix_diff_index(struct diff_options *options) +{ + struct diff_queue_struct *q = &diff_queued_diff; + qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp); +} + void diffcore_std(struct diff_options *options) { if (options->skip_stat_unmatch) @@ -3591,7 +3714,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); @@ -3615,7 +3738,7 @@ int diff_result_code(struct diff_options *opt, int status) void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const unsigned char *sha1, - const char *concatpath) + const char *concatpath, unsigned dirty_submodule) { struct diff_filespec *one, *two; @@ -3647,18 +3770,22 @@ void diff_addremove(struct diff_options *options, if (addremove != '+') fill_filespec(one, sha1, mode); - if (addremove != '-') + if (addremove != '-') { fill_filespec(two, sha1, mode); + two->dirty_submodule = dirty_submodule; + } 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, unsigned old_mode, unsigned new_mode, const unsigned char *old_sha1, const unsigned char *new_sha1, - const char *concatpath) + const char *concatpath, + unsigned old_dirty_submodule, unsigned new_dirty_submodule) { struct diff_filespec *one, *two; @@ -3671,6 +3798,8 @@ void diff_change(struct diff_options *options, const unsigned char *tmp_c; tmp = old_mode; old_mode = new_mode; new_mode = tmp; tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c; + tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule; + new_dirty_submodule = tmp; } if (options->prefix && @@ -3681,9 +3810,12 @@ void diff_change(struct diff_options *options, two = alloc_filespec(concatpath); fill_filespec(one, old_sha1, old_mode); fill_filespec(two, new_sha1, new_mode); + one->dirty_submodule = old_dirty_submodule; + two->dirty_submodule = new_dirty_submodule; 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, @@ -3717,6 +3849,7 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, *arg = NULL; memset(&child, 0, sizeof(child)); + child.use_shell = 1; child.argv = argv; child.out = -1; if (start_command(&child) != 0 || @@ -14,12 +14,13 @@ typedef void (*change_fn_t)(struct diff_options *options, unsigned old_mode, unsigned new_mode, const unsigned char *old_sha1, const unsigned char *new_sha1, - const char *fullpath); + const char *fullpath, + unsigned old_dirty_submodule, unsigned new_dirty_submodule); typedef void (*add_remove_fn_t)(struct diff_options *options, int addremove, unsigned mode, const unsigned char *sha1, - const char *fullpath); + const char *fullpath, unsigned dirty_submodule); typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, struct diff_options *options, void *data); @@ -55,7 +56,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 +67,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 +131,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) \ @@ -173,13 +178,14 @@ extern void diff_addremove(struct diff_options *, int addremove, unsigned mode, const unsigned char *sha1, - const char *fullpath); + const char *fullpath, unsigned dirty_submodule); extern void diff_change(struct diff_options *, unsigned mode1, unsigned mode2, const unsigned char *sha1, const unsigned char *sha2, - const char *fullpath); + const char *fullpath, + unsigned dirty_submodule1, unsigned dirty_submodule2); extern void diff_unmerge(struct diff_options *, const char *path, @@ -204,6 +210,7 @@ extern int diff_setup_done(struct diff_options *); #define DIFF_PICKAXE_REGEX 2 extern void diffcore_std(struct diff_options *); +extern void diffcore_fix_diff_index(struct diff_options *); #define COMMON_DIFF_OPTIONS_HELP \ "\ncommon diff options:\n" \ diff --git a/diffcore.h b/diffcore.h index 5b634585e8..66687c3fe5 100644 --- a/diffcore.h +++ b/diffcore.h @@ -42,6 +42,7 @@ struct diff_filespec { #define DIFF_FILE_VALID(spec) (((spec)->mode) != 0) unsigned should_free : 1; /* data should be free()'ed */ unsigned should_munmap : 1; /* data should be munmap()'ed */ + unsigned dirty_submodule : 1; /* For submodules: its work tree is dirty */ struct userdiff_driver *driver; /* data should be considered "binary"; -1 means "don't know yet" */ @@ -200,11 +200,35 @@ void add_exclude(const char *string, const char *base, which->excludes[which->nr++] = x; } -static int add_excludes_from_file_1(const char *fname, - const char *base, - int baselen, - char **buf_p, - struct exclude_list *which) +static void *read_skip_worktree_file_from_index(const char *path, size_t *size) +{ + int pos, len; + unsigned long sz; + enum object_type type; + void *data; + struct index_state *istate = &the_index; + + len = strlen(path); + pos = index_name_pos(istate, path, len); + if (pos < 0) + return NULL; + if (!ce_skip_worktree(istate->cache[pos])) + return NULL; + data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz); + if (!data || type != OBJ_BLOB) { + free(data); + return NULL; + } + *size = xsize_t(sz); + return data; +} + +int add_excludes_from_file_to_list(const char *fname, + const char *base, + int baselen, + char **buf_p, + struct exclude_list *which, + int check_index) { struct stat st; int fd, i; @@ -212,24 +236,39 @@ static int add_excludes_from_file_1(const char *fname, char *buf, *entry; fd = open(fname, O_RDONLY); - if (fd < 0 || fstat(fd, &st) < 0) - goto err; - size = xsize_t(st.st_size); - if (size == 0) { - close(fd); - return 0; + if (fd < 0 || fstat(fd, &st) < 0) { + if (0 <= fd) + close(fd); + if (!check_index || + (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL) + return -1; + if (size == 0) { + free(buf); + return 0; + } + if (buf[size-1] != '\n') { + buf = xrealloc(buf, size+1); + buf[size++] = '\n'; + } } - buf = xmalloc(size+1); - if (read_in_full(fd, buf, size) != size) - { - free(buf); - goto err; + else { + size = xsize_t(st.st_size); + if (size == 0) { + close(fd); + return 0; + } + buf = xmalloc(size+1); + if (read_in_full(fd, buf, size) != size) { + free(buf); + close(fd); + return -1; + } + buf[size++] = '\n'; + close(fd); } - close(fd); if (buf_p) *buf_p = buf; - buf[size++] = '\n'; entry = buf; for (i = 0; i < size; i++) { if (buf[i] == '\n') { @@ -241,17 +280,12 @@ static int add_excludes_from_file_1(const char *fname, } } return 0; - - err: - if (0 <= fd) - close(fd); - return -1; } void add_excludes_from_file(struct dir_struct *dir, const char *fname) { - if (add_excludes_from_file_1(fname, "", 0, NULL, - &dir->exclude_list[EXC_FILE]) < 0) + if (add_excludes_from_file_to_list(fname, "", 0, NULL, + &dir->exclude_list[EXC_FILE], 0) < 0) die("cannot use %s as an exclude file", fname); } @@ -300,9 +334,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) memcpy(dir->basebuf + current, base + current, stk->baselen - current); strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir); - add_excludes_from_file_1(dir->basebuf, - dir->basebuf, stk->baselen, - &stk->filebuf, el); + add_excludes_from_file_to_list(dir->basebuf, + dir->basebuf, stk->baselen, + &stk->filebuf, el, 1); dir->exclude_stack = stk; current = stk->baselen; } @@ -312,9 +346,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) /* Scan the list and let the last match determine the fate. * Return 1 for exclude, 0 for include and -1 for undecided. */ -static int excluded_1(const char *pathname, - int pathlen, const char *basename, int *dtype, - struct exclude_list *el) +int excluded_from_list(const char *pathname, + int pathlen, const char *basename, int *dtype, + struct exclude_list *el) { int i; @@ -325,6 +359,12 @@ static int excluded_1(const char *pathname, int to_exclude = x->to_exclude; if (x->flags & EXC_FLAG_MUSTBEDIR) { + if (!dtype) { + if (!prefixcmp(pathname, exclude)) + return to_exclude; + else + continue; + } if (*dtype == DT_UNKNOWN) *dtype = get_dtype(NULL, pathname, pathlen); if (*dtype != DT_DIR) @@ -382,8 +422,8 @@ int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p) prep_exclude(dir, pathname, basename-pathname); for (st = EXC_CMDL; st <= EXC_FILE; st++) { - switch (excluded_1(pathname, pathlen, basename, - dtype_p, &dir->exclude_list[st])) { + switch (excluded_from_list(pathname, pathlen, basename, + dtype_p, &dir->exclude_list[st])) { case 0: return 0; case 1: @@ -625,6 +665,92 @@ static int get_dtype(struct dirent *de, const char *path, int len) return dtype; } +enum path_treatment { + path_ignored, + path_handled, + path_recurse, +}; + +static enum path_treatment treat_one_path(struct dir_struct *dir, + char *path, int *len, + const struct path_simplify *simplify, + int dtype, struct dirent *de) +{ + int exclude = excluded(dir, path, &dtype); + if (exclude && (dir->flags & DIR_COLLECT_IGNORED) + && in_pathspec(path, *len, simplify)) + dir_add_ignored(dir, path, *len); + + /* + * Excluded? If we don't explicitly want to show + * ignored files, ignore it + */ + if (exclude && !(dir->flags & DIR_SHOW_IGNORED)) + return path_ignored; + + if (dtype == DT_UNKNOWN) + dtype = get_dtype(de, path, *len); + + /* + * Do we want to see just the ignored files? + * We still need to recurse into directories, + * even if we don't ignore them, since the + * directory may contain files that we do.. + */ + if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) { + if (dtype != DT_DIR) + return path_ignored; + } + + switch (dtype) { + default: + return path_ignored; + case DT_DIR: + memcpy(path + *len, "/", 2); + (*len)++; + switch (treat_directory(dir, path, *len, simplify)) { + case show_directory: + if (exclude != !!(dir->flags + & DIR_SHOW_IGNORED)) + return path_ignored; + break; + case recurse_into_directory: + return path_recurse; + case ignore_directory: + return path_ignored; + } + break; + case DT_REG: + case DT_LNK: + break; + } + return path_handled; +} + +static enum path_treatment treat_path(struct dir_struct *dir, + struct dirent *de, + char *path, int path_max, + int baselen, + const struct path_simplify *simplify, + int *len) +{ + int dtype; + + if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git")) + return path_ignored; + *len = strlen(de->d_name); + /* Ignore overly long pathnames! */ + if (*len + baselen + 8 > path_max) + return path_ignored; + memcpy(path + baselen, de->d_name, *len + 1); + *len += baselen; + if (simplify_away(path, *len, simplify)) + return path_ignored; + + dtype = DTYPE(de); + return treat_one_path(dir, path, len, simplify, dtype, de); +} + /* * Read a directory tree. We currently ignore anything but * directories, regular files and symlinks. That's because git @@ -634,7 +760,10 @@ static int get_dtype(struct dirent *de, const char *path, int len) * Also, we ignore the name ".git" (even if it is not a directory). * That likely will not change. */ -static int read_directory_recursive(struct dir_struct *dir, const char *base, int baselen, int check_only, const struct path_simplify *simplify) +static int read_directory_recursive(struct dir_struct *dir, + const char *base, int baselen, + int check_only, + const struct path_simplify *simplify) { DIR *fdir = opendir(*base ? base : "."); int contents = 0; @@ -645,70 +774,16 @@ static int read_directory_recursive(struct dir_struct *dir, const char *base, in memcpy(path, base, baselen); while ((de = readdir(fdir)) != NULL) { - int len, dtype; - int exclude; - - if (is_dot_or_dotdot(de->d_name) || - !strcmp(de->d_name, ".git")) - continue; - len = strlen(de->d_name); - /* Ignore overly long pathnames! */ - if (len + baselen + 8 > sizeof(path)) + int len; + switch (treat_path(dir, de, path, sizeof(path), + baselen, simplify, &len)) { + case path_recurse: + contents += read_directory_recursive + (dir, path, len, 0, simplify); continue; - memcpy(path + baselen, de->d_name, len+1); - len = baselen + len; - if (simplify_away(path, len, simplify)) + case path_ignored: continue; - - dtype = DTYPE(de); - exclude = excluded(dir, path, &dtype); - if (exclude && (dir->flags & DIR_COLLECT_IGNORED) - && in_pathspec(path, len, simplify)) - dir_add_ignored(dir, path,len); - - /* - * Excluded? If we don't explicitly want to show - * ignored files, ignore it - */ - if (exclude && !(dir->flags & DIR_SHOW_IGNORED)) - continue; - - if (dtype == DT_UNKNOWN) - dtype = get_dtype(de, path, len); - - /* - * Do we want to see just the ignored files? - * We still need to recurse into directories, - * even if we don't ignore them, since the - * directory may contain files that we do.. - */ - if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) { - if (dtype != DT_DIR) - continue; - } - - switch (dtype) { - default: - continue; - case DT_DIR: - memcpy(path + len, "/", 2); - len++; - switch (treat_directory(dir, path, len, simplify)) { - case show_directory: - if (exclude != !!(dir->flags - & DIR_SHOW_IGNORED)) - continue; - break; - case recurse_into_directory: - contents += read_directory_recursive(dir, - path, len, 0, simplify); - continue; - case ignore_directory: - continue; - } - break; - case DT_REG: - case DT_LNK: + case path_handled: break; } contents++; @@ -778,6 +853,41 @@ static void free_simplify(struct path_simplify *simplify) free(simplify); } +static int treat_leading_path(struct dir_struct *dir, + const char *path, int len, + const struct path_simplify *simplify) +{ + char pathbuf[PATH_MAX]; + int baselen, blen; + const char *cp; + + while (len && path[len - 1] == '/') + len--; + if (!len) + return 1; + baselen = 0; + while (1) { + cp = path + baselen + !!baselen; + cp = memchr(cp, '/', path + len - cp); + if (!cp) + baselen = len; + else + baselen = cp - path; + memcpy(pathbuf, path, baselen); + pathbuf[baselen] = '\0'; + if (!is_directory(pathbuf)) + return 0; + if (simplify_away(pathbuf, baselen, simplify)) + return 0; + blen = baselen; + if (treat_one_path(dir, pathbuf, &blen, simplify, + DT_DIR, NULL) == path_ignored) + return 0; /* do not recurse into it */ + if (len <= baselen) + return 1; /* finished checking */ + } +} + int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec) { struct path_simplify *simplify; @@ -786,7 +896,8 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const char return dir->nr; simplify = create_simplify(pathspec); - read_directory_recursive(dir, path, len, 0, simplify); + if (!len || treat_leading_path(dir, path, len, simplify)) + read_directory_recursive(dir, path, len, 0, simplify); free_simplify(simplify); qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name); qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name); @@ -933,7 +1044,7 @@ int remove_path(const char *name) slash = dirs + (slash - name); do { *slash = '\0'; - } while (rmdir(dirs) && (slash = strrchr(dirs, '/'))); + } while (rmdir(dirs) == 0 && (slash = strrchr(dirs, '/'))); free(dirs); } return 0; @@ -69,7 +69,11 @@ extern int match_pathspec(const char **pathspec, const char *name, int namelen, extern int fill_directory(struct dir_struct *dir, const char **pathspec); extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec); +extern int excluded_from_list(const char *pathname, int pathlen, const char *basename, + int *dtype, struct exclude_list *el); extern int excluded(struct dir_struct *, const char *, int *); +extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen, + char **buf_p, struct exclude_list *which, int check_index); extern void add_excludes_from_file(struct dir_struct *, const char *fname); extern void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which); @@ -2,46 +2,43 @@ #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 = "vi"; + 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) + return error("Terminal is dumb, but EDITOR unset"); if (strcmp(editor, ":")) { - size_t len = strlen(editor); - int i = 0; - int failed; - const char *args[6]; - struct strbuf arg0 = STRBUF_INIT; - - if (strcspn(editor, "$ \t'") != len) { - /* there are specials */ - strbuf_addf(&arg0, "%s \"$@\"", editor); - args[i++] = "sh"; - args[i++] = "-c"; - args[i++] = arg0.buf; - } - args[i++] = editor; - args[i++] = path; - args[i] = NULL; - - failed = run_command_v_opt_cd_env(args, 0, NULL, env); - strbuf_release(&arg0); - if (failed) + const char *args[] = { editor, path, NULL }; + + if (run_command_v_opt_cd_env(args, RUN_USING_SHELL, NULL, env)) return error("There was a problem with the editor '%s'.", editor); } @@ -179,7 +179,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout * This is like 'lstat()', except it refuses to follow symlinks * in the path, after skipping "skiplen". */ -int check_path(const char *path, int len, struct stat *st, int skiplen) +static int check_path(const char *path, int len, struct stat *st, int skiplen) { const char *slash = path + len; @@ -206,7 +206,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t len += ce_namelen(ce); if (!check_path(path, len, &st, state->base_dir_len)) { - unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID); + unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); if (!changed) return 0; if (!state->force) { diff --git a/environment.c b/environment.c index 5de6837840..739ec27040 100644 --- a/environment.c +++ b/environment.c @@ -49,7 +49,9 @@ 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; +int core_apply_sparse_checkout; /* Parallel index stat data preload? */ int core_preload_index = 0; @@ -83,6 +85,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 36c5a8ebd9..74f08bd554 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; @@ -160,12 +164,11 @@ Format of STDIN stream: struct object_entry { + struct pack_idx_entry idx; struct object_entry *next; - uint32_t offset; uint32_t type : TYPE_BITS, pack_id : PACK_ID_BITS, depth : DEPTH_BITS; - unsigned char sha1[20]; }; struct object_entry_pool @@ -188,7 +191,7 @@ struct mark_set struct last_object { struct strbuf data; - uint32_t offset; + off_t offset; unsigned int depth; unsigned no_swap : 1; }; @@ -241,6 +244,7 @@ struct branch const char *name; struct tree_entry branch_tree; uintmax_t last_commit; + uintmax_t num_notes; unsigned active : 1; unsigned pack_id : PACK_ID_BITS; unsigned char sha1[20]; @@ -275,7 +279,8 @@ struct recent_command /* Configured limits on output */ static unsigned long max_depth = 10; -static off_t max_packsize = (1LL << 32) - 1; +static off_t max_packsize; +static uintmax_t big_file_threshold = 512 * 1024 * 1024; static int force_update; static int pack_compression_level = Z_DEFAULT_COMPRESSION; static int pack_compression_seen; @@ -291,6 +296,9 @@ static unsigned long branch_count; static unsigned long branch_load_count; static int failure; static FILE *pack_edges; +static unsigned int show_stats = 1; +static int global_argc; +static const char **global_argv; /* Memory pools */ static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool); @@ -304,16 +312,20 @@ static struct atom_str **atom_table; /* The .pack file being generated */ static unsigned int pack_id; +static struct sha1file *pack_file; static struct packed_git *pack_data; static struct packed_git **all_packs; -static unsigned long pack_size; +static off_t pack_size; /* Table of objects we've written. */ static unsigned int object_entry_alloc = 5000; static struct object_entry_pool *blocks; static struct object_entry *object_table[1 << 16]; static struct mark_set *marks; -static const char *mark_file; +static const char *export_marks_file; +static const char *import_marks_file; +static int import_marks_file_from_stream; +static int relative_marks_paths; /* Our last blob */ static struct last_object last_blob = { STRBUF_INIT, 0, 0, 0 }; @@ -347,6 +359,9 @@ static struct recent_command *rc_free; static unsigned int cmd_save = 100; static uintmax_t next_mark; static struct strbuf new_data = STRBUF_INIT; +static int seen_data_command; + +static void parse_argv(void); static void write_branch_report(FILE *rpt, struct branch *b) { @@ -450,8 +465,8 @@ static void write_crash_report(const char *err) fputc('\n', rpt); fputs("Marks\n", rpt); fputs("-----\n", rpt); - if (mark_file) - fprintf(rpt, " exported to %s\n", mark_file); + if (export_marks_file) + fprintf(rpt, " exported to %s\n", export_marks_file); else dump_marks_helper(rpt, 0, marks); @@ -506,7 +521,7 @@ static struct object_entry *new_object(unsigned char *sha1) alloc_objects(object_entry_alloc); e = blocks->next_free++; - hashcpy(e->sha1, sha1); + hashcpy(e->idx.sha1, sha1); return e; } @@ -515,7 +530,7 @@ static struct object_entry *find_object(unsigned char *sha1) unsigned int h = sha1[0] << 8 | sha1[1]; struct object_entry *e; for (e = object_table[h]; e; e = e->next) - if (!hashcmp(sha1, e->sha1)) + if (!hashcmp(sha1, e->idx.sha1)) return e; return NULL; } @@ -527,7 +542,7 @@ static struct object_entry *insert_object(unsigned char *sha1) struct object_entry *p = NULL; while (e) { - if (!hashcmp(sha1, e->sha1)) + if (!hashcmp(sha1, e->idx.sha1)) return e; p = e; e = e->next; @@ -535,7 +550,7 @@ static struct object_entry *insert_object(unsigned char *sha1) e = new_object(sha1); e->next = NULL; - e->offset = 0; + e->idx.offset = 0; if (p) p->next = e; else @@ -689,6 +704,7 @@ static struct branch *new_branch(const char *name) b->table_next_branch = branch_table[hc]; b->branch_tree.versions[0].mode = S_IFDIR; b->branch_tree.versions[1].mode = S_IFDIR; + b->num_notes = 0; b->active = 0; b->pack_id = MAX_PACK_ID; branch_table[hc] = b; @@ -823,11 +839,12 @@ static void start_packfile(void) p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2); strcpy(p->pack_name, tmpfile); p->pack_fd = pack_fd; + pack_file = sha1fd(pack_fd, p->pack_name); hdr.hdr_signature = htonl(PACK_SIGNATURE); hdr.hdr_version = htonl(2); hdr.hdr_entries = 0; - write_or_die(p->pack_fd, &hdr, sizeof(hdr)); + sha1write(pack_file, &hdr, sizeof(hdr)); pack_data = p; pack_size = sizeof(hdr); @@ -837,67 +854,30 @@ static void start_packfile(void) all_packs[pack_id] = p; } -static int oecmp (const void *a_, const void *b_) -{ - struct object_entry *a = *((struct object_entry**)a_); - struct object_entry *b = *((struct object_entry**)b_); - return hashcmp(a->sha1, b->sha1); -} - -static char *create_index(void) +static const char *create_index(void) { - static char tmpfile[PATH_MAX]; - git_SHA_CTX ctx; - struct sha1file *f; - struct object_entry **idx, **c, **last, *e; + const char *tmpfile; + struct pack_idx_entry **idx, **c, **last; + struct object_entry *e; struct object_entry_pool *o; - uint32_t array[256]; - int i, idx_fd; - /* Build the sorted table of object IDs. */ - idx = xmalloc(object_count * sizeof(struct object_entry*)); + /* Build the table of object IDs. */ + idx = xmalloc(object_count * sizeof(*idx)); c = idx; for (o = blocks; o; o = o->next_pool) for (e = o->next_free; e-- != o->entries;) if (pack_id == e->pack_id) - *c++ = e; + *c++ = &e->idx; last = idx + object_count; if (c != last) die("internal consistency error creating the index"); - qsort(idx, object_count, sizeof(struct object_entry*), oecmp); - /* Generate the fan-out array. */ - c = idx; - for (i = 0; i < 256; i++) { - struct object_entry **next = c; - while (next < last) { - if ((*next)->sha1[0] != i) - break; - next++; - } - array[i] = htonl(next - idx); - c = next; - } - - idx_fd = odb_mkstemp(tmpfile, sizeof(tmpfile), - "pack/tmp_idx_XXXXXX"); - f = sha1fd(idx_fd, tmpfile); - sha1write(f, array, 256 * sizeof(int)); - git_SHA1_Init(&ctx); - for (c = idx; c != last; c++) { - uint32_t offset = htonl((*c)->offset); - sha1write(f, &offset, 4); - sha1write(f, (*c)->sha1, sizeof((*c)->sha1)); - git_SHA1_Update(&ctx, (*c)->sha1, 20); - } - sha1write(f, pack_data->sha1, sizeof(pack_data->sha1)); - sha1close(f, NULL, CSUM_FSYNC); + tmpfile = write_idx_file(NULL, idx, object_count, pack_data->sha1); free(idx); - git_SHA1_Final(pack_data->sha1, &ctx); return tmpfile; } -static char *keep_pack(char *curr_index_name) +static char *keep_pack(const char *curr_index_name) { static char name[PATH_MAX]; static const char *keep_msg = "fast-import"; @@ -919,6 +899,7 @@ static char *keep_pack(char *curr_index_name) get_object_directory(), sha1_to_hex(pack_data->sha1)); if (move_temp_to_file(curr_index_name, name)) die("cannot store index file"); + free((void *)curr_index_name); return name; } @@ -941,15 +922,17 @@ static void end_packfile(void) clear_delta_base_cache(); if (object_count) { + unsigned char cur_pack_sha1[20]; char *idx_name; int i; struct branch *b; struct tag *t; close_pack_windows(pack_data); + sha1close(pack_file, cur_pack_sha1, 0); fixup_pack_header_footer(pack_data->pack_fd, pack_data->sha1, pack_data->pack_name, object_count, - NULL, 0); + cur_pack_sha1, pack_size); close(pack_data->pack_fd); idx_name = keep_pack(create_index()); @@ -999,7 +982,7 @@ static void cycle_packfile(void) static size_t encode_header( enum object_type type, - size_t size, + uintmax_t size, unsigned char *hdr) { int n = 1; @@ -1047,25 +1030,21 @@ static int store_object( e = insert_object(sha1); if (mark) insert_mark(mark, e); - if (e->offset) { + if (e->idx.offset) { duplicate_count_by_type[type]++; return 1; } else if (find_sha1_pack(sha1, packed_git)) { e->type = type; e->pack_id = MAX_PACK_ID; - e->offset = 1; /* just not zero! */ + e->idx.offset = 1; /* just not zero! */ duplicate_count_by_type[type]++; return 1; } - if (last && last->data.buf && last->depth < max_depth) { + if (last && last->data.buf && last->depth < max_depth && dat->len > 20) { delta = diff_delta(last->data.buf, last->data.len, dat->buf, dat->len, - &deltalen, 0); - if (delta && deltalen >= dat->len) { - free(delta); - delta = NULL; - } + &deltalen, dat->len - 20); } else delta = NULL; @@ -1085,7 +1064,7 @@ static int store_object( deflateEnd(&s); /* Determine if we should auto-checkpoint. */ - if ((pack_size + 60 + s.total_out) > max_packsize + if ((max_packsize && (pack_size + 60 + s.total_out) > max_packsize) || (pack_size + 60 + s.total_out) < pack_size) { /* This new object needs to *not* have the current pack_id. */ @@ -1111,36 +1090,40 @@ static int store_object( e->type = type; e->pack_id = pack_id; - e->offset = pack_size; + e->idx.offset = pack_size; object_count++; object_count_by_type[type]++; + crc32_begin(pack_file); + if (delta) { - unsigned long ofs = e->offset - last->offset; + off_t ofs = e->idx.offset - last->offset; unsigned pos = sizeof(hdr) - 1; delta_count_by_type[type]++; e->depth = last->depth + 1; hdrlen = encode_header(OBJ_OFS_DELTA, deltalen, hdr); - write_or_die(pack_data->pack_fd, hdr, hdrlen); + sha1write(pack_file, hdr, hdrlen); pack_size += hdrlen; hdr[pos] = ofs & 127; while (ofs >>= 7) hdr[--pos] = 128 | (--ofs & 127); - write_or_die(pack_data->pack_fd, hdr + pos, sizeof(hdr) - pos); + sha1write(pack_file, hdr + pos, sizeof(hdr) - pos); pack_size += sizeof(hdr) - pos; } else { e->depth = 0; hdrlen = encode_header(type, dat->len, hdr); - write_or_die(pack_data->pack_fd, hdr, hdrlen); + sha1write(pack_file, hdr, hdrlen); pack_size += hdrlen; } - write_or_die(pack_data->pack_fd, out, s.total_out); + sha1write(pack_file, out, s.total_out); pack_size += s.total_out; + e->idx.crc32 = crc32_end(pack_file); + free(out); free(delta); if (last) { @@ -1149,12 +1132,137 @@ static int store_object( } else { strbuf_swap(&last->data, dat); } - last->offset = e->offset; + last->offset = e->idx.offset; last->depth = e->depth; } return 0; } +static void truncate_pack(off_t to, git_SHA_CTX *ctx) +{ + if (ftruncate(pack_data->pack_fd, to) + || lseek(pack_data->pack_fd, to, SEEK_SET) != to) + die_errno("cannot truncate pack to skip duplicate"); + pack_size = to; + + /* yes this is a layering violation */ + pack_file->total = to; + pack_file->offset = 0; + pack_file->ctx = *ctx; +} + +static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) +{ + size_t in_sz = 64 * 1024, out_sz = 64 * 1024; + unsigned char *in_buf = xmalloc(in_sz); + unsigned char *out_buf = xmalloc(out_sz); + struct object_entry *e; + unsigned char sha1[20]; + unsigned long hdrlen; + off_t offset; + git_SHA_CTX c; + git_SHA_CTX pack_file_ctx; + z_stream s; + int status = Z_OK; + + /* Determine if we should auto-checkpoint. */ + if ((max_packsize && (pack_size + 60 + len) > max_packsize) + || (pack_size + 60 + len) < pack_size) + cycle_packfile(); + + offset = pack_size; + + /* preserve the pack_file SHA1 ctx in case we have to truncate later */ + sha1flush(pack_file); + pack_file_ctx = pack_file->ctx; + + hdrlen = snprintf((char *)out_buf, out_sz, "blob %" PRIuMAX, len) + 1; + if (out_sz <= hdrlen) + die("impossibly large object header"); + + git_SHA1_Init(&c); + git_SHA1_Update(&c, out_buf, hdrlen); + + crc32_begin(pack_file); + + memset(&s, 0, sizeof(s)); + deflateInit(&s, pack_compression_level); + + hdrlen = encode_header(OBJ_BLOB, len, out_buf); + if (out_sz <= hdrlen) + die("impossibly large object header"); + + s.next_out = out_buf + hdrlen; + s.avail_out = out_sz - hdrlen; + + while (status != Z_STREAM_END) { + if (0 < len && !s.avail_in) { + size_t cnt = in_sz < len ? in_sz : (size_t)len; + size_t n = fread(in_buf, 1, cnt, stdin); + if (!n && feof(stdin)) + die("EOF in data (%" PRIuMAX " bytes remaining)", len); + + git_SHA1_Update(&c, in_buf, n); + s.next_in = in_buf; + s.avail_in = n; + len -= n; + } + + status = deflate(&s, len ? 0 : Z_FINISH); + + if (!s.avail_out || status == Z_STREAM_END) { + size_t n = s.next_out - out_buf; + sha1write(pack_file, out_buf, n); + pack_size += n; + s.next_out = out_buf; + s.avail_out = out_sz; + } + + switch (status) { + case Z_OK: + case Z_BUF_ERROR: + case Z_STREAM_END: + continue; + default: + die("unexpected deflate failure: %d", status); + } + } + deflateEnd(&s); + git_SHA1_Final(sha1, &c); + + if (sha1out) + hashcpy(sha1out, sha1); + + e = insert_object(sha1); + + if (mark) + insert_mark(mark, e); + + if (e->idx.offset) { + duplicate_count_by_type[OBJ_BLOB]++; + truncate_pack(offset, &pack_file_ctx); + + } else if (find_sha1_pack(sha1, packed_git)) { + e->type = OBJ_BLOB; + e->pack_id = MAX_PACK_ID; + e->idx.offset = 1; /* just not zero! */ + duplicate_count_by_type[OBJ_BLOB]++; + truncate_pack(offset, &pack_file_ctx); + + } else { + e->depth = 0; + e->type = OBJ_BLOB; + e->pack_id = pack_id; + e->idx.offset = offset; + e->idx.crc32 = crc32_end(pack_file); + object_count++; + object_count_by_type[OBJ_BLOB]++; + } + + free(in_buf); + free(out_buf); +} + /* All calls must be guarded by find_object() or find_mark() to * ensure the 'struct object_entry' passed was written by this * process instance. We unpack the entry by the offset, avoiding @@ -1189,6 +1297,7 @@ static void *gfi_unpack_entry( * the newly written data. */ close_pack_windows(p); + sha1flush(pack_file); /* We have to offer 20 bytes additional on the end of * the packfile as the core unpacker code assumes the @@ -1198,7 +1307,7 @@ static void *gfi_unpack_entry( */ p->pack_size = pack_size + 20; } - return unpack_entry(p, oe->offset, &type, sizep); + return unpack_entry(p, oe->idx.offset, &type, sizep); } static const char *get_mode(const char *str, uint16_t *modep) @@ -1329,7 +1438,7 @@ static void store_tree(struct tree_entry *root) if (S_ISDIR(root->versions[0].mode) && le && le->pack_id == pack_id) { mktree(t, 0, &old_tree); lo.data = old_tree; - lo.offset = le->offset; + lo.offset = le->idx.offset; lo.depth = t->delta_depth; } @@ -1587,7 +1696,7 @@ static void dump_marks_helper(FILE *f, for (k = 0; k < 1024; k++) { if (m->data.marked[k]) fprintf(f, ":%" PRIuMAX " %s\n", base + k, - sha1_to_hex(m->data.marked[k]->sha1)); + sha1_to_hex(m->data.marked[k]->idx.sha1)); } } } @@ -1598,13 +1707,13 @@ static void dump_marks(void) int mark_fd; FILE *f; - if (!mark_file) + if (!export_marks_file) return; - mark_fd = hold_lock_file_for_update(&mark_lock, mark_file, 0); + mark_fd = hold_lock_file_for_update(&mark_lock, export_marks_file, 0); if (mark_fd < 0) { failure |= error("Unable to write marks file %s: %s", - mark_file, strerror(errno)); + export_marks_file, strerror(errno)); return; } @@ -1613,7 +1722,7 @@ static void dump_marks(void) int saved_errno = errno; rollback_lock_file(&mark_lock); failure |= error("Unable to write marks file %s: %s", - mark_file, strerror(saved_errno)); + export_marks_file, strerror(saved_errno)); return; } @@ -1629,7 +1738,7 @@ static void dump_marks(void) int saved_errno = errno; rollback_lock_file(&mark_lock); failure |= error("Unable to write marks file %s: %s", - mark_file, strerror(saved_errno)); + export_marks_file, strerror(saved_errno)); return; } @@ -1637,11 +1746,47 @@ static void dump_marks(void) int saved_errno = errno; rollback_lock_file(&mark_lock); failure |= error("Unable to commit marks file %s: %s", - mark_file, strerror(saved_errno)); + export_marks_file, strerror(saved_errno)); return; } } +static void read_marks(void) +{ + char line[512]; + FILE *f = fopen(import_marks_file, "r"); + if (!f) + die_errno("cannot read '%s'", import_marks_file); + while (fgets(line, sizeof(line), f)) { + uintmax_t mark; + char *end; + unsigned char sha1[20]; + struct object_entry *e; + + end = strchr(line, '\n'); + if (line[0] != ':' || !end) + die("corrupt mark line: %s", line); + *end = 0; + mark = strtoumax(line + 1, &end, 10); + if (!mark || end == line + 1 + || *end != ' ' || get_sha1(end + 1, sha1)) + die("corrupt mark line: %s", line); + e = find_object(sha1); + if (!e) { + enum object_type type = sha1_object_info(sha1, NULL); + if (type < 0) + die("object not found: %s", sha1_to_hex(sha1)); + e = insert_object(sha1); + e->type = type; + e->pack_id = MAX_PACK_ID; + e->idx.offset = 1; /* just not zero! */ + } + insert_mark(mark, e); + } + fclose(f); +} + + static int read_next_command(void) { static int stdin_eof = 0; @@ -1662,6 +1807,12 @@ static int read_next_command(void) if (stdin_eof) return EOF; + if (!seen_data_command + && prefixcmp(command_buf.buf, "feature ") + && prefixcmp(command_buf.buf, "option ")) { + parse_argv(); + } + rc = rc_free; if (rc) rc_free = rc->next; @@ -1700,7 +1851,7 @@ static void parse_mark(void) next_mark = 0; } -static void parse_data(struct strbuf *sb) +static int parse_data(struct strbuf *sb, uintmax_t limit, uintmax_t *len_res) { strbuf_reset(sb); @@ -1724,9 +1875,15 @@ static void parse_data(struct strbuf *sb) free(term); } else { - size_t n = 0, length; + uintmax_t len = strtoumax(command_buf.buf + 5, NULL, 10); + size_t n = 0, length = (size_t)len; - length = strtoul(command_buf.buf + 5, NULL, 10); + if (limit && limit < len) { + *len_res = len; + return 0; + } + if (length < len) + die("data is too large to use in this context"); while (n < length) { size_t s = strbuf_fread(sb, length - n, stdin); @@ -1738,6 +1895,7 @@ static void parse_data(struct strbuf *sb) } skip_optional_lf(); + return 1; } static int validate_raw_date(const char *src, char *result, int maxlen) @@ -1802,14 +1960,32 @@ static char *parse_ident(const char *buf) return ident; } -static void parse_new_blob(void) +static void parse_and_store_blob( + struct last_object *last, + unsigned char *sha1out, + uintmax_t mark) { static struct strbuf buf = STRBUF_INIT; + uintmax_t len; + if (parse_data(&buf, big_file_threshold, &len)) + store_object(OBJ_BLOB, &buf, last, sha1out, mark); + else { + if (last) { + strbuf_release(&last->data); + last->offset = 0; + last->depth = 0; + } + stream_blob(len, sha1out, mark); + skip_optional_lf(); + } +} + +static void parse_new_blob(void) +{ read_next_command(); parse_mark(); - parse_data(&buf); - store_object(OBJ_BLOB, &buf, &last_blob, NULL, next_mark); + parse_and_store_blob(&last_blob, NULL, next_mark); } static void unload_one_branch(void) @@ -1856,6 +2032,109 @@ static void load_branch(struct branch *b) } } +static unsigned char convert_num_notes_to_fanout(uintmax_t num_notes) +{ + unsigned char fanout = 0; + while ((num_notes >>= 8)) + fanout++; + return fanout; +} + +static void construct_path_with_fanout(const char *hex_sha1, + unsigned char fanout, char *path) +{ + unsigned int i = 0, j = 0; + if (fanout >= 20) + die("Too large fanout (%u)", fanout); + while (fanout) { + path[i++] = hex_sha1[j++]; + path[i++] = hex_sha1[j++]; + path[i++] = '/'; + fanout--; + } + memcpy(path + i, hex_sha1 + j, 40 - j); + path[i + 40 - j] = '\0'; +} + +static uintmax_t do_change_note_fanout( + struct tree_entry *orig_root, struct tree_entry *root, + char *hex_sha1, unsigned int hex_sha1_len, + char *fullpath, unsigned int fullpath_len, + unsigned char fanout) +{ + struct tree_content *t = root->tree; + struct tree_entry *e, leaf; + unsigned int i, tmp_hex_sha1_len, tmp_fullpath_len; + uintmax_t num_notes = 0; + unsigned char sha1[20]; + char realpath[60]; + + for (i = 0; t && i < t->entry_count; i++) { + e = t->entries[i]; + tmp_hex_sha1_len = hex_sha1_len + e->name->str_len; + tmp_fullpath_len = fullpath_len; + + /* + * We're interested in EITHER existing note entries (entries + * with exactly 40 hex chars in path, not including directory + * separators), OR directory entries that may contain note + * entries (with < 40 hex chars in path). + * Also, each path component in a note entry must be a multiple + * of 2 chars. + */ + if (!e->versions[1].mode || + tmp_hex_sha1_len > 40 || + e->name->str_len % 2) + continue; + + /* This _may_ be a note entry, or a subdir containing notes */ + memcpy(hex_sha1 + hex_sha1_len, e->name->str_dat, + e->name->str_len); + if (tmp_fullpath_len) + fullpath[tmp_fullpath_len++] = '/'; + memcpy(fullpath + tmp_fullpath_len, e->name->str_dat, + e->name->str_len); + tmp_fullpath_len += e->name->str_len; + fullpath[tmp_fullpath_len] = '\0'; + + if (tmp_hex_sha1_len == 40 && !get_sha1_hex(hex_sha1, sha1)) { + /* This is a note entry */ + construct_path_with_fanout(hex_sha1, fanout, realpath); + if (!strcmp(fullpath, realpath)) { + /* Note entry is in correct location */ + num_notes++; + continue; + } + + /* Rename fullpath to realpath */ + if (!tree_content_remove(orig_root, fullpath, &leaf)) + die("Failed to remove path %s", fullpath); + tree_content_set(orig_root, realpath, + leaf.versions[1].sha1, + leaf.versions[1].mode, + leaf.tree); + } else if (S_ISDIR(e->versions[1].mode)) { + /* This is a subdir that may contain note entries */ + if (!e->tree) + load_tree(e); + num_notes += do_change_note_fanout(orig_root, e, + hex_sha1, tmp_hex_sha1_len, + fullpath, tmp_fullpath_len, fanout); + } + + /* The above may have reallocated the current tree_content */ + t = root->tree; + } + return num_notes; +} + +static uintmax_t change_note_fanout(struct tree_entry *root, + unsigned char fanout) +{ + char hex_sha1[40], path[60]; + return do_change_note_fanout(root, root, hex_sha1, 0, path, 0, fanout); +} + static void file_change_m(struct branch *b) { const char *p = command_buf.buf + 2; @@ -1885,7 +2164,7 @@ static void file_change_m(struct branch *b) if (*p == ':') { char *x; oe = find_mark(strtoumax(p + 1, &x, 10)); - hashcpy(sha1, oe->sha1); + hashcpy(sha1, oe->idx.sha1); p = x; } else if (!prefixcmp(p, "inline")) { inline_data = 1; @@ -1920,15 +2199,12 @@ static void file_change_m(struct branch *b) * another repository. */ } else 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); + parse_and_store_blob(&last_blob, sha1, 0); } else if (oe) { if (oe->type != OBJ_BLOB) die("Not a blob (actually a %s): %s", @@ -2006,12 +2282,95 @@ static void file_change_cr(struct branch *b, int rename) leaf.tree); } +static void note_change_n(struct branch *b, unsigned char old_fanout) +{ + 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]; + char path[60]; + uint16_t inline_data = 0; + unsigned char new_fanout; + + /* <dataref> or 'inline' */ + if (*p == ':') { + char *x; + oe = find_mark(strtoumax(p + 1, &x, 10)); + hashcpy(sha1, oe->idx.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->idx.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) { + if (p != uq.buf) { + strbuf_addstr(&uq, p); + p = uq.buf; + } + read_next_command(); + parse_and_store_blob(&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 if (!is_null_sha1(sha1)) { + 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); + } + + construct_path_with_fanout(sha1_to_hex(commit_sha1), old_fanout, path); + if (tree_content_remove(&b->branch_tree, path, NULL)) + b->num_notes--; + + if (is_null_sha1(sha1)) + return; /* nothing to insert */ + + b->num_notes++; + new_fanout = convert_num_notes_to_fanout(b->num_notes); + construct_path_with_fanout(sha1_to_hex(commit_sha1), new_fanout, path); + tree_content_set(&b->branch_tree, path, sha1, S_IFREG | 0644, NULL); +} + static void file_change_deleteall(struct branch *b) { release_tree_content_recursive(b->branch_tree.tree); hashclr(b->branch_tree.versions[0].sha1); hashclr(b->branch_tree.versions[1].sha1); load_tree(&b->branch_tree); + b->num_notes = 0; } static void parse_from_commit(struct branch *b, char *buf, unsigned long size) @@ -2068,7 +2427,7 @@ static int parse_from(struct branch *b) struct object_entry *oe = find_mark(idnum); if (oe->type != OBJ_COMMIT) die("Mark :%" PRIuMAX " not a commit", idnum); - hashcpy(b->sha1, oe->sha1); + hashcpy(b->sha1, oe->idx.sha1); if (oe->pack_id != MAX_PACK_ID) { unsigned long size; char *buf = gfi_unpack_entry(oe, &size); @@ -2103,7 +2462,7 @@ static struct hash_list *parse_merge(unsigned int *count) struct object_entry *oe = find_mark(idnum); if (oe->type != OBJ_COMMIT) die("Mark :%" PRIuMAX " not a commit", idnum); - hashcpy(n->sha1, oe->sha1); + hashcpy(n->sha1, oe->idx.sha1); } else if (!get_sha1(from, n->sha1)) { unsigned long size; char *buf = read_object_with_reference(n->sha1, @@ -2135,6 +2494,7 @@ static void parse_new_commit(void) char *committer = NULL; struct hash_list *merge_list = NULL; unsigned int merge_count; + unsigned char prev_fanout, new_fanout; /* Obtain the branch name from the rest of our command */ sp = strchr(command_buf.buf, ' ') + 1; @@ -2154,7 +2514,7 @@ static void parse_new_commit(void) } if (!committer) die("Expected committer but didn't get one"); - parse_data(&msg); + parse_data(&msg, 0, NULL); read_next_command(); parse_from(b); merge_list = parse_merge(&merge_count); @@ -2165,6 +2525,8 @@ static void parse_new_commit(void) load_branch(b); } + prev_fanout = convert_num_notes_to_fanout(b->num_notes); + /* file_change* */ while (command_buf.len > 0) { if (!prefixcmp(command_buf.buf, "M ")) @@ -2175,6 +2537,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, prev_fanout); else if (!strcmp("deleteall", command_buf.buf)) file_change_deleteall(b); else { @@ -2185,6 +2549,10 @@ static void parse_new_commit(void) break; } + new_fanout = convert_num_notes_to_fanout(b->num_notes); + if (new_fanout != prev_fanout) + b->num_notes = change_note_fanout(&b->branch_tree, new_fanout); + /* build the tree and the commit */ store_tree(&b->branch_tree); hashcpy(b->branch_tree.versions[0].sha1, @@ -2252,7 +2620,7 @@ static void parse_new_tag(void) from_mark = strtoumax(from + 1, NULL, 10); oe = find_mark(from_mark); type = oe->type; - hashcpy(sha1, oe->sha1); + hashcpy(sha1, oe->idx.sha1); } else if (!get_sha1(from, sha1)) { unsigned long size; char *buf; @@ -2273,7 +2641,7 @@ static void parse_new_tag(void) tagger = NULL; /* tag payload/message */ - parse_data(&msg); + parse_data(&msg, 0, NULL); /* build the tag object */ strbuf_reset(&new_data); @@ -2340,39 +2708,150 @@ static void parse_progress(void) skip_optional_lf(); } -static void import_marks(const char *input_file) +static char* make_fast_import_path(const char *path) { - char line[512]; - FILE *f = fopen(input_file, "r"); - if (!f) - die_errno("cannot read '%s'", input_file); - while (fgets(line, sizeof(line), f)) { - uintmax_t mark; - char *end; - unsigned char sha1[20]; - struct object_entry *e; + struct strbuf abs_path = STRBUF_INIT; - end = strchr(line, '\n'); - if (line[0] != ':' || !end) - die("corrupt mark line: %s", line); - *end = 0; - mark = strtoumax(line + 1, &end, 10); - if (!mark || end == line + 1 - || *end != ' ' || get_sha1(end + 1, sha1)) - die("corrupt mark line: %s", line); - e = find_object(sha1); - if (!e) { - enum object_type type = sha1_object_info(sha1, NULL); - if (type < 0) - die("object not found: %s", sha1_to_hex(sha1)); - e = insert_object(sha1); - e->type = type; - e->pack_id = MAX_PACK_ID; - e->offset = 1; /* just not zero! */ + if (!relative_marks_paths || is_absolute_path(path)) + return xstrdup(path); + strbuf_addf(&abs_path, "%s/info/fast-import/%s", get_git_dir(), path); + return strbuf_detach(&abs_path, NULL); +} + +static void option_import_marks(const char *marks, int from_stream) +{ + if (import_marks_file) { + if (from_stream) + die("Only one import-marks command allowed per stream"); + + /* read previous mark file */ + if(!import_marks_file_from_stream) + read_marks(); + } + + import_marks_file = make_fast_import_path(marks); + import_marks_file_from_stream = from_stream; +} + +static void option_date_format(const char *fmt) +{ + if (!strcmp(fmt, "raw")) + whenspec = WHENSPEC_RAW; + else if (!strcmp(fmt, "rfc2822")) + whenspec = WHENSPEC_RFC2822; + else if (!strcmp(fmt, "now")) + whenspec = WHENSPEC_NOW; + else + die("unknown --date-format argument %s", fmt); +} + +static void option_depth(const char *depth) +{ + max_depth = strtoul(depth, NULL, 0); + if (max_depth > MAX_DEPTH) + die("--depth cannot exceed %u", MAX_DEPTH); +} + +static void option_active_branches(const char *branches) +{ + max_active_branches = strtoul(branches, NULL, 0); +} + +static void option_export_marks(const char *marks) +{ + export_marks_file = make_fast_import_path(marks); +} + +static void option_export_pack_edges(const char *edges) +{ + if (pack_edges) + fclose(pack_edges); + pack_edges = fopen(edges, "a"); + if (!pack_edges) + die_errno("Cannot open '%s'", edges); +} + +static int parse_one_option(const char *option) +{ + if (!prefixcmp(option, "max-pack-size=")) { + unsigned long v; + if (!git_parse_ulong(option + 14, &v)) + return 0; + if (v < 8192) { + warning("max-pack-size is now in bytes, assuming --max-pack-size=%lum", v); + v *= 1024 * 1024; + } else if (v < 1024 * 1024) { + warning("minimum max-pack-size is 1 MiB"); + v = 1024 * 1024; } - insert_mark(mark, e); + max_packsize = v; + } else if (!prefixcmp(option, "big-file-threshold=")) { + unsigned long v; + if (!git_parse_ulong(option + 19, &v)) + return 0; + big_file_threshold = v; + } else if (!prefixcmp(option, "depth=")) { + option_depth(option + 6); + } else if (!prefixcmp(option, "active-branches=")) { + option_active_branches(option + 16); + } else if (!prefixcmp(option, "export-pack-edges=")) { + option_export_pack_edges(option + 18); + } else if (!prefixcmp(option, "quiet")) { + show_stats = 0; + } else if (!prefixcmp(option, "stats")) { + show_stats = 1; + } else { + return 0; } - fclose(f); + + return 1; +} + +static int parse_one_feature(const char *feature, int from_stream) +{ + if (!prefixcmp(feature, "date-format=")) { + option_date_format(feature + 12); + } else if (!prefixcmp(feature, "import-marks=")) { + option_import_marks(feature + 13, from_stream); + } else if (!prefixcmp(feature, "export-marks=")) { + option_export_marks(feature + 13); + } else if (!prefixcmp(feature, "relative-marks")) { + relative_marks_paths = 1; + } else if (!prefixcmp(feature, "no-relative-marks")) { + relative_marks_paths = 0; + } else if (!prefixcmp(feature, "force")) { + force_update = 1; + } else { + return 0; + } + + return 1; +} + +static void parse_feature(void) +{ + char *feature = command_buf.buf + 8; + + if (seen_data_command) + die("Got feature command '%s' after data command", feature); + + if (parse_one_feature(feature, 1)) + return; + + die("This version of fast-import does not support feature %s.", feature); +} + +static void parse_option(void) +{ + char *option = command_buf.buf + 11; + + if (seen_data_command) + die("Got option command '%s' after data command", option); + + if (parse_one_option(option)) + return; + + die("This version of fast-import does not support option: %s", option); } static int git_pack_config(const char *k, const char *v, void *cb) @@ -2393,18 +2872,62 @@ static int git_pack_config(const char *k, const char *v, void *cb) pack_compression_seen = 1; return 0; } + if (!strcmp(k, "pack.indexversion")) { + pack_idx_default_version = git_config_int(k, v); + if (pack_idx_default_version > 2) + die("bad pack.indexversion=%"PRIu32, + pack_idx_default_version); + return 0; + } + if (!strcmp(k, "pack.packsizelimit")) { + max_packsize = git_config_ulong(k, v); + return 0; + } + if (!strcmp(k, "core.bigfilethreshold")) { + long n = git_config_int(k, v); + big_file_threshold = 0 < n ? n : 0; + } return git_default_config(k, v, cb); } static const char fast_import_usage[] = -"git fast-import [--date-format=f] [--max-pack-size=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]"; +"git fast-import [--date-format=f] [--max-pack-size=n] [--big-file-threshold=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]"; + +static void parse_argv(void) +{ + unsigned int i; + + for (i = 1; i < global_argc; i++) { + const char *a = global_argv[i]; + + if (*a != '-' || !strcmp(a, "--")) + break; + + if (parse_one_option(a + 2)) + continue; + + if (parse_one_feature(a + 2, 0)) + continue; + + die("unknown option %s", a); + } + if (i != global_argc) + usage(fast_import_usage); + + seen_data_command = 1; + if (import_marks_file) + read_marks(); +} int main(int argc, const char **argv) { - unsigned int i, show_stats = 1; + unsigned int i; 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) @@ -2417,52 +2940,8 @@ int main(int argc, const char **argv) avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*)); marks = pool_calloc(1, sizeof(struct mark_set)); - for (i = 1; i < argc; i++) { - const char *a = argv[i]; - - if (*a != '-' || !strcmp(a, "--")) - break; - else if (!prefixcmp(a, "--date-format=")) { - const char *fmt = a + 14; - if (!strcmp(fmt, "raw")) - whenspec = WHENSPEC_RAW; - else if (!strcmp(fmt, "rfc2822")) - whenspec = WHENSPEC_RFC2822; - else if (!strcmp(fmt, "now")) - whenspec = WHENSPEC_NOW; - else - die("unknown --date-format argument %s", fmt); - } - else if (!prefixcmp(a, "--max-pack-size=")) - max_packsize = strtoumax(a + 16, NULL, 0) * 1024 * 1024; - else if (!prefixcmp(a, "--depth=")) { - max_depth = strtoul(a + 8, NULL, 0); - if (max_depth > MAX_DEPTH) - die("--depth cannot exceed %u", MAX_DEPTH); - } - else if (!prefixcmp(a, "--active-branches=")) - max_active_branches = strtoul(a + 18, NULL, 0); - else if (!prefixcmp(a, "--import-marks=")) - import_marks(a + 15); - else if (!prefixcmp(a, "--export-marks=")) - mark_file = a + 15; - else if (!prefixcmp(a, "--export-pack-edges=")) { - if (pack_edges) - fclose(pack_edges); - pack_edges = fopen(a + 20, "a"); - if (!pack_edges) - die_errno("Cannot open '%s'", a + 20); - } else if (!strcmp(a, "--force")) - force_update = 1; - else if (!strcmp(a, "--quiet")) - show_stats = 0; - else if (!strcmp(a, "--stats")) - show_stats = 1; - else - die("unknown option %s", a); - } - if (i != argc) - usage(fast_import_usage); + global_argc = argc; + global_argv = argv; rc_free = pool_alloc(cmd_save * sizeof(*rc_free)); for (i = 0; i < (cmd_save - 1); i++) @@ -2485,9 +2964,20 @@ int main(int argc, const char **argv) parse_checkpoint(); else if (!prefixcmp(command_buf.buf, "progress ")) parse_progress(); + else if (!prefixcmp(command_buf.buf, "feature ")) + parse_feature(); + else if (!prefixcmp(command_buf.buf, "option git ")) + parse_option(); + else if (!prefixcmp(command_buf.buf, "option ")) + /* ignore non-git options*/; else die("Unsupported command: %s", command_buf.buf); } + + /* argv hasn't been parsed yet, do so */ + if (!seen_data_command) + parse_argv(); + end_packfile(); dump_branches(); 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 75b71967a7..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) { @@ -25,11 +25,13 @@ p= pass it through git-apply patch-format= format the patch(es) are in reject pass it through git-apply resolvemsg= override error message when patch failure occurs -r,resolved to be used after a patch failure +continue continue applying patches after resolving a conflict +r,resolved synonyms for --continue skip skip the current patch abort restore the original branch and abort the patching operation. committer-date-is-author-date lie about committer date ignore-date use current timestamp for author date +rerere-autoupdate update the index with reused conflict resolution if possible rebasing* (internal use for git-rebase)" . git-sh-setup @@ -135,7 +137,7 @@ It does not apply to blobs recorded in its index." export GIT_MERGE_VERBOSITY=0 fi git-merge-recursive $orig_tree -- HEAD $his_tree || { - git rerere + git rerere $allow_rerere_autoupdate echo Failed to merge in the changes. exit 1 } @@ -204,7 +206,8 @@ check_patch_format () { # discarding the indented remainder of folded lines, # and see if it looks like that they all begin with the # header field names... - sed -n -e '/^$/q' -e '/^[ ]/d' -e p "$1" | + tr -d '\015' <"$1" | + sed -n -e '/^$/q' -e '/^[ ]/d' -e p | sane_egrep -v '^[!-9;-~]+:' >/dev/null || patch_format=mbox fi @@ -293,6 +296,7 @@ resolvemsg= resume= scissors= no_inbody_headers= git_apply_opt= committer_date_is_author_date= ignore_date= +allow_rerere_autoupdate= while test $# != 0 do @@ -315,7 +319,7 @@ do scissors=t ;; --no-scissors) scissors=f ;; - -r|--resolved) + -r|--resolved|--continue) resolved=t ;; --skip) skip=t ;; @@ -340,6 +344,8 @@ do committer_date_is_author_date=t ;; --ignore-date) ignore_date=t ;; + --rerere-autoupdate|--no-rerere-autoupdate) + allow_rerere_autoupdate="$1" ;; -q|--quiet) GIT_QUIET=t ;; --) @@ -657,7 +663,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 @@ -767,6 +776,5 @@ do go_next done -git gc --auto - rm -fr "$dotest" +git gc --auto 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..a3c4537366 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -96,6 +96,7 @@ #include <sys/poll.h> #include <sys/socket.h> #include <sys/ioctl.h> +#include <termios.h> #ifndef NO_SYS_SELECT_H #include <sys/select.h> #endif @@ -177,6 +178,9 @@ extern char *gitbasename(char *); #ifdef __GNUC__ #define NORETURN __attribute__((__noreturn__)) #define NORETURN_PTR __attribute__((__noreturn__)) +#elif defined(_MSC_VER) +#define NORETURN __declspec(noreturn) +#define NORETURN_PTR #else #define NORETURN #define NORETURN_PTR @@ -189,6 +193,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))); @@ -197,7 +202,7 @@ extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2))) extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params)); extern int prefixcmp(const char *str, const char *prefix); -extern time_t tm_to_time_t(const struct tm *tm); +extern int suffixcmp(const char *str, const char *suffix); static inline const char *skip_prefix(const char *str, const char *prefix) { @@ -343,6 +348,7 @@ extern void release_pack_memory(size_t, int); extern char *xstrdup(const char *str); extern void *xmalloc(size_t size); +extern void *xmallocz(size_t size); extern void *xmemdupz(const void *data, size_t len); extern char *xstrndup(const char *str, size_t len); extern void *xrealloc(void *ptr, size_t size); diff --git a/git-cvsimport.perl b/git-cvsimport.perl index a7d215c8aa..4853bf7a0d 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -609,16 +609,16 @@ $orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE}; my %index; # holds filenames of one index per branch unless (-d $git_dir) { - system("git-init"); + system(qw(git init)); die "Cannot init the GIT db at $git_tree: $?\n" if $?; - system("git-read-tree"); + system(qw(git read-tree)); die "Cannot init an empty tree: $?\n" if $?; $last_branch = $opt_o; $orig_branch = ""; } else { - open(F, "git-symbolic-ref HEAD |") or - die "Cannot run git-symbolic-ref: $!\n"; + open(F, "-|", qw(git symbolic-ref HEAD)) or + die "Cannot run git symbolic-ref: $!\n"; chomp ($last_branch = <F>); $last_branch = basename($last_branch); close(F); @@ -627,12 +627,12 @@ unless (-d $git_dir) { $last_branch = "master"; } $orig_branch = $last_branch; - $tip_at_start = `git-rev-parse --verify HEAD`; + $tip_at_start = `git rev-parse --verify HEAD`; # Get the last import timestamps my $fmt = '($ref, $author) = (%(refname), %(author));'; - open(H, "git-for-each-ref --perl --format='$fmt' $remote |") or - die "Cannot run git-for-each-ref: $!\n"; + my @cmd = ('git', 'for-each-ref', '--perl', "--format=$fmt", $remote); + open(H, "-|", @cmd) or die "Cannot run git for-each-ref: $!\n"; while (defined(my $entry = <H>)) { my ($ref, $author); eval($entry) || die "cannot eval refs list: $@"; @@ -687,7 +687,7 @@ unless ($opt_P) { print $cvspsfh $_; } close CVSPS; - $? == 0 or die "git-cvsimport: fatal: cvsps reported error\n"; + $? == 0 or die "git cvsimport: fatal: cvsps reported error\n"; close $cvspsfh; } else { $cvspsfile = munge_user_filename($opt_P); @@ -716,27 +716,27 @@ my $state = 0; sub update_index (\@\@) { my $old = shift; my $new = shift; - open(my $fh, '|-', qw(git-update-index -z --index-info)) - or die "unable to open git-update-index: $!"; + open(my $fh, '|-', qw(git update-index -z --index-info)) + or die "unable to open git update-index: $!"; print $fh (map { "0 0000000000000000000000000000000000000000\t$_\0" } @$old), (map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\0" } @$new) - or die "unable to write to git-update-index: $!"; + or die "unable to write to git update-index: $!"; close $fh - or die "unable to write to git-update-index: $!"; - $? and die "git-update-index reported error: $?"; + or die "unable to write to git update-index: $!"; + $? and die "git update-index reported error: $?"; } sub write_tree () { - open(my $fh, '-|', qw(git-write-tree)) - or die "unable to open git-write-tree: $!"; + open(my $fh, '-|', qw(git write-tree)) + or die "unable to open git write-tree: $!"; chomp(my $tree = <$fh>); is_sha1($tree) or die "Cannot get tree id ($tree): $!"; close($fh) - or die "Error running git-write-tree: $?\n"; + or die "Error running git write-tree: $?\n"; print "Tree ID $tree\n" if $opt_v; return $tree; } @@ -751,7 +751,7 @@ sub commit { if ($branch eq $opt_o && !$index{branch} && !get_headref("$remote/$branch")) { # looks like an initial commit - # use the index primed by git-init + # use the index primed by git init $ENV{GIT_INDEX_FILE} = "$git_dir/index"; $index{$branch} = "$git_dir/index"; } else { @@ -761,9 +761,9 @@ sub commit { $index{$branch} = tmpnam(); $ENV{GIT_INDEX_FILE} = $index{$branch}; if ($ancestor) { - system("git-read-tree", "$remote/$ancestor"); + system("git", "read-tree", "$remote/$ancestor"); } else { - system("git-read-tree", "$remote/$branch"); + system("git", "read-tree", "$remote/$branch"); } die "read-tree failed: $?\n" if $?; } @@ -798,7 +798,7 @@ sub commit { $ENV{GIT_COMMITTER_EMAIL} = $author_email; $ENV{GIT_COMMITTER_DATE} = $commit_date; my $pid = open2(my $commit_read, my $commit_write, - 'git-commit-tree', $tree, @commit_args); + 'git', 'commit-tree', $tree, @commit_args); # compatibility with git2cvs substr($logmsg,32767) = "" if length($logmsg) > 32767; @@ -811,7 +811,7 @@ sub commit { } print($commit_write "$logmsg\n") && close($commit_write) - or die "Error writing to git-commit-tree: $!\n"; + or die "Error writing to git commit-tree: $!\n"; print "Committed patch $patchset ($branch $commit_date)\n" if $opt_v; chomp(my $cid = <$commit_read>); @@ -820,9 +820,9 @@ sub commit { close($commit_read); waitpid($pid,0); - die "Error running git-commit-tree: $?\n" if $?; + die "Error running git commit-tree: $?\n" if $?; - system('git-update-ref', "$remote/$branch", $cid) == 0 + system('git' , 'update-ref', "$remote/$branch", $cid) == 0 or die "Cannot write branch $branch for update: $!\n"; if ($tag) { @@ -832,7 +832,7 @@ sub commit { $xtag =~ s/[\/]/$opt_s/g; $xtag =~ s/\[//g; - system('git-tag', '-f', $xtag, $cid) == 0 + system('git' , 'tag', '-f', $xtag, $cid) == 0 or die "Cannot create tag $xtag: $!\n"; print "Created tag '$xtag' on '$branch'\n" if $opt_v; @@ -969,7 +969,7 @@ while (<CVS>) { my $pid = open(my $F, '-|'); die $! unless defined $pid; if (!$pid) { - exec("git-hash-object", "-w", $tmpname) + exec("git", "hash-object", "-w", $tmpname) or die "Cannot create object: $!\n"; } my $sha = <$F>; @@ -993,7 +993,7 @@ while (<CVS>) { } commit(); if (($commitcount & 1023) == 0) { - system("git repack -a -d"); + system(qw(git repack -a -d)); } $state = 1; } elsif ($state == 11 and /^-+$/) { @@ -1013,11 +1013,11 @@ unless ($opt_P) { # The heuristic of repacking every 1024 commits can leave a # lot of unpacked data. If there is more than 1MB worth of # not-packed objects, repack once more. -my $line = `git-count-objects`; +my $line = `git count-objects`; if ($line =~ /^(\d+) objects, (\d+) kilobytes$/) { my ($n_objects, $kb) = ($1, $2); 1024 < $kb - and system("git repack -a -d"); + and system(qw(git repack -a -d)); } foreach my $git_index (values %index) { @@ -1038,28 +1038,28 @@ if ($orig_branch) { if ($opt_i) { exit 0; } - my $tip_at_end = `git-rev-parse --verify HEAD`; + my $tip_at_end = `git rev-parse --verify HEAD`; if ($tip_at_start ne $tip_at_end) { for ($tip_at_start, $tip_at_end) { chomp; } print "Fetched into the current branch.\n" if $opt_v; - system(qw(git-read-tree -u -m), + system(qw(git read-tree -u -m), $tip_at_start, $tip_at_end); die "Fast-forward update failed: $?\n" if $?; } else { - system(qw(git-merge cvsimport HEAD), "$remote/$opt_o"); + system(qw(git merge cvsimport HEAD), "$remote/$opt_o"); die "Could not merge $opt_o into the current branch.\n" if $?; } } else { $orig_branch = "master"; print "DONE; creating $orig_branch branch\n" if $opt_v; - system("git-update-ref", "refs/heads/master", "$remote/$opt_o") + system("git", "update-ref", "refs/heads/master", "$remote/$opt_o") unless defined get_headref('refs/heads/master'); - system("git-symbolic-ref", "$remote/HEAD", "$remote/$opt_o") + system("git", "symbolic-ref", "$remote/HEAD", "$remote/$opt_o") if ($opt_r && $opt_o ne 'HEAD'); - system('git-update-ref', 'HEAD', "$orig_branch"); + system('git', 'update-ref', 'HEAD', "$orig_branch"); unless ($opt_i) { - system('git checkout -f'); + system(qw(git checkout -f)); die "checkout failed: $?\n" if $?; } } diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 6dc45f5d45..13751db882 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -104,6 +104,7 @@ $log->info("--------------- STARTING -----------------"); my $usage = "Usage: git cvsserver [options] [pserver|server] [<directory> ...]\n". " --base-path <path> : Prepend to requested CVSROOT\n". + " Can be read from GIT_CVSSERVER_BASE_PATH\n". " --strict-paths : Don't allow recursing into subdirectories\n". " --export-all : Don't check for gitcvs.enabled in config\n". " --version, -V : Print version information and exit\n". @@ -111,7 +112,8 @@ my $usage = "\n". "<directory> ... is a list of allowed directories. If no directories\n". "are given, all are allowed. This is an additional restriction, gitcvs\n". - "access still needs to be enabled by the gitcvs.enabled config option.\n"; + "access still needs to be enabled by the gitcvs.enabled config option.\n". + "Alternately, one directory may be specified in GIT_CVSSERVER_ROOT.\n"; my @opts = ( 'help|h|H', 'version|V', 'base-path=s', 'strict-paths', 'export-all' ); @@ -148,6 +150,24 @@ if ($state->{'export-all'} && !@{$state->{allowed_roots}}) { die "--export-all can only be used together with an explicit whitelist\n"; } +# Environment handling for running under git-shell +if (exists $ENV{GIT_CVSSERVER_BASE_PATH}) { + if ($state->{'base-path'}) { + die "Cannot specify base path both ways.\n"; + } + my $base_path = $ENV{GIT_CVSSERVER_BASE_PATH}; + $state->{'base-path'} = $base_path; + $log->debug("Picked up base path '$base_path' from environment.\n"); +} +if (exists $ENV{GIT_CVSSERVER_ROOT}) { + if (@{$state->{allowed_roots}}) { + die "Cannot specify roots both ways: @ARGV\n"; + } + my $allowed_root = $ENV{GIT_CVSSERVER_ROOT}; + $state->{allowed_roots} = [ $allowed_root ]; + $log->debug("Picked up allowed root '$allowed_root' from environment.\n"); +} + # if we are called with a pserver argument, # deal with the authentication cat before entering the # main loop @@ -388,7 +408,7 @@ sub req_Directory $state->{localdir} = $data; $state->{repository} = $repository; $state->{path} = $repository; - $state->{path} =~ s/^$state->{CVSROOT}\///; + $state->{path} =~ s/^\Q$state->{CVSROOT}\E\///; $state->{module} = $1 if ($state->{path} =~ s/^(.*?)(\/|$)//); $state->{path} .= "/" if ( $state->{path} =~ /\S/ ); @@ -981,6 +1001,8 @@ sub req_update #$log->debug("update state : " . Dumper($state)); + my $last_dirname = "///"; + # foreach file specified on the command line ... foreach my $filename ( @{$state->{args}} ) { @@ -988,6 +1010,20 @@ sub req_update $log->debug("Processing file $filename"); + unless ( $state->{globaloptions}{-Q} || $state->{globaloptions}{-q} ) + { + my $cur_dirname = dirname($filename); + if ( $cur_dirname ne $last_dirname ) + { + $last_dirname = $cur_dirname; + if ( $cur_dirname eq "" ) + { + $cur_dirname = "."; + } + print "E cvs update: Updating $cur_dirname\n"; + } + } + # if we have a -C we should pretend we never saw modified stuff if ( exists ( $state->{opt}{C} ) ) { diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh index 57e8e3256d..524f5ea8ab 100755 --- a/git-difftool--helper.sh +++ b/git-difftool--helper.sh @@ -3,16 +3,16 @@ # This script is typically launched by using the 'git difftool' # convenience command. # -# Copyright (c) 2009 David Aguilar +# Copyright (c) 2009, 2010 David Aguilar -# Load common functions from git-mergetool--lib TOOL_MODE=diff . git-mergetool--lib # difftool.prompt controls the default prompt/no-prompt behavior # and is overridden with $GIT_DIFFTOOL*_PROMPT. should_prompt () { - prompt=$(git config --bool difftool.prompt || echo true) + prompt_merge=$(git config --bool mergetool.prompt || echo true) + prompt=$(git config --bool difftool.prompt || echo $prompt_merge) if test "$prompt" = true; then test -z "$GIT_DIFFTOOL_NO_PROMPT" else @@ -20,7 +20,11 @@ should_prompt () { fi } -# Sets up shell variables and runs a merge tool +# Indicates that --extcmd=... was specified +use_ext_cmd () { + test -n "$GIT_DIFFTOOL_EXTCMD" +} + launch_merge_tool () { # Merged is the filename as it appears in the work tree # Local is the contents of a/filename @@ -35,20 +39,28 @@ launch_merge_tool () { # the user with the real $MERGED name before launching $merge_tool. if should_prompt; then printf "\nViewing: '$MERGED'\n" - printf "Hit return to launch '%s': " "$merge_tool" + if use_ext_cmd; then + printf "Hit return to launch '%s': " \ + "$GIT_DIFFTOOL_EXTCMD" + else + printf "Hit return to launch '%s': " "$merge_tool" + fi read ans fi - # Run the appropriate merge tool command - run_merge_tool "$merge_tool" + if use_ext_cmd; then + eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"' + else + run_merge_tool "$merge_tool" + fi } -# Allow GIT_DIFF_TOOL and GIT_MERGE_TOOL to provide default values -test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL" -test -n "$GIT_DIFF_TOOL" && merge_tool="$GIT_DIFF_TOOL" - -if test -z "$merge_tool"; then - merge_tool="$(get_merge_tool)" || exit +if ! use_ext_cmd; then + if test -n "$GIT_DIFF_TOOL"; then + merge_tool="$GIT_DIFF_TOOL" + else + merge_tool="$(get_merge_tool)" || exit + fi fi # Launch the merge tool on each path provided by 'git diff' diff --git a/git-difftool.perl b/git-difftool.perl index ba5e60a45e..d975d072db 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -1,5 +1,5 @@ #!/usr/bin/env perl -# Copyright (c) 2009 David Aguilar +# Copyright (c) 2009, 2010 David Aguilar # # This is a wrapper around the GIT_EXTERNAL_DIFF-compatible # git-difftool--helper script. @@ -15,13 +15,17 @@ use warnings; use Cwd qw(abs_path); use File::Basename qw(dirname); +require Git; + my $DIR = abs_path(dirname($0)); sub usage { print << 'USAGE'; -usage: git difftool [--tool=<tool>] [-y|--no-prompt] ["git diff" options] +usage: git difftool [-t|--tool=<tool>] [-x|--extcmd=<cmd>] + [-y|--no-prompt] [-g|--gui] + ['git diff' options] USAGE exit 1; } @@ -63,6 +67,24 @@ sub generate_command $ENV{GIT_DIFF_TOOL} = substr($arg, 7); next; } + if ($arg eq '-x' || $arg eq '--extcmd') { + usage() if $#ARGV <= $idx; + $ENV{GIT_DIFFTOOL_EXTCMD} = $ARGV[$idx + 1]; + $skip_next = 1; + next; + } + if ($arg =~ /^--extcmd=/) { + $ENV{GIT_DIFFTOOL_EXTCMD} = substr($arg, 9); + next; + } + if ($arg eq '-g' || $arg eq '--gui') { + my $tool = Git::command_oneline('config', + 'diff.guitool'); + if (length($tool)) { + $ENV{GIT_DIFF_TOOL} = $tool; + } + next; + } if ($arg eq '-y' || $arg eq '--no-prompt') { $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true'; delete $ENV{GIT_DIFFTOOL_PROMPT}; diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 8ef1bde710..88fb0f070e 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/" : '\(.*[^/]\)/*$')/ @@ -200,7 +207,7 @@ t,) ,*) ;; *) - die "Cannot set --prune-empty and --filter-commit at the same time" + die "Cannot set --prune-empty and --commit-filter at the same time" esac case "$force" in @@ -252,20 +259,28 @@ test -s "$tempdir"/heads || GIT_INDEX_FILE="$(pwd)/../index" export GIT_INDEX_FILE -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" @@ -316,7 +331,7 @@ while read commit parents; do die "tree filter failed: $filter_tree" ( - git diff-index -r --name-only $commit && + git diff-index -r --name-only --ignore-submodules $commit && git ls-files --others ) > "$tempdir"/tree-state || exit git update-index --add --replace --remove --stdin \ @@ -345,19 +360,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 @@ -447,11 +462,11 @@ if [ "$filter_tag_name" ]; then "$new_sha1" "$new_ref" git cat-file tag "$ref" | sed -n \ - -e "1,/^$/{ + -e '1,/^$/{ /^object /d /^type /d /^tag /d - }" \ + }' \ -e '/^-----BEGIN PGP SIGNATURE-----/q' \ -e 'p' ) | git mktag) || diff --git a/git-gui/Makefile b/git-gui/Makefile index b3580e9e48..197b55edf3 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@ -7,7 +7,7 @@ all:: # TCL_PATH must be vaild for this to work. # -GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE +GIT-VERSION-FILE: FORCE @$(SHELL_PATH) ./GIT-VERSION-GEN -include GIT-VERSION-FILE @@ -270,7 +270,7 @@ TRACK_VARS = \ GITGUI_MACOSXAPP=$(GITGUI_MACOSXAPP) \ #end TRACK_VARS -GIT-GUI-VARS: .FORCE-GIT-GUI-VARS +GIT-GUI-VARS: FORCE @VARS='$(TRACK_VARS)'; \ if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \ echo 1>&2 " * new locations or Tcl/Tk interpreter"; \ @@ -340,5 +340,4 @@ ifdef GITGUI_WINDOWS_WRAPPER endif .PHONY: all install uninstall dist-version clean -.PHONY: .FORCE-GIT-VERSION-FILE -.PHONY: .FORCE-GIT-GUI-VARS +.PHONY: FORCE diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 14b92ba786..7d5451198c 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -121,6 +121,8 @@ unset oguimsg set _appname {Git Gui} set _gitdir {} +set _gitworktree {} +set _isbare {} set _gitexec {} set _githtmldir {} set _reponame {} @@ -276,6 +278,32 @@ proc get_config {name} { } } +proc is_bare {} { + global _isbare + global _gitdir + global _gitworktree + + if {$_isbare eq {}} { + if {[catch { + set _bare [git rev-parse --is-bare-repository] + switch -- $_bare { + true { set _isbare 1 } + false { set _isbare 0} + default { throw } + } + }]} { + if {[is_config_true core.bare] + || ($_gitworktree eq {} + && [lindex [file split $_gitdir] end] ne {.git})} { + set _isbare 1 + } else { + set _isbare 0 + } + } + } + return $_isbare +} + ###################################################################### ## ## handy utils @@ -649,12 +677,17 @@ if {[is_Windows]} { ## config defaults set cursor_ptr arrow -font create font_diff -family Courier -size 10 font create font_ui -catch { - label .dummy - eval font configure font_ui [font actual [.dummy cget -font]] - destroy .dummy +if {[lsearch -exact [font names] TkDefaultFont] != -1} { + eval [linsert [font actual TkDefaultFont] 0 font configure font_ui] + eval [linsert [font actual TkFixedFont] 0 font create font_diff] +} else { + font create font_diff -family Courier -size 10 + catch { + label .dummy + eval font configure font_ui [font actual [.dummy cget -font]] + destroy .dummy + } } font create font_uiitalic @@ -669,6 +702,9 @@ foreach class {Button Checkbutton Entry Label } if {![is_MacOSX]} { option add *Menu.font font_ui + option add *Entry.borderWidth 1 startupFile + option add *Entry.relief sunken startupFile + option add *RadioButton.anchor w startupFile } unset class @@ -721,6 +757,18 @@ proc apply_config {} { font configure ${font}bold -weight bold font configure ${font}italic -slant italic } + + global use_ttk NS + set use_ttk 0 + set NS {} + if {$repo_config(gui.usettk)} { + set use_ttk [package vsatisfies [package provide Tk] 8.5] + if {$use_ttk} { + set NS ttk + bind [winfo class .] <<ThemeChanged>> [list InitTheme] + pave_toplevel . + } + } } set default_config(branch.autosetupmerge) true @@ -745,6 +793,9 @@ 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 default_config(gui.usettk) 1 set font_descs { {fontui font_ui {mc "Main Font"}} {fontdiff font_diff {mc "Diff/Console Font"}} @@ -1072,6 +1123,8 @@ if {[catch { set _prefix {} }] && [catch { + # beware that from the .git dir this sets _gitdir to . + # and _prefix to the empty string set _gitdir [git rev-parse --git-dir] set _prefix [git rev-parse --show-prefix] } err]} { @@ -1080,6 +1133,14 @@ if {[catch { choose_repository::pick set picked 1 } + +# we expand the _gitdir when it's just a single dot (i.e. when we're being +# run from the .git dir itself) lest the routines to find the worktree +# get confused +if {$_gitdir eq "."} { + set _gitdir [pwd] +} + if {![file isdirectory $_gitdir] && [is_Cygwin]} { catch {set _gitdir [exec cygpath --windows $_gitdir]} } @@ -1088,25 +1149,41 @@ if {![file isdirectory $_gitdir]} { error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"] exit 1 } +# _gitdir exists, so try loading the config +load_config 0 +apply_config +# try to set work tree from environment, falling back to core.worktree +if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} { + set _gitworktree [get_config core.worktree] +} if {$_prefix ne {}} { - regsub -all {[^/]+/} $_prefix ../ cdup + if {$_gitworktree eq {}} { + regsub -all {[^/]+/} $_prefix ../ cdup + } else { + set cdup $_gitworktree + } if {[catch {cd $cdup} err]} { catch {wm withdraw .} error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"] exit 1 } + set _gitworktree [pwd] unset cdup } elseif {![is_enabled bare]} { - if {[lindex [file split $_gitdir] end] ne {.git}} { + if {[is_bare]} { catch {wm withdraw .} - error_popup [strcat [mc "Cannot use funny .git directory:"] "\n\n$_gitdir"] + error_popup [strcat [mc "Cannot use bare repository:"] "\n\n$_gitdir"] exit 1 } - if {[catch {cd [file dirname $_gitdir]} err]} { + if {$_gitworktree eq {}} { + set _gitworktree [file dirname $_gitdir] + } + if {[catch {cd $_gitworktree} err]} { catch {wm withdraw .} - error_popup [strcat [mc "No working directory"] " [file dirname $_gitdir]:\n\n$err"] + error_popup [strcat [mc "No working directory"] " $_gitworktree:\n\n$err"] exit 1 } + set _gitworktree [pwd] } set _reponame [file split [file normalize $_gitdir]] if {[lindex $_reponame end] eq {.git}} { @@ -1115,6 +1192,9 @@ if {[lindex $_reponame end] eq {.git}} { set _reponame [lindex $_reponame end] } +set env(GIT_DIR) $_gitdir +set env(GIT_WORK_TREE) $_gitworktree + ###################################################################### ## ## global init @@ -1132,6 +1212,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 @@ -1610,6 +1691,9 @@ proc merge_state {path new_state {head_info {}} {index_info {}}} { } elseif {$s0 ne {_} && [string index $state 0] eq {_} && $head_info eq {}} { set head_info $index_info + } elseif {$s0 eq {_} && [string index $state 0] ne {_}} { + set index_info $head_info + set head_info {} } set file_states($path) [list $s0$s1 $icon \ @@ -1698,10 +1782,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 +1799,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] @@ -1779,15 +1876,6 @@ static unsigned char file_fulltick_bits[] = { 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f}; } -maskdata $filemask -image create bitmap file_parttick -background white -foreground "#005050" -data { -#define parttick_width 14 -#define parttick_height 15 -static unsigned char parttick_bits[] = { - 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10, - 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10, - 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f}; -} -maskdata $filemask - image create bitmap file_question -background white -foreground black -data { #define file_question_width 14 #define file_question_height 15 @@ -1828,7 +1916,7 @@ set ui_index .vpane.files.index.list set ui_workdir .vpane.files.workdir.list set all_icons(_$ui_index) file_plain -set all_icons(A$ui_index) file_fulltick +set all_icons(A$ui_index) file_plain set all_icons(M$ui_index) file_fulltick set all_icons(D$ui_index) file_removed set all_icons(U$ui_index) file_merge @@ -1904,7 +1992,10 @@ proc incr_font_size {font {amt 1}} { set starting_gitk_msg [mc "Starting gitk... please wait..."] -proc do_gitk {revs} { +proc do_gitk {revs {is_submodule false}} { + global current_diff_path file_states current_diff_side ui_index + global _gitdir _gitworktree + # -- Always start gitk through whatever we were loaded with. This # lets us bypass using shell process on Windows systems. # @@ -1915,23 +2006,78 @@ proc do_gitk {revs} { } else { global env - if {[info exists env(GIT_DIR)]} { - set old_GIT_DIR $env(GIT_DIR) + set pwd [pwd] + + if {!$is_submodule} { + if {![is_bare]} { + cd $_gitworktree + } } else { - set old_GIT_DIR {} + cd $current_diff_path + if {$revs eq {--}} { + set s $file_states($current_diff_path) + set old_sha1 {} + set new_sha1 {} + switch -glob -- [lindex $s 0] { + M_ { set old_sha1 [lindex [lindex $s 2] 1] } + _M { set old_sha1 [lindex [lindex $s 3] 1] } + MM { + if {$current_diff_side eq $ui_index} { + set old_sha1 [lindex [lindex $s 2] 1] + set new_sha1 [lindex [lindex $s 3] 1] + } else { + set old_sha1 [lindex [lindex $s 3] 1] + } + } + } + set revs $old_sha1...$new_sha1 + } + # GIT_DIR and GIT_WORK_TREE for the submodule are not the ones + # we've been using for the main repository, so unset them. + # TODO we could make life easier (start up faster?) for gitk + # by setting these to the appropriate values to allow gitk + # to skip the heuristics to find their proper value + unset env(GIT_DIR) + unset env(GIT_WORK_TREE) } + eval exec $cmd $revs "--" "--" & + + set env(GIT_DIR) $_gitdir + set env(GIT_WORK_TREE) $_gitworktree + cd $pwd + + ui_status $::starting_gitk_msg + after 10000 { + ui_ready $starting_gitk_msg + } + } +} + +proc do_git_gui {} { + global current_diff_path + + # -- Always start git gui through whatever we were loaded with. This + # lets us bypass using shell process on Windows systems. + # + set exe [list [_which git]] + if {$exe eq {}} { + error_popup [mc "Couldn't find git gui in PATH"] + } else { + global env + global _gitdir _gitworktree + + # see note in do_gitk about unsetting these vars when + # running tools in a submodule + unset env(GIT_DIR) + unset env(GIT_WORK_TREE) set pwd [pwd] - cd [file dirname [gitdir]] - set env(GIT_DIR) [file tail [gitdir]] + cd $current_diff_path - eval exec $cmd $revs & + eval exec $exe gui & - if {$old_GIT_DIR eq {}} { - unset env(GIT_DIR) - } else { - set env(GIT_DIR) $old_GIT_DIR - } + set env(GIT_DIR) $_gitdir + set env(GIT_WORK_TREE) $_gitworktree cd $pwd ui_status $::starting_gitk_msg @@ -1942,6 +2088,7 @@ proc do_gitk {revs} { } proc do_explore {} { + global _gitworktree set explorer {} if {[is_Cygwin] || [is_Windows]} { set explorer "explorer.exe" @@ -1951,7 +2098,7 @@ proc do_explore {} { # freedesktop.org-conforming system is our best shot set explorer "xdg-open" } - eval exec $explorer [list [file nativename [file dirname [gitdir]]]] & + eval exec $explorer $_gitworktree & } set is_quitting 0 @@ -1967,7 +2114,7 @@ proc do_quit {{rc {1}}} { global ui_comm is_quitting repo_config commit_type global GITGUI_BCK_exists GITGUI_BCK_i global ui_comm_spell - global ret_code + global ret_code use_ttk if {$is_quitting} return set is_quitting 1 @@ -2010,10 +2157,28 @@ 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] - lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1] + if {$use_ttk} { + lappend cfg_geometry [.vpane sashpos 0] + lappend cfg_geometry [.vpane.files sashpos 0] + } else { + lappend cfg_geometry [lindex [.vpane sash coord 0] 0] + lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1] + } if {[catch {set rc_geometry $repo_config(gui.geometry)}]} { set rc_geometry {} } @@ -2023,6 +2188,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 . } @@ -2297,8 +2467,6 @@ proc show_less_context {} { ## ## ui construction -load_config 0 -apply_config set ui_comm {} # -- Menu Bar @@ -2330,10 +2498,12 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { # menu .mbar.repository -.mbar.repository add command \ - -label [mc "Explore Working Copy"] \ - -command {do_explore} -.mbar.repository add separator +if {![is_bare]} { + .mbar.repository add command \ + -label [mc "Explore Working Copy"] \ + -command {do_explore} + .mbar.repository add separator +} .mbar.repository add command \ -label [mc "Browse Current Branch's Files"] \ @@ -2509,12 +2679,14 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label [mc "Unstage From Commit"] \ - -command do_unstage_selection + -command do_unstage_selection \ + -accelerator $M1T-U lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label [mc "Revert Changes"] \ - -command do_revert_selection + -command do_revert_selection \ + -accelerator $M1T-J lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] @@ -2773,14 +2945,13 @@ default { # -- Branch Control # -frame .branch \ - -borderwidth 1 \ - -relief sunken -label .branch.l1 \ +${NS}::frame .branch +if {!$use_ttk} {.branch configure -borderwidth 1 -relief sunken} +${NS}::label .branch.l1 \ -text [mc "Current Branch:"] \ -anchor w \ -justify left -label .branch.cb \ +${NS}::label .branch.cb \ -textvariable current_branch \ -anchor w \ -justify left @@ -2790,15 +2961,20 @@ pack .branch -side top -fill x # -- Main Window Layout # -panedwindow .vpane -orient horizontal -panedwindow .vpane.files -orient vertical -.vpane add .vpane.files -sticky nsew -height 100 -width 200 +${NS}::panedwindow .vpane -orient horizontal +${NS}::panedwindow .vpane.files -orient vertical +if {$use_ttk} { + .vpane add .vpane.files +} else { + .vpane add .vpane.files -sticky nsew -height 100 -width 200 +} pack .vpane -anchor n -side top -fill both -expand 1 # -- Index File List # -frame .vpane.files.index -height 100 -width 200 -label .vpane.files.index.title -text [mc "Staged Changes (Will Commit)"] \ +${NS}::frame .vpane.files.index -height 100 -width 200 +tlabel .vpane.files.index.title \ + -text [mc "Staged Changes (Will Commit)"] \ -background lightgreen -foreground black text $ui_index -background white -foreground black \ -borderwidth 0 \ @@ -2808,8 +2984,8 @@ text $ui_index -background white -foreground black \ -xscrollcommand {.vpane.files.index.sx set} \ -yscrollcommand {.vpane.files.index.sy set} \ -state disabled -scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview] -scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview] +${NS}::scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview] +${NS}::scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview] pack .vpane.files.index.title -side top -fill x pack .vpane.files.index.sx -side bottom -fill x pack .vpane.files.index.sy -side right -fill y @@ -2817,8 +2993,8 @@ pack $ui_index -side left -fill both -expand 1 # -- Working Directory File List # -frame .vpane.files.workdir -height 100 -width 200 -label .vpane.files.workdir.title -text [mc "Unstaged Changes"] \ +${NS}::frame .vpane.files.workdir -height 100 -width 200 +tlabel .vpane.files.workdir.title -text [mc "Unstaged Changes"] \ -background lightsalmon -foreground black text $ui_workdir -background white -foreground black \ -borderwidth 0 \ @@ -2828,15 +3004,19 @@ text $ui_workdir -background white -foreground black \ -xscrollcommand {.vpane.files.workdir.sx set} \ -yscrollcommand {.vpane.files.workdir.sy set} \ -state disabled -scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview] -scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview] +${NS}::scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview] +${NS}::scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview] pack .vpane.files.workdir.title -side top -fill x pack .vpane.files.workdir.sx -side bottom -fill x pack .vpane.files.workdir.sy -side right -fill y pack $ui_workdir -side left -fill both -expand 1 -.vpane.files add .vpane.files.workdir -sticky nsew -.vpane.files add .vpane.files.index -sticky nsew +.vpane.files add .vpane.files.workdir +.vpane.files add .vpane.files.index +if {!$use_ttk} { + .vpane.files paneconfigure .vpane.files.workdir -sticky news + .vpane.files paneconfigure .vpane.files.index -sticky news +} foreach i [list $ui_index $ui_workdir] { rmsel_tag $i @@ -2846,68 +3026,69 @@ unset i # -- Diff and Commit Area # -frame .vpane.lower -height 300 -width 400 -frame .vpane.lower.commarea -frame .vpane.lower.diff -relief sunken -borderwidth 1 +${NS}::frame .vpane.lower -height 300 -width 400 +${NS}::frame .vpane.lower.commarea +${NS}::frame .vpane.lower.diff -relief sunken -borderwidth 1 pack .vpane.lower.diff -fill both -expand 1 pack .vpane.lower.commarea -side bottom -fill x -.vpane add .vpane.lower -sticky nsew +.vpane add .vpane.lower +if {!$use_ttk} {.vpane paneconfigure .vpane.lower -sticky nsew} # -- Commit Area Buttons # -frame .vpane.lower.commarea.buttons -label .vpane.lower.commarea.buttons.l -text {} \ +${NS}::frame .vpane.lower.commarea.buttons +${NS}::label .vpane.lower.commarea.buttons.l -text {} \ -anchor w \ -justify left pack .vpane.lower.commarea.buttons.l -side top -fill x pack .vpane.lower.commarea.buttons -side left -fill y -button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \ +${NS}::button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \ -command ui_do_rescan pack .vpane.lower.commarea.buttons.rescan -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.rescan conf -state} -button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \ +${NS}::button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \ -command do_add_all pack .vpane.lower.commarea.buttons.incall -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.incall conf -state} if {![is_enabled nocommitmsg]} { - button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \ + ${NS}::button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \ -command do_signoff pack .vpane.lower.commarea.buttons.signoff -side top -fill x } -button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \ +${NS}::button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \ -command do_commit pack .vpane.lower.commarea.buttons.commit -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.commit conf -state} if {![is_enabled nocommit]} { - button .vpane.lower.commarea.buttons.push -text [mc Push] \ + ${NS}::button .vpane.lower.commarea.buttons.push -text [mc Push] \ -command do_push_anywhere pack .vpane.lower.commarea.buttons.push -side top -fill x } # -- Commit Message Buffer # -frame .vpane.lower.commarea.buffer -frame .vpane.lower.commarea.buffer.header +${NS}::frame .vpane.lower.commarea.buffer +${NS}::frame .vpane.lower.commarea.buffer.header set ui_comm .vpane.lower.commarea.buffer.t set ui_coml .vpane.lower.commarea.buffer.header.l if {![is_enabled nocommit]} { - radiobutton .vpane.lower.commarea.buffer.header.new \ + ${NS}::radiobutton .vpane.lower.commarea.buffer.header.new \ -text [mc "New Commit"] \ -command do_select_commit_type \ -variable selected_commit_type \ -value new lappend disable_on_lock \ [list .vpane.lower.commarea.buffer.header.new conf -state] - radiobutton .vpane.lower.commarea.buffer.header.amend \ + ${NS}::radiobutton .vpane.lower.commarea.buffer.header.amend \ -text [mc "Amend Last Commit"] \ -command do_select_commit_type \ -variable selected_commit_type \ @@ -2916,7 +3097,7 @@ if {![is_enabled nocommit]} { [list .vpane.lower.commarea.buffer.header.amend conf -state] } -label $ui_coml \ +${NS}::label $ui_coml \ -anchor w \ -justify left proc trace_commit_type {varname args} { @@ -2948,7 +3129,7 @@ text $ui_comm -background white -foreground black \ -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \ -font font_diff \ -yscrollcommand {.vpane.lower.commarea.buffer.sby set} -scrollbar .vpane.lower.commarea.buffer.sby \ +${NS}::scrollbar .vpane.lower.commarea.buffer.sby \ -command [list $ui_comm yview] pack .vpane.lower.commarea.buffer.header -side top -fill x pack .vpane.lower.commarea.buffer.sby -side right -fill y @@ -3014,19 +3195,19 @@ proc trace_current_diff_path {varname args} { } trace add variable current_diff_path write trace_current_diff_path -frame .vpane.lower.diff.header -background gold -label .vpane.lower.diff.header.status \ +gold_frame .vpane.lower.diff.header +tlabel .vpane.lower.diff.header.status \ -background gold \ -foreground black \ -width $max_status_desc \ -anchor w \ -justify left -label .vpane.lower.diff.header.file \ +tlabel .vpane.lower.diff.header.file \ -background gold \ -foreground black \ -anchor w \ -justify left -label .vpane.lower.diff.header.path \ +tlabel .vpane.lower.diff.header.path \ -background gold \ -foreground black \ -anchor w \ @@ -3050,18 +3231,18 @@ bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y" # -- Diff Body # -frame .vpane.lower.diff.body +${NS}::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} \ -state disabled -scrollbar .vpane.lower.diff.body.sbx -orient horizontal \ +${NS}::scrollbar .vpane.lower.diff.body.sbx -orient horizontal \ -command [list $ui_diff xview] -scrollbar .vpane.lower.diff.body.sby -orient vertical \ +${NS}::scrollbar .vpane.lower.diff.body.sby -orient vertical \ -command [list $ui_diff yview] pack .vpane.lower.diff.body.sbx -side bottom -fill x pack .vpane.lower.diff.body.sby -side right -fill y @@ -3106,15 +3287,6 @@ $ui_diff tag raise sel proc create_common_diff_popup {ctxm} { $ctxm add command \ - -label [mc "Show Less Context"] \ - -command show_less_context - lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] - $ctxm add command \ - -label [mc "Show More Context"] \ - -command show_more_context - lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] - $ctxm add separator - $ctxm add command \ -label [mc Refresh] \ -command reshow_diff lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] @@ -3165,10 +3337,19 @@ set ui_diff_applyhunk [$ctxm index last] lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state] $ctxm add command \ -label [mc "Apply/Reverse Line"] \ - -command {apply_line $cursorX $cursorY; do_rescan} + -command {apply_range_or_line $cursorX $cursorY; do_rescan} set ui_diff_applyline [$ctxm index last] lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state] $ctxm add separator +$ctxm add command \ + -label [mc "Show Less Context"] \ + -command show_less_context +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add command \ + -label [mc "Show More Context"] \ + -command show_more_context +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add separator create_common_diff_popup $ctxm set ctxmmg .vpane.lower.diff.body.ctxmmg @@ -3191,9 +3372,40 @@ $ctxmmg add command \ -command {merge_resolve_one 1} lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state] $ctxmmg add separator +$ctxmmg add command \ + -label [mc "Show Less Context"] \ + -command show_less_context +lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state] +$ctxmmg add command \ + -label [mc "Show More Context"] \ + -command show_more_context +lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state] +$ctxmmg add separator create_common_diff_popup $ctxmmg -proc popup_diff_menu {ctxm ctxmmg x y X Y} { +set ctxmsm .vpane.lower.diff.body.ctxmsm +menu $ctxmsm -tearoff 0 +$ctxmsm add command \ + -label [mc "Visualize These Changes In The Submodule"] \ + -command {do_gitk -- true} +lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state] +$ctxmsm add command \ + -label [mc "Visualize Current Branch History In The Submodule"] \ + -command {do_gitk {} true} +lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state] +$ctxmsm add command \ + -label [mc "Visualize All Branch History In The Submodule"] \ + -command {do_gitk --all true} +lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state] +$ctxmsm add separator +$ctxmsm add command \ + -label [mc "Start git gui In The Submodule"] \ + -command {do_git_gui} +lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state] +$ctxmsm add separator +create_common_diff_popup $ctxmsm + +proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} { global current_diff_path file_states set ::cursorX $x set ::cursorY $y @@ -3204,13 +3416,24 @@ proc popup_diff_menu {ctxm ctxmmg x y X Y} { } if {[string first {U} $state] >= 0} { tk_popup $ctxmmg $X $Y + } elseif {$::is_submodule_diff} { + tk_popup $ctxmsm $X $Y } else { + set has_range [expr {[$::ui_diff tag nextrange sel 0.0] != {}}] if {$::ui_index eq $::current_diff_side} { set l [mc "Unstage Hunk From Commit"] - set t [mc "Unstage Line From Commit"] + if {$has_range} { + set t [mc "Unstage Lines From Commit"] + } else { + set t [mc "Unstage Line From Commit"] + } } else { set l [mc "Stage Hunk For Commit"] - set t [mc "Stage Line For Commit"] + if {$has_range} { + set t [mc "Stage Lines For Commit"] + } else { + set t [mc "Stage Line For Commit"] + } } if {$::is_3way_diff || $current_diff_path eq {} @@ -3227,7 +3450,7 @@ proc popup_diff_menu {ctxm ctxmmg x y X Y} { tk_popup $ctxm $X $Y } } -bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg %x %y %X %Y] +bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg $ctxmsm %x %y %X %Y] # -- Status Bar # @@ -3240,20 +3463,37 @@ $main_status show [mc "Initializing..."] catch { set gm $repo_config(gui.geometry) wm geometry . [lindex $gm 0] -.vpane sash place 0 \ - [lindex $gm 1] \ - [lindex [.vpane sash coord 0] 1] -.vpane.files sash place 0 \ - [lindex [.vpane.files sash coord 0] 0] \ - [lindex $gm 2] +if {$use_ttk} { + .vpane sashpos 0 [lindex $gm 1] + .vpane.files sashpos 0 [lindex $gm 2] +} else { + .vpane sash place 0 \ + [lindex $gm 1] \ + [lindex [.vpane sash coord 0] 1] + .vpane.files sash place 0 \ + [lindex [.vpane.files sash coord 0] 0] \ + [lindex $gm 2] +} 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} bind $ui_comm <$M1B-Key-t> {do_add_selection;break} bind $ui_comm <$M1B-Key-T> {do_add_selection;break} +bind $ui_comm <$M1B-Key-u> {do_unstage_selection;break} +bind $ui_comm <$M1B-Key-U> {do_unstage_selection;break} +bind $ui_comm <$M1B-Key-j> {do_revert_selection;break} +bind $ui_comm <$M1B-Key-J> {do_revert_selection;break} bind $ui_comm <$M1B-Key-i> {do_add_all;break} bind $ui_comm <$M1B-Key-I> {do_add_all;break} bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break} @@ -3310,6 +3550,8 @@ bind . <$M1B-Key-s> do_signoff bind . <$M1B-Key-S> do_signoff bind . <$M1B-Key-t> do_add_selection bind . <$M1B-Key-T> do_add_selection +bind . <$M1B-Key-j> do_revert_selection +bind . <$M1B-Key-J> do_revert_selection bind . <$M1B-Key-i> do_add_all bind . <$M1B-Key-I> do_add_all bind . <$M1B-Key-minus> {show_less_context;break} @@ -3328,7 +3570,7 @@ unset i set file_lists($ui_index) [list] set file_lists($ui_workdir) [list] -wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]" +wm title . "[appname] ([reponame]) [file normalize $_gitworktree]" focus -force $ui_comm # -- Warn the user about environmental problems. Cygwin's Tcl @@ -3501,3 +3743,9 @@ if {[is_enabled retcode]} { if {$picked && [is_config_true gui.autoexplore]} { do_explore } + +# Local variables: +# mode: tcl +# indent-tabs-mode: t +# tab-width: 4 +# End: diff --git a/git-gui/lib/about.tcl b/git-gui/lib/about.tcl index 241ab892cd..cfa50fca87 100644 --- a/git-gui/lib/about.tcl +++ b/git-gui/lib/about.tcl @@ -4,31 +4,26 @@ proc do_about {} { global appvers copyright oguilib global tcl_patchLevel tk_patchLevel - global ui_comm_spell + global ui_comm_spell NS use_ttk set w .about_dialog - toplevel $w + Dialog $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" pack [git_logo $w.git_logo] -side left -fill y -padx 10 -pady 10 - label $w.header -text [mc "About %s" [appname]] \ - -font font_uibold + ${NS}::label $w.header -text [mc "About %s" [appname]] \ + -font font_uibold -anchor center pack $w.header -side top -fill x - frame $w.buttons - button $w.buttons.close -text {Close} \ + ${NS}::frame $w.buttons + ${NS}::button $w.buttons.close -text {Close} \ -default active \ -command [list destroy $w] pack $w.buttons.close -side right pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - label $w.desc \ - -text "[mc "git-gui - a graphical user interface for Git."]\n$copyright" \ - -padx 5 -pady 5 \ - -justify left \ - -anchor w \ - -borderwidth 1 \ - -relief solid + paddedlabel $w.desc \ + -text "[mc "git-gui - a graphical user interface for Git."]\n$copyright" pack $w.desc -side top -fill x -padx 5 -pady 5 set v {} @@ -52,22 +47,10 @@ proc do_about {} { append d "git exec dir: [gitexec]\n" append d "git-gui lib: $oguilib" - label $w.vers \ - -text $v \ - -padx 5 -pady 5 \ - -justify left \ - -anchor w \ - -borderwidth 1 \ - -relief solid + paddedlabel $w.vers -text $v pack $w.vers -side top -fill x -padx 5 -pady 5 - label $w.dirs \ - -text $d \ - -padx 5 -pady 5 \ - -justify left \ - -anchor w \ - -borderwidth 1 \ - -relief solid + paddedlabel $w.dirs -text $d pack $w.dirs -side top -fill x -padx 5 -pady 5 menu $w.ctxm -tearoff 0 diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl index 1f3b08f9ef..786b50b8c2 100644 --- a/git-gui/lib/blame.tcl +++ b/git-gui/lib/blame.tcl @@ -61,7 +61,7 @@ field tooltip_timer {} ; # Current timer event for our tooltip field tooltip_commit {} ; # Commit(s) in tooltip constructor new {i_commit i_path i_jump} { - global cursor_ptr M1B M1T have_tk85 + global cursor_ptr M1B M1T have_tk85 use_ttk NS variable active_color variable group_colors @@ -73,15 +73,15 @@ constructor new {i_commit i_path i_jump} { set font_w [font measure font_diff "0"] - frame $w.header -background gold - label $w.header.commit_l \ + gold_frame $w.header + tlabel $w.header.commit_l \ -text [mc "Commit:"] \ -background gold \ -foreground black \ -anchor w \ -justify left set w_back $w.header.commit_b - label $w_back \ + tlabel $w_back \ -image ::blame::img_back_arrow \ -borderwidth 0 \ -relief flat \ @@ -94,20 +94,20 @@ constructor new {i_commit i_path i_jump} { [cb _history_menu] } " - label $w.header.commit \ + tlabel $w.header.commit \ -textvariable @commit \ -background gold \ -foreground black \ -anchor w \ -justify left - label $w.header.path_l \ + tlabel $w.header.path_l \ -text [mc "File:"] \ -background gold \ -foreground black \ -anchor w \ -justify left set w_path $w.header.path - label $w_path \ + tlabel $w_path \ -background gold \ -foreground black \ -anchor w \ @@ -209,10 +209,10 @@ constructor new {i_commit i_path i_jump} { set w_columns [list $w_amov $w_asim $w_line $w_file] - scrollbar $w.file_pane.out.sbx \ + ${NS}::scrollbar $w.file_pane.out.sbx \ -orient h \ -command [list $w_file xview] - scrollbar $w.file_pane.out.sby \ + ${NS}::scrollbar $w.file_pane.out.sby \ -orient v \ -command [list scrollbar2many $w_columns yview] eval grid $w_columns $w.file_pane.out.sby -sticky nsew @@ -254,10 +254,10 @@ constructor new {i_commit i_path i_jump} { -background $active_color \ -font font_ui $w_cviewer tag raise sel - scrollbar $w.file_pane.cm.sbx \ + ${NS}::scrollbar $w.file_pane.cm.sbx \ -orient h \ -command [list $w_cviewer xview] - scrollbar $w.file_pane.cm.sby \ + ${NS}::scrollbar $w.file_pane.cm.sby \ -orient v \ -command [list $w_cviewer yview] pack $w.file_pane.cm.sby -side right -fill y @@ -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/branch_checkout.tcl b/git-gui/lib/branch_checkout.tcl index 6603703ea1..2e459a8297 100644 --- a/git-gui/lib/branch_checkout.tcl +++ b/git-gui/lib/branch_checkout.tcl @@ -10,21 +10,24 @@ field opt_fetch 1; # refetch tracking branch if used? field opt_detach 0; # force a detached head case? constructor dialog {} { - make_toplevel top w + global use_ttk NS + make_dialog top w + wm withdraw $w wm title $top [append "[appname] ([reponame]): " [mc "Checkout Branch"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } - label $w.header -text [mc "Checkout Branch"] -font font_uibold + ${NS}::label $w.header -text [mc "Checkout Branch"] \ + -font font_uibold -anchor center pack $w.header -side top -fill x - frame $w.buttons - button $w.buttons.create -text [mc Checkout] \ + ${NS}::frame $w.buttons + ${NS}::button $w.buttons.create -text [mc Checkout] \ -default active \ -command [cb _checkout] pack $w.buttons.create -side right - button $w.buttons.cancel -text [mc Cancel] \ + ${NS}::button $w.buttons.cancel -text [mc Cancel] \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 @@ -33,14 +36,14 @@ constructor dialog {} { $w_rev bind_listbox <Double-Button-1> [cb _checkout] pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5 - labelframe $w.options -text [mc Options] + ${NS}::labelframe $w.options -text [mc Options] - checkbutton $w.options.fetch \ + ${NS}::checkbutton $w.options.fetch \ -text [mc "Fetch Tracking Branch"] \ -variable @opt_fetch pack $w.options.fetch -anchor nw - checkbutton $w.options.detach \ + ${NS}::checkbutton $w.options.detach \ -text [mc "Detach From Local Branch"] \ -variable @opt_detach pack $w.options.detach -anchor nw @@ -50,6 +53,7 @@ constructor dialog {} { bind $w <Visibility> [cb _visible] bind $w <Key-Escape> [list destroy $w] bind $w <Key-Return> [cb _checkout]\;break + wm deiconify $w tkwait window $w } diff --git a/git-gui/lib/branch_create.tcl b/git-gui/lib/branch_create.tcl index 3817771b94..4bb907705c 100644 --- a/git-gui/lib/branch_create.tcl +++ b/git-gui/lib/branch_create.tcl @@ -16,48 +16,48 @@ field opt_fetch 1; # refetch tracking branch if used? field reset_ok 0; # did the user agree to reset? constructor dialog {} { - global repo_config + global repo_config use_ttk NS - make_toplevel top w + make_dialog top w + wm withdraw $w wm title $top [append "[appname] ([reponame]): " [mc "Create Branch"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } - label $w.header -text [mc "Create New Branch"] -font font_uibold + ${NS}::label $w.header -text [mc "Create New Branch"] \ + -font font_uibold -anchor center pack $w.header -side top -fill x - frame $w.buttons - button $w.buttons.create -text [mc Create] \ + ${NS}::frame $w.buttons + ${NS}::button $w.buttons.create -text [mc Create] \ -default active \ -command [cb _create] pack $w.buttons.create -side right - button $w.buttons.cancel -text [mc Cancel] \ + ${NS}::button $w.buttons.cancel -text [mc Cancel] \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.desc -text [mc "Branch Name"] - radiobutton $w.desc.name_r \ - -anchor w \ + ${NS}::labelframe $w.desc -text [mc "Branch Name"] + ${NS}::radiobutton $w.desc.name_r \ -text [mc "Name:"] \ -value user \ -variable @name_type + if {!$use_ttk} {$w.desc.name_r configure -anchor w} set w_name $w.desc.name_t - entry $w_name \ - -borderwidth 1 \ - -relief sunken \ + ${NS}::entry $w_name \ -width 40 \ -textvariable @name \ -validate key \ -validatecommand [cb _validate %d %S] grid $w.desc.name_r $w_name -sticky we -padx {0 5} - radiobutton $w.desc.match_r \ - -anchor w \ + ${NS}::radiobutton $w.desc.match_r \ -text [mc "Match Tracking Branch Name"] \ -value match \ -variable @name_type + if {!$use_ttk} {$w.desc.match_r configure -anchor w} grid $w.desc.match_r -sticky we -padx {0 5} -columnspan 2 grid columnconfigure $w.desc 1 -weight 1 @@ -66,34 +66,34 @@ constructor dialog {} { set w_rev [::choose_rev::new $w.rev [mc "Starting Revision"]] pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5 - labelframe $w.options -text [mc Options] + ${NS}::labelframe $w.options -text [mc Options] - frame $w.options.merge - label $w.options.merge.l -text [mc "Update Existing Branch:"] + ${NS}::frame $w.options.merge + ${NS}::label $w.options.merge.l -text [mc "Update Existing Branch:"] pack $w.options.merge.l -side left - radiobutton $w.options.merge.no \ + ${NS}::radiobutton $w.options.merge.no \ -text [mc No] \ -value none \ -variable @opt_merge pack $w.options.merge.no -side left - radiobutton $w.options.merge.ff \ + ${NS}::radiobutton $w.options.merge.ff \ -text [mc "Fast Forward Only"] \ -value ff \ -variable @opt_merge pack $w.options.merge.ff -side left - radiobutton $w.options.merge.reset \ + ${NS}::radiobutton $w.options.merge.reset \ -text [mc Reset] \ -value reset \ -variable @opt_merge pack $w.options.merge.reset -side left pack $w.options.merge -anchor nw - checkbutton $w.options.fetch \ + ${NS}::checkbutton $w.options.fetch \ -text [mc "Fetch Tracking Branch"] \ -variable @opt_fetch pack $w.options.fetch -anchor nw - checkbutton $w.options.checkout \ + ${NS}::checkbutton $w.options.checkout \ -text [mc "Checkout After Creation"] \ -variable @opt_checkout pack $w.options.checkout -anchor nw @@ -109,6 +109,7 @@ constructor dialog {} { bind $w <Visibility> [cb _visible] bind $w <Key-Escape> [list destroy $w] bind $w <Key-Return> [cb _create]\;break + wm deiconify $w tkwait window $w } diff --git a/git-gui/lib/branch_delete.tcl b/git-gui/lib/branch_delete.tcl index 20d5e42307..867938ec6a 100644 --- a/git-gui/lib/branch_delete.tcl +++ b/git-gui/lib/branch_delete.tcl @@ -9,41 +9,40 @@ field w_check ; # revision picker for merge test field w_delete ; # delete button constructor dialog {} { - global current_branch + global current_branch use_ttk NS - make_toplevel top w + make_dialog top w + wm withdraw $w wm title $top [append "[appname] ([reponame]): " [mc "Delete Branch"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } - label $w.header -text [mc "Delete Local Branch"] -font font_uibold + ${NS}::label $w.header -text [mc "Delete Local Branch"] \ + -font font_uibold -anchor center pack $w.header -side top -fill x - frame $w.buttons + ${NS}::frame $w.buttons set w_delete $w.buttons.delete - button $w_delete \ + ${NS}::button $w_delete \ -text [mc Delete] \ -default active \ -state disabled \ -command [cb _delete] pack $w_delete -side right - button $w.buttons.cancel \ + ${NS}::button $w.buttons.cancel \ -text [mc Cancel] \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.list -text [mc "Local Branches"] + ${NS}::labelframe $w.list -text [mc "Local Branches"] set w_heads $w.list.l - listbox $w_heads \ + slistbox $w_heads \ -height 10 \ -width 70 \ -selectmode extended \ - -exportselection false \ - -yscrollcommand [list $w.list.sby set] - scrollbar $w.list.sby -command [list $w.list.l yview] - pack $w.list.sby -side right -fill y + -exportselection false pack $w.list.l -side left -fill both -expand 1 pack $w.list -fill both -expand 1 -pady 5 -padx 5 @@ -67,6 +66,7 @@ constructor dialog {} { " bind $w <Key-Escape> [list destroy $w] bind $w <Key-Return> [cb _delete]\;break + wm deiconify $w tkwait window $w } diff --git a/git-gui/lib/branch_rename.tcl b/git-gui/lib/branch_rename.tcl index 166538808f..63988773ba 100644 --- a/git-gui/lib/branch_rename.tcl +++ b/git-gui/lib/branch_rename.tcl @@ -8,9 +8,10 @@ field oldname field newname constructor dialog {} { - global current_branch + global current_branch use_ttk NS - make_toplevel top w + make_dialog top w + wm withdraw $w wm title $top [append "[appname] ([reponame]): " [mc "Rename Branch"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" @@ -19,27 +20,31 @@ constructor dialog {} { set oldname $current_branch set newname [get_config gui.newbranchtemplate] - label $w.header -text [mc "Rename Branch"] -font font_uibold + ${NS}::label $w.header -text [mc "Rename Branch"]\ + -font font_uibold -anchor center pack $w.header -side top -fill x - frame $w.buttons - button $w.buttons.rename -text [mc Rename] \ + ${NS}::frame $w.buttons + ${NS}::button $w.buttons.rename -text [mc Rename] \ -default active \ -command [cb _rename] pack $w.buttons.rename -side right - button $w.buttons.cancel -text [mc Cancel] \ + ${NS}::button $w.buttons.cancel -text [mc Cancel] \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - frame $w.rename - label $w.rename.oldname_l -text [mc "Branch:"] - eval tk_optionMenu $w.rename.oldname_m @oldname [load_all_heads] + ${NS}::frame $w.rename + ${NS}::label $w.rename.oldname_l -text [mc "Branch:"] + if {$use_ttk} { + ttk::combobox $w.rename.oldname_m -textvariable @oldname \ + -values [load_all_heads] -state readonly + } else { + eval tk_optionMenu $w.rename.oldname_m @oldname [load_all_heads] + } - label $w.rename.newname_l -text [mc "New Name:"] - entry $w.rename.newname_t \ - -borderwidth 1 \ - -relief sunken \ + ${NS}::label $w.rename.newname_l -text [mc "New Name:"] + ${NS}::entry $w.rename.newname_t \ -width 40 \ -textvariable @newname \ -validate key \ @@ -60,6 +65,7 @@ constructor dialog {} { $w.rename.newname_t icursor end focus $w.rename.newname_t " + wm deiconify $w tkwait window $w } diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl index 0410cc68df..c2415729e0 100644 --- a/git-gui/lib/browser.tcl +++ b/git-gui/lib/browser.tcl @@ -21,23 +21,23 @@ field browser_busy 1 field ls_buf {}; # Buffered record output from ls-tree constructor new {commit {path {}}} { - global cursor_ptr M1B - make_toplevel top w + global cursor_ptr M1B use_ttk NS + make_dialog top w + wm withdraw $top wm title $top [append "[appname] ([reponame]): " [mc "File Browser"]] set browser_commit $commit set browser_path $browser_commit:$path - label $w.path \ + ${NS}::label $w.path \ -textvariable @browser_path \ -anchor w \ -justify left \ - -borderwidth 1 \ - -relief sunken \ -font font_uibold + if {!$use_ttk} { $w.path configure -borderwidth 1 -relief sunken} pack $w.path -anchor w -side top -fill x - frame $w.list + ${NS}::frame $w.list set w_list $w.list.l text $w_list -background white -foreground black \ -borderwidth 0 \ @@ -49,19 +49,18 @@ constructor new {commit {path {}}} { -xscrollcommand [list $w.list.sbx set] \ -yscrollcommand [list $w.list.sby set] rmsel_tag $w_list - scrollbar $w.list.sbx -orient h -command [list $w_list xview] - scrollbar $w.list.sby -orient v -command [list $w_list yview] + ${NS}::scrollbar $w.list.sbx -orient h -command [list $w_list xview] + ${NS}::scrollbar $w.list.sby -orient v -command [list $w_list yview] pack $w.list.sbx -side bottom -fill x pack $w.list.sby -side right -fill y pack $w_list -side left -fill both -expand 1 pack $w.list -side top -fill both -expand 1 - label $w.status \ + ${NS}::label $w.status \ -textvariable @browser_status \ -anchor w \ - -justify left \ - -borderwidth 1 \ - -relief sunken + -justify left + if {!$use_ttk} { $w.status configure -borderwidth 1 -relief sunken} pack $w.status -anchor w -side bottom -fill x bind $w_list <Button-1> "[cb _click 0 @%x,%y];break" @@ -78,6 +77,7 @@ constructor new {commit {path {}}} { bind $w_list <Right> break bind $w_list <Visibility> [list focus $w_list] + wm deiconify $top set w $w_list if {$path ne {}} { _ls $this $browser_commit:$path $path @@ -263,23 +263,27 @@ field w ; # widget path field w_rev ; # mega-widget to pick the initial revision constructor dialog {} { - make_toplevel top w + global use_ttk NS + make_dialog top w + wm withdraw $top wm title $top [append "[appname] ([reponame]): " [mc "Browse Branch Files"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" + wm transient $top . } - label $w.header \ + ${NS}::label $w.header \ -text [mc "Browse Branch Files"] \ - -font font_uibold + -font font_uibold \ + -anchor center pack $w.header -side top -fill x - frame $w.buttons - button $w.buttons.browse -text [mc Browse] \ + ${NS}::frame $w.buttons + ${NS}::button $w.buttons.browse -text [mc Browse] \ -default active \ -command [cb _open] pack $w.buttons.browse -side right - button $w.buttons.cancel -text [mc Cancel] \ + ${NS}::button $w.buttons.cancel -text [mc Cancel] \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 @@ -291,6 +295,7 @@ constructor dialog {} { bind $w <Visibility> [cb _visible] bind $w <Key-Escape> [list destroy $w] bind $w <Key-Return> [cb _open]\;break + wm deiconify $top tkwait window $w } diff --git a/git-gui/lib/choose_font.tcl b/git-gui/lib/choose_font.tcl index 56443b042c..ebe50bd7d0 100644 --- a/git-gui/lib/choose_font.tcl +++ b/git-gui/lib/choose_font.tcl @@ -17,6 +17,7 @@ variable all_families [list] ; # All fonts known to Tk constructor pick {path title a_family a_size} { variable all_families + global use_ttk NS set v_family $a_family set v_size $a_size @@ -27,29 +28,30 @@ constructor pick {path title a_family a_size} { set f_family $pv_family set f_size $pv_size - make_toplevel top w + make_dialog top w + wm withdraw $top wm title $top "[appname] ([reponame]): $title" wm geometry $top "+[winfo rootx $path]+[winfo rooty $path]" - label $w.header -text $title -font font_uibold + ${NS}::label $w.header -text $title -font font_uibold -anchor center pack $w.header -side top -fill x - frame $w.buttons - button $w.buttons.select \ + ${NS}::frame $w.buttons + ${NS}::button $w.buttons.select \ -text [mc Select] \ -default active \ -command [cb _select] - button $w.buttons.cancel \ + ${NS}::button $w.buttons.cancel \ -text [mc Cancel] \ -command [list destroy $w] pack $w.buttons.select -side right pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - frame $w.inner + ${NS}::frame $w.inner - frame $w.inner.family - label $w.inner.family.l \ + ${NS}::frame $w.inner.family + ${NS}::label $w.inner.family.l \ -text [mc "Font Family"] \ -anchor w set w_family $w.inner.family.v @@ -64,16 +66,16 @@ constructor pick {path title a_family a_size} { -height 10 \ -yscrollcommand [list $w.inner.family.sby set] rmsel_tag $w_family - scrollbar $w.inner.family.sby -command [list $w_family yview] + ${NS}::scrollbar $w.inner.family.sby -command [list $w_family yview] pack $w.inner.family.l -side top -fill x pack $w.inner.family.sby -side right -fill y pack $w_family -fill both -expand 1 - frame $w.inner.size - label $w.inner.size.l \ + ${NS}::frame $w.inner.size + ${NS}::label $w.inner.size.l \ -text [mc "Font Size"] \ -anchor w - spinbox $w.inner.size.v \ + tspinbox $w.inner.size.v \ -textvariable @f_size \ -from 2 -to 80 -increment 1 \ -width 3 @@ -86,8 +88,8 @@ constructor pick {path title a_family a_size} { grid columnconfigure $w.inner 0 -weight 1 pack $w.inner -fill both -expand 1 -padx 5 -pady 5 - frame $w.example - label $w.example.l \ + ${NS}::frame $w.example + ${NS}::label $w.example.l \ -text [mc "Font Example"] \ -anchor w set w_example $w.example.t @@ -129,6 +131,7 @@ constructor pick {path title a_family a_size} { grab $w focus $w " + wm deiconify $w tkwait window $w } diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl index 633cc572bb..64f06748b6 100644 --- a/git-gui/lib/choose_repository.tcl +++ b/git-gui/lib/choose_repository.tcl @@ -22,9 +22,9 @@ field readtree_err ; # Error output from read-tree (if any) field sorted_recent ; # recent repositories (sorted) constructor pick {} { - global M1T M1B + global M1T M1B use_ttk NS - make_toplevel top w + make_dialog top w wm title $top [mc "Git Gui"] if {$top eq {.}} { @@ -71,11 +71,11 @@ constructor pick {} { set w_body $w.body set opts $w_body.options - frame $w_body + ${NS}::frame $w_body text $opts \ -cursor $::cursor_ptr \ -relief flat \ - -background [$w_body cget -background] \ + -background [get_bg_color $w_body] \ -wrap none \ -spacing1 5 \ -width 50 \ @@ -132,15 +132,15 @@ constructor pick {} { -label [mc "Recent Repositories"] } - label $w_body.space - label $w_body.recentlabel \ + ${NS}::label $w_body.space + ${NS}::label $w_body.recentlabel \ -anchor w \ -text [mc "Open Recent Repository:"] set w_recentlist $w_body.recentlist text $w_recentlist \ -cursor $::cursor_ptr \ -relief flat \ - -background [$w_body.recentlabel cget -background] \ + -background [get_bg_color $w_body.recentlabel] \ -wrap none \ -width 50 \ -height 10 @@ -176,10 +176,10 @@ constructor pick {} { } pack $w_body -fill x -padx 10 -pady 10 - frame $w.buttons + ${NS}::frame $w.buttons set w_next $w.buttons.next set w_quit $w.buttons.quit - button $w_quit \ + ${NS}::button $w_quit \ -text [mc "Quit"] \ -command exit pack $w_quit -side right -padx 5 @@ -203,6 +203,7 @@ constructor pick {} { wm deiconify $top tkwait variable @done + grab release $top if {$top eq {.}} { eval destroy [winfo children $top] } @@ -235,6 +236,8 @@ proc _get_recentrepos {} { foreach p [get_config gui.recentrepo] { if {[_is_git [file join $p .git]]} { lappend recent $p + } else { + _unset_recentrepo $p } } return [lsort $recent] @@ -243,6 +246,7 @@ proc _get_recentrepos {} { proc _unset_recentrepo {p} { regsub -all -- {([()\[\]{}\.^$+*?\\])} $p {\\\1} p git config --global --unset gui.recentrepo "^$p\$" + load_config 1 } proc _append_recentrepos {path} { @@ -261,6 +265,7 @@ proc _append_recentrepos {path} { lappend recent $path git config --global --add gui.recentrepo $path + load_config 1 while {[llength $recent] > 10} { _unset_recentrepo [lindex $recent 0] @@ -280,9 +285,10 @@ method _open_recent_path {p} { } method _next {action} { + global NS destroy $w_body if {![winfo exists $w_next]} { - button $w_next -default active + ${NS}::button $w_next -default active pack $w_next -side right -padx 5 -before $w_quit } _do_$action $this @@ -371,26 +377,25 @@ proc _objdir {path} { ## Create New Repository method _do_new {} { + global use_ttk NS $w_next conf \ -state disabled \ -command [cb _do_new2] \ -text [mc "Create"] - frame $w_body - label $w_body.h \ - -font font_uibold \ + ${NS}::frame $w_body + ${NS}::label $w_body.h \ + -font font_uibold -anchor center \ -text [mc "Create New Repository"] pack $w_body.h -side top -fill x -pady 10 pack $w_body -fill x -padx 10 - frame $w_body.where - label $w_body.where.l -text [mc "Directory:"] - entry $w_body.where.t \ + ${NS}::frame $w_body.where + ${NS}::label $w_body.where.l -text [mc "Directory:"] + ${NS}::entry $w_body.where.t \ -textvariable @local_path \ - -borderwidth 1 \ - -relief sunken \ -width 50 - button $w_body.where.b \ + ${NS}::button $w_body.where.b \ -text [mc "Browse"] \ -command [cb _new_local_path] set w_localpath $w_body.where.t @@ -456,63 +461,57 @@ proc _new_ok {p} { ## Clone Existing Repository method _do_clone {} { + global use_ttk NS $w_next conf \ -state disabled \ -command [cb _do_clone2] \ -text [mc "Clone"] - frame $w_body - label $w_body.h \ - -font font_uibold \ + ${NS}::frame $w_body + ${NS}::label $w_body.h \ + -font font_uibold -anchor center \ -text [mc "Clone Existing Repository"] pack $w_body.h -side top -fill x -pady 10 pack $w_body -fill x -padx 10 set args $w_body.args - frame $w_body.args + ${NS}::frame $w_body.args pack $args -fill both - label $args.origin_l -text [mc "Source Location:"] - entry $args.origin_t \ + ${NS}::label $args.origin_l -text [mc "Source Location:"] + ${NS}::entry $args.origin_t \ -textvariable @origin_url \ - -borderwidth 1 \ - -relief sunken \ -width 50 - button $args.origin_b \ + ${NS}::button $args.origin_b \ -text [mc "Browse"] \ -command [cb _open_origin] grid $args.origin_l $args.origin_t $args.origin_b -sticky ew - label $args.where_l -text [mc "Target Directory:"] - entry $args.where_t \ + ${NS}::label $args.where_l -text [mc "Target Directory:"] + ${NS}::entry $args.where_t \ -textvariable @local_path \ - -borderwidth 1 \ - -relief sunken \ -width 50 - button $args.where_b \ + ${NS}::button $args.where_b \ -text [mc "Browse"] \ -command [cb _new_local_path] grid $args.where_l $args.where_t $args.where_b -sticky ew set w_localpath $args.where_t - label $args.type_l -text [mc "Clone Type:"] - frame $args.type_f + ${NS}::label $args.type_l -text [mc "Clone Type:"] + ${NS}::frame $args.type_f set w_types [list] - lappend w_types [radiobutton $args.type_f.hardlink \ + lappend w_types [${NS}::radiobutton $args.type_f.hardlink \ -state disabled \ - -anchor w \ -text [mc "Standard (Fast, Semi-Redundant, Hardlinks)"] \ -variable @clone_type \ -value hardlink] - lappend w_types [radiobutton $args.type_f.full \ + lappend w_types [${NS}::radiobutton $args.type_f.full \ -state disabled \ - -anchor w \ -text [mc "Full Copy (Slower, Redundant Backup)"] \ -variable @clone_type \ -value full] - lappend w_types [radiobutton $args.type_f.shared \ + lappend w_types [${NS}::radiobutton $args.type_f.shared \ -state disabled \ - -anchor w \ -text [mc "Shared (Fastest, Not Recommended, No Backup)"] \ -variable @clone_type \ -value shared] @@ -1001,26 +1000,25 @@ method _postcheckout_wait {fd_ph} { ## Open Existing Repository method _do_open {} { + global NS $w_next conf \ -state disabled \ -command [cb _do_open2] \ -text [mc "Open"] - frame $w_body - label $w_body.h \ - -font font_uibold \ + ${NS}::frame $w_body + ${NS}::label $w_body.h \ + -font font_uibold -anchor center \ -text [mc "Open Existing Repository"] pack $w_body.h -side top -fill x -pady 10 pack $w_body -fill x -padx 10 - frame $w_body.where - label $w_body.where.l -text [mc "Repository:"] - entry $w_body.where.t \ + ${NS}::frame $w_body.where + ${NS}::label $w_body.where.l -text [mc "Repository:"] + ${NS}::entry $w_body.where.t \ -textvariable @local_path \ - -borderwidth 1 \ - -relief sunken \ -width 50 - button $w_body.where.b \ + ${NS}::button $w_body.where.b \ -text [mc "Browse"] \ -command [cb _open_local_path] diff --git a/git-gui/lib/choose_rev.tcl b/git-gui/lib/choose_rev.tcl index c8821c1463..c12d5e1698 100644 --- a/git-gui/lib/choose_rev.tcl +++ b/git-gui/lib/choose_rev.tcl @@ -10,7 +10,7 @@ field w_list ; # list of currently filtered specs field w_filter ; # filter entry for $w_list field c_expr {}; # current revision expression -field filter ; # current filter string +field filter ""; # current filter string field revtype head; # type of revision chosen field cur_specs [list]; # list of specs for $revtype field spec_head ; # list of all head specs @@ -32,7 +32,7 @@ proc new_unmerged {path {title {}}} { } constructor _new {path unmerged_only title} { - global current_branch is_detached + global current_branch is_detached use_ttk NS if {![info exists ::all_remotes]} { load_all_remotes @@ -41,65 +41,65 @@ constructor _new {path unmerged_only title} { set w $path if {$title ne {}} { - labelframe $w -text $title + ${NS}::labelframe $w -text $title } else { - frame $w + ${NS}::frame $w } bind $w <Destroy> [cb _delete %W] if {$is_detached} { - radiobutton $w.detachedhead_r \ - -anchor w \ + ${NS}::radiobutton $w.detachedhead_r \ -text [mc "This Detached Checkout"] \ -value HEAD \ -variable @revtype + if {!$use_ttk} {$w.detachedhead_r configure -anchor w} grid $w.detachedhead_r -sticky we -padx {0 5} -columnspan 2 } - radiobutton $w.expr_r \ + ${NS}::radiobutton $w.expr_r \ -text [mc "Revision Expression:"] \ -value expr \ -variable @revtype - entry $w.expr_t \ - -borderwidth 1 \ - -relief sunken \ + ${NS}::entry $w.expr_t \ -width 50 \ -textvariable @c_expr \ -validate key \ -validatecommand [cb _validate %d %S] grid $w.expr_r $w.expr_t -sticky we -padx {0 5} - frame $w.types - radiobutton $w.types.head_r \ + ${NS}::frame $w.types + ${NS}::radiobutton $w.types.head_r \ -text [mc "Local Branch"] \ -value head \ -variable @revtype pack $w.types.head_r -side left - radiobutton $w.types.trck_r \ + ${NS}::radiobutton $w.types.trck_r \ -text [mc "Tracking Branch"] \ -value trck \ -variable @revtype pack $w.types.trck_r -side left - radiobutton $w.types.tag_r \ + ${NS}::radiobutton $w.types.tag_r \ -text [mc "Tag"] \ -value tag \ -variable @revtype pack $w.types.tag_r -side left set w_filter $w.types.filter - entry $w_filter \ - -borderwidth 1 \ - -relief sunken \ + ${NS}::entry $w_filter \ -width 12 \ -textvariable @filter \ -validate key \ -validatecommand [cb _filter %P] pack $w_filter -side right - pack [label $w.types.filter_icon \ + pack [${NS}::label $w.types.filter_icon \ -image ::choose_rev::img_find \ ] -side right grid $w.types -sticky we -padx {0 5} -columnspan 2 - frame $w.list + if {$use_ttk} { + ttk::frame $w.list -style SListbox.TFrame -padding 2 + } else { + frame $w.list + } set w_list $w.list.l listbox $w_list \ -font font_diff \ @@ -109,6 +109,9 @@ constructor _new {path unmerged_only title} { -exportselection false \ -xscrollcommand [cb _sb_set $w.list.sbx h] \ -yscrollcommand [cb _sb_set $w.list.sby v] + if {$use_ttk} { + $w_list configure -relief flat -highlightthickness 0 -borderwidth 0 + } pack $w_list -fill both -expand 1 grid $w.list -sticky nswe -padx {20 5} -columnspan 2 bind $w_list <Any-Motion> [cb _show_tooltip @%x,%y] @@ -235,11 +238,12 @@ constructor _new {path unmerged_only title} { } method none {text} { + global NS use_ttk if {![winfo exists $w.none_r]} { - radiobutton $w.none_r \ - -anchor w \ + ${NS}::radiobutton $w.none_r \ -value none \ -variable @revtype + if {!$use_ttk} {$w.none_r configure -anchor w} grid $w.none_r -sticky we -padx {0 5} -columnspan 2 } $w.none_r configure -text $text @@ -425,6 +429,7 @@ method _delete {current} { } method _sb_set {sb orient first last} { + global NS set old_focus [focus -lastfor $w] if {$first == 0 && $last == 1} { @@ -440,10 +445,10 @@ method _sb_set {sb orient first last} { if {![winfo exists $sb]} { if {$orient eq {h}} { - scrollbar $sb -orient h -command [list $w_list xview] + ${NS}::scrollbar $sb -orient h -command [list $w_list xview] pack $sb -fill x -side bottom -before $w_list } else { - scrollbar $sb -orient v -command [list $w_list yview] + ${NS}::scrollbar $sb -orient v -command [list $w_list yview] pack $sb -fill y -side right -before $w_list } if {$old_focus ne {}} { diff --git a/git-gui/lib/class.tcl b/git-gui/lib/class.tcl index dc2141192a..c27b71476a 100644 --- a/git-gui/lib/class.tcl +++ b/git-gui/lib/class.tcl @@ -134,6 +134,13 @@ proc delete_this {{t {}}} { if {[namespace exists $t]} {namespace delete $t} } +proc make_dialog {t w args} { + upvar $t top $w pfx this this + global use_ttk + uplevel [linsert $args 0 make_toplevel $t $w] + pave_toplevel $pfx +} + proc make_toplevel {t w args} { upvar $t top $w pfx this this diff --git a/git-gui/lib/console.tcl b/git-gui/lib/console.tcl index c112464ec3..1f3248ffd1 100644 --- a/git-gui/lib/console.tcl +++ b/git-gui/lib/console.tcl @@ -27,20 +27,20 @@ constructor embed {path title} { } method _init {} { - global M1B + global M1B use_ttk NS if {$is_toplevel} { - make_toplevel top w -autodelete 0 + make_dialog top w -autodelete 0 wm title $top "[appname] ([reponame]): $t_short" } else { - frame $w + ${NS}::frame $w } set console_cr 1.0 set w_t $w.m.t - frame $w.m - label $w.m.l1 \ + ${NS}::frame $w.m + ${NS}::label $w.m.l1 \ -textvariable @t_long \ -anchor w \ -justify left \ @@ -78,7 +78,7 @@ method _init {} { " if {$is_toplevel} { - button $w.ok -text [mc "Close"] \ + ${NS}::button $w.ok -text [mc "Close"] \ -state disabled \ -command [list destroy $w] pack $w.ok -side bottom -anchor e -pady 10 -padx 10 @@ -206,13 +206,14 @@ method done {ok} { } method _sb_set {sb orient first last} { + global NS if {![winfo exists $sb]} { if {$first == $last || ($first == 0 && $last == 1)} return if {$orient eq {h}} { - scrollbar $sb -orient h -command [list $w_t xview] + ${NS}::scrollbar $sb -orient h -command [list $w_t xview] pack $sb -fill x -side bottom -before $w_t } else { - scrollbar $sb -orient v -command [list $w_t yview] + ${NS}::scrollbar $sb -orient v -command [list $w_t yview] pack $sb -fill y -side right -before $w_t } } diff --git a/git-gui/lib/database.tcl b/git-gui/lib/database.tcl index a18ac8b430..1f187ed286 100644 --- a/git-gui/lib/database.tcl +++ b/git-gui/lib/database.tcl @@ -2,6 +2,7 @@ # Copyright (C) 2006, 2007 Shawn Pearce proc do_stats {} { + global use_ttk NS set fd [git_read count-objects -v] while {[gets $fd line] > 0} { if {[regexp {^([^:]+): (\d+)$} $line _ name value]} { @@ -21,24 +22,22 @@ proc do_stats {} { } set w .stats_view - toplevel $w + Dialog $w + wm withdraw $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - label $w.header -text [mc "Database Statistics"] - pack $w.header -side top -fill x - - frame $w.buttons -border 1 - button $w.buttons.close -text [mc Close] \ + ${NS}::frame $w.buttons + ${NS}::button $w.buttons.close -text [mc Close] \ -default active \ -command [list destroy $w] - button $w.buttons.gc -text [mc "Compress Database"] \ + ${NS}::button $w.buttons.gc -text [mc "Compress Database"] \ -default normal \ -command "destroy $w;do_gc" pack $w.buttons.close -side right pack $w.buttons.gc -side left pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - frame $w.stat -borderwidth 1 -relief solid + ${NS}::labelframe $w.stat -text [mc "Database Statistics"] foreach s { {count {mc "Number of loose objects"}} {size {mc "Disk space used by loose objects"} { KiB}} @@ -55,8 +54,8 @@ proc do_stats {} { set value "$value[lindex $s 2]" } - label $w.stat.l_$name -text "$label:" -anchor w - label $w.stat.v_$name -text $value -anchor w + ${NS}::label $w.stat.l_$name -text "$label:" -anchor w + ${NS}::label $w.stat.v_$name -text $value -anchor w grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5} } pack $w.stat -pady 10 -padx 10 @@ -65,6 +64,7 @@ proc do_stats {} { bind $w <Key-Escape> [list destroy $w] bind $w <Key-Return> [list destroy $w] wm title $w [append "[appname] ([reponame]): " [mc "Database Statistics"]] + wm deiconify $w tkwait window $w } @@ -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..ec8c11eeb7 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 {} @@ -280,6 +281,15 @@ proc start_show_diff {cont_info {add_opts {}}} { } } + if {[string match {160000 *} [lindex $s 2]] + || [string match {160000 *} [lindex $s 3]]} { + set is_submodule_diff 1 + + if {[git-version >= "1.6.6"]} { + lappend cmd --submodule + } + } + lappend cmd -p lappend cmd --no-color if {$repo_config(gui.diffcontext) >= 1} { @@ -295,6 +305,14 @@ proc start_show_diff {cont_info {add_opts {}}} { lappend cmd $path } + if {$is_submodule_diff && [git-version < "1.6.6"]} { + 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 +330,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 +392,25 @@ proc read_diff {fd cont_info} { set tags {} } } + } elseif {$is_submodule_diff} { + if {$line == ""} continue + if {[regexp {^Submodule } $line]} { + set tags d_@ + } elseif {[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 { @@ -505,10 +542,23 @@ proc apply_hunk {x y} { } } -proc apply_line {x y} { +proc apply_range_or_line {x y} { global current_diff_path current_diff_header current_diff_side global ui_diff ui_index file_states + set selected [$ui_diff tag nextrange sel 0.0] + + if {$selected == {}} { + set first [$ui_diff index "@$x,$y"] + set last $first + } else { + set first [lindex $selected 0] + set last [lindex $selected 1] + } + + set first_l [$ui_diff index "$first linestart"] + set last_l [$ui_diff index "$last lineend"] + if {$current_diff_path eq {} || $current_diff_header eq {}} return if {![lock_index apply_hunk]} return @@ -531,119 +581,147 @@ proc apply_line {x y} { } } - set the_l [$ui_diff index @$x,$y] - - # operate only on change lines - set c1 [$ui_diff get "$the_l linestart"] - if {$c1 ne {+} && $c1 ne {-}} { - unlock_index - return - } - set sign $c1 + set wholepatch {} - set i_l [$ui_diff search -backwards -regexp ^@@ $the_l 0.0] - if {$i_l eq {}} { - unlock_index - return - } - # $i_l is now at the beginning of a line + while {$first_l < $last_l} { + set i_l [$ui_diff search -backwards -regexp ^@@ $first_l 0.0] + if {$i_l eq {}} { + # If there's not a @@ above, then the selected range + # must have come before the first_l @@ + set i_l [$ui_diff search -regexp ^@@ $first_l $last_l] + } + if {$i_l eq {}} { + unlock_index + return + } + # $i_l is now at the beginning of a line - # pick start line number from hunk header - set hh [$ui_diff get $i_l "$i_l + 1 lines"] - set hh [lindex [split $hh ,] 0] - set hln [lindex [split $hh -] 1] + # pick start line number from hunk header + set hh [$ui_diff get $i_l "$i_l + 1 lines"] + set hh [lindex [split $hh ,] 0] + set hln [lindex [split $hh -] 1] - # There is a special situation to take care of. Consider this hunk: - # - # @@ -10,4 +10,4 @@ - # context before - # -old 1 - # -old 2 - # +new 1 - # +new 2 - # context after - # - # We used to keep the context lines in the order they appear in the - # hunk. But then it is not possible to correctly stage only - # "-old 1" and "+new 1" - it would result in this staged text: - # - # context before - # old 2 - # new 1 - # context after - # - # (By symmetry it is not possible to *un*stage "old 2" and "new 2".) - # - # We resolve the problem by introducing an asymmetry, namely, when - # a "+" line is *staged*, it is moved in front of the context lines - # that are generated from the "-" lines that are immediately before - # the "+" block. That is, we construct this patch: - # - # @@ -10,4 +10,5 @@ - # context before - # +new 1 - # old 1 - # old 2 - # context after - # - # But we do *not* treat "-" lines that are *un*staged in a special - # way. - # - # With this asymmetry it is possible to stage the change - # "old 1" -> "new 1" directly, and to stage the change - # "old 2" -> "new 2" by first staging the entire hunk and - # then unstaging the change "old 1" -> "new 1". - - # This is non-empty if and only if we are _staging_ changes; - # then it accumulates the consecutive "-" lines (after converting - # them to context lines) in order to be moved after the "+" change - # line. - set pre_context {} - - set n 0 - set i_l [$ui_diff index "$i_l + 1 lines"] - set patch {} - while {[$ui_diff compare $i_l < "end - 1 chars"] && - [$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} { - set next_l [$ui_diff index "$i_l + 1 lines"] - set c1 [$ui_diff get $i_l] - if {[$ui_diff compare $i_l <= $the_l] && - [$ui_diff compare $the_l < $next_l]} { - # the line to stage/unstage - set ln [$ui_diff get $i_l $next_l] - if {$c1 eq {-}} { - set n [expr $n+1] + # There is a special situation to take care of. Consider this + # hunk: + # + # @@ -10,4 +10,4 @@ + # context before + # -old 1 + # -old 2 + # +new 1 + # +new 2 + # context after + # + # We used to keep the context lines in the order they appear in + # the hunk. But then it is not possible to correctly stage only + # "-old 1" and "+new 1" - it would result in this staged text: + # + # context before + # old 2 + # new 1 + # context after + # + # (By symmetry it is not possible to *un*stage "old 2" and "new + # 2".) + # + # We resolve the problem by introducing an asymmetry, namely, + # when a "+" line is *staged*, it is moved in front of the + # context lines that are generated from the "-" lines that are + # immediately before the "+" block. That is, we construct this + # patch: + # + # @@ -10,4 +10,5 @@ + # context before + # +new 1 + # old 1 + # old 2 + # context after + # + # But we do *not* treat "-" lines that are *un*staged in a + # special way. + # + # With this asymmetry it is possible to stage the change "old + # 1" -> "new 1" directly, and to stage the change "old 2" -> + # "new 2" by first staging the entire hunk and then unstaging + # the change "old 1" -> "new 1". + # + # Applying multiple lines adds complexity to the special + # situation. The pre_context must be moved after the entire + # first block of consecutive staged "+" lines, so that + # staging both additions gives the following patch: + # + # @@ -10,4 +10,6 @@ + # context before + # +new 1 + # +new 2 + # old 1 + # old 2 + # context after + + # This is non-empty if and only if we are _staging_ changes; + # then it accumulates the consecutive "-" lines (after + # converting them to context lines) in order to be moved after + # "+" change lines. + set pre_context {} + + set n 0 + set m 0 + set i_l [$ui_diff index "$i_l + 1 lines"] + set patch {} + while {[$ui_diff compare $i_l < "end - 1 chars"] && + [$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} { + set next_l [$ui_diff index "$i_l + 1 lines"] + set c1 [$ui_diff get $i_l] + if {[$ui_diff compare $first_l <= $i_l] && + [$ui_diff compare $i_l < $last_l] && + ($c1 eq {-} || $c1 eq {+})} { + # a line to stage/unstage + set ln [$ui_diff get $i_l $next_l] + if {$c1 eq {-}} { + set n [expr $n+1] + set patch "$patch$pre_context$ln" + set pre_context {} + } else { + set m [expr $m+1] + set patch "$patch$ln" + } + } elseif {$c1 ne {-} && $c1 ne {+}} { + # context line + set ln [$ui_diff get $i_l $next_l] set patch "$patch$pre_context$ln" + set n [expr $n+1] + set m [expr $m+1] + set pre_context {} + } elseif {$c1 eq $to_context} { + # turn change line into context line + set ln [$ui_diff get "$i_l + 1 chars" $next_l] + if {$c1 eq {-}} { + set pre_context "$pre_context $ln" + } else { + set patch "$patch $ln" + } + set n [expr $n+1] + set m [expr $m+1] } else { - set patch "$patch$ln$pre_context" - } - set pre_context {} - } elseif {$c1 ne {-} && $c1 ne {+}} { - # context line - set ln [$ui_diff get $i_l $next_l] - set patch "$patch$pre_context$ln" - set n [expr $n+1] - set pre_context {} - } elseif {$c1 eq $to_context} { - # turn change line into context line - set ln [$ui_diff get "$i_l + 1 chars" $next_l] - if {$c1 eq {-}} { - set pre_context "$pre_context $ln" - } else { - set patch "$patch $ln" + # a change in the opposite direction of + # to_context which is outside the range of + # lines to apply. + set patch "$patch$pre_context" + set pre_context {} } - set n [expr $n+1] + set i_l $next_l } - set i_l $next_l + set patch "$patch$pre_context" + set wholepatch "$wholepatch@@ -$hln,$n +$hln,$m @@\n$patch" + set first_l [$ui_diff index "$next_l + 1 lines"] } - set patch "@@ -$hln,$n +$hln,[eval expr $n $sign 1] @@\n$patch" if {[catch { set enc [get_path_encoding $current_diff_path] set p [eval git_write $apply_cmd] fconfigure $p -translation binary -encoding $enc puts -nonewline $p $current_diff_header - puts -nonewline $p $patch + puts -nonewline $p $wholepatch close $p} err]} { error_popup [append $failed_msg "\n\n$err"] } diff --git a/git-gui/lib/error.tcl b/git-gui/lib/error.tcl index 75650157e5..c0fa69af56 100644 --- a/git-gui/lib/error.tcl +++ b/git-gui/lib/error.tcl @@ -71,11 +71,13 @@ proc ask_popup {msg} { } proc hook_failed_popup {hook msg {is_fatal 1}} { + global use_ttk NS set w .hookfail - toplevel $w + Dialog $w + wm withdraw $w - frame $w.m - label $w.m.l1 -text "$hook hook failed:" \ + ${NS}::frame $w.m + ${NS}::label $w.m.l1 -text "$hook hook failed:" \ -anchor w \ -justify left \ -font font_uibold @@ -87,10 +89,10 @@ proc hook_failed_popup {hook msg {is_fatal 1}} { -width 80 -height 10 \ -font font_diff \ -yscrollcommand [list $w.m.sby set] - scrollbar $w.m.sby -command [list $w.m.t yview] + ${NS}::scrollbar $w.m.sby -command [list $w.m.t yview] pack $w.m.l1 -side top -fill x if {$is_fatal} { - label $w.m.l2 \ + ${NS}::label $w.m.l2 \ -text [mc "You must correct the above errors before committing."] \ -anchor w \ -justify left \ @@ -104,7 +106,7 @@ proc hook_failed_popup {hook msg {is_fatal 1}} { $w.m.t insert 1.0 $msg $w.m.t conf -state disabled - button $w.ok -text OK \ + ${NS}::button $w.ok -text OK \ -width 15 \ -command "destroy $w" pack $w.ok -side bottom -anchor e -pady 10 -padx 10 @@ -112,5 +114,6 @@ proc hook_failed_popup {hook msg {is_fatal 1}} { bind $w <Visibility> "grab $w; focus $w" bind $w <Key-Return> "destroy $w" wm title $w [strcat "[appname] ([reponame]): " [mc "error"]] + wm deiconify $w tkwait window $w } diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl index d33896a0ce..e9db0c4989 100644 --- a/git-gui/lib/index.tcl +++ b/git-gui/lib/index.tcl @@ -8,36 +8,41 @@ proc _delete_indexlock {} { } proc _close_updateindex {fd after} { + global use_ttk NS fconfigure $fd -blocking 1 if {[catch {close $fd} err]} { set w .indexfried - toplevel $w + Dialog $w + wm withdraw $w wm title $w [strcat "[appname] ([reponame]): " [mc "Index Error"]] wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - pack [label $w.msg \ - -justify left \ - -anchor w \ - -text [strcat \ - [mc "Updating the Git index failed. A rescan will be automatically started to resynchronize git-gui."] \ - "\n\n$err"] \ - ] -anchor w - - frame $w.buttons - button $w.buttons.continue \ + set s [mc "Updating the Git index failed. A rescan will be automatically started to resynchronize git-gui."] + text $w.msg -yscrollcommand [list $w.vs set] \ + -width [string length $s] -relief flat \ + -borderwidth 0 -highlightthickness 0 \ + -background [get_bg_color $w] + $w.msg tag configure bold -font font_uibold -justify center + ${NS}::scrollbar $w.vs -command [list $w.msg yview] + $w.msg insert end $s bold \n\n$err {} + $w.msg configure -state disabled + + ${NS}::button $w.continue \ -text [mc "Continue"] \ -command [list destroy $w] - pack $w.buttons.continue -side right -padx 5 - button $w.buttons.unlock \ + ${NS}::button $w.unlock \ -text [mc "Unlock Index"] \ -command "destroy $w; _delete_indexlock" - pack $w.buttons.unlock -side right - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + grid $w.msg - $w.vs -sticky news + grid $w.unlock $w.continue - -sticky se -padx 2 -pady 2 + grid columnconfigure $w 0 -weight 1 + grid rowconfigure $w 0 -weight 1 wm protocol $w WM_DELETE_WINDOW update - bind $w.buttons.continue <Visibility> " + bind $w.continue <Visibility> " grab $w - focus $w.buttons.continue + focus %W " + wm deiconify $w tkwait window $w $::main_status stop diff --git a/git-gui/lib/merge.tcl b/git-gui/lib/merge.tcl index 283e4915e9..5cded2341c 100644 --- a/git-gui/lib/merge.tcl +++ b/git-gui/lib/merge.tcl @@ -139,14 +139,14 @@ method _finish {cons ok} { constructor dialog {} { global current_branch - global M1B + global M1B use_ttk NS if {![_can_merge $this]} { delete_this return } - make_toplevel top w + make_dialog top w wm title $top [append "[appname] ([reponame]): " [mc "Merge"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" @@ -154,21 +154,21 @@ constructor dialog {} { set _start [cb _start] - label $w.header \ + ${NS}::label $w.header \ -text [mc "Merge Into %s" $current_branch] \ -font font_uibold pack $w.header -side top -fill x - frame $w.buttons - button $w.buttons.visualize \ + ${NS}::frame $w.buttons + ${NS}::button $w.buttons.visualize \ -text [mc Visualize] \ -command [cb _visualize] pack $w.buttons.visualize -side left - button $w.buttons.merge \ + ${NS}::button $w.buttons.merge \ -text [mc Merge] \ -command $_start pack $w.buttons.merge -side right - button $w.buttons.cancel \ + ${NS}::button $w.buttons.cancel \ -text [mc "Cancel"] \ -command [cb _cancel] pack $w.buttons.cancel -side right -padx 5 diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl index 1d55b49c9b..d4c5e45c8a 100644 --- a/git-gui/lib/option.tcl +++ b/git-gui/lib/option.tcl @@ -91,7 +91,7 @@ proc save_config {} { proc do_options {} { global repo_config global_config font_descs global repo_config_new global_config_new - global ui_comm_spell + global ui_comm_spell use_ttk NS array unset repo_config_new array unset global_config_new @@ -110,26 +110,28 @@ proc do_options {} { } set w .options_editor - toplevel $w + Dialog $w + wm withdraw $w + wm transient $w [winfo parent $w] wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - frame $w.buttons - button $w.buttons.restore -text [mc "Restore Defaults"] \ + ${NS}::frame $w.buttons + ${NS}::button $w.buttons.restore -text [mc "Restore Defaults"] \ -default normal \ -command do_restore_defaults pack $w.buttons.restore -side left - button $w.buttons.save -text [mc Save] \ + ${NS}::button $w.buttons.save -text [mc Save] \ -default active \ -command [list do_save_config $w] pack $w.buttons.save -side right - button $w.buttons.cancel -text [mc "Cancel"] \ + ${NS}::button $w.buttons.cancel -text [mc "Cancel"] \ -default normal \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.repo -text [mc "%s Repository" [reponame]] - labelframe $w.global -text [mc "Global (All Repositories)"] + ${NS}::labelframe $w.repo -text [mc "%s Repository" [reponame]] + ${NS}::labelframe $w.global -text [mc "Global (All Repositories)"] pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5 pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 @@ -161,7 +163,7 @@ proc do_options {} { foreach f {repo global} { switch -glob -- $type { b { - checkbutton $w.$f.$optid -text $text \ + ${NS}::checkbutton $w.$f.$optid -text $text \ -variable ${f}_config_new($name) \ -onvalue true \ -offvalue false @@ -169,10 +171,10 @@ proc do_options {} { } i-* { regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max - frame $w.$f.$optid - label $w.$f.$optid.l -text "$text:" + ${NS}::frame $w.$f.$optid + ${NS}::label $w.$f.$optid.l -text "$text:" pack $w.$f.$optid.l -side left -anchor w -fill x - spinbox $w.$f.$optid.v \ + tspinbox $w.$f.$optid.v \ -textvariable ${f}_config_new($name) \ -from $min \ -to $max \ @@ -184,11 +186,9 @@ proc do_options {} { } c - t { - frame $w.$f.$optid - label $w.$f.$optid.l -text "$text:" - entry $w.$f.$optid.v \ - -borderwidth 1 \ - -relief sunken \ + ${NS}::frame $w.$f.$optid + ${NS}::label $w.$f.$optid.l -text "$text:" + ${NS}::entry $w.$f.$optid.v \ -width 20 \ -textvariable ${f}_config_new($name) pack $w.$f.$optid.l -side left -anchor w @@ -199,7 +199,7 @@ proc do_options {} { menu $w.$f.$optid.m build_encoding_menu $w.$f.$optid.m \ [list set ${f}_config_new($name)] 1 - button $w.$f.$optid.b \ + ${NS}::button $w.$f.$optid.b \ -text [mc "Change"] \ -command [list popup_btn_menu \ $w.$f.$optid.m $w.$f.$optid.b] @@ -226,11 +226,17 @@ proc do_options {} { set ${f}_config_new(gui.spellingdictionary) $value } - frame $w.$f.$optid - label $w.$f.$optid.l -text [mc "Spelling Dictionary:"] - eval tk_optionMenu $w.$f.$optid.v \ - ${f}_config_new(gui.spellingdictionary) \ - $all_dicts + ${NS}::frame $w.$f.$optid + ${NS}::label $w.$f.$optid.l -text [mc "Spelling Dictionary:"] + if {$use_ttk} { + ttk::combobox $w.$f.$optid.v \ + -textvariable ${f}_config_new(gui.spellingdictionary) \ + -values $all_dicts -state readonly + } else { + eval tk_optionMenu $w.$f.$optid.v \ + ${f}_config_new(gui.spellingdictionary) \ + $all_dicts + } pack $w.$f.$optid.l -side left -anchor w -fill x pack $w.$f.$optid.v -side right -anchor e -padx 5 pack $w.$f.$optid -side top -anchor w -fill x @@ -248,20 +254,20 @@ proc do_options {} { set global_config_new(gui.$font^^size) \ [font configure $font -size] - frame $w.global.$name - label $w.global.$name.l -text "$text:" - button $w.global.$name.b \ + ${NS}::frame $w.global.$name + ${NS}::label $w.global.$name.l -text "$text:" + ${NS}::button $w.global.$name.b \ -text [mc "Change Font"] \ -command [list \ - choose_font::pick \ + tchoosefont \ $w \ [mc "Choose %s" $text] \ global_config_new(gui.$font^^family) \ global_config_new(gui.$font^^size) \ ] - label $w.global.$name.f -textvariable global_config_new(gui.$font^^family) - label $w.global.$name.s -textvariable global_config_new(gui.$font^^size) - label $w.global.$name.pt -text [mc "pt."] + ${NS}::label $w.global.$name.f -textvariable global_config_new(gui.$font^^family) + ${NS}::label $w.global.$name.s -textvariable global_config_new(gui.$font^^size) + ${NS}::label $w.global.$name.pt -text [mc "pt."] pack $w.global.$name.l -side left -anchor w pack $w.global.$name.b -side right -anchor e pack $w.global.$name.pt -side right -anchor w @@ -280,6 +286,7 @@ proc do_options {} { set t [mc "Options"] } wm title $w "[appname] ([reponame]): $t" + wm deiconify $w tkwait window $w } diff --git a/git-gui/lib/remote_add.tcl b/git-gui/lib/remote_add.tcl index fb29422aa7..50029d0cee 100644 --- a/git-gui/lib/remote_add.tcl +++ b/git-gui/lib/remote_add.tcl @@ -13,45 +13,43 @@ field location {}; # location of the remote the user has chosen field opt_action fetch; # action to do after registering the remote locally constructor dialog {} { - global repo_config + global repo_config use_ttk NS - make_toplevel top w + make_dialog top w + wm withdraw $top wm title $top [append "[appname] ([reponame]): " [mc "Add Remote"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } - label $w.header -text [mc "Add New Remote"] -font font_uibold + ${NS}::label $w.header -text [mc "Add New Remote"] \ + -font font_uibold -anchor center pack $w.header -side top -fill x - frame $w.buttons - button $w.buttons.create -text [mc Add] \ + ${NS}::frame $w.buttons + ${NS}::button $w.buttons.create -text [mc Add] \ -default active \ -command [cb _add] pack $w.buttons.create -side right - button $w.buttons.cancel -text [mc Cancel] \ + ${NS}::button $w.buttons.cancel -text [mc Cancel] \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.desc -text [mc "Remote Details"] + ${NS}::labelframe $w.desc -text [mc "Remote Details"] - label $w.desc.name_l -text [mc "Name:"] + ${NS}::label $w.desc.name_l -text [mc "Name:"] set w_name $w.desc.name_t - entry $w_name \ - -borderwidth 1 \ - -relief sunken \ + ${NS}::entry $w_name \ -width 40 \ -textvariable @name \ -validate key \ -validatecommand [cb _validate_name %d %S] grid $w.desc.name_l $w_name -sticky we -padx {0 5} - label $w.desc.loc_l -text [mc "Location:"] + ${NS}::label $w.desc.loc_l -text [mc "Location:"] set w_loc $w.desc.loc_t - entry $w_loc \ - -borderwidth 1 \ - -relief sunken \ + ${NS}::entry $w_loc \ -width 40 \ -textvariable @location grid $w.desc.loc_l $w_loc -sticky we -padx {0 5} @@ -59,21 +57,21 @@ constructor dialog {} { grid columnconfigure $w.desc 1 -weight 1 pack $w.desc -anchor nw -fill x -pady 5 -padx 5 - labelframe $w.action -text [mc "Further Action"] + ${NS}::labelframe $w.action -text [mc "Further Action"] - radiobutton $w.action.fetch \ + ${NS}::radiobutton $w.action.fetch \ -text [mc "Fetch Immediately"] \ -value fetch \ -variable @opt_action pack $w.action.fetch -anchor nw - radiobutton $w.action.push \ + ${NS}::radiobutton $w.action.push \ -text [mc "Initialize Remote Repository and Push"] \ -value push \ -variable @opt_action pack $w.action.push -anchor nw - radiobutton $w.action.none \ + ${NS}::radiobutton $w.action.none \ -text [mc "Do Nothing Else Now"] \ -value none \ -variable @opt_action @@ -85,6 +83,7 @@ constructor dialog {} { bind $w <Visibility> [cb _visible] bind $w <Key-Escape> [list destroy $w] bind $w <Key-Return> [cb _add]\;break + wm deiconify $top tkwait window $w } diff --git a/git-gui/lib/remote_branch_delete.tcl b/git-gui/lib/remote_branch_delete.tcl index 4e02fc0d39..f872a3d89d 100644 --- a/git-gui/lib/remote_branch_delete.tcl +++ b/git-gui/lib/remote_branch_delete.tcl @@ -23,34 +23,40 @@ field full_cache field cached constructor dialog {} { - global all_remotes M1B + global all_remotes M1B use_ttk NS - make_toplevel top w + make_dialog top w wm title $top [append "[appname] ([reponame]): " [mc "Delete Branch Remotely"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } - label $w.header -text [mc "Delete Branch Remotely"] -font font_uibold + ${NS}::label $w.header -text [mc "Delete Branch Remotely"] \ + -font font_uibold -anchor center pack $w.header -side top -fill x - frame $w.buttons - button $w.buttons.delete -text [mc Delete] \ + ${NS}::frame $w.buttons + ${NS}::button $w.buttons.delete -text [mc Delete] \ -default active \ -command [cb _delete] pack $w.buttons.delete -side right - button $w.buttons.cancel -text [mc "Cancel"] \ + ${NS}::button $w.buttons.cancel -text [mc "Cancel"] \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.dest -text [mc "From Repository"] + ${NS}::labelframe $w.dest -text [mc "From Repository"] if {$all_remotes ne {}} { - radiobutton $w.dest.remote_r \ + ${NS}::radiobutton $w.dest.remote_r \ -text [mc "Remote:"] \ -value remote \ -variable @urltype - eval tk_optionMenu $w.dest.remote_m @remote $all_remotes + if {$use_ttk} { + ttk::combobox $w.dest.remote_m -textvariable @remote \ + -values $all_remotes -state readonly + } else { + eval tk_optionMenu $w.dest.remote_m @remote $all_remotes + } grid $w.dest.remote_r $w.dest.remote_m -sticky w if {[lsearch -sorted -exact $all_remotes origin] != -1} { set remote origin @@ -62,13 +68,11 @@ constructor dialog {} { } else { set urltype url } - radiobutton $w.dest.url_r \ + ${NS}::radiobutton $w.dest.url_r \ -text [mc "Arbitrary Location:"] \ -value url \ -variable @urltype - entry $w.dest.url_t \ - -borderwidth 1 \ - -relief sunken \ + ${NS}::entry $w.dest.url_t \ -width 50 \ -textvariable @url \ -validate key \ @@ -81,33 +85,30 @@ constructor dialog {} { grid columnconfigure $w.dest 1 -weight 1 pack $w.dest -anchor nw -fill x -pady 5 -padx 5 - labelframe $w.heads -text [mc "Branches"] - listbox $w.heads.l \ + ${NS}::labelframe $w.heads -text [mc "Branches"] + slistbox $w.heads.l \ -height 10 \ -width 70 \ -listvariable @head_list \ - -selectmode extended \ - -yscrollcommand [list $w.heads.sby set] - scrollbar $w.heads.sby -command [list $w.heads.l yview] + -selectmode extended - frame $w.heads.footer - label $w.heads.footer.status \ + ${NS}::frame $w.heads.footer + ${NS}::label $w.heads.footer.status \ -textvariable @status \ -anchor w \ -justify left - button $w.heads.footer.rescan \ + ${NS}::button $w.heads.footer.rescan \ -text [mc "Rescan"] \ -command [cb _rescan] pack $w.heads.footer.status -side left -fill x pack $w.heads.footer.rescan -side right pack $w.heads.footer -side bottom -fill x - pack $w.heads.sby -side right -fill y pack $w.heads.l -side left -fill both -expand 1 pack $w.heads -fill both -expand 1 -pady 5 -padx 5 - labelframe $w.validate -text [mc "Delete Only If"] - radiobutton $w.validate.head_r \ + ${NS}::labelframe $w.validate -text [mc "Delete Only If"] + ${NS}::radiobutton $w.validate.head_r \ -text [mc "Merged Into:"] \ -value head \ -variable @checktype @@ -115,7 +116,7 @@ constructor dialog {} { trace add variable @head_list write [cb _write_head_list] trace add variable @check_head write [cb _write_check_head] grid $w.validate.head_r $w.validate.head_m -sticky w - radiobutton $w.validate.always_r \ + ${NS}::radiobutton $w.validate.always_r \ -text [mc "Always (Do not perform merge checks)"] \ -value always \ -variable @checktype @@ -208,13 +209,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 +251,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 +261,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/lib/search.tcl b/git-gui/lib/search.tcl index b371e9a30a..7fdbf87bcd 100644 --- a/git-gui/lib/search.tcl +++ b/git-gui/lib/search.tcl @@ -14,15 +14,16 @@ field smarktop field smarkbot constructor new {i_w i_text args} { + global use_ttk NS set w $i_w set ctext $i_text - frame $w - label $w.l -text [mc Find:] + ${NS}::frame $w + ${NS}::label $w.l -text [mc Find:] entry $w.ent -textvariable ${__this}::searchstring -background lightgreen - button $w.bn -text [mc Next] -command [cb find_next] - button $w.bp -text [mc Prev] -command [cb find_prev] - checkbutton $w.cs -text [mc Case-Sensitive] \ + ${NS}::button $w.bn -text [mc Next] -command [cb find_next] + ${NS}::button $w.bp -text [mc Prev] -command [cb find_prev] + ${NS}::checkbutton $w.cs -text [mc Case-Sensitive] \ -variable ${__this}::casesensitive -command [cb _incrsearch] pack $w.l -side left pack $w.cs -side right diff --git a/git-gui/lib/shortcut.tcl b/git-gui/lib/shortcut.tcl index 2f20eb39c0..79c1888e11 100644 --- a/git-gui/lib/shortcut.tcl +++ b/git-gui/lib/shortcut.tcl @@ -2,6 +2,7 @@ # Copyright (C) 2006, 2007 Shawn Pearce proc do_windows_shortcut {} { + global _gitworktree set fn [tk_getSaveFile \ -parent . \ -title [append "[appname] ([reponame]): " [mc "Create Desktop Icon"]] \ @@ -15,7 +16,7 @@ proc do_windows_shortcut {} { [info nameofexecutable] \ [file normalize $::argv0] \ ] \ - [file dirname [file normalize [gitdir]]] + [file normalize [$_gitworktree]] } err]} { error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"] } @@ -23,7 +24,7 @@ proc do_windows_shortcut {} { } proc do_cygwin_shortcut {} { - global argv0 + global argv0 _gitworktree if {[catch { set desktop [exec cygpath \ @@ -56,7 +57,7 @@ proc do_cygwin_shortcut {} { $sh -c \ "CHERE_INVOKING=1 source /etc/profile;[sq $me] &" \ ] \ - [file dirname [file normalize [gitdir]]] + [file normalize [$_gitworktree]] } err]} { error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"] } diff --git a/git-gui/lib/sshkey.tcl b/git-gui/lib/sshkey.tcl index 82a1a80ff4..5f75bc96b3 100644 --- a/git-gui/lib/sshkey.tcl +++ b/git-gui/lib/sshkey.tcl @@ -15,7 +15,7 @@ proc find_ssh_key {} { } proc do_ssh_key {} { - global sshkey_title have_tk85 sshkey_fd + global sshkey_title have_tk85 sshkey_fd use_ttk NS set w .sshkey_dialog if {[winfo exists $w]} { @@ -23,7 +23,7 @@ proc do_ssh_key {} { return } - toplevel $w + Dialog $w wm transient $w . set finfo [find_ssh_key] @@ -35,9 +35,9 @@ proc do_ssh_key {} { set gen_state disabled } - frame $w.header -relief flat - label $w.header.lbl -textvariable sshkey_title -anchor w - button $w.header.gen -text [mc "Generate Key"] \ + ${NS}::frame $w.header + ${NS}::label $w.header.lbl -textvariable sshkey_title -anchor w + ${NS}::button $w.header.gen -text [mc "Generate Key"] \ -command [list make_ssh_key $w] -state $gen_state pack $w.header.lbl -side left -expand 1 -fill x pack $w.header.gen -side right @@ -46,14 +46,16 @@ proc do_ssh_key {} { text $w.contents -width 60 -height 10 -wrap char -relief sunken pack $w.contents -fill both -expand 1 if {$have_tk85} { - $w.contents configure -inactiveselectbackground darkblue + set clr darkblue + if {$use_ttk} { set clr [ttk::style lookup . -selectbackground] } + $w.contents configure -inactiveselectbackground $clr } - frame $w.buttons - button $w.buttons.close -text [mc Close] \ + ${NS}::frame $w.buttons + ${NS}::button $w.buttons.close -text [mc Close] \ -default active -command [list destroy $w] pack $w.buttons.close -side right - button $w.buttons.copy -text [mc "Copy To Clipboard"] \ + ${NS}::button $w.buttons.copy -text [mc "Copy To Clipboard"] \ -command [list tk_textCopy $w.contents] pack $w.buttons.copy -side left pack $w.buttons -side bottom -fill x -pady 5 -padx 5 diff --git a/git-gui/lib/status_bar.tcl b/git-gui/lib/status_bar.tcl index 51d4177551..5fe3aad382 100644 --- a/git-gui/lib/status_bar.tcl +++ b/git-gui/lib/status_bar.tcl @@ -13,14 +13,16 @@ field units {}; # unit of progress field meter {}; # current core git progress meter (if active) constructor new {path} { + global use_ttk NS set w $path set w_l $w.l set w_c $w.c - frame $w \ - -borderwidth 1 \ - -relief sunken - label $w_l \ + ${NS}::frame $w + if {!$use_ttk} { + $w configure -borderwidth 1 -relief sunken + } + ${NS}::label $w_l \ -textvariable @status \ -anchor w \ -justify left @@ -41,8 +43,8 @@ constructor two_line {path} { set w_l $w.l set w_c $w.c - frame $w - label $w_l \ + ${NS}::frame $w + ${NS}::label $w_l \ -textvariable @status \ -anchor w \ -justify left diff --git a/git-gui/lib/themed.tcl b/git-gui/lib/themed.tcl new file mode 100644 index 0000000000..1da458673b --- /dev/null +++ b/git-gui/lib/themed.tcl @@ -0,0 +1,174 @@ +# Functions for supporting the use of themed Tk widgets in git-gui. +# Copyright (C) 2009 Pat Thoyts <patthoyts@users.sourceforge.net> + +proc InitTheme {} { + # Create a color label style (bg can be overridden by widget option) + ttk::style layout Color.TLabel { + Color.Label.border -sticky news -children { + Color.label.fill -sticky news -children { + Color.Label.padding -sticky news -children { + Color.Label.label -sticky news}}}} + eval [linsert [ttk::style configure TLabel] 0 \ + ttk::style configure Color.TLabel] + ttk::style configure Color.TLabel \ + -borderwidth 0 -relief flat -padding 2 + ttk::style map Color.TLabel -background {{} gold} + # We also need a padded label. + ttk::style configure Padded.TLabel \ + -padding {5 5} -borderwidth 1 -relief solid + # We need a gold frame. + ttk::style layout Gold.TFrame { + Gold.Frame.border -sticky nswe -children { + Gold.Frame.fill -sticky nswe}} + ttk::style configure Gold.TFrame -background gold -relief flat + # listboxes should have a theme border so embed in ttk::frame + ttk::style layout SListbox.TFrame { + SListbox.Frame.Entry.field -sticky news -border true -children { + SListbox.Frame.padding -sticky news + } + } +} + +proc gold_frame {w args} { + global use_ttk + if {$use_ttk} { + eval [linsert $args 0 ttk::frame $w -style Gold.TFrame] + } else { + eval [linsert $args 0 frame $w -background gold] + } +} + +proc tlabel {w args} { + global use_ttk + if {$use_ttk} { + set cmd [list ttk::label $w -style Color.TLabel] + foreach {k v} $args { + switch -glob -- $k { + -activebackground {} + default { lappend cmd $k $v } + } + } + eval $cmd + } else { + eval [linsert $args 0 label $w] + } +} + +# The padded label gets used in the about class. +proc paddedlabel {w args} { + global use_ttk + if {$use_ttk} { + eval [linsert $args 0 ttk::label $w -style Padded.TLabel] + } else { + eval [linsert $args 0 label $w \ + -padx 5 -pady 5 \ + -justify left \ + -anchor w \ + -borderwidth 1 \ + -relief solid] + } +} + +# Create a toplevel for use as a dialog. +# If available, sets the EWMH dialog hint and if ttk is enabled +# place a themed frame over the surface. +proc Dialog {w args} { + eval [linsert $args 0 toplevel $w -class Dialog] + pave_toplevel $w + return $w +} + +# Tk toplevels are not themed - so pave it over with a themed frame to get +# the base color correct per theme. +proc pave_toplevel {w} { + global use_ttk + if {$use_ttk && ![winfo exists $w.!paving]} { + set paving [ttk::frame $w.!paving] + place $paving -x 0 -y 0 -relwidth 1 -relheight 1 + lower $paving + } +} + +# Create a scrolled listbox with appropriate border for the current theme. +# On many themes the border for a scrolled listbox needs to go around the +# listbox and the scrollbar. +proc slistbox {w args} { + global use_ttk NS + if {$use_ttk} { + set f [ttk::frame $w -style SListbox.TFrame -padding 2] + } else { + set f [frame $w -relief flat] + } + if {[catch { + if {$use_ttk} { + eval [linsert $args 0 listbox $f.list -relief flat \ + -highlightthickness 0 -borderwidth 0] + } else { + eval [linsert $args 0 listbox $f.list] + } + ${NS}::scrollbar $f.vs -command [list $f.list yview] + $f.list configure -yscrollcommand [list $f.vs set] + grid $f.list $f.vs -sticky news + grid rowconfigure $f 0 -weight 1 + grid columnconfigure $f 0 -weight 1 + bind $f.list <<ListboxSelect>> \ + [list event generate $w <<ListboxSelect>>] + interp hide {} $w + interp alias {} $w {} $f.list + } err]} { + destroy $f + return -code error $err + } + return $w +} + +# fetch the background color from a widget. +proc get_bg_color {w} { + global use_ttk + if {$use_ttk} { + set bg [ttk::style lookup [winfo class $w] -background] + } else { + set bg [$w cget -background] + } + return $bg +} + +# ttk::spinbox didn't get added until 8.6 +proc tspinbox {w args} { + global use_ttk + if {$use_ttk && [llength [info commands ttk::spinbox]] > 0} { + eval [linsert $args 0 ttk::spinbox $w] + } else { + eval [linsert $args 0 spinbox $w] + } +} + +# Tk 8.6 provides a standard font selection dialog. This uses the native +# dialogs on Windows and MacOSX or a standard Tk dialog on X11. +proc tchoosefont {w title familyvar sizevar} { + if {[package vsatisfies [package provide Tk] 8.6]} { + upvar #0 $familyvar family + upvar #0 $sizevar size + tk fontchooser configure -parent $w -title $title \ + -font [list $family $size] \ + -command [list on_choosefont $familyvar $sizevar] + tk fontchooser show + } else { + choose_font::pick $w $title $familyvar $sizevar + } +} + +# Called when the Tk 8.6 fontchooser selects a font. +proc on_choosefont {familyvar sizevar font} { + upvar #0 $familyvar family + upvar #0 $sizevar size + set font [font actual $font] + set family [dict get $font -family] + set size [dict get $font -size] +} + +# Local variables: +# mode: tcl +# indent-tabs-mode: t +# tab-width: 4 +# End: diff --git a/git-gui/lib/tools_dlg.tcl b/git-gui/lib/tools_dlg.tcl index 5f7f08e239..7eeda9daf2 100644 --- a/git-gui/lib/tools_dlg.tcl +++ b/git-gui/lib/tools_dlg.tcl @@ -16,53 +16,50 @@ field ask_branch 0; # ask for a revision field ask_args 0; # ask for additional args constructor dialog {} { - global repo_config + global repo_config use_ttk NS - make_toplevel top w + make_dialog top w wm title $top [append "[appname] ([reponame]): " [mc "Add Tool"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" wm transient $top . } - label $w.header -text [mc "Add New Tool Command"] -font font_uibold + ${NS}::label $w.header -text [mc "Add New Tool Command"] \ + -font font_uibold -anchor center pack $w.header -side top -fill x - frame $w.buttons - checkbutton $w.buttons.global \ + ${NS}::frame $w.buttons + ${NS}::checkbutton $w.buttons.global \ -text [mc "Add globally"] \ -variable @add_global pack $w.buttons.global -side left -padx 5 - button $w.buttons.create -text [mc Add] \ + ${NS}::button $w.buttons.create -text [mc Add] \ -default active \ -command [cb _add] pack $w.buttons.create -side right - button $w.buttons.cancel -text [mc Cancel] \ + ${NS}::button $w.buttons.cancel -text [mc Cancel] \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.desc -text [mc "Tool Details"] + ${NS}::labelframe $w.desc -text [mc "Tool Details"] - label $w.desc.name_cmnt -anchor w\ + ${NS}::label $w.desc.name_cmnt -anchor w\ -text [mc "Use '/' separators to create a submenu tree:"] grid x $w.desc.name_cmnt -sticky we -padx {0 5} -pady {0 2} - label $w.desc.name_l -text [mc "Name:"] + ${NS}::label $w.desc.name_l -text [mc "Name:"] set w_name $w.desc.name_t - entry $w_name \ - -borderwidth 1 \ - -relief sunken \ + ${NS}::entry $w_name \ -width 40 \ -textvariable @name \ -validate key \ -validatecommand [cb _validate_name %d %S] grid $w.desc.name_l $w_name -sticky we -padx {0 5} - label $w.desc.cmd_l -text [mc "Command:"] + ${NS}::label $w.desc.cmd_l -text [mc "Command:"] set w_cmd $w.desc.cmd_t - entry $w_cmd \ - -borderwidth 1 \ - -relief sunken \ + ${NS}::entry $w_cmd \ -width 40 \ -textvariable @command grid $w.desc.cmd_l $w_cmd -sticky we -padx {0 5} -pady {0 3} @@ -70,30 +67,30 @@ constructor dialog {} { grid columnconfigure $w.desc 1 -weight 1 pack $w.desc -anchor nw -fill x -pady 5 -padx 5 - checkbutton $w.confirm \ + ${NS}::checkbutton $w.confirm \ -text [mc "Show a dialog before running"] \ -variable @confirm -command [cb _check_enable_dlg] - labelframe $w.dlg -labelwidget $w.confirm + ${NS}::labelframe $w.dlg -labelwidget $w.confirm - checkbutton $w.dlg.askbranch \ + ${NS}::checkbutton $w.dlg.askbranch \ -text [mc "Ask the user to select a revision (sets \$REVISION)"] \ -variable @ask_branch -state disabled pack $w.dlg.askbranch -anchor w -padx 15 - checkbutton $w.dlg.askargs \ + ${NS}::checkbutton $w.dlg.askargs \ -text [mc "Ask the user for additional arguments (sets \$ARGS)"] \ -variable @ask_args -state disabled pack $w.dlg.askargs -anchor w -padx 15 pack $w.dlg -anchor nw -fill x -pady {0 8} -padx 5 - checkbutton $w.noconsole \ + ${NS}::checkbutton $w.noconsole \ -text [mc "Don't show the command output window"] \ -variable @no_console pack $w.noconsole -anchor w -padx 5 - checkbutton $w.needsfile \ + ${NS}::checkbutton $w.needsfile \ -text [mc "Run only if a diff is selected (\$FILENAME not empty)"] \ -variable @needs_file pack $w.needsfile -anchor w -padx 5 @@ -182,40 +179,38 @@ field w ; # widget path field w_names ; # name list constructor dialog {} { - global repo_config global_config system_config + global repo_config global_config system_config use_ttk NS load_config 1 - make_toplevel top w + make_dialog top w wm title $top [append "[appname] ([reponame]): " [mc "Remove Tool"]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" wm transient $top . } - label $w.header -text [mc "Remove Tool Commands"] -font font_uibold + ${NS}::label $w.header -text [mc "Remove Tool Commands"] \ + -font font_uibold -anchor center pack $w.header -side top -fill x - frame $w.buttons - button $w.buttons.create -text [mc Remove] \ + ${NS}::frame $w.buttons + ${NS}::button $w.buttons.create -text [mc Remove] \ -default active \ -command [cb _remove] pack $w.buttons.create -side right - button $w.buttons.cancel -text [mc Cancel] \ + ${NS}::button $w.buttons.cancel -text [mc Cancel] \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - frame $w.list + ${NS}::frame $w.list set w_names $w.list.l - listbox $w_names \ + slistbox $w_names \ -height 10 \ -width 30 \ -selectmode extended \ - -exportselection false \ - -yscrollcommand [list $w.list.sby set] - scrollbar $w.list.sby -command [list $w.list.l yview] - pack $w.list.sby -side right -fill y + -exportselection false pack $w.list.l -side left -fill both -expand 1 pack $w.list -fill both -expand 1 -pady 5 -padx 5 @@ -232,7 +227,7 @@ constructor dialog {} { } if {$local_cnt > 0} { - label $w.colorlbl -foreground blue \ + ${NS}::label $w.colorlbl -foreground blue \ -text [mc "(Blue denotes repository-local tools)"] pack $w.colorlbl -fill x -pady 5 -padx 5 } @@ -277,14 +272,14 @@ field is_ok 0; # ok to start field argstr {}; # arguments constructor dialog {fullname} { - global M1B + global M1B use_ttk NS set title [get_config "guitool.$fullname.title"] if {$title eq {}} { regsub {/} $fullname { / } title } - make_toplevel top w -autodelete 0 + make_dialog top w -autodelete 0 wm title $top [append "[appname] ([reponame]): " $title] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" @@ -297,7 +292,7 @@ constructor dialog {fullname} { set prompt [mc "Run Command: %s" $command] } - label $w.header -text $prompt -font font_uibold + ${NS}::label $w.header -text $prompt -font font_uibold -anchor center pack $w.header -side top -fill x set argprompt [get_config "guitool.$fullname.argprompt"] @@ -311,12 +306,10 @@ constructor dialog {fullname} { set argprompt [mc "Arguments"] } - labelframe $w.arg -text $argprompt + ${NS}::labelframe $w.arg -text $argprompt set w_args $w.arg.txt - entry $w_args \ - -borderwidth 1 \ - -relief sunken \ + ${NS}::entry $w_args \ -width 40 \ -textvariable @argstr pack $w_args -padx 5 -pady 5 -fill both @@ -337,18 +330,18 @@ constructor dialog {fullname} { pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5 } - frame $w.buttons + ${NS}::frame $w.buttons if {$is_ask_revs} { - button $w.buttons.visualize \ + ${NS}::button $w.buttons.visualize \ -text [mc Visualize] \ -command [cb _visualize] pack $w.buttons.visualize -side left } - button $w.buttons.ok \ + ${NS}::button $w.buttons.ok \ -text [mc OK] \ -command [cb _start] pack $w.buttons.ok -side right - button $w.buttons.cancel \ + ${NS}::button $w.buttons.cancel \ -text [mc "Cancel"] \ -command [cb _cancel] pack $w.buttons.cancel -side right -padx 5 diff --git a/git-gui/lib/transport.tcl b/git-gui/lib/transport.tcl index b18d9c7a1b..60e3a642c5 100644 --- a/git-gui/lib/transport.tcl +++ b/git-gui/lib/transport.tcl @@ -91,50 +91,55 @@ trace add variable push_remote write \ proc do_push_anywhere {} { global all_remotes current_branch global push_urltype push_remote push_url push_thin push_tags - global push_force + global push_force use_ttk NS set w .push_setup toplevel $w + wm withdraw $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + pave_toplevel $w - label $w.header -text [mc "Push Branches"] -font font_uibold + ${NS}::label $w.header -text [mc "Push Branches"] \ + -font font_uibold -anchor center pack $w.header -side top -fill x - frame $w.buttons - button $w.buttons.create -text [mc Push] \ + ${NS}::frame $w.buttons + ${NS}::button $w.buttons.create -text [mc Push] \ -default active \ -command [list start_push_anywhere_action $w] pack $w.buttons.create -side right - button $w.buttons.cancel -text [mc "Cancel"] \ + ${NS}::button $w.buttons.cancel -text [mc "Cancel"] \ -default normal \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.source -text [mc "Source Branches"] - listbox $w.source.l \ + ${NS}::labelframe $w.source -text [mc "Source Branches"] + slistbox $w.source.l \ -height 10 \ -width 70 \ - -selectmode extended \ - -yscrollcommand [list $w.source.sby set] + -selectmode extended foreach h [load_all_heads] { $w.source.l insert end $h if {$h eq $current_branch} { $w.source.l select set end } } - scrollbar $w.source.sby -command [list $w.source.l yview] - pack $w.source.sby -side right -fill y pack $w.source.l -side left -fill both -expand 1 pack $w.source -fill both -expand 1 -pady 5 -padx 5 - labelframe $w.dest -text [mc "Destination Repository"] + ${NS}::labelframe $w.dest -text [mc "Destination Repository"] if {$all_remotes ne {}} { - radiobutton $w.dest.remote_r \ + ${NS}::radiobutton $w.dest.remote_r \ -text [mc "Remote:"] \ -value remote \ -variable push_urltype - eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes + if {$use_ttk} { + ttk::combobox $w.dest.remote_m -textvariable push_remote \ + -values $all_remotes + } else { + eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes + } grid $w.dest.remote_r $w.dest.remote_m -sticky w if {[lsearch -sorted -exact $all_remotes origin] != -1} { set push_remote origin @@ -145,13 +150,11 @@ proc do_push_anywhere {} { } else { set push_urltype url } - radiobutton $w.dest.url_r \ + ${NS}::radiobutton $w.dest.url_r \ -text [mc "Arbitrary Location:"] \ -value url \ -variable push_urltype - entry $w.dest.url_t \ - -borderwidth 1 \ - -relief sunken \ + ${NS}::entry $w.dest.url_t \ -width 50 \ -textvariable push_url \ -validate key \ @@ -166,16 +169,16 @@ proc do_push_anywhere {} { grid columnconfigure $w.dest 1 -weight 1 pack $w.dest -anchor nw -fill x -pady 5 -padx 5 - labelframe $w.options -text [mc "Transfer Options"] - checkbutton $w.options.force \ + ${NS}::labelframe $w.options -text [mc "Transfer Options"] + ${NS}::checkbutton $w.options.force \ -text [mc "Force overwrite existing branch (may discard changes)"] \ -variable push_force grid $w.options.force -columnspan 2 -sticky w - checkbutton $w.options.thin \ + ${NS}::checkbutton $w.options.thin \ -text [mc "Use thin pack (for slow network connections)"] \ -variable push_thin grid $w.options.thin -columnspan 2 -sticky w - checkbutton $w.options.tags \ + ${NS}::checkbutton $w.options.tags \ -text [mc "Include tags"] \ -variable push_tags grid $w.options.tags -columnspan 2 -sticky w @@ -191,5 +194,6 @@ proc do_push_anywhere {} { bind $w <Key-Escape> "destroy $w" bind $w <Key-Return> [list start_push_anywhere_action $w] wm title $w [append "[appname] ([reponame]): " [mc "Push"]] + wm deiconify $w tkwait window $w } diff --git a/git-gui/po/de.po b/git-gui/po/de.po index 51abb50bb6..44c5ddc8b6 100644 --- a/git-gui/po/de.po +++ b/git-gui/po/de.po @@ -7,41 +7,41 @@ msgid "" msgstr "" "Project-Id-Version: git-gui\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-12-06 20:51+0100\n" -"PO-Revision-Date: 2008-12-06 21:22+0100\n" +"POT-Creation-Date: 2010-01-26 22:22+0100\n" +"PO-Revision-Date: 2010-01-26 22:25+0100\n" "Last-Translator: Christian Stimming <stimming@tuhh.de>\n" "Language-Team: German\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\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 +#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903 +#: git-gui.sh:922 msgid "git-gui: fatal error" msgstr "git-gui: Programmfehler" -#: git-gui.sh:593 +#: git-gui.sh:743 #, tcl-format msgid "Invalid font specified in %s:" msgstr "Ungültige Zeichensatz-Angabe in %s:" -#: git-gui.sh:620 +#: git-gui.sh:779 msgid "Main Font" msgstr "Programmschriftart" -#: git-gui.sh:621 +#: git-gui.sh:780 msgid "Diff/Console Font" msgstr "Vergleich-Schriftart" -#: git-gui.sh:635 +#: git-gui.sh:794 msgid "Cannot find git in PATH." msgstr "Git kann im PATH nicht gefunden werden." -#: git-gui.sh:662 +#: git-gui.sh:821 msgid "Cannot parse Git version string:" msgstr "Git Versionsangabe kann nicht erkannt werden:" -#: git-gui.sh:680 +#: git-gui.sh:839 #, tcl-format msgid "" "Git version cannot be determined.\n" @@ -60,447 +60,479 @@ msgstr "" "\n" "Soll angenommen werden, »%s« sei Version 1.5.0?\n" -#: git-gui.sh:918 +#: git-gui.sh:1128 msgid "Git directory not found:" msgstr "Git-Verzeichnis nicht gefunden:" -#: git-gui.sh:925 +#: git-gui.sh:1146 msgid "Cannot move to top of working directory:" msgstr "" "Es konnte nicht in das oberste Verzeichnis der Arbeitskopie gewechselt " "werden:" -#: git-gui.sh:932 -msgid "Cannot use funny .git directory:" -msgstr "Unerwartete Struktur des .git Verzeichnis:" +#: git-gui.sh:1154 +msgid "Cannot use bare repository:" +msgstr "Leeres Projektarchiv kann nicht benutzt werden:" -#: git-gui.sh:937 +#: git-gui.sh:1162 msgid "No working directory" msgstr "Kein Arbeitsverzeichnis" -#: git-gui.sh:1084 lib/checkout_op.tcl:283 +#: git-gui.sh:1334 lib/checkout_op.tcl:306 msgid "Refreshing file status..." msgstr "Dateistatus aktualisieren..." -#: git-gui.sh:1149 +#: git-gui.sh:1390 msgid "Scanning for modified files ..." msgstr "Nach geänderten Dateien suchen..." -#: git-gui.sh:1367 +#: git-gui.sh:1454 msgid "Calling prepare-commit-msg hook..." msgstr "Aufrufen der Eintragen-Vorbereiten-Kontrolle..." -#: git-gui.sh:1384 +#: git-gui.sh:1471 msgid "Commit declined by prepare-commit-msg hook." -msgstr "Eintragen abgelehnt durch Eintragen-Vorbereiten-Kontrolle (»prepare-commit hook«)." +msgstr "" +"Eintragen abgelehnt durch Eintragen-Vorbereiten-Kontrolle (»prepare-commit " +"hook«)." -#: git-gui.sh:1542 lib/browser.tcl:246 +#: git-gui.sh:1629 lib/browser.tcl:246 msgid "Ready." msgstr "Bereit." -#: git-gui.sh:1590 +#: git-gui.sh:1787 +#, tcl-format +msgid "Displaying only %s of %s files." +msgstr "Nur %s von %s Dateien werden angezeigt." + +#: git-gui.sh:1913 msgid "Unmodified" msgstr "Unverändert" -#: git-gui.sh:1592 +#: git-gui.sh:1915 msgid "Modified, not staged" msgstr "Verändert, nicht bereitgestellt" -#: git-gui.sh:1593 git-gui.sh:1598 +#: git-gui.sh:1916 git-gui.sh:1924 msgid "Staged for commit" msgstr "Bereitgestellt zum Eintragen" -#: git-gui.sh:1594 git-gui.sh:1599 +#: git-gui.sh:1917 git-gui.sh:1925 msgid "Portions staged for commit" msgstr "Teilweise bereitgestellt zum Eintragen" -#: git-gui.sh:1595 git-gui.sh:1600 +#: git-gui.sh:1918 git-gui.sh:1926 msgid "Staged for commit, missing" msgstr "Bereitgestellt zum Eintragen, fehlend" -#: git-gui.sh:1658 +#: git-gui.sh:1920 msgid "File type changed, not staged" msgstr "Dateityp geändert, nicht bereitgestellt" -#: git-gui.sh:1659 +#: git-gui.sh:1921 msgid "File type changed, staged" msgstr "Dateityp geändert, bereitgestellt" -#: git-gui.sh:1661 +#: git-gui.sh:1923 msgid "Untracked, not staged" msgstr "Nicht unter Versionskontrolle, nicht bereitgestellt" -#: git-gui.sh:1602 +#: git-gui.sh:1928 msgid "Missing" msgstr "Fehlend" -#: git-gui.sh:1603 +#: git-gui.sh:1929 msgid "Staged for removal" msgstr "Bereitgestellt zum Löschen" -#: git-gui.sh:1604 +#: git-gui.sh:1930 msgid "Staged for removal, still present" msgstr "Bereitgestellt zum Löschen, trotzdem vorhanden" -#: git-gui.sh:1606 git-gui.sh:1607 git-gui.sh:1608 git-gui.sh:1609 +#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935 +#: git-gui.sh:1936 git-gui.sh:1937 msgid "Requires merge resolution" msgstr "Konfliktauflösung nötig" -#: git-gui.sh:1644 +#: git-gui.sh:1972 msgid "Starting gitk... please wait..." msgstr "Gitk wird gestartet... bitte warten." -#: git-gui.sh:1698 +#: git-gui.sh:1984 msgid "Couldn't find gitk in PATH" msgstr "Gitk kann im PATH nicht gefunden werden." -#: git-gui.sh:1948 lib/choose_repository.tcl:36 +#: git-gui.sh:2043 +msgid "Couldn't find git gui in PATH" +msgstr "»Git gui« kann im PATH nicht gefunden werden." + +#: git-gui.sh:2455 lib/choose_repository.tcl:36 msgid "Repository" msgstr "Projektarchiv" -#: git-gui.sh:1861 +#: git-gui.sh:2456 msgid "Edit" msgstr "Bearbeiten" -#: git-gui.sh:1863 lib/choose_rev.tcl:561 +#: git-gui.sh:2458 lib/choose_rev.tcl:561 msgid "Branch" msgstr "Zweig" -#: git-gui.sh:1866 lib/choose_rev.tcl:548 +#: git-gui.sh:2461 lib/choose_rev.tcl:548 msgid "Commit@@noun" msgstr "Version" -#: git-gui.sh:1869 lib/merge.tcl:120 lib/merge.tcl:149 lib/merge.tcl:167 +#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 msgid "Merge" msgstr "Zusammenführen" -#: git-gui.sh:1870 lib/choose_rev.tcl:557 +#: git-gui.sh:2465 lib/choose_rev.tcl:557 msgid "Remote" msgstr "Andere Archive" -#: git-gui.sh:2293 +#: git-gui.sh:2468 msgid "Tools" msgstr "Werkzeuge" -#: git-gui.sh:2302 +#: git-gui.sh:2477 msgid "Explore Working Copy" msgstr "Arbeitskopie im Dateimanager" -#: git-gui.sh:2247 +#: git-gui.sh:2483 msgid "Browse Current Branch's Files" msgstr "Aktuellen Zweig durchblättern" -#: git-gui.sh:1883 +#: git-gui.sh:2487 msgid "Browse Branch Files..." msgstr "Einen Zweig durchblättern..." -#: git-gui.sh:1888 +#: git-gui.sh:2492 msgid "Visualize Current Branch's History" msgstr "Aktuellen Zweig darstellen" -#: git-gui.sh:1892 +#: git-gui.sh:2496 msgid "Visualize All Branch History" msgstr "Alle Zweige darstellen" -#: git-gui.sh:1899 +#: git-gui.sh:2503 #, tcl-format msgid "Browse %s's Files" msgstr "Zweig »%s« durchblättern" -#: git-gui.sh:1901 +#: git-gui.sh:2505 #, tcl-format msgid "Visualize %s's History" msgstr "Historie von »%s« darstellen" -#: git-gui.sh:1906 lib/database.tcl:27 lib/database.tcl:67 +#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67 msgid "Database Statistics" msgstr "Datenbankstatistik" -#: git-gui.sh:1909 lib/database.tcl:34 +#: git-gui.sh:2513 lib/database.tcl:34 msgid "Compress Database" msgstr "Datenbank komprimieren" -#: git-gui.sh:1912 +#: git-gui.sh:2516 msgid "Verify Database" msgstr "Datenbank überprüfen" -#: git-gui.sh:1919 git-gui.sh:1923 git-gui.sh:1927 lib/shortcut.tcl:7 -#: lib/shortcut.tcl:39 lib/shortcut.tcl:71 +#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8 +#: lib/shortcut.tcl:40 lib/shortcut.tcl:72 msgid "Create Desktop Icon" msgstr "Desktop-Icon erstellen" -#: git-gui.sh:1932 lib/choose_repository.tcl:177 lib/choose_repository.tcl:185 +#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 msgid "Quit" msgstr "Beenden" -#: git-gui.sh:1939 +#: git-gui.sh:2547 msgid "Undo" msgstr "Rückgängig" -#: git-gui.sh:1942 +#: git-gui.sh:2550 msgid "Redo" msgstr "Wiederholen" -#: git-gui.sh:1946 git-gui.sh:2443 +#: git-gui.sh:2554 git-gui.sh:3109 msgid "Cut" msgstr "Ausschneiden" -#: git-gui.sh:1949 git-gui.sh:2446 git-gui.sh:2520 git-gui.sh:2614 +#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259 #: lib/console.tcl:69 msgid "Copy" msgstr "Kopieren" -#: git-gui.sh:1952 git-gui.sh:2449 +#: git-gui.sh:2560 git-gui.sh:3115 msgid "Paste" msgstr "Einfügen" -#: git-gui.sh:1955 git-gui.sh:2452 lib/branch_delete.tcl:26 +#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26 #: lib/remote_branch_delete.tcl:38 msgid "Delete" msgstr "Löschen" -#: git-gui.sh:1959 git-gui.sh:2456 git-gui.sh:2618 lib/console.tcl:71 +#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71 msgid "Select All" msgstr "Alle auswählen" -#: git-gui.sh:1968 +#: git-gui.sh:2576 msgid "Create..." msgstr "Erstellen..." -#: git-gui.sh:1974 +#: git-gui.sh:2582 msgid "Checkout..." msgstr "Umstellen..." -#: git-gui.sh:1980 +#: git-gui.sh:2588 msgid "Rename..." msgstr "Umbenennen..." -#: git-gui.sh:1985 git-gui.sh:2085 +#: git-gui.sh:2593 msgid "Delete..." msgstr "Löschen..." -#: git-gui.sh:1990 +#: git-gui.sh:2598 msgid "Reset..." msgstr "Zurücksetzen..." -#: git-gui.sh:2372 +#: git-gui.sh:2608 msgid "Done" msgstr "Fertig" -#: git-gui.sh:2374 +#: git-gui.sh:2610 msgid "Commit@@verb" msgstr "Eintragen" -#: git-gui.sh:2383 git-gui.sh:2786 +#: git-gui.sh:2619 git-gui.sh:3050 msgid "New Commit" msgstr "Neue Version" -#: git-gui.sh:2010 git-gui.sh:2396 +#: git-gui.sh:2627 git-gui.sh:3057 msgid "Amend Last Commit" msgstr "Letzte nachbessern" -#: git-gui.sh:2019 git-gui.sh:2356 lib/remote_branch_delete.tcl:99 +#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99 msgid "Rescan" msgstr "Neu laden" -#: git-gui.sh:2025 +#: git-gui.sh:2643 msgid "Stage To Commit" msgstr "Zum Eintragen bereitstellen" -#: git-gui.sh:2031 +#: git-gui.sh:2649 msgid "Stage Changed Files To Commit" msgstr "Geänderte Dateien bereitstellen" -#: git-gui.sh:2037 +#: git-gui.sh:2655 msgid "Unstage From Commit" msgstr "Aus der Bereitstellung herausnehmen" -#: git-gui.sh:2042 lib/index.tcl:395 +#: git-gui.sh:2661 lib/index.tcl:412 msgid "Revert Changes" msgstr "Änderungen verwerfen" -#: git-gui.sh:2141 git-gui.sh:2702 +#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341 msgid "Show Less Context" msgstr "Weniger Zeilen anzeigen" -#: git-gui.sh:2145 git-gui.sh:2706 +#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345 msgid "Show More Context" msgstr "Mehr Zeilen anzeigen" -#: git-gui.sh:2151 git-gui.sh:2470 git-gui.sh:2569 +#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133 msgid "Sign Off" msgstr "Abzeichnen" -#: git-gui.sh:2458 +#: git-gui.sh:2696 msgid "Local Merge..." msgstr "Lokales Zusammenführen..." -#: git-gui.sh:2069 +#: git-gui.sh:2701 msgid "Abort Merge..." msgstr "Zusammenführen abbrechen..." -#: git-gui.sh:2475 +#: git-gui.sh:2713 git-gui.sh:2741 msgid "Add..." msgstr "Hinzufügen..." -#: git-gui.sh:2479 +#: git-gui.sh:2717 msgid "Push..." msgstr "Versenden..." -#: git-gui.sh:2483 +#: git-gui.sh:2721 msgid "Delete Branch..." msgstr "Zweig löschen..." -#: git-gui.sh:2493 git-gui.sh:2515 lib/about.tcl:14 -#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50 -#, tcl-format -msgid "About %s" -msgstr "Über %s" - -#: git-gui.sh:2099 -msgid "Preferences..." -msgstr "Einstellungen..." - -#: git-gui.sh:2107 git-gui.sh:2639 +#: git-gui.sh:2731 git-gui.sh:3292 msgid "Options..." msgstr "Optionen..." -#: git-gui.sh:2576 +#: git-gui.sh:2742 msgid "Remove..." msgstr "Entfernen..." -#: git-gui.sh:2585 lib/choose_repository.tcl:50 +#: git-gui.sh:2751 lib/choose_repository.tcl:50 msgid "Help" msgstr "Hilfe" -#: git-gui.sh:2154 +#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 +#, tcl-format +msgid "About %s" +msgstr "Über %s" + +#: git-gui.sh:2783 msgid "Online Documentation" msgstr "Online-Dokumentation" -#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 +#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 msgid "Show SSH Key" msgstr "SSH-Schlüssel anzeigen" -#: git-gui.sh:2707 +#: git-gui.sh:2893 #, tcl-format msgid "fatal: cannot stat path %s: No such file or directory" msgstr "" "Fehler: Verzeichnis »%s« kann nicht gelesen werden: Datei oder Verzeichnis " "nicht gefunden" -#: git-gui.sh:2271 +#: git-gui.sh:2926 msgid "Current Branch:" msgstr "Aktueller Zweig:" -#: git-gui.sh:2292 +#: git-gui.sh:2947 msgid "Staged Changes (Will Commit)" msgstr "Bereitstellung (zum Eintragen)" -#: git-gui.sh:2312 +#: git-gui.sh:2967 msgid "Unstaged Changes" msgstr "Nicht bereitgestellte Änderungen" -#: git-gui.sh:2362 +#: git-gui.sh:3017 msgid "Stage Changed" msgstr "Alles bereitstellen" -#: git-gui.sh:2378 lib/transport.tcl:93 lib/transport.tcl:182 +#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193 msgid "Push" msgstr "Versenden" -#: git-gui.sh:2408 +#: git-gui.sh:3071 msgid "Initial Commit Message:" msgstr "Erste Versionsbeschreibung:" -#: git-gui.sh:2409 +#: git-gui.sh:3072 msgid "Amended Commit Message:" msgstr "Nachgebesserte Beschreibung:" -#: git-gui.sh:2410 +#: git-gui.sh:3073 msgid "Amended Initial Commit Message:" msgstr "Nachgebesserte erste Beschreibung:" -#: git-gui.sh:2411 +#: git-gui.sh:3074 msgid "Amended Merge Commit Message:" msgstr "Nachgebesserte Zusammenführungs-Beschreibung:" -#: git-gui.sh:2412 +#: git-gui.sh:3075 msgid "Merge Commit Message:" msgstr "Zusammenführungs-Beschreibung:" -#: git-gui.sh:2413 +#: git-gui.sh:3076 msgid "Commit Message:" msgstr "Versionsbeschreibung:" -#: git-gui.sh:2459 git-gui.sh:2622 lib/console.tcl:73 +#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73 msgid "Copy All" msgstr "Alle kopieren" -#: git-gui.sh:2483 lib/blame.tcl:107 +#: git-gui.sh:3149 lib/blame.tcl:104 msgid "File:" msgstr "Datei:" -#: git-gui.sh:2834 +#: git-gui.sh:3255 msgid "Refresh" msgstr "Aktualisieren" -#: git-gui.sh:2631 +#: git-gui.sh:3276 msgid "Decrease Font Size" msgstr "Schriftgröße verkleinern" -#: git-gui.sh:2635 +#: git-gui.sh:3280 msgid "Increase Font Size" msgstr "Schriftgröße vergrößern" -#: git-gui.sh:3033 lib/blame.tcl:281 +#: git-gui.sh:3288 lib/blame.tcl:281 msgid "Encoding" msgstr "Zeichenkodierung" -#: git-gui.sh:3044 +#: git-gui.sh:3299 msgid "Apply/Reverse Hunk" msgstr "Kontext anwenden/umkehren" -#: git-gui.sh:2875 +#: git-gui.sh:3304 msgid "Apply/Reverse Line" msgstr "Zeile anwenden/umkehren" -#: git-gui.sh:2885 +#: git-gui.sh:3323 msgid "Run Merge Tool" msgstr "Zusammenführungswerkzeug" -#: git-gui.sh:2890 +#: git-gui.sh:3328 msgid "Use Remote Version" msgstr "Entfernte Version benutzen" -#: git-gui.sh:2894 +#: git-gui.sh:3332 msgid "Use Local Version" msgstr "Lokale Version benutzen" -#: git-gui.sh:2898 +#: git-gui.sh:3336 msgid "Revert To Base" msgstr "Ursprüngliche Version benutzen" -#: git-gui.sh:3091 +#: git-gui.sh:3354 +msgid "Visualize These Changes In The Submodule" +msgstr "Diese Änderungen im Untermodul darstellen" + +#: git-gui.sh:3358 +msgid "Visualize Current Branch History In The Submodule" +msgstr "Aktuellen Zweig im Untermodul darstellen" + +#: git-gui.sh:3362 +msgid "Visualize All Branch History In The Submodule" +msgstr "Alle Zweige im Untermodul darstellen" + +#: git-gui.sh:3367 +msgid "Start git gui In The Submodule" +msgstr "Git gui im Untermodul starten" + +#: git-gui.sh:3389 msgid "Unstage Hunk From Commit" msgstr "Kontext aus Bereitstellung herausnehmen" -#: git-gui.sh:2748 +#: git-gui.sh:3391 +msgid "Unstage Lines From Commit" +msgstr "Zeilen aus der Bereitstellung herausnehmen" + +#: git-gui.sh:3393 msgid "Unstage Line From Commit" msgstr "Zeile aus der Bereitstellung herausnehmen" -#: git-gui.sh:2750 +#: git-gui.sh:3396 msgid "Stage Hunk For Commit" msgstr "Kontext zur Bereitstellung hinzufügen" -#: git-gui.sh:2751 +#: git-gui.sh:3398 +msgid "Stage Lines For Commit" +msgstr "Zeilen zur Bereitstellung hinzufügen" + +#: git-gui.sh:3400 msgid "Stage Line For Commit" msgstr "Zeile zur Bereitstellung hinzufügen" -#: git-gui.sh:2771 +#: git-gui.sh:3424 msgid "Initializing..." msgstr "Initialisieren..." -#: git-gui.sh:2762 +#: git-gui.sh:3541 #, tcl-format msgid "" "Possible environment issues exist.\n" @@ -516,7 +548,7 @@ msgstr "" "von %s an Git weitergegeben werden:\n" "\n" -#: git-gui.sh:2792 +#: git-gui.sh:3570 msgid "" "\n" "This is due to a known issue with the\n" @@ -526,7 +558,7 @@ msgstr "" "Dies ist ein bekanntes Problem der Tcl-Version, die\n" "in Cygwin mitgeliefert wird." -#: git-gui.sh:2797 +#: git-gui.sh:3575 #, tcl-format msgid "" "\n" @@ -546,15 +578,15 @@ msgstr "" msgid "git-gui - a graphical user interface for Git." msgstr "git-gui - eine grafische Oberfläche für Git." -#: lib/blame.tcl:77 +#: lib/blame.tcl:72 msgid "File Viewer" msgstr "Datei-Browser" -#: lib/blame.tcl:81 +#: lib/blame.tcl:78 msgid "Commit:" msgstr "Version:" -#: lib/blame.tcl:264 +#: lib/blame.tcl:271 msgid "Copy Commit" msgstr "Version kopieren" @@ -566,88 +598,88 @@ msgstr "Text suchen..." msgid "Do Full Copy Detection" msgstr "Volle Kopie-Erkennung" -#: lib/blame.tcl:263 +#: lib/blame.tcl:288 msgid "Show History Context" msgstr "Historien-Kontext anzeigen" -#: lib/blame.tcl:266 +#: lib/blame.tcl:291 msgid "Blame Parent Commit" msgstr "Elternversion annotieren" -#: lib/blame.tcl:394 +#: lib/blame.tcl:450 #, tcl-format msgid "Reading %s..." msgstr "%s lesen..." -#: lib/blame.tcl:488 +#: lib/blame.tcl:557 msgid "Loading copy/move tracking annotations..." msgstr "Annotierungen für Kopieren/Verschieben werden geladen..." -#: lib/blame.tcl:508 +#: lib/blame.tcl:577 msgid "lines annotated" msgstr "Zeilen annotiert" -#: lib/blame.tcl:689 +#: lib/blame.tcl:769 msgid "Loading original location annotations..." msgstr "Annotierungen für ursprünglichen Ort werden geladen..." -#: lib/blame.tcl:692 +#: lib/blame.tcl:772 msgid "Annotation complete." msgstr "Annotierung vollständig." -#: lib/blame.tcl:737 +#: lib/blame.tcl:802 msgid "Busy" msgstr "Verarbeitung läuft" -#: lib/blame.tcl:738 +#: lib/blame.tcl:803 msgid "Annotation process is already running." msgstr "Annotierung läuft bereits." -#: lib/blame.tcl:777 +#: lib/blame.tcl:842 msgid "Running thorough copy detection..." msgstr "Intensive Kopie-Erkennung läuft..." -#: lib/blame.tcl:827 +#: lib/blame.tcl:910 msgid "Loading annotation..." msgstr "Annotierung laden..." -#: lib/blame.tcl:802 +#: lib/blame.tcl:963 msgid "Author:" msgstr "Autor:" -#: lib/blame.tcl:806 +#: lib/blame.tcl:967 msgid "Committer:" msgstr "Eintragender:" -#: lib/blame.tcl:811 +#: lib/blame.tcl:972 msgid "Original File:" msgstr "Ursprüngliche Datei:" -#: lib/blame.tcl:1021 +#: lib/blame.tcl:1020 msgid "Cannot find HEAD commit:" msgstr "Zweigspitze (»HEAD«) kann nicht gefunden werden:" -#: lib/blame.tcl:1076 +#: lib/blame.tcl:1075 msgid "Cannot find parent commit:" msgstr "Elternversion kann nicht gefunden werden:" -#: lib/blame.tcl:1001 +#: lib/blame.tcl:1090 msgid "Unable to display parent" msgstr "Elternversion kann nicht angezeigt werden" -#: lib/blame.tcl:1002 lib/diff.tcl:191 +#: lib/blame.tcl:1091 lib/diff.tcl:320 msgid "Error loading diff:" msgstr "Fehler beim Laden des Vergleichs:" -#: lib/blame.tcl:1142 +#: lib/blame.tcl:1231 msgid "Originally By:" msgstr "Ursprünglich von:" -#: lib/blame.tcl:931 +#: lib/blame.tcl:1237 msgid "In File:" msgstr "In Datei:" -#: lib/blame.tcl:936 +#: lib/blame.tcl:1242 msgid "Copied Or Moved Here By:" msgstr "Kopiert oder verschoben durch:" @@ -661,16 +693,18 @@ msgstr "Umstellen" #: 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 +#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42 +#: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352 +#: lib/transport.tcl:108 msgid "Cancel" msgstr "Abbrechen" -#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 +#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 lib/tools_dlg.tcl:328 msgid "Revision" msgstr "Version" -#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:242 +#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:280 msgid "Options" msgstr "Optionen" @@ -690,7 +724,7 @@ msgstr "Zweig erstellen" msgid "Create New Branch" msgstr "Neuen Zweig erstellen" -#: lib/branch_create.tcl:31 lib/choose_repository.tcl:371 +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381 msgid "Create" msgstr "Erstellen" @@ -698,7 +732,7 @@ msgstr "Erstellen" msgid "Branch Name" msgstr "Zweigname" -#: lib/branch_create.tcl:43 +#: lib/branch_create.tcl:43 lib/remote_add.tcl:39 lib/tools_dlg.tcl:50 msgid "Name:" msgstr "Name:" @@ -722,7 +756,7 @@ msgstr "Nein" msgid "Fast Forward Only" msgstr "Nur Schnellzusammenführung" -#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514 +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571 msgid "Reset" msgstr "Zurücksetzen" @@ -764,15 +798,26 @@ msgstr "Lokale Zweige" msgid "Delete Only If Merged Into" msgstr "Nur löschen, wenn zusammengeführt nach" -#: lib/branch_delete.tcl:54 -msgid "Always (Do not perform merge test.)" -msgstr "Immer (ohne Zusammenführungstest)" +#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" +msgstr "Immer (Keine Zusammenführungsprüfung)" #: lib/branch_delete.tcl:103 #, tcl-format msgid "The following branches are not completely merged into %s:" msgstr "Folgende Zweige sind noch nicht mit »%s« zusammengeführt:" +#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"Das Wiederherstellen von gelöschten Zweigen ist nur mit größerem Aufwand " +"möglich.\n" +"\n" +"Sollen die ausgewählten Zweige gelöscht werden?" + #: lib/branch_delete.tcl:141 #, tcl-format msgid "" @@ -802,7 +847,7 @@ msgstr "Neuer Name:" msgid "Please select a branch to rename." msgstr "Bitte wählen Sie einen Zweig zum umbenennen." -#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179 +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202 #, tcl-format msgid "Branch '%s' already exists." msgstr "Zweig »%s« existiert bereits." @@ -833,37 +878,38 @@ msgstr "[Nach oben]" msgid "Browse Branch Files" msgstr "Dateien des Zweigs durchblättern" -#: lib/browser.tcl:278 lib/choose_repository.tcl:387 -#: lib/choose_repository.tcl:474 lib/choose_repository.tcl:484 -#: lib/choose_repository.tcl:987 +#: lib/browser.tcl:278 lib/choose_repository.tcl:398 +#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497 +#: lib/choose_repository.tcl:1028 msgid "Browse" msgstr "Blättern" -#: lib/checkout_op.tcl:79 +#: lib/checkout_op.tcl:85 #, tcl-format msgid "Fetching %s from %s" msgstr "Änderungen »%s« von »%s« anfordern" -#: lib/checkout_op.tcl:127 +#: lib/checkout_op.tcl:133 #, tcl-format msgid "fatal: Cannot resolve %s" msgstr "Fehler: »%s« kann nicht als Zweig oder Version erkannt werden" -#: lib/checkout_op.tcl:140 lib/console.tcl:81 lib/database.tcl:31 +#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31 +#: lib/sshkey.tcl:53 msgid "Close" msgstr "Schließen" -#: lib/checkout_op.tcl:169 +#: lib/checkout_op.tcl:175 #, tcl-format msgid "Branch '%s' does not exist." msgstr "Zweig »%s« existiert nicht." -#: lib/checkout_op.tcl:193 +#: lib/checkout_op.tcl:194 #, tcl-format msgid "Failed to configure simplified git-pull for '%s'." msgstr "Fehler beim Einrichten der vereinfachten git-pull für »%s«." -#: lib/checkout_op.tcl:228 +#: lib/checkout_op.tcl:229 #, tcl-format msgid "" "Branch '%s' already exists.\n" @@ -876,21 +922,21 @@ msgstr "" "Zweig kann nicht mit »%s« schnellzusammengeführt werden. Reguläres " "Zusammenführen ist notwendig." -#: lib/checkout_op.tcl:220 +#: lib/checkout_op.tcl:243 #, tcl-format msgid "Merge strategy '%s' not supported." msgstr "Zusammenführungsmethode »%s« nicht unterstützt." -#: lib/checkout_op.tcl:239 +#: lib/checkout_op.tcl:262 #, tcl-format msgid "Failed to update '%s'." msgstr "Aktualisieren von »%s« fehlgeschlagen." -#: lib/checkout_op.tcl:251 +#: lib/checkout_op.tcl:274 msgid "Staging area (index) is already locked." msgstr "Bereitstellung (»index«) ist zur Bearbeitung gesperrt (»locked«)." -#: lib/checkout_op.tcl:266 +#: lib/checkout_op.tcl:289 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -906,32 +952,32 @@ msgstr "" "\n" "Es wird gleich neu geladen.\n" -#: lib/checkout_op.tcl:322 +#: lib/checkout_op.tcl:345 #, tcl-format msgid "Updating working directory to '%s'..." msgstr "Arbeitskopie umstellen auf »%s«..." -#: lib/checkout_op.tcl:323 +#: lib/checkout_op.tcl:346 msgid "files checked out" msgstr "Dateien aktualisiert" -#: lib/checkout_op.tcl:353 +#: lib/checkout_op.tcl:376 #, tcl-format msgid "Aborted checkout of '%s' (file level merging is required)." msgstr "" "Auf Zweig »%s« umstellen abgebrochen (Zusammenführen der Dateien ist " "notwendig)." -#: lib/checkout_op.tcl:354 +#: lib/checkout_op.tcl:377 msgid "File level merge required." msgstr "Zusammenführen der Dateien ist notwendig." -#: lib/checkout_op.tcl:358 +#: lib/checkout_op.tcl:381 #, tcl-format msgid "Staying on branch '%s'." msgstr "Es wird auf Zweig »%s« verblieben." -#: lib/checkout_op.tcl:429 +#: lib/checkout_op.tcl:452 msgid "" "You are no longer on a local branch.\n" "\n" @@ -943,32 +989,32 @@ msgstr "" "Wenn Sie auf einem Zweig arbeiten möchten, erstellen Sie bitte jetzt einen " "Zweig mit der Auswahl »Abgetrennte Arbeitskopie-Version«." -#: lib/checkout_op.tcl:446 lib/checkout_op.tcl:450 +#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507 #, tcl-format msgid "Checked out '%s'." msgstr "Umgestellt auf »%s«." -#: lib/checkout_op.tcl:478 +#: lib/checkout_op.tcl:535 #, tcl-format msgid "Resetting '%s' to '%s' will lose the following commits:" msgstr "Zurücksetzen von »%s« nach »%s« wird folgende Versionen verwerfen:" -#: lib/checkout_op.tcl:500 +#: lib/checkout_op.tcl:557 msgid "Recovering lost commits may not be easy." msgstr "" "Verworfene Versionen können nur mit größerem Aufwand wiederhergestellt " "werden." -#: lib/checkout_op.tcl:505 +#: lib/checkout_op.tcl:562 #, tcl-format msgid "Reset '%s'?" msgstr "»%s« zurücksetzen?" -#: lib/checkout_op.tcl:510 lib/merge.tcl:163 +#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343 msgid "Visualize" msgstr "Darstellen" -#: lib/checkout_op.tcl:578 +#: lib/checkout_op.tcl:635 #, tcl-format msgid "" "Failed to set current branch.\n" @@ -1014,231 +1060,231 @@ msgstr "" msgid "Git Gui" msgstr "Git Gui" -#: lib/choose_repository.tcl:81 lib/choose_repository.tcl:376 +#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386 msgid "Create New Repository" msgstr "Neues Projektarchiv" -#: lib/choose_repository.tcl:87 +#: lib/choose_repository.tcl:93 msgid "New..." msgstr "Neu..." -#: lib/choose_repository.tcl:94 lib/choose_repository.tcl:460 +#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471 msgid "Clone Existing Repository" msgstr "Projektarchiv klonen" -#: lib/choose_repository.tcl:100 +#: lib/choose_repository.tcl:106 msgid "Clone..." msgstr "Klonen..." -#: lib/choose_repository.tcl:107 lib/choose_repository.tcl:976 +#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016 msgid "Open Existing Repository" msgstr "Projektarchiv öffnen" -#: lib/choose_repository.tcl:113 +#: lib/choose_repository.tcl:119 msgid "Open..." msgstr "Öffnen..." -#: lib/choose_repository.tcl:126 +#: lib/choose_repository.tcl:132 msgid "Recent Repositories" msgstr "Zuletzt benutzte Projektarchive" -#: lib/choose_repository.tcl:132 +#: lib/choose_repository.tcl:138 msgid "Open Recent Repository:" msgstr "Zuletzt benutztes Projektarchiv öffnen:" -#: lib/choose_repository.tcl:296 lib/choose_repository.tcl:303 -#: lib/choose_repository.tcl:310 +#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313 +#: lib/choose_repository.tcl:320 #, tcl-format msgid "Failed to create repository %s:" msgstr "Projektarchiv »%s« konnte nicht erstellt werden:" -#: lib/choose_repository.tcl:387 +#: lib/choose_repository.tcl:391 msgid "Directory:" msgstr "Verzeichnis:" -#: lib/choose_repository.tcl:412 lib/choose_repository.tcl:537 -#: lib/choose_repository.tcl:1011 +#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550 +#: lib/choose_repository.tcl:1052 msgid "Git Repository" msgstr "Git Projektarchiv" -#: lib/choose_repository.tcl:442 +#: lib/choose_repository.tcl:448 #, tcl-format msgid "Directory %s already exists." msgstr "Verzeichnis »%s« existiert bereits." -#: lib/choose_repository.tcl:446 +#: lib/choose_repository.tcl:452 #, tcl-format msgid "File %s already exists." msgstr "Datei »%s« existiert bereits." -#: lib/choose_repository.tcl:455 +#: lib/choose_repository.tcl:466 msgid "Clone" msgstr "Klonen" -#: lib/choose_repository.tcl:473 +#: lib/choose_repository.tcl:479 msgid "Source Location:" msgstr "Herkunft:" -#: lib/choose_repository.tcl:484 +#: lib/choose_repository.tcl:490 msgid "Target Directory:" msgstr "Zielverzeichnis:" -#: lib/choose_repository.tcl:490 +#: lib/choose_repository.tcl:502 msgid "Clone Type:" msgstr "Art des Klonens:" -#: lib/choose_repository.tcl:495 +#: lib/choose_repository.tcl:508 msgid "Standard (Fast, Semi-Redundant, Hardlinks)" msgstr "Standard (schnell, teilweise redundant, Hardlinks)" -#: lib/choose_repository.tcl:501 +#: lib/choose_repository.tcl:514 msgid "Full Copy (Slower, Redundant Backup)" msgstr "Alles kopieren (langsamer, volle Redundanz)" -#: lib/choose_repository.tcl:507 +#: lib/choose_repository.tcl:520 msgid "Shared (Fastest, Not Recommended, No Backup)" msgstr "Verknüpft (schnell, nicht empfohlen, kein Backup)" -#: 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 +#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603 +#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819 +#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066 #, tcl-format msgid "Not a Git repository: %s" msgstr "Kein Git-Projektarchiv in »%s« gefunden." -#: lib/choose_repository.tcl:579 +#: lib/choose_repository.tcl:592 msgid "Standard only available for local repository." msgstr "Standard ist nur für lokale Projektarchive verfügbar." -#: lib/choose_repository.tcl:583 +#: lib/choose_repository.tcl:596 msgid "Shared only available for local repository." msgstr "Verknüpft ist nur für lokale Projektarchive verfügbar." -#: lib/choose_repository.tcl:604 +#: lib/choose_repository.tcl:617 #, tcl-format msgid "Location %s already exists." msgstr "Projektarchiv »%s« existiert bereits." -#: lib/choose_repository.tcl:615 +#: lib/choose_repository.tcl:628 msgid "Failed to configure origin" msgstr "Der Ursprungsort konnte nicht eingerichtet werden" -#: lib/choose_repository.tcl:627 +#: lib/choose_repository.tcl:640 msgid "Counting objects" msgstr "Objekte werden gezählt" -#: lib/choose_repository.tcl:628 +#: lib/choose_repository.tcl:641 msgid "buckets" msgstr "Buckets" -#: lib/choose_repository.tcl:652 +#: lib/choose_repository.tcl:665 #, tcl-format msgid "Unable to copy objects/info/alternates: %s" msgstr "Kopien von Objekten/Info/Alternates konnten nicht erstellt werden: %s" -#: lib/choose_repository.tcl:688 +#: lib/choose_repository.tcl:701 #, tcl-format msgid "Nothing to clone from %s." msgstr "Von »%s« konnte nichts geklont werden." -#: lib/choose_repository.tcl:690 lib/choose_repository.tcl:904 -#: lib/choose_repository.tcl:916 +#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917 +#: lib/choose_repository.tcl:929 msgid "The 'master' branch has not been initialized." msgstr "Der »master«-Zweig wurde noch nicht initialisiert." -#: lib/choose_repository.tcl:703 +#: lib/choose_repository.tcl:716 msgid "Hardlinks are unavailable. Falling back to copying." msgstr "Hardlinks nicht verfügbar. Stattdessen wird kopiert." -#: lib/choose_repository.tcl:715 +#: lib/choose_repository.tcl:728 #, tcl-format msgid "Cloning from %s" msgstr "Kopieren von »%s«" -#: lib/choose_repository.tcl:746 +#: lib/choose_repository.tcl:759 msgid "Copying objects" msgstr "Objektdatenbank kopieren" -#: lib/choose_repository.tcl:747 +#: lib/choose_repository.tcl:760 msgid "KiB" msgstr "KB" -#: lib/choose_repository.tcl:771 +#: lib/choose_repository.tcl:784 #, tcl-format msgid "Unable to copy object: %s" msgstr "Objekt kann nicht kopiert werden: %s" -#: lib/choose_repository.tcl:781 +#: lib/choose_repository.tcl:794 msgid "Linking objects" msgstr "Objekte verlinken" -#: lib/choose_repository.tcl:782 +#: lib/choose_repository.tcl:795 msgid "objects" msgstr "Objekte" -#: lib/choose_repository.tcl:790 +#: lib/choose_repository.tcl:803 #, tcl-format msgid "Unable to hardlink object: %s" msgstr "Für Objekt konnte kein Hardlink erstellt werden: %s" -#: lib/choose_repository.tcl:845 +#: lib/choose_repository.tcl:858 msgid "Cannot fetch branches and objects. See console output for details." msgstr "" "Zweige und Objekte konnten nicht angefordert werden. Kontrollieren Sie die " "Ausgaben auf der Konsole für weitere Angaben." -#: lib/choose_repository.tcl:856 +#: lib/choose_repository.tcl:869 msgid "Cannot fetch tags. See console output for details." msgstr "" "Markierungen konnten nicht angefordert werden. Kontrollieren Sie die " "Ausgaben auf der Konsole für weitere Angaben." -#: lib/choose_repository.tcl:880 +#: lib/choose_repository.tcl:893 msgid "Cannot determine HEAD. See console output for details." msgstr "" "Die Zweigspitze (HEAD) konnte nicht gefunden werden. Kontrollieren Sie die " "Ausgaben auf der Konsole für weitere Angaben." -#: lib/choose_repository.tcl:889 +#: lib/choose_repository.tcl:902 #, tcl-format msgid "Unable to cleanup %s" msgstr "Verzeichnis »%s« kann nicht aufgeräumt werden." -#: lib/choose_repository.tcl:895 +#: lib/choose_repository.tcl:908 msgid "Clone failed." msgstr "Klonen fehlgeschlagen." -#: lib/choose_repository.tcl:902 +#: lib/choose_repository.tcl:915 msgid "No default branch obtained." msgstr "Kein voreingestellter Zweig gefunden." -#: lib/choose_repository.tcl:913 +#: lib/choose_repository.tcl:926 #, tcl-format msgid "Cannot resolve %s as a commit." msgstr "»%s« wurde nicht als Version gefunden." -#: lib/choose_repository.tcl:925 +#: lib/choose_repository.tcl:938 msgid "Creating working directory" msgstr "Arbeitskopie erstellen" -#: lib/choose_repository.tcl:926 lib/index.tcl:65 lib/index.tcl:127 -#: lib/index.tcl:193 +#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130 +#: lib/index.tcl:198 msgid "files" msgstr "Dateien" -#: lib/choose_repository.tcl:955 +#: lib/choose_repository.tcl:968 msgid "Initial file checkout failed." msgstr "Erstellen der Arbeitskopie fehlgeschlagen." -#: lib/choose_repository.tcl:971 +#: lib/choose_repository.tcl:1011 msgid "Open" msgstr "Öffnen" -#: lib/choose_repository.tcl:981 +#: lib/choose_repository.tcl:1021 msgid "Repository:" msgstr "Projektarchiv:" -#: lib/choose_repository.tcl:1031 +#: lib/choose_repository.tcl:1072 #, tcl-format msgid "Failed to open repository %s:" msgstr "Projektarchiv »%s« konnte nicht geöffnet werden." @@ -1311,19 +1357,24 @@ msgstr "" "unfertige Zusammenführung existiert. Dazu müssen Sie die Zusammenführung " "beenden oder abbrechen.\n" -#: lib/commit.tcl:49 +#: lib/commit.tcl:48 msgid "Error loading commit data for amend:" msgstr "Fehler beim Laden der Versionsdaten für Nachbessern:" -#: lib/commit.tcl:76 +#: lib/commit.tcl:75 msgid "Unable to obtain your identity:" msgstr "Benutzername konnte nicht bestimmt werden:" -#: lib/commit.tcl:81 +#: lib/commit.tcl:80 msgid "Invalid GIT_COMMITTER_IDENT:" msgstr "Ungültiger Wert von GIT_COMMITTER_INDENT:" -#: lib/commit.tcl:133 +#: lib/commit.tcl:129 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "Warning: Tcl/Tk unterstützt die Zeichencodierung »%s« nicht." + +#: lib/commit.tcl:149 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -1339,7 +1390,7 @@ msgstr "" "\n" "Es wird gleich neu geladen.\n" -#: lib/commit.tcl:154 +#: lib/commit.tcl:172 #, tcl-format msgid "" "Unmerged files cannot be committed.\n" @@ -1352,7 +1403,7 @@ msgstr "" "Die Datei »%s« hat noch nicht aufgelöste Zusammenführungs-Konflikte. Sie " "müssen diese Konflikte auflösen, bevor Sie eintragen können.\n" -#: lib/commit.tcl:162 +#: lib/commit.tcl:180 #, tcl-format msgid "" "Unknown file state %s detected.\n" @@ -1363,7 +1414,7 @@ msgstr "" "\n" "Datei »%s« kann nicht eingetragen werden.\n" -#: lib/commit.tcl:170 +#: lib/commit.tcl:188 msgid "" "No changes to commit.\n" "\n" @@ -1373,7 +1424,7 @@ msgstr "" "\n" "Sie müssen mindestens eine Datei bereitstellen, bevor Sie eintragen können.\n" -#: lib/commit.tcl:183 +#: lib/commit.tcl:203 msgid "" "Please supply a commit message.\n" "\n" @@ -1393,47 +1444,42 @@ msgstr "" "\n" "- Rest: Eine ausführliche Beschreibung, warum diese Änderung hilfreich ist.\n" -#: lib/commit.tcl:207 -#, tcl-format -msgid "warning: Tcl does not support encoding '%s'." -msgstr "Warning: Tcl/Tk unterstützt die Zeichencodierung »%s« nicht." - -#: lib/commit.tcl:221 +#: lib/commit.tcl:234 msgid "Calling pre-commit hook..." msgstr "Aufrufen der Vor-Eintragen-Kontrolle..." -#: lib/commit.tcl:236 +#: lib/commit.tcl:249 msgid "Commit declined by pre-commit hook." msgstr "Eintragen abgelehnt durch Vor-Eintragen-Kontrolle (»pre-commit hook«)." -#: lib/commit.tcl:259 +#: lib/commit.tcl:272 msgid "Calling commit-msg hook..." msgstr "Aufrufen der Versionsbeschreibungs-Kontrolle..." -#: lib/commit.tcl:274 +#: lib/commit.tcl:287 msgid "Commit declined by commit-msg hook." msgstr "" "Eintragen abgelehnt durch Versionsbeschreibungs-Kontrolle (»commit-message " "hook«)." -#: lib/commit.tcl:287 +#: lib/commit.tcl:300 msgid "Committing changes..." msgstr "Änderungen eintragen..." -#: lib/commit.tcl:303 +#: lib/commit.tcl:316 msgid "write-tree failed:" msgstr "write-tree fehlgeschlagen:" -#: lib/commit.tcl:304 lib/commit.tcl:348 lib/commit.tcl:368 +#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382 msgid "Commit failed." msgstr "Eintragen fehlgeschlagen." -#: lib/commit.tcl:321 +#: lib/commit.tcl:334 #, tcl-format msgid "Commit %s appears to be corrupt" msgstr "Version »%s« scheint beschädigt zu sein" -#: lib/commit.tcl:326 +#: lib/commit.tcl:339 msgid "" "No changes to commit.\n" "\n" @@ -1448,19 +1494,19 @@ msgstr "" "\n" "Das Arbeitsverzeichnis wird daher jetzt neu geladen.\n" -#: lib/commit.tcl:333 +#: lib/commit.tcl:346 msgid "No changes to commit." msgstr "Keine Änderungen, die eingetragen werden können." -#: lib/commit.tcl:347 +#: lib/commit.tcl:360 msgid "commit-tree failed:" msgstr "commit-tree fehlgeschlagen:" -#: lib/commit.tcl:367 +#: lib/commit.tcl:381 msgid "update-ref failed:" msgstr "update-ref fehlgeschlagen:" -#: lib/commit.tcl:454 +#: lib/commit.tcl:469 #, tcl-format msgid "Created commit %s: %s" msgstr "Version %s übertragen: %s" @@ -1513,21 +1559,19 @@ msgstr "Objektdatenbank komprimieren" msgid "Verifying the object database with fsck-objects" msgstr "Die Objektdatenbank durch »fsck-objects« überprüfen lassen" -#: lib/database.tcl:108 +#: lib/database.tcl:107 #, 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" +"the database.\n" "\n" "Compress the database now?" msgstr "" "Dieses Projektarchiv enthält ungefähr %i nicht verknüpfte Objekte.\n" "\n" -"Für eine optimale Performance wird empfohlen, die Datenbank des " -"Projektarchivs zu komprimieren, sobald mehr als %i nicht verknüpfte Objekte " -"vorliegen.\n" +"Für eine optimale Performance wird empfohlen, die Datenbank des Projektarchivs zu komprimieren.\n" "\n" "Soll die Datenbank jetzt komprimiert werden?" @@ -1536,7 +1580,7 @@ msgstr "" msgid "Invalid date from Git: %s" msgstr "Ungültiges Datum von Git: %s" -#: lib/diff.tcl:42 +#: lib/diff.tcl:64 #, tcl-format msgid "" "No differences detected.\n" @@ -1551,19 +1595,19 @@ msgid "" msgstr "" "Keine Änderungen feststellbar.\n" "\n" -"»%s« enthält keine Änderungen. Zwar wurde das Änderungsdatum dieser Datei " -"von einem anderen Programm modifiziert, aber der Inhalt der Datei ist " +"»%s« enthält keine Änderungen. Zwar wurde das Änderungsdatum dieser Datei von " +"einem anderen Programm modifiziert, aber der Inhalt der Datei ist " "unverändert.\n" "\n" "Das Arbeitsverzeichnis wird jetzt neu geladen, um diese Änderung bei allen " "Dateien zu prüfen." -#: lib/diff.tcl:81 +#: lib/diff.tcl:104 #, tcl-format msgid "Loading diff of %s..." msgstr "Vergleich von »%s« laden..." -#: lib/diff.tcl:120 +#: lib/diff.tcl:125 msgid "" "LOCAL: deleted\n" "REMOTE:\n" @@ -1571,7 +1615,7 @@ msgstr "" "LOKAL: gelöscht\n" "ANDERES:\n" -#: lib/diff.tcl:125 +#: lib/diff.tcl:130 msgid "" "REMOTE: deleted\n" "LOCAL:\n" @@ -1579,32 +1623,32 @@ msgstr "" "ANDERES: gelöscht\n" "LOKAL:\n" -#: lib/diff.tcl:132 +#: lib/diff.tcl:137 msgid "LOCAL:\n" msgstr "LOKAL:\n" -#: lib/diff.tcl:135 +#: lib/diff.tcl:140 msgid "REMOTE:\n" msgstr "ANDERES:\n" -#: lib/diff.tcl:197 lib/diff.tcl:296 +#: lib/diff.tcl:202 lib/diff.tcl:319 #, tcl-format msgid "Unable to display %s" msgstr "Datei »%s« kann nicht angezeigt werden" -#: lib/diff.tcl:115 +#: lib/diff.tcl:203 msgid "Error loading file:" msgstr "Fehler beim Laden der Datei:" -#: lib/diff.tcl:122 +#: lib/diff.tcl:210 msgid "Git Repository (subproject)" msgstr "Git-Projektarchiv (Unterprojekt)" -#: lib/diff.tcl:134 +#: lib/diff.tcl:222 msgid "* Binary file (not showing content)." msgstr "* Binärdatei (Inhalt wird nicht angezeigt)" -#: lib/diff.tcl:222 +#: lib/diff.tcl:227 #, tcl-format msgid "" "* Untracked file is %d bytes.\n" @@ -1613,7 +1657,7 @@ msgstr "" "* Datei nicht unter Versionskontrolle, Dateigröße %d Bytes.\n" "* Nur erste %d Bytes werden angezeigt.\n" -#: lib/diff.tcl:228 +#: lib/diff.tcl:233 #, tcl-format msgid "" "\n" @@ -1624,20 +1668,20 @@ msgstr "" "* Datei nicht unter Versionskontrolle, hier abgeschnitten durch %s.\n" "* Zum Ansehen der vollständigen Datei externen Editor benutzen.\n" -#: lib/diff.tcl:436 +#: lib/diff.tcl:482 msgid "Failed to unstage selected hunk." msgstr "" "Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung." -#: lib/diff.tcl:310 +#: lib/diff.tcl:489 msgid "Failed to stage selected hunk." msgstr "Fehler beim Bereitstellen des gewählten Kontexts." -#: lib/diff.tcl:386 +#: lib/diff.tcl:568 msgid "Failed to unstage selected line." msgstr "Fehler beim Herausnehmen der gewählten Zeile aus der Bereitstellung." -#: lib/diff.tcl:394 +#: lib/diff.tcl:576 msgid "Failed to stage selected line." msgstr "Fehler beim Bereitstellen der gewählten Zeile." @@ -1675,7 +1719,7 @@ msgstr "Bereitstellung kann nicht wieder freigegeben werden." msgid "Index Error" msgstr "Fehler in Bereitstellung" -#: lib/index.tcl:21 +#: lib/index.tcl:17 msgid "" "Updating the Git index failed. A rescan will be automatically started to " "resynchronize git-gui." @@ -1684,7 +1728,7 @@ msgstr "" "Git-Aktualisierung wird jetzt gestartet, um git-gui wieder mit git zu " "synchronisieren." -#: lib/index.tcl:27 +#: lib/index.tcl:28 msgid "Continue" msgstr "Fortsetzen" @@ -1692,44 +1736,44 @@ msgstr "Fortsetzen" msgid "Unlock Index" msgstr "Bereitstellung freigeben" -#: lib/index.tcl:282 +#: lib/index.tcl:289 #, tcl-format msgid "Unstaging %s from commit" msgstr "Datei »%s« aus der Bereitstellung herausnehmen" -#: lib/index.tcl:313 +#: lib/index.tcl:328 msgid "Ready to commit." msgstr "Bereit zum Eintragen." -#: lib/index.tcl:326 +#: lib/index.tcl:341 #, tcl-format msgid "Adding %s" msgstr "»%s« hinzufügen..." -#: lib/index.tcl:381 +#: lib/index.tcl:398 #, tcl-format msgid "Revert changes in file %s?" msgstr "Änderungen in Datei »%s« verwerfen?" -#: lib/index.tcl:383 +#: lib/index.tcl:400 #, tcl-format msgid "Revert changes in these %i files?" msgstr "Änderungen in den gewählten %i Dateien verwerfen?" -#: lib/index.tcl:391 +#: lib/index.tcl:408 msgid "Any unstaged changes will be permanently lost by the revert." msgstr "" "Alle nicht bereitgestellten Änderungen werden beim Verwerfen verloren gehen." -#: lib/index.tcl:394 +#: lib/index.tcl:411 msgid "Do Nothing" msgstr "Nichts tun" -#: lib/index.tcl:419 +#: lib/index.tcl:429 msgid "Reverting selected files" msgstr "Änderungen in gewählten Dateien verwerfen" -#: lib/index.tcl:423 +#: lib/index.tcl:433 #, tcl-format msgid "Reverting %s" msgstr "Änderungen in %s verwerfen" @@ -1761,7 +1805,7 @@ msgstr "" "\n" "Es wird gleich neu geladen.\n" -#: lib/merge.tcl:44 +#: lib/merge.tcl:45 #, tcl-format msgid "" "You are in the middle of a conflicted merge.\n" @@ -1778,7 +1822,7 @@ msgstr "" "bereitstellen und eintragen, um die Zusammenführung abzuschließen. Erst " "danach kann eine neue Zusammenführung begonnen werden.\n" -#: lib/merge.tcl:54 +#: lib/merge.tcl:55 #, tcl-format msgid "" "You are in the middle of a change.\n" @@ -1795,34 +1839,34 @@ msgstr "" "Reihenfolge können Sie mögliche Konflikte beim Zusammenführen wesentlich " "einfacher beheben oder abbrechen.\n" -#: lib/merge.tcl:106 +#: lib/merge.tcl:107 #, tcl-format msgid "%s of %s" msgstr "%s von %s" -#: lib/merge.tcl:119 +#: lib/merge.tcl:120 #, tcl-format msgid "Merging %s and %s..." msgstr "Zusammenführen von %s und %s..." -#: lib/merge.tcl:130 +#: lib/merge.tcl:131 msgid "Merge completed successfully." msgstr "Zusammenführen erfolgreich abgeschlossen." -#: lib/merge.tcl:132 +#: lib/merge.tcl:133 msgid "Merge failed. Conflict resolution is required." msgstr "Zusammenführen fehlgeschlagen. Konfliktauflösung ist notwendig." -#: lib/merge.tcl:157 +#: lib/merge.tcl:158 #, tcl-format msgid "Merge Into %s" msgstr "Zusammenführen in »%s«" -#: lib/merge.tcl:176 +#: lib/merge.tcl:177 msgid "Revision To Merge" msgstr "Zusammenzuführende Version" -#: lib/merge.tcl:211 +#: lib/merge.tcl:212 msgid "" "Cannot abort while amending.\n" "\n" @@ -1832,7 +1876,7 @@ msgstr "" "\n" "Sie müssen die Nachbesserung der Version abschließen.\n" -#: lib/merge.tcl:221 +#: lib/merge.tcl:222 msgid "" "Abort merge?\n" "\n" @@ -1847,7 +1891,7 @@ msgstr "" "\n" "Zusammenführen jetzt abbrechen?" -#: lib/merge.tcl:227 +#: lib/merge.tcl:228 msgid "" "Reset changes?\n" "\n" @@ -1862,35 +1906,35 @@ msgstr "" "\n" "Änderungen jetzt zurücksetzen?" -#: lib/merge.tcl:238 +#: lib/merge.tcl:239 msgid "Aborting" msgstr "Abbruch" -#: lib/merge.tcl:238 +#: lib/merge.tcl:239 msgid "files reset" msgstr "Dateien zurückgesetzt" -#: lib/merge.tcl:265 +#: lib/merge.tcl:267 msgid "Abort failed." msgstr "Abbruch fehlgeschlagen." -#: lib/merge.tcl:267 +#: lib/merge.tcl:269 msgid "Abort completed. Ready." msgstr "Abbruch durchgeführt. Bereit." -#: lib/mergetool.tcl:14 +#: lib/mergetool.tcl:8 msgid "Force resolution to the base version?" msgstr "Konflikt durch Basisversion ersetzen?" -#: lib/mergetool.tcl:15 +#: lib/mergetool.tcl:9 msgid "Force resolution to this branch?" msgstr "Konflikt durch diesen Zweig ersetzen?" -#: lib/mergetool.tcl:16 +#: lib/mergetool.tcl:10 msgid "Force resolution to the other branch?" msgstr "Konflikt durch anderen Zweig ersetzen?" -#: lib/mergetool.tcl:20 +#: lib/mergetool.tcl:14 #, tcl-format msgid "" "Note that the diff shows only conflicting changes.\n" @@ -1916,31 +1960,31 @@ msgstr "Datei »%s« hat nicht aufgelöste Konflikte. Trotzdem bereitstellen?" msgid "Adding resolution for %s" msgstr "Auflösung hinzugefügt für %s" -#: lib/mergetool.tcl:119 +#: lib/mergetool.tcl:141 msgid "Cannot resolve deletion or link conflicts using a tool" msgstr "" "Konflikte durch gelöschte Dateien oder symbolische Links können nicht durch " "das Zusamenführungswerkzeug gelöst werden." -#: lib/mergetool.tcl:124 +#: lib/mergetool.tcl:146 msgid "Conflict file does not exist" msgstr "Konflikt-Datei existiert nicht" -#: lib/mergetool.tcl:236 +#: lib/mergetool.tcl:264 #, tcl-format msgid "Not a GUI merge tool: '%s'" msgstr "Kein GUI Zusammenführungswerkzeug: »%s«" -#: lib/mergetool.tcl:240 +#: lib/mergetool.tcl:268 #, tcl-format msgid "Unsupported merge tool '%s'" msgstr "Unbekanntes Zusammenführungswerkzeug: »%s«" -#: lib/mergetool.tcl:275 +#: lib/mergetool.tcl:303 msgid "Merge tool is already running, terminate it?" msgstr "Zusammenführungswerkzeug läuft bereits. Soll es abgebrochen werden?" -#: lib/mergetool.tcl:295 +#: lib/mergetool.tcl:323 #, tcl-format msgid "" "Error retrieving versions:\n" @@ -1949,7 +1993,7 @@ msgstr "" "Fehler beim Abrufen der Dateiversionen:\n" "%s" -#: lib/mergetool.tcl:315 +#: lib/mergetool.tcl:343 #, tcl-format msgid "" "Could not start the merge tool:\n" @@ -1960,11 +2004,11 @@ msgstr "" "\n" "%s" -#: lib/mergetool.tcl:319 +#: lib/mergetool.tcl:347 msgid "Running merge tool..." msgstr "Zusammenführungswerkzeug starten..." -#: lib/mergetool.tcl:347 lib/mergetool.tcl:363 +#: lib/mergetool.tcl:375 lib/mergetool.tcl:383 msgid "Merge tool failed." msgstr "Zusammenführungswerkzeug fehlgeschlagen." @@ -1982,76 +2026,76 @@ msgstr "Ungültige Archiv-Zeichenkodierung »%s«" msgid "Restore Defaults" msgstr "Voreinstellungen wiederherstellen" -#: lib/option.tcl:99 +#: lib/option.tcl:121 msgid "Save" msgstr "Speichern" -#: lib/option.tcl:109 +#: lib/option.tcl:131 #, tcl-format msgid "%s Repository" msgstr "Projektarchiv %s" -#: lib/option.tcl:110 +#: lib/option.tcl:132 msgid "Global (All Repositories)" msgstr "Global (Alle Projektarchive)" -#: lib/option.tcl:116 +#: lib/option.tcl:138 msgid "User Name" msgstr "Benutzername" -#: lib/option.tcl:117 +#: lib/option.tcl:139 msgid "Email Address" msgstr "E-Mail-Adresse" -#: lib/option.tcl:119 +#: lib/option.tcl:141 msgid "Summarize Merge Commits" msgstr "Zusammenführungs-Versionen zusammenfassen" -#: lib/option.tcl:120 +#: lib/option.tcl:142 msgid "Merge Verbosity" msgstr "Ausführlichkeit der Zusammenführen-Meldungen" -#: lib/option.tcl:121 +#: lib/option.tcl:143 msgid "Show Diffstat After Merge" msgstr "Vergleichsstatistik nach Zusammenführen anzeigen" -#: lib/option.tcl:122 +#: lib/option.tcl:144 msgid "Use Merge Tool" msgstr "Zusammenführungswerkzeug" -#: lib/option.tcl:124 +#: lib/option.tcl:146 msgid "Trust File Modification Timestamps" msgstr "Auf Dateiänderungsdatum verlassen" -#: lib/option.tcl:124 +#: lib/option.tcl:147 msgid "Prune Tracking Branches During Fetch" msgstr "Übernahmezweige aufräumen während Anforderung" -#: lib/option.tcl:125 +#: lib/option.tcl:148 msgid "Match Tracking Branches" msgstr "Passend zu Übernahmezweig" -#: lib/option.tcl:126 +#: lib/option.tcl:149 msgid "Blame Copy Only On Changed Files" msgstr "Kopie-Annotieren nur bei geänderten Dateien" -#: lib/option.tcl:127 +#: lib/option.tcl:150 msgid "Minimum Letters To Blame Copy On" msgstr "Mindestzahl Zeichen für Kopie-Annotieren" -#: lib/option.tcl:128 +#: lib/option.tcl:151 msgid "Blame History Context Radius (days)" msgstr "Anzahl Tage für Historien-Kontext" -#: lib/option.tcl:129 +#: lib/option.tcl:152 msgid "Number of Diff Context Lines" msgstr "Anzahl der Kontextzeilen beim Vergleich" -#: lib/option.tcl:127 +#: lib/option.tcl:153 msgid "Commit Message Text Width" msgstr "Textbreite der Versionsbeschreibung" -#: lib/option.tcl:128 +#: lib/option.tcl:154 msgid "New Branch Name Template" msgstr "Namensvorschlag für neue Zweige" @@ -2067,24 +2111,24 @@ msgstr "Ändern" msgid "Spelling Dictionary:" msgstr "Wörterbuch Rechtschreibprüfung:" -#: lib/option.tcl:216 +#: lib/option.tcl:254 msgid "Change Font" msgstr "Schriftart ändern" -#: lib/option.tcl:220 +#: lib/option.tcl:258 #, tcl-format msgid "Choose %s" msgstr "%s wählen" -#: lib/option.tcl:226 +#: lib/option.tcl:264 msgid "pt." msgstr "pt." -#: lib/option.tcl:240 +#: lib/option.tcl:278 msgid "Preferences" msgstr "Einstellungen" -#: lib/option.tcl:275 +#: lib/option.tcl:314 msgid "Failed to completely save options:" msgstr "Optionen konnten nicht gespeichert werden:" @@ -2096,7 +2140,7 @@ msgstr "Anderes Archiv hinzufügen" msgid "Add New Remote" msgstr "Neues anderes Archiv hinzufügen" -#: lib/remote_add.tcl:28 +#: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36 msgid "Add" msgstr "Hinzufügen" @@ -2151,8 +2195,7 @@ msgstr "»%s« anfordern" #: lib/remote_add.tcl:157 #, tcl-format msgid "Do not know how to initialize repository at location '%s'." -msgstr "" -"Initialisieren eines anderen Archivs an Adresse »%s« ist nicht möglich." +msgstr "Initialisieren eines anderen Archivs an Adresse »%s« ist nicht möglich." #: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63 #: lib/transport.tcl:81 @@ -2173,11 +2216,11 @@ msgstr "Zweig in anderem Archiv löschen" msgid "From Repository" msgstr "In Projektarchiv" -#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 +#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134 msgid "Remote:" msgstr "Anderes Archiv:" -#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138 +#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149 msgid "Arbitrary Location:" msgstr "Adresse:" @@ -2193,10 +2236,6 @@ msgstr "Nur löschen, wenn" msgid "Merged Into:" msgstr "Zusammengeführt mit:" -#: lib/remote_branch_delete.tcl:119 -msgid "Always (Do not perform merge checks)" -msgstr "Immer (Keine Zusammenführungsprüfung)" - #: lib/remote_branch_delete.tcl:152 msgid "A branch is required for 'Merged Into'." msgstr "Für »Zusammenführen mit« muss ein Zweig angegeben werden." @@ -2226,27 +2265,16 @@ msgstr "" msgid "Please select one or more branches to delete." msgstr "Bitte wählen Sie mindestens einen Zweig, der gelöscht werden soll." -#: lib/remote_branch_delete.tcl:216 -msgid "" -"Recovering deleted branches is difficult.\n" -"\n" -"Delete the selected branches?" -msgstr "" -"Das Wiederherstellen von gelöschten Zweigen ist nur mit größerem Aufwand " -"möglich.\n" -"\n" -"Sollen die ausgewählten Zweige gelöscht werden?" - #: lib/remote_branch_delete.tcl:226 #, tcl-format msgid "Deleting branches from %s" msgstr "Zweige auf »%s« werden gelöscht" -#: lib/remote_branch_delete.tcl:286 +#: lib/remote_branch_delete.tcl:292 msgid "No repository selected." msgstr "Kein Projektarchiv ausgewählt." -#: lib/remote_branch_delete.tcl:291 +#: lib/remote_branch_delete.tcl:297 #, tcl-format msgid "Scanning %s..." msgstr "»%s« laden..." @@ -2259,11 +2287,11 @@ msgstr "Anderes Archiv entfernen" msgid "Prune from" msgstr "Aufräumen von" -#: lib/remote.tcl:170 +#: lib/remote.tcl:173 msgid "Fetch from" msgstr "Anfordern von" -#: lib/remote.tcl:213 +#: lib/remote.tcl:215 msgid "Push to" msgstr "Versenden nach" @@ -2271,11 +2299,11 @@ msgstr "Versenden nach" msgid "Find:" msgstr "Suchen:" -#: lib/search.tcl:22 +#: lib/search.tcl:23 msgid "Next" msgstr "Nächster" -#: lib/search.tcl:23 +#: lib/search.tcl:24 msgid "Prev" msgstr "Voriger" @@ -2283,11 +2311,11 @@ msgstr "Voriger" msgid "Case-Sensitive" msgstr "Groß-/Kleinschreibung unterscheiden" -#: lib/shortcut.tcl:20 lib/shortcut.tcl:61 +#: lib/shortcut.tcl:21 lib/shortcut.tcl:62 msgid "Cannot write shortcut:" msgstr "Fehler beim Schreiben der Verknüpfung:" -#: lib/shortcut.tcl:136 +#: lib/shortcut.tcl:137 msgid "Cannot write icon:" msgstr "Fehler beim Erstellen des Icons:" @@ -2316,15 +2344,15 @@ msgstr "Rechtschreibprüfungsprogramm mit Fehler abgebrochen" msgid "Unrecognized spell checker" msgstr "Unbekanntes Rechtschreibprüfungsprogramm" -#: lib/spellcheck.tcl:180 +#: lib/spellcheck.tcl:186 msgid "No Suggestions" msgstr "Keine Vorschläge" -#: lib/spellcheck.tcl:381 +#: lib/spellcheck.tcl:388 msgid "Unexpected EOF from spell checker" msgstr "Unerwartetes EOF vom Rechtschreibprüfungsprogramm" -#: lib/spellcheck.tcl:385 +#: lib/spellcheck.tcl:392 msgid "Spell Checker Failed" msgstr "Rechtschreibprüfung fehlgeschlagen" @@ -2534,31 +2562,31 @@ msgstr "Spiegeln nach %s" msgid "Pushing %s %s to %s" msgstr "%s %s nach %s versenden" -#: lib/transport.tcl:89 +#: lib/transport.tcl:100 msgid "Push Branches" msgstr "Zweige versenden" -#: lib/transport.tcl:103 +#: lib/transport.tcl:114 msgid "Source Branches" msgstr "Lokale Zweige" -#: lib/transport.tcl:120 +#: lib/transport.tcl:131 msgid "Destination Repository" msgstr "Ziel-Projektarchiv" -#: lib/transport.tcl:158 +#: lib/transport.tcl:169 msgid "Transfer Options" msgstr "Netzwerk-Einstellungen" -#: lib/transport.tcl:160 +#: lib/transport.tcl:171 msgid "Force overwrite existing branch (may discard changes)" msgstr "" "Überschreiben von existierenden Zweigen erzwingen (könnte Änderungen löschen)" -#: lib/transport.tcl:164 +#: lib/transport.tcl:175 msgid "Use thin pack (for slow network connections)" msgstr "Kompaktes Datenformat benutzen (für langsame Netzverbindungen)" -#: lib/transport.tcl:168 +#: lib/transport.tcl:179 msgid "Include tags" msgstr "Mit Markierungen übertragen" 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/fr.po b/git-gui/po/fr.po index a944ace6ce..81706965f3 100644 --- a/git-gui/po/fr.po +++ b/git-gui/po/fr.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: fr\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-11-16 13:56-0800\n" -"PO-Revision-Date: 2008-11-20 10:20+0100\n" +"POT-Creation-Date: 2010-01-26 15:47-0800\n" +"PO-Revision-Date: 2010-02-02 12:59+0100\n" "Last-Translator: Christian Couder <chriscool@tuxfamily.org>\n" "Language-Team: French\n" "MIME-Version: 1.0\n" @@ -19,33 +19,33 @@ msgstr "" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: git-gui.sh:41 git-gui.sh:737 git-gui.sh:751 git-gui.sh:764 git-gui.sh:847 -#: git-gui.sh:866 +#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903 +#: git-gui.sh:922 msgid "git-gui: fatal error" msgstr "git-gui: erreur fatale" -#: git-gui.sh:689 +#: git-gui.sh:743 #, tcl-format msgid "Invalid font specified in %s:" msgstr "Police invalide spécifiée dans %s :" -#: git-gui.sh:723 +#: git-gui.sh:779 msgid "Main Font" msgstr "Police principale" -#: git-gui.sh:724 +#: git-gui.sh:780 msgid "Diff/Console Font" msgstr "Police diff/console" -#: git-gui.sh:738 +#: git-gui.sh:794 msgid "Cannot find git in PATH." msgstr "Impossible de trouver git dans PATH." -#: git-gui.sh:765 +#: git-gui.sh:821 msgid "Cannot parse Git version string:" msgstr "Impossible de parser la version de Git :" -#: git-gui.sh:783 +#: git-gui.sh:839 #, tcl-format msgid "" "Git version cannot be determined.\n" @@ -64,446 +64,475 @@ msgstr "" "\n" "Peut-on considérer que '%s' est en version 1.5.0 ?\n" -#: git-gui.sh:1062 +#: git-gui.sh:1128 msgid "Git directory not found:" msgstr "Impossible de trouver le répertoire git :" -#: git-gui.sh:1069 +#: git-gui.sh:1146 msgid "Cannot move to top of working directory:" msgstr "Impossible d'aller à la racine du répertoire de travail :" -#: git-gui.sh:1076 -msgid "Cannot use funny .git directory:" -msgstr "Impossible d'utiliser le répertoire .git:" +#: git-gui.sh:1154 +msgid "Cannot use bare repository:" +msgstr "Impossible d'utiliser un dépôt nu (bare) :" -#: git-gui.sh:1081 +#: git-gui.sh:1162 msgid "No working directory" msgstr "Aucun répertoire de travail" -#: git-gui.sh:1247 lib/checkout_op.tcl:305 +#: git-gui.sh:1334 lib/checkout_op.tcl:306 msgid "Refreshing file status..." msgstr "Rafraîchissement du statut des fichiers..." -#: git-gui.sh:1303 +#: git-gui.sh:1390 msgid "Scanning for modified files ..." msgstr "Recherche de fichiers modifiés..." -#: git-gui.sh:1367 +#: git-gui.sh:1454 msgid "Calling prepare-commit-msg hook..." msgstr "Lancement de l'action de préparation du message de commit..." -#: git-gui.sh:1384 +#: git-gui.sh:1471 msgid "Commit declined by prepare-commit-msg hook." msgstr "Commit refusé par l'action de préparation du message de commit." -#: git-gui.sh:1542 lib/browser.tcl:246 +#: git-gui.sh:1629 lib/browser.tcl:246 msgid "Ready." msgstr "Prêt." -#: git-gui.sh:1819 +#: git-gui.sh:1787 +#, tcl-format +msgid "Displaying only %s of %s files." +msgstr "Affiche seulement %s fichiers sur %s." + +#: git-gui.sh:1913 msgid "Unmodified" msgstr "Non modifié" -#: git-gui.sh:1821 +#: git-gui.sh:1915 msgid "Modified, not staged" msgstr "Modifié, pas indexé" -#: git-gui.sh:1822 git-gui.sh:1830 +#: git-gui.sh:1916 git-gui.sh:1924 msgid "Staged for commit" msgstr "Indexé" -#: git-gui.sh:1823 git-gui.sh:1831 +#: git-gui.sh:1917 git-gui.sh:1925 msgid "Portions staged for commit" msgstr "Portions indexées" -#: git-gui.sh:1824 git-gui.sh:1832 +#: git-gui.sh:1918 git-gui.sh:1926 msgid "Staged for commit, missing" msgstr "Indexés, manquant" -#: git-gui.sh:1826 +#: git-gui.sh:1920 msgid "File type changed, not staged" msgstr "Le type de fichier a changé, non indexé" -#: git-gui.sh:1827 +#: git-gui.sh:1921 msgid "File type changed, staged" msgstr "Le type de fichier a changé, indexé" -#: git-gui.sh:1829 +#: git-gui.sh:1923 msgid "Untracked, not staged" msgstr "Non versionné, non indexé" -#: git-gui.sh:1834 +#: git-gui.sh:1928 msgid "Missing" msgstr "Manquant" -#: git-gui.sh:1835 +#: git-gui.sh:1929 msgid "Staged for removal" msgstr "Indexé pour suppression" -#: git-gui.sh:1836 +#: git-gui.sh:1930 msgid "Staged for removal, still present" msgstr "Indexé pour suppression, toujours présent" -#: git-gui.sh:1838 git-gui.sh:1839 git-gui.sh:1840 git-gui.sh:1841 -#: git-gui.sh:1842 git-gui.sh:1843 +#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935 +#: git-gui.sh:1936 git-gui.sh:1937 msgid "Requires merge resolution" msgstr "Nécessite la résolution d'une fusion" -#: git-gui.sh:1878 +#: git-gui.sh:1972 msgid "Starting gitk... please wait..." msgstr "Lancement de gitk... un instant..." -#: git-gui.sh:1887 +#: git-gui.sh:1984 msgid "Couldn't find gitk in PATH" msgstr "Impossible de trouver gitk dans PATH." -#: git-gui.sh:2280 lib/choose_repository.tcl:36 +#: git-gui.sh:2043 +msgid "Couldn't find git gui in PATH" +msgstr "Impossible de trouver git gui dans PATH" + +#: git-gui.sh:2455 lib/choose_repository.tcl:36 msgid "Repository" msgstr "Dépôt" -#: git-gui.sh:2281 +#: git-gui.sh:2456 msgid "Edit" msgstr "Édition" -#: git-gui.sh:2283 lib/choose_rev.tcl:561 +#: git-gui.sh:2458 lib/choose_rev.tcl:561 msgid "Branch" msgstr "Branche" -#: git-gui.sh:2286 lib/choose_rev.tcl:548 +#: git-gui.sh:2461 lib/choose_rev.tcl:548 msgid "Commit@@noun" msgstr "Commit" -#: git-gui.sh:2289 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 msgid "Merge" msgstr "Fusionner" -#: git-gui.sh:2290 lib/choose_rev.tcl:557 +#: git-gui.sh:2465 lib/choose_rev.tcl:557 msgid "Remote" msgstr "Dépôt distant" -#: git-gui.sh:2293 +#: git-gui.sh:2468 msgid "Tools" msgstr "Outils" -#: git-gui.sh:2302 +#: git-gui.sh:2477 msgid "Explore Working Copy" msgstr "Explorer la copie de travail" -#: git-gui.sh:2307 +#: git-gui.sh:2483 msgid "Browse Current Branch's Files" msgstr "Naviguer dans la branche courante" -#: git-gui.sh:2311 +#: git-gui.sh:2487 msgid "Browse Branch Files..." msgstr "Naviguer dans la branche..." -#: git-gui.sh:2316 +#: git-gui.sh:2492 msgid "Visualize Current Branch's History" msgstr "Visualiser l'historique de la branche courante" -#: git-gui.sh:2320 +#: git-gui.sh:2496 msgid "Visualize All Branch History" msgstr "Voir l'historique de toutes les branches" -#: git-gui.sh:2327 +#: git-gui.sh:2503 #, tcl-format msgid "Browse %s's Files" msgstr "Parcourir l'arborescence de %s" -#: git-gui.sh:2329 +#: git-gui.sh:2505 #, tcl-format msgid "Visualize %s's History" msgstr "Voir l'historique de la branche : %s" -#: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67 +#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67 msgid "Database Statistics" msgstr "Statistiques du dépôt" -#: git-gui.sh:2337 lib/database.tcl:34 +#: git-gui.sh:2513 lib/database.tcl:34 msgid "Compress Database" msgstr "Comprimer le dépôt" -#: git-gui.sh:2340 +#: git-gui.sh:2516 msgid "Verify Database" msgstr "Vérifier le dépôt" -#: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7 -#: lib/shortcut.tcl:39 lib/shortcut.tcl:71 +#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8 +#: lib/shortcut.tcl:40 lib/shortcut.tcl:72 msgid "Create Desktop Icon" msgstr "Créer une icône sur le bureau" -#: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 +#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 msgid "Quit" msgstr "Quitter" -#: git-gui.sh:2371 +#: git-gui.sh:2547 msgid "Undo" msgstr "Défaire" -#: git-gui.sh:2374 +#: git-gui.sh:2550 msgid "Redo" msgstr "Refaire" -#: git-gui.sh:2378 git-gui.sh:2923 +#: git-gui.sh:2554 git-gui.sh:3109 msgid "Cut" msgstr "Couper" -#: git-gui.sh:2381 git-gui.sh:2926 git-gui.sh:3000 git-gui.sh:3082 +#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259 #: lib/console.tcl:69 msgid "Copy" msgstr "Copier" -#: git-gui.sh:2384 git-gui.sh:2929 +#: git-gui.sh:2560 git-gui.sh:3115 msgid "Paste" msgstr "Coller" -#: git-gui.sh:2387 git-gui.sh:2932 lib/branch_delete.tcl:26 +#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26 #: lib/remote_branch_delete.tcl:38 msgid "Delete" msgstr "Supprimer" -#: git-gui.sh:2391 git-gui.sh:2936 git-gui.sh:3086 lib/console.tcl:71 +#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71 msgid "Select All" msgstr "Tout sélectionner" -#: git-gui.sh:2400 +#: git-gui.sh:2576 msgid "Create..." msgstr "Créer..." -#: git-gui.sh:2406 +#: git-gui.sh:2582 msgid "Checkout..." msgstr "Charger (checkout)..." -#: git-gui.sh:2412 +#: git-gui.sh:2588 msgid "Rename..." msgstr "Renommer..." -#: git-gui.sh:2417 +#: git-gui.sh:2593 msgid "Delete..." msgstr "Supprimer..." -#: git-gui.sh:2422 +#: git-gui.sh:2598 msgid "Reset..." msgstr "Réinitialiser..." -#: git-gui.sh:2432 +#: git-gui.sh:2608 msgid "Done" msgstr "Effectué" -#: git-gui.sh:2434 +#: git-gui.sh:2610 msgid "Commit@@verb" msgstr "Commiter@@verb" -#: git-gui.sh:2443 git-gui.sh:2864 +#: git-gui.sh:2619 git-gui.sh:3050 msgid "New Commit" msgstr "Nouveau commit" -#: git-gui.sh:2451 git-gui.sh:2871 +#: git-gui.sh:2627 git-gui.sh:3057 msgid "Amend Last Commit" msgstr "Corriger dernier commit" -#: git-gui.sh:2461 git-gui.sh:2825 lib/remote_branch_delete.tcl:99 +#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99 msgid "Rescan" msgstr "Recharger modifs." -#: git-gui.sh:2467 +#: git-gui.sh:2643 msgid "Stage To Commit" msgstr "Indexer" -#: git-gui.sh:2473 +#: git-gui.sh:2649 msgid "Stage Changed Files To Commit" msgstr "Indexer toutes modifications" -#: git-gui.sh:2479 +#: git-gui.sh:2655 msgid "Unstage From Commit" msgstr "Désindexer" -#: git-gui.sh:2484 lib/index.tcl:410 +#: git-gui.sh:2661 lib/index.tcl:412 msgid "Revert Changes" msgstr "Annuler les modifications" -#: git-gui.sh:2491 git-gui.sh:3069 +#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341 msgid "Show Less Context" msgstr "Montrer moins de contexte" -#: git-gui.sh:2495 git-gui.sh:3073 +#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345 msgid "Show More Context" msgstr "Montrer plus de contexte" -#: git-gui.sh:2502 git-gui.sh:2838 git-gui.sh:2947 +#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133 msgid "Sign Off" msgstr "Signer" -#: git-gui.sh:2518 +#: git-gui.sh:2696 msgid "Local Merge..." msgstr "Fusion locale..." -#: git-gui.sh:2523 +#: git-gui.sh:2701 msgid "Abort Merge..." msgstr "Abandonner fusion..." -#: git-gui.sh:2535 git-gui.sh:2575 +#: git-gui.sh:2713 git-gui.sh:2741 msgid "Add..." msgstr "Ajouter..." -#: git-gui.sh:2539 +#: git-gui.sh:2717 msgid "Push..." msgstr "Pousser..." -#: git-gui.sh:2543 +#: git-gui.sh:2721 msgid "Delete Branch..." msgstr "Supprimer branche..." -#: git-gui.sh:2553 git-gui.sh:2589 lib/about.tcl:14 -#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 -#, tcl-format -msgid "About %s" -msgstr "À propos de %s" - -#: git-gui.sh:2557 -msgid "Preferences..." -msgstr "Préférences..." - -#: git-gui.sh:2565 git-gui.sh:3115 +#: git-gui.sh:2731 git-gui.sh:3292 msgid "Options..." msgstr "Options..." -#: git-gui.sh:2576 +#: git-gui.sh:2742 msgid "Remove..." msgstr "Supprimer..." -#: git-gui.sh:2585 lib/choose_repository.tcl:50 +#: git-gui.sh:2751 lib/choose_repository.tcl:50 msgid "Help" msgstr "Aide" -#: git-gui.sh:2611 +#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 +#, tcl-format +msgid "About %s" +msgstr "À propos de %s" + +#: git-gui.sh:2783 msgid "Online Documentation" msgstr "Documentation en ligne" -#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 +#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 msgid "Show SSH Key" msgstr "Montrer la clé SSH" -#: git-gui.sh:2707 +#: git-gui.sh:2893 #, tcl-format msgid "fatal: cannot stat path %s: No such file or directory" msgstr "" "erreur fatale : pas d'infos sur le chemin %s : Fichier ou répertoire " "inexistant" -#: git-gui.sh:2740 +#: git-gui.sh:2926 msgid "Current Branch:" msgstr "Branche courante :" -#: git-gui.sh:2761 +#: git-gui.sh:2947 msgid "Staged Changes (Will Commit)" msgstr "Modifs. indexées (pour commit)" -#: git-gui.sh:2781 +#: git-gui.sh:2967 msgid "Unstaged Changes" msgstr "Modifs. non indexées" -#: git-gui.sh:2831 +#: git-gui.sh:3017 msgid "Stage Changed" msgstr "Indexer modifs." -#: git-gui.sh:2850 lib/transport.tcl:93 lib/transport.tcl:182 +#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193 msgid "Push" msgstr "Pousser" -#: git-gui.sh:2885 +#: git-gui.sh:3071 msgid "Initial Commit Message:" msgstr "Message de commit initial :" -#: git-gui.sh:2886 +#: git-gui.sh:3072 msgid "Amended Commit Message:" msgstr "Message de commit corrigé :" -#: git-gui.sh:2887 +#: git-gui.sh:3073 msgid "Amended Initial Commit Message:" msgstr "Message de commit initial corrigé :" -#: git-gui.sh:2888 +#: git-gui.sh:3074 msgid "Amended Merge Commit Message:" msgstr "Message de commit de fusion corrigé :" -#: git-gui.sh:2889 +#: git-gui.sh:3075 msgid "Merge Commit Message:" msgstr "Message de commit de fusion :" -#: git-gui.sh:2890 +#: git-gui.sh:3076 msgid "Commit Message:" msgstr "Message de commit :" -#: git-gui.sh:2939 git-gui.sh:3090 lib/console.tcl:73 +#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73 msgid "Copy All" msgstr "Copier tout" -#: git-gui.sh:2963 lib/blame.tcl:104 +#: git-gui.sh:3149 lib/blame.tcl:104 msgid "File:" msgstr "Fichier :" -#: git-gui.sh:3078 +#: git-gui.sh:3255 msgid "Refresh" msgstr "Rafraîchir" -#: git-gui.sh:3099 +#: git-gui.sh:3276 msgid "Decrease Font Size" msgstr "Diminuer la police" -#: git-gui.sh:3103 +#: git-gui.sh:3280 msgid "Increase Font Size" msgstr "Agrandir la police" -#: git-gui.sh:3111 lib/blame.tcl:281 +#: git-gui.sh:3288 lib/blame.tcl:281 msgid "Encoding" msgstr "Codage des caractères" -#: git-gui.sh:3122 +#: git-gui.sh:3299 msgid "Apply/Reverse Hunk" msgstr "Appliquer/Inverser section" -#: git-gui.sh:3127 +#: git-gui.sh:3304 msgid "Apply/Reverse Line" msgstr "Appliquer/Inverser la ligne" -#: git-gui.sh:3137 +#: git-gui.sh:3323 msgid "Run Merge Tool" msgstr "Lancer l'outil de fusion" -#: git-gui.sh:3142 +#: git-gui.sh:3328 msgid "Use Remote Version" msgstr "Utiliser la version distante" -#: git-gui.sh:3146 +#: git-gui.sh:3332 msgid "Use Local Version" msgstr "Utiliser la version locale" -#: git-gui.sh:3150 +#: git-gui.sh:3336 msgid "Revert To Base" msgstr "Revenir à la version de base" -#: git-gui.sh:3169 +#: git-gui.sh:3354 +msgid "Visualize These Changes In The Submodule" +msgstr "Voir les changments dans le sous-module" + +#: git-gui.sh:3358 +msgid "Visualize Current Branch History In The Submodule" +msgstr "Voir l'historique de la branche courante du sous-module" + +#: git-gui.sh:3362 +msgid "Visualize All Branch History In The Submodule" +msgstr "Voir l'historique de toutes les branches du sous-module" + +#: git-gui.sh:3367 +msgid "Start git gui In The Submodule" +msgstr "Démarrer git gui dans le sous-module" + +#: git-gui.sh:3389 msgid "Unstage Hunk From Commit" msgstr "Désindexer la section" -#: git-gui.sh:3170 +#: git-gui.sh:3391 +msgid "Unstage Lines From Commit" +msgstr "Désindexer la ligne du commit" + +#: git-gui.sh:3393 msgid "Unstage Line From Commit" msgstr "Désindexer la ligne" -#: git-gui.sh:3172 +#: git-gui.sh:3396 msgid "Stage Hunk For Commit" msgstr "Indexer la section" -#: git-gui.sh:3173 +#: git-gui.sh:3398 +msgid "Stage Lines For Commit" +msgstr "Indexer les lignes" + +#: git-gui.sh:3400 msgid "Stage Line For Commit" msgstr "Indexer la ligne" -#: git-gui.sh:3196 +#: git-gui.sh:3424 msgid "Initializing..." msgstr "Initialisation..." -#: git-gui.sh:3301 +#: git-gui.sh:3541 #, tcl-format msgid "" "Possible environment issues exist.\n" @@ -520,7 +549,7 @@ msgstr "" "sous-processus de Git lancés par %s\n" "\n" -#: git-gui.sh:3331 +#: git-gui.sh:3570 msgid "" "\n" "This is due to a known issue with the\n" @@ -530,7 +559,7 @@ msgstr "" "Ceci est dû à un problème connu avec\n" "le binaire Tcl distribué par Cygwin." -#: git-gui.sh:3336 +#: git-gui.sh:3575 #, tcl-format msgid "" "\n" @@ -616,43 +645,43 @@ msgstr "Recherche de copie approfondie en cours..." msgid "Loading annotation..." msgstr "Chargement des annotations..." -#: lib/blame.tcl:964 +#: lib/blame.tcl:963 msgid "Author:" msgstr "Auteur :" -#: lib/blame.tcl:968 +#: lib/blame.tcl:967 msgid "Committer:" msgstr "Commiteur :" -#: lib/blame.tcl:973 +#: lib/blame.tcl:972 msgid "Original File:" msgstr "Fichier original :" -#: lib/blame.tcl:1021 +#: lib/blame.tcl:1020 msgid "Cannot find HEAD commit:" msgstr "Impossible de trouver le commit HEAD :" -#: lib/blame.tcl:1076 +#: lib/blame.tcl:1075 msgid "Cannot find parent commit:" msgstr "Impossible de trouver le commit parent :" -#: lib/blame.tcl:1091 +#: lib/blame.tcl:1090 msgid "Unable to display parent" msgstr "Impossible d'afficher le parent" -#: lib/blame.tcl:1092 lib/diff.tcl:297 +#: lib/blame.tcl:1091 lib/diff.tcl:320 msgid "Error loading diff:" msgstr "Erreur lors du chargement des différences :" -#: lib/blame.tcl:1232 +#: lib/blame.tcl:1231 msgid "Originally By:" msgstr "À l'origine par :" -#: lib/blame.tcl:1238 +#: lib/blame.tcl:1237 msgid "In File:" msgstr "Dans le fichier :" -#: lib/blame.tcl:1243 +#: lib/blame.tcl:1242 msgid "Copied Or Moved Here By:" msgstr "Copié ou déplacé ici par :" @@ -666,10 +695,10 @@ msgstr "Charger (checkout)" #: 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:544 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172 #: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42 #: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352 -#: lib/transport.tcl:97 +#: lib/transport.tcl:108 msgid "Cancel" msgstr "Annuler" @@ -697,7 +726,7 @@ msgstr "Créer une branche" msgid "Create New Branch" msgstr "Créer une nouvelle branche" -#: lib/branch_create.tcl:31 lib/choose_repository.tcl:377 +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381 msgid "Create" msgstr "Créer" @@ -729,7 +758,7 @@ msgstr "Non" msgid "Fast Forward Only" msgstr "Mise à jour rectiligne seulement (fast-forward)" -#: lib/branch_create.tcl:85 lib/checkout_op.tcl:536 +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571 msgid "Reset" msgstr "Réinitialiser" @@ -771,15 +800,25 @@ msgstr "Branches locales" msgid "Delete Only If Merged Into" msgstr "Supprimer seulement si fusionnée dans :" -#: lib/branch_delete.tcl:54 -msgid "Always (Do not perform merge test.)" -msgstr "Toujours (Ne pas faire de test de fusion.)" +#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" +msgstr "Toujours (ne pas vérifier les fusions)" #: lib/branch_delete.tcl:103 #, tcl-format msgid "The following branches are not completely merged into %s:" msgstr "Les branches suivantes ne sont pas complètement fusionnées dans %s :" +#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"Il est difficile de récupérer des branches supprimées.\n" +"\n" +"Supprimer les branches sélectionnées ?" + #: lib/branch_delete.tcl:141 #, tcl-format msgid "" @@ -809,7 +848,7 @@ msgstr "Nouveau nom :" msgid "Please select a branch to rename." msgstr "Merci de sélectionner une branche à renommer." -#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:201 +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202 #, tcl-format msgid "Branch '%s' already exists." msgstr "La branche '%s' existe déjà." @@ -840,38 +879,38 @@ msgstr "[Jusqu'au parent]" msgid "Browse Branch Files" msgstr "Naviguer dans les fichiers de le branche" -#: lib/browser.tcl:278 lib/choose_repository.tcl:394 -#: lib/choose_repository.tcl:480 lib/choose_repository.tcl:491 -#: lib/choose_repository.tcl:995 +#: lib/browser.tcl:278 lib/choose_repository.tcl:398 +#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497 +#: lib/choose_repository.tcl:1028 msgid "Browse" msgstr "Naviguer" -#: lib/checkout_op.tcl:84 +#: lib/checkout_op.tcl:85 #, tcl-format msgid "Fetching %s from %s" msgstr "Récupération de %s à partir de %s" -#: lib/checkout_op.tcl:132 +#: lib/checkout_op.tcl:133 #, tcl-format msgid "fatal: Cannot resolve %s" msgstr "erreur fatale : Impossible de résoudre %s" -#: lib/checkout_op.tcl:145 lib/console.tcl:81 lib/database.tcl:31 +#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31 #: lib/sshkey.tcl:53 msgid "Close" msgstr "Fermer" -#: lib/checkout_op.tcl:174 +#: lib/checkout_op.tcl:175 #, tcl-format msgid "Branch '%s' does not exist." msgstr "La branche '%s' n'existe pas." -#: lib/checkout_op.tcl:193 +#: lib/checkout_op.tcl:194 #, tcl-format msgid "Failed to configure simplified git-pull for '%s'." msgstr "Échec de la configuration simplifiée de git-pull pour '%s'." -#: lib/checkout_op.tcl:228 +#: lib/checkout_op.tcl:229 #, tcl-format msgid "" "Branch '%s' already exists.\n" @@ -884,21 +923,21 @@ msgstr "" "Impossible de faire une avance rapide (fast forward) vers %s.\n" "Une fusion est nécessaire." -#: lib/checkout_op.tcl:242 +#: lib/checkout_op.tcl:243 #, tcl-format msgid "Merge strategy '%s' not supported." msgstr "La stratégie de fusion '%s' n'est pas supportée." -#: lib/checkout_op.tcl:261 +#: lib/checkout_op.tcl:262 #, tcl-format msgid "Failed to update '%s'." msgstr "La mise à jour de '%s' a échoué." -#: lib/checkout_op.tcl:273 +#: lib/checkout_op.tcl:274 msgid "Staging area (index) is already locked." msgstr "L'index (staging area) est déjà verrouillé." -#: lib/checkout_op.tcl:288 +#: lib/checkout_op.tcl:289 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -916,30 +955,31 @@ msgstr "" "\n" "Cela va être fait tout de suite automatiquement.\n" -#: lib/checkout_op.tcl:344 +#: lib/checkout_op.tcl:345 #, tcl-format msgid "Updating working directory to '%s'..." msgstr "Mise à jour du répertoire courant avec '%s'..." -#: lib/checkout_op.tcl:345 +#: lib/checkout_op.tcl:346 msgid "files checked out" msgstr "fichiers chargés" -#: lib/checkout_op.tcl:375 +#: lib/checkout_op.tcl:376 #, tcl-format msgid "Aborted checkout of '%s' (file level merging is required)." -msgstr "Chargement de '%s' abandonné (il est nécessaire de fusionner des fichiers)." +msgstr "" +"Chargement de '%s' abandonné (il est nécessaire de fusionner des fichiers)." -#: lib/checkout_op.tcl:376 +#: lib/checkout_op.tcl:377 msgid "File level merge required." msgstr "Il est nécessaire de fusionner des fichiers." -#: lib/checkout_op.tcl:380 +#: lib/checkout_op.tcl:381 #, tcl-format msgid "Staying on branch '%s'." msgstr "Le répertoire de travail reste sur la branche '%s'." -#: lib/checkout_op.tcl:451 +#: lib/checkout_op.tcl:452 msgid "" "You are no longer on a local branch.\n" "\n" @@ -951,30 +991,30 @@ msgstr "" "Si vous vouliez être sur une branche, créez-en une maintenant en partant de " "'Cet emprunt détaché'." -#: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472 +#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507 #, tcl-format msgid "Checked out '%s'." msgstr "'%s' chargé." -#: lib/checkout_op.tcl:500 +#: lib/checkout_op.tcl:535 #, tcl-format msgid "Resetting '%s' to '%s' will lose the following commits:" msgstr "Réinitialiser '%s' à '%s' va faire perdre les commits suivants :" -#: lib/checkout_op.tcl:522 +#: lib/checkout_op.tcl:557 msgid "Recovering lost commits may not be easy." msgstr "Récupérer les commits perdus ne sera peut être pas facile." -#: lib/checkout_op.tcl:527 +#: lib/checkout_op.tcl:562 #, tcl-format msgid "Reset '%s'?" msgstr "Réinitialiser '%s' ?" -#: lib/checkout_op.tcl:532 lib/merge.tcl:164 lib/tools_dlg.tcl:343 +#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343 msgid "Visualize" msgstr "Visualiser" -#: lib/checkout_op.tcl:600 +#: lib/checkout_op.tcl:635 #, tcl-format msgid "" "Failed to set current branch.\n" @@ -1020,7 +1060,7 @@ msgstr "" msgid "Git Gui" msgstr "Git Gui" -#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:382 +#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386 msgid "Create New Repository" msgstr "Créer nouveau dépôt" @@ -1028,7 +1068,7 @@ msgstr "Créer nouveau dépôt" msgid "New..." msgstr "Nouveau..." -#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465 +#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471 msgid "Clone Existing Repository" msgstr "Cloner un dépôt existant" @@ -1036,7 +1076,7 @@ msgstr "Cloner un dépôt existant" msgid "Clone..." msgstr "Cloner..." -#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983 +#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016 msgid "Open Existing Repository" msgstr "Ouvrir un dépôt existant" @@ -1052,197 +1092,199 @@ msgstr "Dépôts récemment utilisés" msgid "Open Recent Repository:" msgstr "Ouvrir un dépôt récent :" -#: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309 -#: lib/choose_repository.tcl:316 +#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313 +#: lib/choose_repository.tcl:320 #, tcl-format msgid "Failed to create repository %s:" msgstr "La création du dépôt %s a échoué :" -#: lib/choose_repository.tcl:387 +#: lib/choose_repository.tcl:391 msgid "Directory:" msgstr "Répertoire :" -#: lib/choose_repository.tcl:417 lib/choose_repository.tcl:544 -#: lib/choose_repository.tcl:1017 +#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550 +#: lib/choose_repository.tcl:1052 msgid "Git Repository" msgstr "Dépôt Git" -#: lib/choose_repository.tcl:442 +#: lib/choose_repository.tcl:448 #, tcl-format msgid "Directory %s already exists." msgstr "Le répertoire %s existe déjà." -#: lib/choose_repository.tcl:446 +#: lib/choose_repository.tcl:452 #, tcl-format msgid "File %s already exists." msgstr "Le fichier %s existe déjà." -#: lib/choose_repository.tcl:460 +#: lib/choose_repository.tcl:466 msgid "Clone" msgstr "Cloner" -#: lib/choose_repository.tcl:473 +#: lib/choose_repository.tcl:479 msgid "Source Location:" msgstr "Emplacement source :" -#: lib/choose_repository.tcl:484 +#: lib/choose_repository.tcl:490 msgid "Target Directory:" msgstr "Répertoire cible :" -#: lib/choose_repository.tcl:496 +#: lib/choose_repository.tcl:502 msgid "Clone Type:" msgstr "Type de clonage :" -#: lib/choose_repository.tcl:502 +#: lib/choose_repository.tcl:508 msgid "Standard (Fast, Semi-Redundant, Hardlinks)" msgstr "Standard (rapide, semi-redondant, liens durs)" -#: lib/choose_repository.tcl:508 +#: lib/choose_repository.tcl:514 msgid "Full Copy (Slower, Redundant Backup)" msgstr "Copy complète (plus lent, sauvegarde redondante)" -#: lib/choose_repository.tcl:514 +#: lib/choose_repository.tcl:520 msgid "Shared (Fastest, Not Recommended, No Backup)" msgstr "Partagé (le plus rapide, non recommandé, pas de sauvegarde)" -#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597 -#: lib/choose_repository.tcl:743 lib/choose_repository.tcl:813 -#: lib/choose_repository.tcl:1023 lib/choose_repository.tcl:1031 +#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603 +#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819 +#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066 #, tcl-format msgid "Not a Git repository: %s" msgstr "'%s' n'est pas un dépôt Git." -#: lib/choose_repository.tcl:586 +#: lib/choose_repository.tcl:592 msgid "Standard only available for local repository." msgstr "Standard n'est disponible que pour un dépôt local." -#: lib/choose_repository.tcl:590 +#: lib/choose_repository.tcl:596 msgid "Shared only available for local repository." msgstr "Partagé n'est disponible que pour un dépôt local." -#: lib/choose_repository.tcl:611 +#: lib/choose_repository.tcl:617 #, tcl-format msgid "Location %s already exists." msgstr "L'emplacement %s existe déjà." -#: lib/choose_repository.tcl:622 +#: lib/choose_repository.tcl:628 msgid "Failed to configure origin" msgstr "La configuration de l'origine a échoué." -#: lib/choose_repository.tcl:634 +#: lib/choose_repository.tcl:640 msgid "Counting objects" msgstr "Décompte des objets" -#: lib/choose_repository.tcl:635 +#: lib/choose_repository.tcl:641 msgid "buckets" msgstr "paniers" -#: lib/choose_repository.tcl:659 +#: lib/choose_repository.tcl:665 #, tcl-format msgid "Unable to copy objects/info/alternates: %s" msgstr "Impossible de copier 'objects/info/alternates' : %s" -#: lib/choose_repository.tcl:695 +#: lib/choose_repository.tcl:701 #, tcl-format msgid "Nothing to clone from %s." msgstr "Il n'y a rien à cloner depuis %s." -#: lib/choose_repository.tcl:697 lib/choose_repository.tcl:911 -#: lib/choose_repository.tcl:923 +#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917 +#: lib/choose_repository.tcl:929 msgid "The 'master' branch has not been initialized." msgstr "La branche 'master' n'a pas été initialisée." -#: lib/choose_repository.tcl:710 +#: lib/choose_repository.tcl:716 msgid "Hardlinks are unavailable. Falling back to copying." -msgstr "Les liens durs ne sont pas supportés. Une copie sera effectuée à la place." +msgstr "" +"Les liens durs ne sont pas supportés. Une copie sera effectuée à la place." -#: lib/choose_repository.tcl:722 +#: lib/choose_repository.tcl:728 #, tcl-format msgid "Cloning from %s" msgstr "Clonage depuis %s" -#: lib/choose_repository.tcl:753 +#: lib/choose_repository.tcl:759 msgid "Copying objects" msgstr "Copie des objets" -#: lib/choose_repository.tcl:754 +#: lib/choose_repository.tcl:760 msgid "KiB" msgstr "KiB" -#: lib/choose_repository.tcl:778 +#: lib/choose_repository.tcl:784 #, tcl-format msgid "Unable to copy object: %s" msgstr "Impossible de copier l'objet : %s" -#: lib/choose_repository.tcl:788 +#: lib/choose_repository.tcl:794 msgid "Linking objects" msgstr "Liaison des objets" -#: lib/choose_repository.tcl:789 +#: lib/choose_repository.tcl:795 msgid "objects" msgstr "objets" -#: lib/choose_repository.tcl:797 +#: lib/choose_repository.tcl:803 #, tcl-format msgid "Unable to hardlink object: %s" msgstr "Impossible créer un lien dur pour l'objet : %s" -#: lib/choose_repository.tcl:852 +#: lib/choose_repository.tcl:858 msgid "Cannot fetch branches and objects. See console output for details." msgstr "" "Impossible de récupérer les branches et objets. Voir la sortie console pour " "plus de détails." -#: lib/choose_repository.tcl:863 +#: lib/choose_repository.tcl:869 msgid "Cannot fetch tags. See console output for details." msgstr "" "Impossible de récupérer les marques (tags). Voir la sortie console pour plus " "de détails." -#: lib/choose_repository.tcl:887 +#: lib/choose_repository.tcl:893 msgid "Cannot determine HEAD. See console output for details." -msgstr "Impossible de déterminer HEAD. Voir la sortie console pour plus de détails." +msgstr "" +"Impossible de déterminer HEAD. Voir la sortie console pour plus de détails." -#: lib/choose_repository.tcl:896 +#: lib/choose_repository.tcl:902 #, tcl-format msgid "Unable to cleanup %s" msgstr "Impossible de nettoyer %s" -#: lib/choose_repository.tcl:902 +#: lib/choose_repository.tcl:908 msgid "Clone failed." msgstr "Le clonage a échoué." -#: lib/choose_repository.tcl:909 +#: lib/choose_repository.tcl:915 msgid "No default branch obtained." msgstr "Aucune branche par défaut n'a été obtenue." -#: lib/choose_repository.tcl:920 +#: lib/choose_repository.tcl:926 #, tcl-format msgid "Cannot resolve %s as a commit." msgstr "Impossible de résoudre %s comme commit." -#: lib/choose_repository.tcl:932 +#: lib/choose_repository.tcl:938 msgid "Creating working directory" msgstr "Création du répertoire de travail" -#: lib/choose_repository.tcl:933 lib/index.tcl:65 lib/index.tcl:128 -#: lib/index.tcl:196 +#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130 +#: lib/index.tcl:198 msgid "files" msgstr "fichiers" -#: lib/choose_repository.tcl:962 +#: lib/choose_repository.tcl:968 msgid "Initial file checkout failed." msgstr "Le chargement initial du fichier a échoué." -#: lib/choose_repository.tcl:978 +#: lib/choose_repository.tcl:1011 msgid "Open" msgstr "Ouvrir" -#: lib/choose_repository.tcl:988 +#: lib/choose_repository.tcl:1021 msgid "Repository:" msgstr "Dépôt :" -#: lib/choose_repository.tcl:1037 +#: lib/choose_repository.tcl:1072 #, tcl-format msgid "Failed to open repository %s:" msgstr "Impossible d'ouvrir le dépôt %s :" @@ -1314,19 +1356,24 @@ msgstr "" "terminée. Vous ne pouvez pas corriger le commit précédent sauf si vous " "abandonnez la fusion courante.\n" -#: lib/commit.tcl:49 +#: lib/commit.tcl:48 msgid "Error loading commit data for amend:" msgstr "Erreur lors du chargement des données de commit pour correction :" -#: lib/commit.tcl:76 +#: lib/commit.tcl:75 msgid "Unable to obtain your identity:" msgstr "Impossible d'obtenir votre identité :" -#: lib/commit.tcl:81 +#: lib/commit.tcl:80 msgid "Invalid GIT_COMMITTER_IDENT:" msgstr "GIT_COMMITTER_IDENT invalide :" -#: lib/commit.tcl:133 +#: lib/commit.tcl:129 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "attention : Tcl ne supporte pas le codage '%s'." + +#: lib/commit.tcl:149 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -1344,7 +1391,7 @@ msgstr "" "\n" "Cela va être fait tout de suite automatiquement.\n" -#: lib/commit.tcl:156 +#: lib/commit.tcl:172 #, tcl-format msgid "" "Unmerged files cannot be committed.\n" @@ -1357,7 +1404,7 @@ msgstr "" "Le fichier %s a des conflicts de fusion. Vous devez les résoudre et pré-" "commiter le fichier avant de pouvoir commiter.\n" -#: lib/commit.tcl:164 +#: lib/commit.tcl:180 #, tcl-format msgid "" "Unknown file state %s detected.\n" @@ -1368,7 +1415,7 @@ msgstr "" "\n" "Le fichier %s ne peut pas être commité par ce programme.\n" -#: lib/commit.tcl:172 +#: lib/commit.tcl:188 msgid "" "No changes to commit.\n" "\n" @@ -1378,7 +1425,7 @@ msgstr "" "\n" "Vous devez indexer au moins 1 fichier avant de pouvoir commiter.\n" -#: lib/commit.tcl:187 +#: lib/commit.tcl:203 msgid "" "Please supply a commit message.\n" "\n" @@ -1396,45 +1443,40 @@ msgstr "" "- Deuxième ligne : rien.\n" "- Lignes suivantes : Décrire pourquoi ces modifications sont bonnes.\n" -#: lib/commit.tcl:211 -#, tcl-format -msgid "warning: Tcl does not support encoding '%s'." -msgstr "attention : Tcl ne supporte pas le codage '%s'." - -#: lib/commit.tcl:227 +#: lib/commit.tcl:234 msgid "Calling pre-commit hook..." msgstr "Lancement de l'action d'avant-commit..." -#: lib/commit.tcl:242 +#: lib/commit.tcl:249 msgid "Commit declined by pre-commit hook." msgstr "Commit refusé par l'action d'avant-commit." -#: lib/commit.tcl:265 +#: lib/commit.tcl:272 msgid "Calling commit-msg hook..." msgstr "Lancement de l'action \"message de commit\"..." -#: lib/commit.tcl:280 +#: lib/commit.tcl:287 msgid "Commit declined by commit-msg hook." msgstr "Commit refusé par l'action \"message de commit\"." -#: lib/commit.tcl:293 +#: lib/commit.tcl:300 msgid "Committing changes..." msgstr "Commit des modifications..." -#: lib/commit.tcl:309 +#: lib/commit.tcl:316 msgid "write-tree failed:" msgstr "write-tree a échoué :" -#: lib/commit.tcl:310 lib/commit.tcl:354 lib/commit.tcl:374 +#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382 msgid "Commit failed." msgstr "Le commit a échoué." -#: lib/commit.tcl:327 +#: lib/commit.tcl:334 #, tcl-format msgid "Commit %s appears to be corrupt" msgstr "Le commit %s semble être corrompu" -#: lib/commit.tcl:332 +#: lib/commit.tcl:339 msgid "" "No changes to commit.\n" "\n" @@ -1449,19 +1491,19 @@ msgstr "" "\n" "Une resynchronisation va être lancée tout de suite automatiquement.\n" -#: lib/commit.tcl:339 +#: lib/commit.tcl:346 msgid "No changes to commit." msgstr "Pas de modifications à commiter." -#: lib/commit.tcl:353 +#: lib/commit.tcl:360 msgid "commit-tree failed:" msgstr "commit-tree a échoué :" -#: lib/commit.tcl:373 +#: lib/commit.tcl:381 msgid "update-ref failed:" msgstr "update-ref a échoué :" -#: lib/commit.tcl:461 +#: lib/commit.tcl:469 #, tcl-format msgid "Created commit %s: %s" msgstr "Commit %s créé : %s" @@ -1514,13 +1556,13 @@ msgstr "Compression de la base des objets" msgid "Verifying the object database with fsck-objects" msgstr "Vérification de la base des objets avec fsck-objects" -#: lib/database.tcl:108 +#: lib/database.tcl:107 #, 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" +"the database.\n" "\n" "Compress the database now?" msgstr "" @@ -1528,8 +1570,7 @@ msgstr "" "particulier.\n" "\n" "Pour conserver une performance optimale, il est fortement recommandé de " -"comprimer la base quand plus de %i objets ayant leur fichier particulier " -"existent.\n" +"comprimer la base de donnée.\n" "\n" "Comprimer la base maintenant ?" @@ -1538,7 +1579,7 @@ msgstr "" msgid "Invalid date from Git: %s" msgstr "Date invalide de Git : %s" -#: lib/diff.tcl:59 +#: lib/diff.tcl:64 #, tcl-format msgid "" "No differences detected.\n" @@ -1561,12 +1602,12 @@ msgstr "" "Une resynchronisation va être lancée automatiquement pour trouver d'autres " "fichiers qui pourraient se trouver dans le même état." -#: lib/diff.tcl:99 +#: lib/diff.tcl:104 #, tcl-format msgid "Loading diff of %s..." msgstr "Chargement des différences de %s..." -#: lib/diff.tcl:120 +#: lib/diff.tcl:125 msgid "" "LOCAL: deleted\n" "REMOTE:\n" @@ -1574,7 +1615,7 @@ msgstr "" "LOCAL : supprimé\n" "DISTANT :\n" -#: lib/diff.tcl:125 +#: lib/diff.tcl:130 msgid "" "REMOTE: deleted\n" "LOCAL:\n" @@ -1582,32 +1623,32 @@ msgstr "" "DISTANT : supprimé\n" "LOCAL :\n" -#: lib/diff.tcl:132 +#: lib/diff.tcl:137 msgid "LOCAL:\n" msgstr "LOCAL :\n" -#: lib/diff.tcl:135 +#: lib/diff.tcl:140 msgid "REMOTE:\n" msgstr "DISTANT :\n" -#: lib/diff.tcl:197 lib/diff.tcl:296 +#: lib/diff.tcl:202 lib/diff.tcl:319 #, tcl-format msgid "Unable to display %s" msgstr "Impossible d'afficher %s" -#: lib/diff.tcl:198 +#: lib/diff.tcl:203 msgid "Error loading file:" msgstr "Erreur lors du chargement du fichier :" -#: lib/diff.tcl:205 +#: lib/diff.tcl:210 msgid "Git Repository (subproject)" msgstr "Dépôt Git (sous projet)" -#: lib/diff.tcl:217 +#: lib/diff.tcl:222 msgid "* Binary file (not showing content)." msgstr "* Fichier binaire (pas d'apperçu du contenu)." -#: lib/diff.tcl:222 +#: lib/diff.tcl:227 #, tcl-format msgid "" "* Untracked file is %d bytes.\n" @@ -1616,7 +1657,7 @@ msgstr "" "* Le fichier non suivi fait %d octets.\n" "* Seuls les %d premiers octets sont montrés.\n" -#: lib/diff.tcl:228 +#: lib/diff.tcl:233 #, tcl-format msgid "" "\n" @@ -1627,19 +1668,19 @@ msgstr "" "* Fichier suivi raccourcis ici de %s.\n" "* Pour voir le fichier entier, utilisez un éditeur externe.\n" -#: lib/diff.tcl:436 +#: lib/diff.tcl:482 msgid "Failed to unstage selected hunk." msgstr "Échec lors de la désindexation de la section sélectionnée." -#: lib/diff.tcl:443 +#: lib/diff.tcl:489 msgid "Failed to stage selected hunk." msgstr "Échec lors de l'indexation de la section." -#: lib/diff.tcl:509 +#: lib/diff.tcl:568 msgid "Failed to unstage selected line." msgstr "Échec lors de la désindexation de la ligne sélectionnée." -#: lib/diff.tcl:517 +#: lib/diff.tcl:576 msgid "Failed to stage selected line." msgstr "Échec lors de l'indexation de la ligne." @@ -1676,7 +1717,7 @@ msgstr "Impossible de déverrouiller l'index." msgid "Index Error" msgstr "Erreur de l'index" -#: lib/index.tcl:21 +#: lib/index.tcl:17 msgid "" "Updating the Git index failed. A rescan will be automatically started to " "resynchronize git-gui." @@ -1684,7 +1725,7 @@ msgstr "" "Échec de la mise à jour de l'index. Une resynchronisation va être lancée " "automatiquement." -#: lib/index.tcl:27 +#: lib/index.tcl:28 msgid "Continue" msgstr "Continuer" @@ -1692,45 +1733,45 @@ msgstr "Continuer" msgid "Unlock Index" msgstr "Déverrouiller l'index" -#: lib/index.tcl:287 +#: lib/index.tcl:289 #, tcl-format msgid "Unstaging %s from commit" msgstr "Désindexation de : %s" -#: lib/index.tcl:326 +#: lib/index.tcl:328 msgid "Ready to commit." msgstr "Prêt à être commité." -#: lib/index.tcl:339 +#: lib/index.tcl:341 #, tcl-format msgid "Adding %s" msgstr "Ajout de %s" -#: lib/index.tcl:396 +#: lib/index.tcl:398 #, tcl-format msgid "Revert changes in file %s?" msgstr "Annuler les modifications dans le fichier %s ? " -#: lib/index.tcl:398 +#: lib/index.tcl:400 #, tcl-format msgid "Revert changes in these %i files?" msgstr "Annuler les modifications dans ces %i fichiers ?" -#: lib/index.tcl:406 +#: lib/index.tcl:408 msgid "Any unstaged changes will be permanently lost by the revert." msgstr "" "Toutes les modifications non-indexées seront définitivement perdues par " "l'annulation." -#: lib/index.tcl:409 +#: lib/index.tcl:411 msgid "Do Nothing" msgstr "Ne rien faire" -#: lib/index.tcl:427 +#: lib/index.tcl:429 msgid "Reverting selected files" msgstr "Annuler modifications dans fichiers selectionnés" -#: lib/index.tcl:431 +#: lib/index.tcl:433 #, tcl-format msgid "Reverting %s" msgstr "Annulation des modifications dans %s" @@ -1913,7 +1954,8 @@ msgstr "" #: lib/mergetool.tcl:45 #, tcl-format msgid "File %s seems to have unresolved conflicts, still stage?" -msgstr "Le fichier %s semble avoir des conflits non résolus, indexer quand même ?" +msgstr "" +"Le fichier %s semble avoir des conflits non résolus, indexer quand même ?" #: lib/mergetool.tcl:60 #, tcl-format @@ -1922,7 +1964,9 @@ msgstr "Ajouter une résolution pour %s" #: lib/mergetool.tcl:141 msgid "Cannot resolve deletion or link conflicts using a tool" -msgstr "Impossible de résoudre la suppression ou de relier des conflits en utilisant un outil" +msgstr "" +"Impossible de résoudre la suppression ou de relier des conflits en utilisant " +"un outil" #: lib/mergetool.tcl:146 msgid "Conflict file does not exist" @@ -2171,7 +2215,8 @@ msgstr "Récupération de %s" msgid "Do not know how to initialize repository at location '%s'." msgstr "Pas de méthode connue pour initialiser le dépôt à l'emplacement '%s'." -#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:71 +#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63 +#: lib/transport.tcl:81 #, tcl-format msgid "push %s" msgstr "pousser %s" @@ -2189,11 +2234,11 @@ msgstr "Supprimer une branche à distance" msgid "From Repository" msgstr "Dépôt source" -#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 +#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134 msgid "Remote:" msgstr "Branche distante :" -#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138 +#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149 msgid "Arbitrary Location:" msgstr "Emplacement arbitraire :" @@ -2209,10 +2254,6 @@ msgstr "Supprimer seulement si" msgid "Merged Into:" msgstr "Fusionné dans :" -#: lib/remote_branch_delete.tcl:119 -msgid "Always (Do not perform merge checks)" -msgstr "Toujours (ne pas vérifier les fusions)" - #: lib/remote_branch_delete.tcl:152 msgid "A branch is required for 'Merged Into'." msgstr "Une branche est nécessaire pour 'Fusionné dans'." @@ -2235,32 +2276,23 @@ msgid "" "necessary commits. Try fetching from %s first." msgstr "" "Un ou plusieurs des tests de fusion ont échoué parce que vous n'avez pas " -"récupéré les commits nécessaires. Essayez de récupérer à partir de %s d'abord." +"récupéré les commits nécessaires. Essayez de récupérer à partir de %s " +"d'abord." #: lib/remote_branch_delete.tcl:207 msgid "Please select one or more branches to delete." msgstr "Merci de sélectionner une ou plusieurs branches à supprimer." -#: lib/remote_branch_delete.tcl:216 -msgid "" -"Recovering deleted branches is difficult.\n" -"\n" -"Delete the selected branches?" -msgstr "" -"Il est difficile de récupérer des branches supprimées.\n" -"\n" -"Supprimer les branches sélectionnées ?" - #: lib/remote_branch_delete.tcl:226 #, tcl-format msgid "Deleting branches from %s" msgstr "Suppression des branches de %s" -#: lib/remote_branch_delete.tcl:286 +#: lib/remote_branch_delete.tcl:292 msgid "No repository selected." msgstr "Aucun dépôt n'est sélectionné." -#: lib/remote_branch_delete.tcl:291 +#: lib/remote_branch_delete.tcl:297 #, tcl-format msgid "Scanning %s..." msgstr "Synchronisation de %s..." @@ -2281,11 +2313,11 @@ msgstr "Précédent" msgid "Case-Sensitive" msgstr "Sensible à la casse" -#: lib/shortcut.tcl:20 lib/shortcut.tcl:61 +#: lib/shortcut.tcl:21 lib/shortcut.tcl:62 msgid "Cannot write shortcut:" msgstr "Impossible d'écrire le raccourci :" -#: lib/shortcut.tcl:136 +#: lib/shortcut.tcl:137 msgid "Cannot write icon:" msgstr "Impossible d'écrire l'icône :" @@ -2440,7 +2472,8 @@ msgstr "Montrer une boîte de dialogue avant le lancement" #: lib/tools_dlg.tcl:80 msgid "Ask the user to select a revision (sets $REVISION)" -msgstr "Demander à l'utilisateur de sélectionner une révision (change $REVISION)" +msgstr "" +"Demander à l'utilisateur de sélectionner une révision (change $REVISION)" #: lib/tools_dlg.tcl:85 msgid "Ask the user for additional arguments (sets $ARGS)" @@ -2521,38 +2554,51 @@ msgstr "Nettoyer les branches de suivi supprimées de %s" msgid "Pushing changes to %s" msgstr "Les modifications sont poussées vers %s" -#: lib/transport.tcl:72 +#: lib/transport.tcl:64 +#, tcl-format +msgid "Mirroring to %s" +msgstr "Dupliquer dans %s" + +#: lib/transport.tcl:82 #, tcl-format msgid "Pushing %s %s to %s" msgstr "Pousse %s %s vers %s" -#: lib/transport.tcl:89 +#: lib/transport.tcl:100 msgid "Push Branches" msgstr "Pousser branches" -#: lib/transport.tcl:103 +#: lib/transport.tcl:114 msgid "Source Branches" msgstr "Branches source" -#: lib/transport.tcl:120 +#: lib/transport.tcl:131 msgid "Destination Repository" msgstr "Dépôt de destination" -#: lib/transport.tcl:158 +#: lib/transport.tcl:169 msgid "Transfer Options" msgstr "Options de transfert" -#: lib/transport.tcl:160 +#: lib/transport.tcl:171 msgid "Force overwrite existing branch (may discard changes)" msgstr "" "Forcer l'écrasement d'une branche existante (peut supprimer des " "modifications)" -#: lib/transport.tcl:164 +#: lib/transport.tcl:175 msgid "Use thin pack (for slow network connections)" msgstr "Utiliser des petits paquets (pour les connexions lentes)" -#: lib/transport.tcl:168 +#: lib/transport.tcl:179 msgid "Include tags" msgstr "Inclure les marques (tags)" +#~ msgid "Cannot use funny .git directory:" +#~ msgstr "Impossible d'utiliser le répertoire .git:" + +#~ msgid "Preferences..." +#~ msgstr "Préférences..." + +#~ msgid "Always (Do not perform merge test.)" +#~ msgstr "Toujours (Ne pas faire de test de fusion.)" diff --git a/git-gui/po/git-gui.pot b/git-gui/po/git-gui.pot index 53b7d3634d..1ae2aaa0b3 100644 --- a/git-gui/po/git-gui.pot +++ b/git-gui/po/git-gui.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-12-08 08:31-0800\n" +"POT-Creation-Date: 2010-01-26 15:47-0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -16,33 +16,33 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: git-gui.sh:41 git-gui.sh:737 git-gui.sh:751 git-gui.sh:764 git-gui.sh:847 -#: git-gui.sh:866 +#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903 +#: git-gui.sh:922 msgid "git-gui: fatal error" msgstr "" -#: git-gui.sh:689 +#: git-gui.sh:743 #, tcl-format msgid "Invalid font specified in %s:" msgstr "" -#: git-gui.sh:723 +#: git-gui.sh:779 msgid "Main Font" msgstr "" -#: git-gui.sh:724 +#: git-gui.sh:780 msgid "Diff/Console Font" msgstr "" -#: git-gui.sh:738 +#: git-gui.sh:794 msgid "Cannot find git in PATH." msgstr "" -#: git-gui.sh:765 +#: git-gui.sh:821 msgid "Cannot parse Git version string:" msgstr "" -#: git-gui.sh:783 +#: git-gui.sh:839 #, tcl-format msgid "" "Git version cannot be determined.\n" @@ -54,444 +54,473 @@ msgid "" "Assume '%s' is version 1.5.0?\n" msgstr "" -#: git-gui.sh:1062 +#: git-gui.sh:1128 msgid "Git directory not found:" msgstr "" -#: git-gui.sh:1069 +#: git-gui.sh:1146 msgid "Cannot move to top of working directory:" msgstr "" -#: git-gui.sh:1076 -msgid "Cannot use funny .git directory:" +#: git-gui.sh:1154 +msgid "Cannot use bare repository:" msgstr "" -#: git-gui.sh:1081 +#: git-gui.sh:1162 msgid "No working directory" msgstr "" -#: git-gui.sh:1247 lib/checkout_op.tcl:305 +#: git-gui.sh:1334 lib/checkout_op.tcl:306 msgid "Refreshing file status..." msgstr "" -#: git-gui.sh:1303 +#: git-gui.sh:1390 msgid "Scanning for modified files ..." msgstr "" -#: git-gui.sh:1367 +#: git-gui.sh:1454 msgid "Calling prepare-commit-msg hook..." msgstr "" -#: git-gui.sh:1384 +#: git-gui.sh:1471 msgid "Commit declined by prepare-commit-msg hook." msgstr "" -#: git-gui.sh:1542 lib/browser.tcl:246 +#: git-gui.sh:1629 lib/browser.tcl:246 msgid "Ready." msgstr "" -#: git-gui.sh:1819 +#: git-gui.sh:1787 +#, tcl-format +msgid "Displaying only %s of %s files." +msgstr "" + +#: git-gui.sh:1913 msgid "Unmodified" msgstr "" -#: git-gui.sh:1821 +#: git-gui.sh:1915 msgid "Modified, not staged" msgstr "" -#: git-gui.sh:1822 git-gui.sh:1830 +#: git-gui.sh:1916 git-gui.sh:1924 msgid "Staged for commit" msgstr "" -#: git-gui.sh:1823 git-gui.sh:1831 +#: git-gui.sh:1917 git-gui.sh:1925 msgid "Portions staged for commit" msgstr "" -#: git-gui.sh:1824 git-gui.sh:1832 +#: git-gui.sh:1918 git-gui.sh:1926 msgid "Staged for commit, missing" msgstr "" -#: git-gui.sh:1826 +#: git-gui.sh:1920 msgid "File type changed, not staged" msgstr "" -#: git-gui.sh:1827 +#: git-gui.sh:1921 msgid "File type changed, staged" msgstr "" -#: git-gui.sh:1829 +#: git-gui.sh:1923 msgid "Untracked, not staged" msgstr "" -#: git-gui.sh:1834 +#: git-gui.sh:1928 msgid "Missing" msgstr "" -#: git-gui.sh:1835 +#: git-gui.sh:1929 msgid "Staged for removal" msgstr "" -#: git-gui.sh:1836 +#: git-gui.sh:1930 msgid "Staged for removal, still present" msgstr "" -#: git-gui.sh:1838 git-gui.sh:1839 git-gui.sh:1840 git-gui.sh:1841 -#: git-gui.sh:1842 git-gui.sh:1843 +#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935 +#: git-gui.sh:1936 git-gui.sh:1937 msgid "Requires merge resolution" msgstr "" -#: git-gui.sh:1878 +#: git-gui.sh:1972 msgid "Starting gitk... please wait..." msgstr "" -#: git-gui.sh:1887 +#: git-gui.sh:1984 msgid "Couldn't find gitk in PATH" msgstr "" -#: git-gui.sh:2280 lib/choose_repository.tcl:36 +#: git-gui.sh:2043 +msgid "Couldn't find git gui in PATH" +msgstr "" + +#: git-gui.sh:2455 lib/choose_repository.tcl:36 msgid "Repository" msgstr "" -#: git-gui.sh:2281 +#: git-gui.sh:2456 msgid "Edit" msgstr "" -#: git-gui.sh:2283 lib/choose_rev.tcl:561 +#: git-gui.sh:2458 lib/choose_rev.tcl:561 msgid "Branch" msgstr "" -#: git-gui.sh:2286 lib/choose_rev.tcl:548 +#: git-gui.sh:2461 lib/choose_rev.tcl:548 msgid "Commit@@noun" msgstr "" -#: git-gui.sh:2289 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 msgid "Merge" msgstr "" -#: git-gui.sh:2290 lib/choose_rev.tcl:557 +#: git-gui.sh:2465 lib/choose_rev.tcl:557 msgid "Remote" msgstr "" -#: git-gui.sh:2293 +#: git-gui.sh:2468 msgid "Tools" msgstr "" -#: git-gui.sh:2302 +#: git-gui.sh:2477 msgid "Explore Working Copy" msgstr "" -#: git-gui.sh:2307 +#: git-gui.sh:2483 msgid "Browse Current Branch's Files" msgstr "" -#: git-gui.sh:2311 +#: git-gui.sh:2487 msgid "Browse Branch Files..." msgstr "" -#: git-gui.sh:2316 +#: git-gui.sh:2492 msgid "Visualize Current Branch's History" msgstr "" -#: git-gui.sh:2320 +#: git-gui.sh:2496 msgid "Visualize All Branch History" msgstr "" -#: git-gui.sh:2327 +#: git-gui.sh:2503 #, tcl-format msgid "Browse %s's Files" msgstr "" -#: git-gui.sh:2329 +#: git-gui.sh:2505 #, tcl-format msgid "Visualize %s's History" msgstr "" -#: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67 +#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67 msgid "Database Statistics" msgstr "" -#: git-gui.sh:2337 lib/database.tcl:34 +#: git-gui.sh:2513 lib/database.tcl:34 msgid "Compress Database" msgstr "" -#: git-gui.sh:2340 +#: git-gui.sh:2516 msgid "Verify Database" msgstr "" -#: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7 -#: lib/shortcut.tcl:39 lib/shortcut.tcl:71 +#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8 +#: lib/shortcut.tcl:40 lib/shortcut.tcl:72 msgid "Create Desktop Icon" msgstr "" -#: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 +#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 msgid "Quit" msgstr "" -#: git-gui.sh:2371 +#: git-gui.sh:2547 msgid "Undo" msgstr "" -#: git-gui.sh:2374 +#: git-gui.sh:2550 msgid "Redo" msgstr "" -#: git-gui.sh:2378 git-gui.sh:2937 +#: git-gui.sh:2554 git-gui.sh:3109 msgid "Cut" msgstr "" -#: git-gui.sh:2381 git-gui.sh:2940 git-gui.sh:3014 git-gui.sh:3096 +#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259 #: lib/console.tcl:69 msgid "Copy" msgstr "" -#: git-gui.sh:2384 git-gui.sh:2943 +#: git-gui.sh:2560 git-gui.sh:3115 msgid "Paste" msgstr "" -#: git-gui.sh:2387 git-gui.sh:2946 lib/branch_delete.tcl:26 +#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26 #: lib/remote_branch_delete.tcl:38 msgid "Delete" msgstr "" -#: git-gui.sh:2391 git-gui.sh:2950 git-gui.sh:3100 lib/console.tcl:71 +#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71 msgid "Select All" msgstr "" -#: git-gui.sh:2400 +#: git-gui.sh:2576 msgid "Create..." msgstr "" -#: git-gui.sh:2406 +#: git-gui.sh:2582 msgid "Checkout..." msgstr "" -#: git-gui.sh:2412 +#: git-gui.sh:2588 msgid "Rename..." msgstr "" -#: git-gui.sh:2417 +#: git-gui.sh:2593 msgid "Delete..." msgstr "" -#: git-gui.sh:2422 +#: git-gui.sh:2598 msgid "Reset..." msgstr "" -#: git-gui.sh:2432 +#: git-gui.sh:2608 msgid "Done" msgstr "" -#: git-gui.sh:2434 +#: git-gui.sh:2610 msgid "Commit@@verb" msgstr "" -#: git-gui.sh:2443 git-gui.sh:2878 +#: git-gui.sh:2619 git-gui.sh:3050 msgid "New Commit" msgstr "" -#: git-gui.sh:2451 git-gui.sh:2885 +#: git-gui.sh:2627 git-gui.sh:3057 msgid "Amend Last Commit" msgstr "" -#: git-gui.sh:2461 git-gui.sh:2839 lib/remote_branch_delete.tcl:99 +#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99 msgid "Rescan" msgstr "" -#: git-gui.sh:2467 +#: git-gui.sh:2643 msgid "Stage To Commit" msgstr "" -#: git-gui.sh:2473 +#: git-gui.sh:2649 msgid "Stage Changed Files To Commit" msgstr "" -#: git-gui.sh:2479 +#: git-gui.sh:2655 msgid "Unstage From Commit" msgstr "" -#: git-gui.sh:2484 lib/index.tcl:410 +#: git-gui.sh:2661 lib/index.tcl:412 msgid "Revert Changes" msgstr "" -#: git-gui.sh:2491 git-gui.sh:3083 +#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341 msgid "Show Less Context" msgstr "" -#: git-gui.sh:2495 git-gui.sh:3087 +#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345 msgid "Show More Context" msgstr "" -#: git-gui.sh:2502 git-gui.sh:2852 git-gui.sh:2961 +#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133 msgid "Sign Off" msgstr "" -#: git-gui.sh:2518 +#: git-gui.sh:2696 msgid "Local Merge..." msgstr "" -#: git-gui.sh:2523 +#: git-gui.sh:2701 msgid "Abort Merge..." msgstr "" -#: git-gui.sh:2535 git-gui.sh:2575 +#: git-gui.sh:2713 git-gui.sh:2741 msgid "Add..." msgstr "" -#: git-gui.sh:2539 +#: git-gui.sh:2717 msgid "Push..." msgstr "" -#: git-gui.sh:2543 +#: git-gui.sh:2721 msgid "Delete Branch..." msgstr "" -#: git-gui.sh:2553 git-gui.sh:2589 lib/about.tcl:14 -#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 -#, tcl-format -msgid "About %s" -msgstr "" - -#: git-gui.sh:2557 -msgid "Preferences..." -msgstr "" - -#: git-gui.sh:2565 git-gui.sh:3129 +#: git-gui.sh:2731 git-gui.sh:3292 msgid "Options..." msgstr "" -#: git-gui.sh:2576 +#: git-gui.sh:2742 msgid "Remove..." msgstr "" -#: git-gui.sh:2585 lib/choose_repository.tcl:50 +#: git-gui.sh:2751 lib/choose_repository.tcl:50 msgid "Help" msgstr "" -#: git-gui.sh:2611 +#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 +#, tcl-format +msgid "About %s" +msgstr "" + +#: git-gui.sh:2783 msgid "Online Documentation" msgstr "" -#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 +#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 msgid "Show SSH Key" msgstr "" -#: git-gui.sh:2721 +#: git-gui.sh:2893 #, tcl-format msgid "fatal: cannot stat path %s: No such file or directory" msgstr "" -#: git-gui.sh:2754 +#: git-gui.sh:2926 msgid "Current Branch:" msgstr "" -#: git-gui.sh:2775 +#: git-gui.sh:2947 msgid "Staged Changes (Will Commit)" msgstr "" -#: git-gui.sh:2795 +#: git-gui.sh:2967 msgid "Unstaged Changes" msgstr "" -#: git-gui.sh:2845 +#: git-gui.sh:3017 msgid "Stage Changed" msgstr "" -#: git-gui.sh:2864 lib/transport.tcl:104 lib/transport.tcl:193 +#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193 msgid "Push" msgstr "" -#: git-gui.sh:2899 +#: git-gui.sh:3071 msgid "Initial Commit Message:" msgstr "" -#: git-gui.sh:2900 +#: git-gui.sh:3072 msgid "Amended Commit Message:" msgstr "" -#: git-gui.sh:2901 +#: git-gui.sh:3073 msgid "Amended Initial Commit Message:" msgstr "" -#: git-gui.sh:2902 +#: git-gui.sh:3074 msgid "Amended Merge Commit Message:" msgstr "" -#: git-gui.sh:2903 +#: git-gui.sh:3075 msgid "Merge Commit Message:" msgstr "" -#: git-gui.sh:2904 +#: git-gui.sh:3076 msgid "Commit Message:" msgstr "" -#: git-gui.sh:2953 git-gui.sh:3104 lib/console.tcl:73 +#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73 msgid "Copy All" msgstr "" -#: git-gui.sh:2977 lib/blame.tcl:104 +#: git-gui.sh:3149 lib/blame.tcl:104 msgid "File:" msgstr "" -#: git-gui.sh:3092 +#: git-gui.sh:3255 msgid "Refresh" msgstr "" -#: git-gui.sh:3113 +#: git-gui.sh:3276 msgid "Decrease Font Size" msgstr "" -#: git-gui.sh:3117 +#: git-gui.sh:3280 msgid "Increase Font Size" msgstr "" -#: git-gui.sh:3125 lib/blame.tcl:281 +#: git-gui.sh:3288 lib/blame.tcl:281 msgid "Encoding" msgstr "" -#: git-gui.sh:3136 +#: git-gui.sh:3299 msgid "Apply/Reverse Hunk" msgstr "" -#: git-gui.sh:3141 +#: git-gui.sh:3304 msgid "Apply/Reverse Line" msgstr "" -#: git-gui.sh:3151 +#: git-gui.sh:3323 msgid "Run Merge Tool" msgstr "" -#: git-gui.sh:3156 +#: git-gui.sh:3328 msgid "Use Remote Version" msgstr "" -#: git-gui.sh:3160 +#: git-gui.sh:3332 msgid "Use Local Version" msgstr "" -#: git-gui.sh:3164 +#: git-gui.sh:3336 msgid "Revert To Base" msgstr "" -#: git-gui.sh:3183 +#: git-gui.sh:3354 +msgid "Visualize These Changes In The Submodule" +msgstr "" + +#: git-gui.sh:3358 +msgid "Visualize Current Branch History In The Submodule" +msgstr "" + +#: git-gui.sh:3362 +msgid "Visualize All Branch History In The Submodule" +msgstr "" + +#: git-gui.sh:3367 +msgid "Start git gui In The Submodule" +msgstr "" + +#: git-gui.sh:3389 msgid "Unstage Hunk From Commit" msgstr "" -#: git-gui.sh:3184 +#: git-gui.sh:3391 +msgid "Unstage Lines From Commit" +msgstr "" + +#: git-gui.sh:3393 msgid "Unstage Line From Commit" msgstr "" -#: git-gui.sh:3186 +#: git-gui.sh:3396 msgid "Stage Hunk For Commit" msgstr "" -#: git-gui.sh:3187 +#: git-gui.sh:3398 +msgid "Stage Lines For Commit" +msgstr "" + +#: git-gui.sh:3400 msgid "Stage Line For Commit" msgstr "" -#: git-gui.sh:3210 +#: git-gui.sh:3424 msgid "Initializing..." msgstr "" -#: git-gui.sh:3315 +#: git-gui.sh:3541 #, tcl-format msgid "" "Possible environment issues exist.\n" @@ -502,14 +531,14 @@ msgid "" "\n" msgstr "" -#: git-gui.sh:3345 +#: git-gui.sh:3570 msgid "" "\n" "This is due to a known issue with the\n" "Tcl binary distributed by Cygwin." msgstr "" -#: git-gui.sh:3350 +#: git-gui.sh:3575 #, tcl-format msgid "" "\n" @@ -613,7 +642,7 @@ msgstr "" msgid "Unable to display parent" msgstr "" -#: lib/blame.tcl:1091 lib/diff.tcl:297 +#: lib/blame.tcl:1091 lib/diff.tcl:320 msgid "Error loading diff:" msgstr "" @@ -639,7 +668,7 @@ 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:544 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172 #: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42 #: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352 #: lib/transport.tcl:108 @@ -670,7 +699,7 @@ msgstr "" msgid "Create New Branch" msgstr "" -#: lib/branch_create.tcl:31 lib/choose_repository.tcl:377 +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381 msgid "Create" msgstr "" @@ -702,7 +731,7 @@ msgstr "" msgid "Fast Forward Only" msgstr "" -#: lib/branch_create.tcl:85 lib/checkout_op.tcl:536 +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571 msgid "Reset" msgstr "" @@ -744,8 +773,8 @@ msgstr "" msgid "Delete Only If Merged Into" msgstr "" -#: lib/branch_delete.tcl:54 -msgid "Always (Do not perform merge test.)" +#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" msgstr "" #: lib/branch_delete.tcl:103 @@ -753,6 +782,13 @@ msgstr "" msgid "The following branches are not completely merged into %s:" msgstr "" +#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" + #: lib/branch_delete.tcl:141 #, tcl-format msgid "" @@ -780,7 +816,7 @@ msgstr "" msgid "Please select a branch to rename." msgstr "" -#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:201 +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202 #, tcl-format msgid "Branch '%s' already exists." msgstr "" @@ -811,38 +847,38 @@ msgstr "" msgid "Browse Branch Files" msgstr "" -#: lib/browser.tcl:278 lib/choose_repository.tcl:394 -#: lib/choose_repository.tcl:480 lib/choose_repository.tcl:491 -#: lib/choose_repository.tcl:995 +#: lib/browser.tcl:278 lib/choose_repository.tcl:398 +#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497 +#: lib/choose_repository.tcl:1028 msgid "Browse" msgstr "" -#: lib/checkout_op.tcl:84 +#: lib/checkout_op.tcl:85 #, tcl-format msgid "Fetching %s from %s" msgstr "" -#: lib/checkout_op.tcl:132 +#: lib/checkout_op.tcl:133 #, tcl-format msgid "fatal: Cannot resolve %s" msgstr "" -#: lib/checkout_op.tcl:145 lib/console.tcl:81 lib/database.tcl:31 +#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31 #: lib/sshkey.tcl:53 msgid "Close" msgstr "" -#: lib/checkout_op.tcl:174 +#: lib/checkout_op.tcl:175 #, tcl-format msgid "Branch '%s' does not exist." msgstr "" -#: lib/checkout_op.tcl:193 +#: lib/checkout_op.tcl:194 #, tcl-format msgid "Failed to configure simplified git-pull for '%s'." msgstr "" -#: lib/checkout_op.tcl:228 +#: lib/checkout_op.tcl:229 #, tcl-format msgid "" "Branch '%s' already exists.\n" @@ -851,21 +887,21 @@ msgid "" "A merge is required." msgstr "" -#: lib/checkout_op.tcl:242 +#: lib/checkout_op.tcl:243 #, tcl-format msgid "Merge strategy '%s' not supported." msgstr "" -#: lib/checkout_op.tcl:261 +#: lib/checkout_op.tcl:262 #, tcl-format msgid "Failed to update '%s'." msgstr "" -#: lib/checkout_op.tcl:273 +#: lib/checkout_op.tcl:274 msgid "Staging area (index) is already locked." msgstr "" -#: lib/checkout_op.tcl:288 +#: lib/checkout_op.tcl:289 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -875,30 +911,30 @@ msgid "" "The rescan will be automatically started now.\n" msgstr "" -#: lib/checkout_op.tcl:344 +#: lib/checkout_op.tcl:345 #, tcl-format msgid "Updating working directory to '%s'..." msgstr "" -#: lib/checkout_op.tcl:345 +#: lib/checkout_op.tcl:346 msgid "files checked out" msgstr "" -#: lib/checkout_op.tcl:375 +#: lib/checkout_op.tcl:376 #, tcl-format msgid "Aborted checkout of '%s' (file level merging is required)." msgstr "" -#: lib/checkout_op.tcl:376 +#: lib/checkout_op.tcl:377 msgid "File level merge required." msgstr "" -#: lib/checkout_op.tcl:380 +#: lib/checkout_op.tcl:381 #, tcl-format msgid "Staying on branch '%s'." msgstr "" -#: lib/checkout_op.tcl:451 +#: lib/checkout_op.tcl:452 msgid "" "You are no longer on a local branch.\n" "\n" @@ -906,30 +942,30 @@ msgid "" "Checkout'." msgstr "" -#: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472 +#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507 #, tcl-format msgid "Checked out '%s'." msgstr "" -#: lib/checkout_op.tcl:500 +#: lib/checkout_op.tcl:535 #, tcl-format msgid "Resetting '%s' to '%s' will lose the following commits:" msgstr "" -#: lib/checkout_op.tcl:522 +#: lib/checkout_op.tcl:557 msgid "Recovering lost commits may not be easy." msgstr "" -#: lib/checkout_op.tcl:527 +#: lib/checkout_op.tcl:562 #, tcl-format msgid "Reset '%s'?" msgstr "" -#: lib/checkout_op.tcl:532 lib/merge.tcl:164 lib/tools_dlg.tcl:343 +#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343 msgid "Visualize" msgstr "" -#: lib/checkout_op.tcl:600 +#: lib/checkout_op.tcl:635 #, tcl-format msgid "" "Failed to set current branch.\n" @@ -966,7 +1002,7 @@ msgstr "" msgid "Git Gui" msgstr "" -#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:382 +#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386 msgid "Create New Repository" msgstr "" @@ -974,7 +1010,7 @@ msgstr "" msgid "New..." msgstr "" -#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465 +#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471 msgid "Clone Existing Repository" msgstr "" @@ -982,7 +1018,7 @@ msgstr "" msgid "Clone..." msgstr "" -#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983 +#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016 msgid "Open Existing Repository" msgstr "" @@ -998,193 +1034,193 @@ msgstr "" msgid "Open Recent Repository:" msgstr "" -#: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309 -#: lib/choose_repository.tcl:316 +#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313 +#: lib/choose_repository.tcl:320 #, tcl-format msgid "Failed to create repository %s:" msgstr "" -#: lib/choose_repository.tcl:387 +#: lib/choose_repository.tcl:391 msgid "Directory:" msgstr "" -#: lib/choose_repository.tcl:417 lib/choose_repository.tcl:544 -#: lib/choose_repository.tcl:1017 +#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550 +#: lib/choose_repository.tcl:1052 msgid "Git Repository" msgstr "" -#: lib/choose_repository.tcl:442 +#: lib/choose_repository.tcl:448 #, tcl-format msgid "Directory %s already exists." msgstr "" -#: lib/choose_repository.tcl:446 +#: lib/choose_repository.tcl:452 #, tcl-format msgid "File %s already exists." msgstr "" -#: lib/choose_repository.tcl:460 +#: lib/choose_repository.tcl:466 msgid "Clone" msgstr "" -#: lib/choose_repository.tcl:473 +#: lib/choose_repository.tcl:479 msgid "Source Location:" msgstr "" -#: lib/choose_repository.tcl:484 +#: lib/choose_repository.tcl:490 msgid "Target Directory:" msgstr "" -#: lib/choose_repository.tcl:496 +#: lib/choose_repository.tcl:502 msgid "Clone Type:" msgstr "" -#: lib/choose_repository.tcl:502 +#: lib/choose_repository.tcl:508 msgid "Standard (Fast, Semi-Redundant, Hardlinks)" msgstr "" -#: lib/choose_repository.tcl:508 +#: lib/choose_repository.tcl:514 msgid "Full Copy (Slower, Redundant Backup)" msgstr "" -#: lib/choose_repository.tcl:514 +#: lib/choose_repository.tcl:520 msgid "Shared (Fastest, Not Recommended, No Backup)" msgstr "" -#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597 -#: lib/choose_repository.tcl:743 lib/choose_repository.tcl:813 -#: lib/choose_repository.tcl:1023 lib/choose_repository.tcl:1031 +#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603 +#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819 +#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066 #, tcl-format msgid "Not a Git repository: %s" msgstr "" -#: lib/choose_repository.tcl:586 +#: lib/choose_repository.tcl:592 msgid "Standard only available for local repository." msgstr "" -#: lib/choose_repository.tcl:590 +#: lib/choose_repository.tcl:596 msgid "Shared only available for local repository." msgstr "" -#: lib/choose_repository.tcl:611 +#: lib/choose_repository.tcl:617 #, tcl-format msgid "Location %s already exists." msgstr "" -#: lib/choose_repository.tcl:622 +#: lib/choose_repository.tcl:628 msgid "Failed to configure origin" msgstr "" -#: lib/choose_repository.tcl:634 +#: lib/choose_repository.tcl:640 msgid "Counting objects" msgstr "" -#: lib/choose_repository.tcl:635 +#: lib/choose_repository.tcl:641 msgid "buckets" msgstr "" -#: lib/choose_repository.tcl:659 +#: lib/choose_repository.tcl:665 #, tcl-format msgid "Unable to copy objects/info/alternates: %s" msgstr "" -#: lib/choose_repository.tcl:695 +#: lib/choose_repository.tcl:701 #, tcl-format msgid "Nothing to clone from %s." msgstr "" -#: lib/choose_repository.tcl:697 lib/choose_repository.tcl:911 -#: lib/choose_repository.tcl:923 +#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917 +#: lib/choose_repository.tcl:929 msgid "The 'master' branch has not been initialized." msgstr "" -#: lib/choose_repository.tcl:710 +#: lib/choose_repository.tcl:716 msgid "Hardlinks are unavailable. Falling back to copying." msgstr "" -#: lib/choose_repository.tcl:722 +#: lib/choose_repository.tcl:728 #, tcl-format msgid "Cloning from %s" msgstr "" -#: lib/choose_repository.tcl:753 +#: lib/choose_repository.tcl:759 msgid "Copying objects" msgstr "" -#: lib/choose_repository.tcl:754 +#: lib/choose_repository.tcl:760 msgid "KiB" msgstr "" -#: lib/choose_repository.tcl:778 +#: lib/choose_repository.tcl:784 #, tcl-format msgid "Unable to copy object: %s" msgstr "" -#: lib/choose_repository.tcl:788 +#: lib/choose_repository.tcl:794 msgid "Linking objects" msgstr "" -#: lib/choose_repository.tcl:789 +#: lib/choose_repository.tcl:795 msgid "objects" msgstr "" -#: lib/choose_repository.tcl:797 +#: lib/choose_repository.tcl:803 #, tcl-format msgid "Unable to hardlink object: %s" msgstr "" -#: lib/choose_repository.tcl:852 +#: lib/choose_repository.tcl:858 msgid "Cannot fetch branches and objects. See console output for details." msgstr "" -#: lib/choose_repository.tcl:863 +#: lib/choose_repository.tcl:869 msgid "Cannot fetch tags. See console output for details." msgstr "" -#: lib/choose_repository.tcl:887 +#: lib/choose_repository.tcl:893 msgid "Cannot determine HEAD. See console output for details." msgstr "" -#: lib/choose_repository.tcl:896 +#: lib/choose_repository.tcl:902 #, tcl-format msgid "Unable to cleanup %s" msgstr "" -#: lib/choose_repository.tcl:902 +#: lib/choose_repository.tcl:908 msgid "Clone failed." msgstr "" -#: lib/choose_repository.tcl:909 +#: lib/choose_repository.tcl:915 msgid "No default branch obtained." msgstr "" -#: lib/choose_repository.tcl:920 +#: lib/choose_repository.tcl:926 #, tcl-format msgid "Cannot resolve %s as a commit." msgstr "" -#: lib/choose_repository.tcl:932 +#: lib/choose_repository.tcl:938 msgid "Creating working directory" msgstr "" -#: lib/choose_repository.tcl:933 lib/index.tcl:65 lib/index.tcl:128 -#: lib/index.tcl:196 +#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130 +#: lib/index.tcl:198 msgid "files" msgstr "" -#: lib/choose_repository.tcl:962 +#: lib/choose_repository.tcl:968 msgid "Initial file checkout failed." msgstr "" -#: lib/choose_repository.tcl:978 +#: lib/choose_repository.tcl:1011 msgid "Open" msgstr "" -#: lib/choose_repository.tcl:988 +#: lib/choose_repository.tcl:1021 msgid "Repository:" msgstr "" -#: lib/choose_repository.tcl:1037 +#: lib/choose_repository.tcl:1072 #, tcl-format msgid "Failed to open repository %s:" msgstr "" @@ -1259,7 +1295,12 @@ msgstr "" msgid "Invalid GIT_COMMITTER_IDENT:" msgstr "" -#: lib/commit.tcl:132 +#: lib/commit.tcl:129 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "" + +#: lib/commit.tcl:149 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -1269,7 +1310,7 @@ msgid "" "The rescan will be automatically started now.\n" msgstr "" -#: lib/commit.tcl:155 +#: lib/commit.tcl:172 #, tcl-format msgid "" "Unmerged files cannot be committed.\n" @@ -1278,7 +1319,7 @@ msgid "" "before committing.\n" msgstr "" -#: lib/commit.tcl:163 +#: lib/commit.tcl:180 #, tcl-format msgid "" "Unknown file state %s detected.\n" @@ -1286,14 +1327,14 @@ msgid "" "File %s cannot be committed by this program.\n" msgstr "" -#: lib/commit.tcl:171 +#: lib/commit.tcl:188 msgid "" "No changes to commit.\n" "\n" "You must stage at least 1 file before you can commit.\n" msgstr "" -#: lib/commit.tcl:186 +#: lib/commit.tcl:203 msgid "" "Please supply a commit message.\n" "\n" @@ -1304,45 +1345,40 @@ msgid "" "- Remaining lines: Describe why this change is good.\n" msgstr "" -#: lib/commit.tcl:210 -#, tcl-format -msgid "warning: Tcl does not support encoding '%s'." -msgstr "" - -#: lib/commit.tcl:226 +#: lib/commit.tcl:234 msgid "Calling pre-commit hook..." msgstr "" -#: lib/commit.tcl:241 +#: lib/commit.tcl:249 msgid "Commit declined by pre-commit hook." msgstr "" -#: lib/commit.tcl:264 +#: lib/commit.tcl:272 msgid "Calling commit-msg hook..." msgstr "" -#: lib/commit.tcl:279 +#: lib/commit.tcl:287 msgid "Commit declined by commit-msg hook." msgstr "" -#: lib/commit.tcl:292 +#: lib/commit.tcl:300 msgid "Committing changes..." msgstr "" -#: lib/commit.tcl:308 +#: lib/commit.tcl:316 msgid "write-tree failed:" msgstr "" -#: lib/commit.tcl:309 lib/commit.tcl:353 lib/commit.tcl:373 +#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382 msgid "Commit failed." msgstr "" -#: lib/commit.tcl:326 +#: lib/commit.tcl:334 #, tcl-format msgid "Commit %s appears to be corrupt" msgstr "" -#: lib/commit.tcl:331 +#: lib/commit.tcl:339 msgid "" "No changes to commit.\n" "\n" @@ -1351,19 +1387,19 @@ msgid "" "A rescan will be automatically started now.\n" msgstr "" -#: lib/commit.tcl:338 +#: lib/commit.tcl:346 msgid "No changes to commit." msgstr "" -#: lib/commit.tcl:352 +#: lib/commit.tcl:360 msgid "commit-tree failed:" msgstr "" -#: lib/commit.tcl:372 +#: lib/commit.tcl:381 msgid "update-ref failed:" msgstr "" -#: lib/commit.tcl:460 +#: lib/commit.tcl:469 #, tcl-format msgid "Created commit %s: %s" msgstr "" @@ -1416,13 +1452,13 @@ msgstr "" msgid "Verifying the object database with fsck-objects" msgstr "" -#: lib/database.tcl:108 +#: lib/database.tcl:107 #, 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" +"the database.\n" "\n" "Compress the database now?" msgstr "" @@ -1432,7 +1468,7 @@ msgstr "" msgid "Invalid date from Git: %s" msgstr "" -#: lib/diff.tcl:59 +#: lib/diff.tcl:64 #, tcl-format msgid "" "No differences detected.\n" @@ -1446,56 +1482,56 @@ msgid "" "the same state." msgstr "" -#: lib/diff.tcl:99 +#: lib/diff.tcl:104 #, tcl-format msgid "Loading diff of %s..." msgstr "" -#: lib/diff.tcl:120 +#: lib/diff.tcl:125 msgid "" "LOCAL: deleted\n" "REMOTE:\n" msgstr "" -#: lib/diff.tcl:125 +#: lib/diff.tcl:130 msgid "" "REMOTE: deleted\n" "LOCAL:\n" msgstr "" -#: lib/diff.tcl:132 +#: lib/diff.tcl:137 msgid "LOCAL:\n" msgstr "" -#: lib/diff.tcl:135 +#: lib/diff.tcl:140 msgid "REMOTE:\n" msgstr "" -#: lib/diff.tcl:197 lib/diff.tcl:296 +#: lib/diff.tcl:202 lib/diff.tcl:319 #, tcl-format msgid "Unable to display %s" msgstr "" -#: lib/diff.tcl:198 +#: lib/diff.tcl:203 msgid "Error loading file:" msgstr "" -#: lib/diff.tcl:205 +#: lib/diff.tcl:210 msgid "Git Repository (subproject)" msgstr "" -#: lib/diff.tcl:217 +#: lib/diff.tcl:222 msgid "* Binary file (not showing content)." msgstr "" -#: lib/diff.tcl:222 +#: lib/diff.tcl:227 #, tcl-format msgid "" "* Untracked file is %d bytes.\n" "* Showing only first %d bytes.\n" msgstr "" -#: lib/diff.tcl:228 +#: lib/diff.tcl:233 #, tcl-format msgid "" "\n" @@ -1503,19 +1539,19 @@ msgid "" "* To see the entire file, use an external editor.\n" msgstr "" -#: lib/diff.tcl:436 +#: lib/diff.tcl:482 msgid "Failed to unstage selected hunk." msgstr "" -#: lib/diff.tcl:443 +#: lib/diff.tcl:489 msgid "Failed to stage selected hunk." msgstr "" -#: lib/diff.tcl:509 +#: lib/diff.tcl:568 msgid "Failed to unstage selected line." msgstr "" -#: lib/diff.tcl:517 +#: lib/diff.tcl:576 msgid "Failed to stage selected line." msgstr "" @@ -1552,13 +1588,13 @@ msgstr "" msgid "Index Error" msgstr "" -#: lib/index.tcl:21 +#: lib/index.tcl:17 msgid "" "Updating the Git index failed. A rescan will be automatically started to " "resynchronize git-gui." msgstr "" -#: lib/index.tcl:27 +#: lib/index.tcl:28 msgid "Continue" msgstr "" @@ -1566,43 +1602,43 @@ msgstr "" msgid "Unlock Index" msgstr "" -#: lib/index.tcl:287 +#: lib/index.tcl:289 #, tcl-format msgid "Unstaging %s from commit" msgstr "" -#: lib/index.tcl:326 +#: lib/index.tcl:328 msgid "Ready to commit." msgstr "" -#: lib/index.tcl:339 +#: lib/index.tcl:341 #, tcl-format msgid "Adding %s" msgstr "" -#: lib/index.tcl:396 +#: lib/index.tcl:398 #, tcl-format msgid "Revert changes in file %s?" msgstr "" -#: lib/index.tcl:398 +#: lib/index.tcl:400 #, tcl-format msgid "Revert changes in these %i files?" msgstr "" -#: lib/index.tcl:406 +#: lib/index.tcl:408 msgid "Any unstaged changes will be permanently lost by the revert." msgstr "" -#: lib/index.tcl:409 +#: lib/index.tcl:411 msgid "Do Nothing" msgstr "" -#: lib/index.tcl:427 +#: lib/index.tcl:429 msgid "Reverting selected files" msgstr "" -#: lib/index.tcl:431 +#: lib/index.tcl:433 #, tcl-format msgid "Reverting %s" msgstr "" @@ -2031,10 +2067,6 @@ msgstr "" 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 "" @@ -2058,23 +2090,16 @@ msgstr "" 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 "" - #: lib/remote_branch_delete.tcl:226 #, tcl-format msgid "Deleting branches from %s" msgstr "" -#: lib/remote_branch_delete.tcl:286 +#: lib/remote_branch_delete.tcl:292 msgid "No repository selected." msgstr "" -#: lib/remote_branch_delete.tcl:291 +#: lib/remote_branch_delete.tcl:297 #, tcl-format msgid "Scanning %s..." msgstr "" @@ -2095,11 +2120,11 @@ msgstr "" msgid "Case-Sensitive" msgstr "" -#: lib/shortcut.tcl:20 lib/shortcut.tcl:61 +#: lib/shortcut.tcl:21 lib/shortcut.tcl:62 msgid "Cannot write shortcut:" msgstr "" -#: lib/shortcut.tcl:136 +#: lib/shortcut.tcl:137 msgid "Cannot write icon:" 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/it.po b/git-gui/po/it.po index 762632c22f..aa15a204d8 100644 --- a/git-gui/po/it.po +++ b/git-gui/po/it.po @@ -9,41 +9,41 @@ msgid "" msgstr "" "Project-Id-Version: git-gui\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-12-08 08:31-0800\n" -"PO-Revision-Date: 2008-12-09 13:04+0100\n" +"POT-Creation-Date: 2010-01-26 15:47-0800\n" +"PO-Revision-Date: 2010-01-28 10:04+0100\n" "Last-Translator: Michele Ballabio <barra_cuda@katamail.com>\n" "Language-Team: Italian <tp@lists.linux.it>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: git-gui.sh:41 git-gui.sh:737 git-gui.sh:751 git-gui.sh:764 git-gui.sh:847 -#: git-gui.sh:866 +#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903 +#: git-gui.sh:922 msgid "git-gui: fatal error" msgstr "git-gui: errore grave" -#: git-gui.sh:689 +#: git-gui.sh:743 #, tcl-format msgid "Invalid font specified in %s:" msgstr "Caratteri non validi specificati in %s:" -#: git-gui.sh:723 +#: git-gui.sh:779 msgid "Main Font" msgstr "Caratteri principali" -#: git-gui.sh:724 +#: git-gui.sh:780 msgid "Diff/Console Font" msgstr "Caratteri per confronti e terminale" -#: git-gui.sh:738 +#: git-gui.sh:794 msgid "Cannot find git in PATH." msgstr "Impossibile trovare git nel PATH" -#: git-gui.sh:765 +#: git-gui.sh:821 msgid "Cannot parse Git version string:" msgstr "Impossibile determinare la versione di Git:" -#: git-gui.sh:783 +#: git-gui.sh:839 #, tcl-format msgid "" "Git version cannot be determined.\n" @@ -62,446 +62,475 @@ msgstr "" "\n" "Assumere che '%s' sia alla versione 1.5.0?\n" -#: git-gui.sh:1062 +#: git-gui.sh:1128 msgid "Git directory not found:" msgstr "Non trovo la directory di git: " -#: git-gui.sh:1069 +#: git-gui.sh:1146 msgid "Cannot move to top of working directory:" msgstr "Impossibile spostarsi sulla directory principale del progetto:" -#: git-gui.sh:1076 -msgid "Cannot use funny .git directory:" -msgstr "Impossibile usare una .git directory strana:" +#: git-gui.sh:1154 +msgid "Cannot use bare repository:" +msgstr "Impossibile usare un archivio senza directory di lavoro:" -#: git-gui.sh:1081 +#: git-gui.sh:1162 msgid "No working directory" msgstr "Nessuna directory di lavoro" -#: git-gui.sh:1247 lib/checkout_op.tcl:305 +#: git-gui.sh:1334 lib/checkout_op.tcl:306 msgid "Refreshing file status..." msgstr "Controllo dello stato dei file in corso..." -#: git-gui.sh:1303 +#: git-gui.sh:1390 msgid "Scanning for modified files ..." msgstr "Ricerca di file modificati in corso..." -#: git-gui.sh:1367 +#: git-gui.sh:1454 msgid "Calling prepare-commit-msg hook..." msgstr "Avvio prepare-commit-msg hook..." -#: git-gui.sh:1384 +#: git-gui.sh:1471 msgid "Commit declined by prepare-commit-msg hook." msgstr "Revisione rifiutata dal prepare-commit-msg hook." -#: git-gui.sh:1542 lib/browser.tcl:246 +#: git-gui.sh:1629 lib/browser.tcl:246 msgid "Ready." msgstr "Pronto." -#: git-gui.sh:1819 +#: git-gui.sh:1787 +#, tcl-format +msgid "Displaying only %s of %s files." +msgstr "Saranno mostrati solo %s file su %s." + +#: git-gui.sh:1913 msgid "Unmodified" msgstr "Non modificato" -#: git-gui.sh:1821 +#: git-gui.sh:1915 msgid "Modified, not staged" msgstr "Modificato, non preparato per una nuova revisione" -#: git-gui.sh:1822 git-gui.sh:1830 +#: git-gui.sh:1916 git-gui.sh:1924 msgid "Staged for commit" msgstr "Preparato per una nuova revisione" -#: git-gui.sh:1823 git-gui.sh:1831 +#: git-gui.sh:1917 git-gui.sh:1925 msgid "Portions staged for commit" msgstr "Parti preparate per una nuova revisione" -#: git-gui.sh:1824 git-gui.sh:1832 +#: git-gui.sh:1918 git-gui.sh:1926 msgid "Staged for commit, missing" msgstr "Preparato per una nuova revisione, mancante" -#: git-gui.sh:1826 +#: git-gui.sh:1920 msgid "File type changed, not staged" msgstr "Tipo di file modificato, non preparato per una nuova revisione" -#: git-gui.sh:1827 +#: git-gui.sh:1921 msgid "File type changed, staged" msgstr "Tipo di file modificato, preparato per una nuova revisione" -#: git-gui.sh:1829 +#: git-gui.sh:1923 msgid "Untracked, not staged" msgstr "Non tracciato, non preparato per una nuova revisione" -#: git-gui.sh:1834 +#: git-gui.sh:1928 msgid "Missing" msgstr "Mancante" -#: git-gui.sh:1835 +#: git-gui.sh:1929 msgid "Staged for removal" msgstr "Preparato per la rimozione" -#: git-gui.sh:1836 +#: git-gui.sh:1930 msgid "Staged for removal, still present" msgstr "Preparato alla rimozione, ancora presente" -#: git-gui.sh:1838 git-gui.sh:1839 git-gui.sh:1840 git-gui.sh:1841 -#: git-gui.sh:1842 git-gui.sh:1843 +#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935 +#: git-gui.sh:1936 git-gui.sh:1937 msgid "Requires merge resolution" msgstr "Richiede risoluzione dei conflitti" -#: git-gui.sh:1878 +#: git-gui.sh:1972 msgid "Starting gitk... please wait..." msgstr "Avvio di gitk... attendere..." -#: git-gui.sh:1887 +#: git-gui.sh:1984 msgid "Couldn't find gitk in PATH" msgstr "Impossibile trovare gitk nel PATH" -#: git-gui.sh:2280 lib/choose_repository.tcl:36 +#: git-gui.sh:2043 +msgid "Couldn't find git gui in PATH" +msgstr "Impossibile trovare git gui nel PATH" + +#: git-gui.sh:2455 lib/choose_repository.tcl:36 msgid "Repository" msgstr "Archivio" -#: git-gui.sh:2281 +#: git-gui.sh:2456 msgid "Edit" msgstr "Modifica" -#: git-gui.sh:2283 lib/choose_rev.tcl:561 +#: git-gui.sh:2458 lib/choose_rev.tcl:561 msgid "Branch" msgstr "Ramo" -#: git-gui.sh:2286 lib/choose_rev.tcl:548 +#: git-gui.sh:2461 lib/choose_rev.tcl:548 msgid "Commit@@noun" msgstr "Revisione" -#: git-gui.sh:2289 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 msgid "Merge" msgstr "Fusione (Merge)" -#: git-gui.sh:2290 lib/choose_rev.tcl:557 +#: git-gui.sh:2465 lib/choose_rev.tcl:557 msgid "Remote" msgstr "Remoto" -#: git-gui.sh:2293 +#: git-gui.sh:2468 msgid "Tools" -msgstr "Strumenti" +msgstr "Accessori" -#: git-gui.sh:2302 +#: git-gui.sh:2477 msgid "Explore Working Copy" msgstr "Esplora copia di lavoro" -#: git-gui.sh:2307 +#: git-gui.sh:2483 msgid "Browse Current Branch's Files" msgstr "Esplora i file del ramo attuale" -#: git-gui.sh:2311 +#: git-gui.sh:2487 msgid "Browse Branch Files..." msgstr "Esplora i file del ramo..." -#: git-gui.sh:2316 +#: git-gui.sh:2492 msgid "Visualize Current Branch's History" msgstr "Visualizza la cronologia del ramo attuale" -#: git-gui.sh:2320 +#: git-gui.sh:2496 msgid "Visualize All Branch History" msgstr "Visualizza la cronologia di tutti i rami" -#: git-gui.sh:2327 +#: git-gui.sh:2503 #, tcl-format msgid "Browse %s's Files" msgstr "Esplora i file di %s" -#: git-gui.sh:2329 +#: git-gui.sh:2505 #, tcl-format msgid "Visualize %s's History" msgstr "Visualizza la cronologia di %s" -#: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67 +#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67 msgid "Database Statistics" msgstr "Statistiche dell'archivio" -#: git-gui.sh:2337 lib/database.tcl:34 +#: git-gui.sh:2513 lib/database.tcl:34 msgid "Compress Database" msgstr "Comprimi l'archivio" -#: git-gui.sh:2340 +#: git-gui.sh:2516 msgid "Verify Database" msgstr "Verifica l'archivio" -#: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7 -#: lib/shortcut.tcl:39 lib/shortcut.tcl:71 +#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8 +#: lib/shortcut.tcl:40 lib/shortcut.tcl:72 msgid "Create Desktop Icon" msgstr "Crea icona desktop" -#: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 +#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 msgid "Quit" msgstr "Esci" -#: git-gui.sh:2371 +#: git-gui.sh:2547 msgid "Undo" msgstr "Annulla" -#: git-gui.sh:2374 +#: git-gui.sh:2550 msgid "Redo" msgstr "Ripeti" -#: git-gui.sh:2378 git-gui.sh:2937 +#: git-gui.sh:2554 git-gui.sh:3109 msgid "Cut" msgstr "Taglia" -#: git-gui.sh:2381 git-gui.sh:2940 git-gui.sh:3014 git-gui.sh:3096 +#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259 #: lib/console.tcl:69 msgid "Copy" msgstr "Copia" -#: git-gui.sh:2384 git-gui.sh:2943 +#: git-gui.sh:2560 git-gui.sh:3115 msgid "Paste" msgstr "Incolla" -#: git-gui.sh:2387 git-gui.sh:2946 lib/branch_delete.tcl:26 +#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26 #: lib/remote_branch_delete.tcl:38 msgid "Delete" msgstr "Elimina" -#: git-gui.sh:2391 git-gui.sh:2950 git-gui.sh:3100 lib/console.tcl:71 +#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71 msgid "Select All" msgstr "Seleziona tutto" -#: git-gui.sh:2400 +#: git-gui.sh:2576 msgid "Create..." msgstr "Crea..." -#: git-gui.sh:2406 +#: git-gui.sh:2582 msgid "Checkout..." msgstr "Attiva..." -#: git-gui.sh:2412 +#: git-gui.sh:2588 msgid "Rename..." msgstr "Rinomina" -#: git-gui.sh:2417 +#: git-gui.sh:2593 msgid "Delete..." msgstr "Elimina..." -#: git-gui.sh:2422 +#: git-gui.sh:2598 msgid "Reset..." msgstr "Ripristina..." -#: git-gui.sh:2432 +#: git-gui.sh:2608 msgid "Done" msgstr "Fatto" -#: git-gui.sh:2434 +#: git-gui.sh:2610 msgid "Commit@@verb" msgstr "Nuova revisione" -#: git-gui.sh:2443 git-gui.sh:2878 +#: git-gui.sh:2619 git-gui.sh:3050 msgid "New Commit" msgstr "Nuova revisione" -#: git-gui.sh:2451 git-gui.sh:2885 +#: git-gui.sh:2627 git-gui.sh:3057 msgid "Amend Last Commit" msgstr "Correggi l'ultima revisione" -#: git-gui.sh:2461 git-gui.sh:2839 lib/remote_branch_delete.tcl:99 +#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99 msgid "Rescan" msgstr "Analizza nuovamente" -#: git-gui.sh:2467 +#: git-gui.sh:2643 msgid "Stage To Commit" msgstr "Prepara per una nuova revisione" -#: git-gui.sh:2473 +#: git-gui.sh:2649 msgid "Stage Changed Files To Commit" msgstr "Prepara i file modificati per una nuova revisione" -#: git-gui.sh:2479 +#: git-gui.sh:2655 msgid "Unstage From Commit" msgstr "Annulla preparazione" -#: git-gui.sh:2484 lib/index.tcl:410 +#: git-gui.sh:2661 lib/index.tcl:412 msgid "Revert Changes" msgstr "Annulla modifiche" -#: git-gui.sh:2491 git-gui.sh:3083 +#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341 msgid "Show Less Context" msgstr "Mostra meno contesto" -#: git-gui.sh:2495 git-gui.sh:3087 +#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345 msgid "Show More Context" msgstr "Mostra più contesto" -#: git-gui.sh:2502 git-gui.sh:2852 git-gui.sh:2961 +#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133 msgid "Sign Off" msgstr "Sign Off" -#: git-gui.sh:2518 +#: git-gui.sh:2696 msgid "Local Merge..." msgstr "Fusione locale..." -#: git-gui.sh:2523 +#: git-gui.sh:2701 msgid "Abort Merge..." msgstr "Interrompi fusione..." -#: git-gui.sh:2535 git-gui.sh:2575 +#: git-gui.sh:2713 git-gui.sh:2741 msgid "Add..." msgstr "Aggiungi..." -#: git-gui.sh:2539 +#: git-gui.sh:2717 msgid "Push..." msgstr "Propaga..." -#: git-gui.sh:2543 +#: git-gui.sh:2721 msgid "Delete Branch..." msgstr "Elimina ramo..." -#: git-gui.sh:2553 git-gui.sh:2589 lib/about.tcl:14 -#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 -#, tcl-format -msgid "About %s" -msgstr "Informazioni su %s" - -#: git-gui.sh:2557 -msgid "Preferences..." -msgstr "Preferenze..." - -#: git-gui.sh:2565 git-gui.sh:3129 +#: git-gui.sh:2731 git-gui.sh:3292 msgid "Options..." msgstr "Opzioni..." -#: git-gui.sh:2576 +#: git-gui.sh:2742 msgid "Remove..." msgstr "Rimuovi..." -#: git-gui.sh:2585 lib/choose_repository.tcl:50 +#: git-gui.sh:2751 lib/choose_repository.tcl:50 msgid "Help" msgstr "Aiuto" -#: git-gui.sh:2611 +#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 +#, tcl-format +msgid "About %s" +msgstr "Informazioni su %s" + +#: git-gui.sh:2783 msgid "Online Documentation" msgstr "Documentazione sul web" -#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 +#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 msgid "Show SSH Key" msgstr "Mostra chave SSH" -#: git-gui.sh:2721 +#: git-gui.sh:2893 #, tcl-format msgid "fatal: cannot stat path %s: No such file or directory" msgstr "" "errore grave: impossibile effettuare lo stat del path %s: file o directory " "non trovata" -#: git-gui.sh:2754 +#: git-gui.sh:2926 msgid "Current Branch:" msgstr "Ramo attuale:" -#: git-gui.sh:2775 +#: git-gui.sh:2947 msgid "Staged Changes (Will Commit)" msgstr "Modifiche preparate (saranno nella nuova revisione)" -#: git-gui.sh:2795 +#: git-gui.sh:2967 msgid "Unstaged Changes" msgstr "Modifiche non preparate" -#: git-gui.sh:2845 +#: git-gui.sh:3017 msgid "Stage Changed" msgstr "Prepara modificati" -#: git-gui.sh:2864 lib/transport.tcl:104 lib/transport.tcl:193 +#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193 msgid "Push" msgstr "Propaga (Push)" -#: git-gui.sh:2899 +#: git-gui.sh:3071 msgid "Initial Commit Message:" msgstr "Messaggio di revisione iniziale:" -#: git-gui.sh:2900 +#: git-gui.sh:3072 msgid "Amended Commit Message:" msgstr "Messaggio di revisione corretto:" -#: git-gui.sh:2901 +#: git-gui.sh:3073 msgid "Amended Initial Commit Message:" msgstr "Messaggio iniziale di revisione corretto:" -#: git-gui.sh:2902 +#: git-gui.sh:3074 msgid "Amended Merge Commit Message:" msgstr "Messaggio di fusione corretto:" -#: git-gui.sh:2903 +#: git-gui.sh:3075 msgid "Merge Commit Message:" msgstr "Messaggio di fusione:" -#: git-gui.sh:2904 +#: git-gui.sh:3076 msgid "Commit Message:" msgstr "Messaggio di revisione:" -#: git-gui.sh:2953 git-gui.sh:3104 lib/console.tcl:73 +#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73 msgid "Copy All" msgstr "Copia tutto" -#: git-gui.sh:2977 lib/blame.tcl:104 +#: git-gui.sh:3149 lib/blame.tcl:104 msgid "File:" msgstr "File:" -#: git-gui.sh:3092 +#: git-gui.sh:3255 msgid "Refresh" msgstr "Rinfresca" -#: git-gui.sh:3113 +#: git-gui.sh:3276 msgid "Decrease Font Size" msgstr "Diminuisci dimensione caratteri" -#: git-gui.sh:3117 +#: git-gui.sh:3280 msgid "Increase Font Size" msgstr "Aumenta dimensione caratteri" -#: git-gui.sh:3125 lib/blame.tcl:281 +#: git-gui.sh:3288 lib/blame.tcl:281 msgid "Encoding" msgstr "Codifica" -#: git-gui.sh:3136 +#: git-gui.sh:3299 msgid "Apply/Reverse Hunk" msgstr "Applica/Inverti sezione" -#: git-gui.sh:3141 +#: git-gui.sh:3304 msgid "Apply/Reverse Line" msgstr "Applica/Inverti riga" -#: git-gui.sh:3151 +#: git-gui.sh:3323 msgid "Run Merge Tool" msgstr "Avvia programma esterno per la risoluzione dei conflitti" -#: git-gui.sh:3156 +#: git-gui.sh:3328 msgid "Use Remote Version" msgstr "Usa versione remota" -#: git-gui.sh:3160 +#: git-gui.sh:3332 msgid "Use Local Version" msgstr "Usa versione locale" -#: git-gui.sh:3164 +#: git-gui.sh:3336 msgid "Revert To Base" msgstr "Ritorna alla revisione comune" -#: git-gui.sh:3183 +#: git-gui.sh:3354 +msgid "Visualize These Changes In The Submodule" +msgstr "Visualizza queste modifiche nel sottomodulo" + +#: git-gui.sh:3358 +msgid "Visualize Current Branch History In The Submodule" +msgstr "Visualizza la cronologia del ramo attuale nel sottomodulo" + +#: git-gui.sh:3362 +msgid "Visualize All Branch History In The Submodule" +msgstr "Visualizza la cronologia di tutti i rami nel sottomodulo" + +#: git-gui.sh:3367 +msgid "Start git gui In The Submodule" +msgstr "Avvia git gui nel sottomodulo" + +#: git-gui.sh:3389 msgid "Unstage Hunk From Commit" msgstr "Annulla preparazione della sezione per una nuova revisione" -#: git-gui.sh:3184 +#: git-gui.sh:3391 +msgid "Unstage Lines From Commit" +msgstr "Annulla preparazione delle linee per una nuova revisione" + +#: git-gui.sh:3393 msgid "Unstage Line From Commit" msgstr "Annulla preparazione della linea per una nuova revisione" -#: git-gui.sh:3186 +#: git-gui.sh:3396 msgid "Stage Hunk For Commit" msgstr "Prepara sezione per una nuova revisione" -#: git-gui.sh:3187 +#: git-gui.sh:3398 +msgid "Stage Lines For Commit" +msgstr "Prepara linee per una nuova revisione" + +#: git-gui.sh:3400 msgid "Stage Line For Commit" msgstr "Prepara linea per una nuova revisione" -#: git-gui.sh:3210 +#: git-gui.sh:3424 msgid "Initializing..." msgstr "Inizializzazione..." -#: git-gui.sh:3315 +#: git-gui.sh:3541 #, tcl-format msgid "" "Possible environment issues exist.\n" @@ -518,7 +547,7 @@ msgstr "" "da %s:\n" "\n" -#: git-gui.sh:3345 +#: git-gui.sh:3570 msgid "" "\n" "This is due to a known issue with the\n" @@ -528,7 +557,7 @@ msgstr "" "Ciò è dovuto a un problema conosciuto\n" "causato dall'eseguibile Tcl distribuito da Cygwin." -#: git-gui.sh:3350 +#: git-gui.sh:3575 #, tcl-format msgid "" "\n" @@ -637,7 +666,7 @@ msgstr "Impossibile trovare la revisione precedente:" msgid "Unable to display parent" msgstr "Impossibile visualizzare la revisione precedente" -#: lib/blame.tcl:1091 lib/diff.tcl:297 +#: lib/blame.tcl:1091 lib/diff.tcl:320 msgid "Error loading diff:" msgstr "Errore nel caricamento delle differenze:" @@ -663,7 +692,7 @@ msgstr "Attiva" #: 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:544 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172 #: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42 #: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352 #: lib/transport.tcl:108 @@ -694,7 +723,7 @@ msgstr "Crea ramo" msgid "Create New Branch" msgstr "Crea nuovo ramo" -#: lib/branch_create.tcl:31 lib/choose_repository.tcl:377 +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381 msgid "Create" msgstr "Crea" @@ -726,7 +755,7 @@ msgstr "No" msgid "Fast Forward Only" msgstr "Solo fast forward" -#: lib/branch_create.tcl:85 lib/checkout_op.tcl:536 +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571 msgid "Reset" msgstr "Ripristina" @@ -769,15 +798,25 @@ msgstr "Rami locali" msgid "Delete Only If Merged Into" msgstr "Cancella solo se fuso con un altro ramo" -#: lib/branch_delete.tcl:54 -msgid "Always (Do not perform merge test.)" -msgstr "Sempre (Non effettuare verifiche di fusione)." +#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" +msgstr "Sempre (non verificare le fusioni)" #: lib/branch_delete.tcl:103 #, tcl-format msgid "The following branches are not completely merged into %s:" msgstr "I rami seguenti non sono stati fusi completamente in %s:" +#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"Ripristinare rami cancellati è difficile.\n" +"\n" +"Cancellare i rami selezionati?" + #: lib/branch_delete.tcl:141 #, tcl-format msgid "" @@ -807,7 +846,7 @@ msgstr "Nuovo Nome:" msgid "Please select a branch to rename." msgstr "Scegliere un ramo da rinominare." -#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:201 +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202 #, tcl-format msgid "Branch '%s' already exists." msgstr "Il ramo '%s' esiste già." @@ -838,38 +877,38 @@ msgstr "[Directory superiore]" msgid "Browse Branch Files" msgstr "Esplora i file del ramo" -#: lib/browser.tcl:278 lib/choose_repository.tcl:394 -#: lib/choose_repository.tcl:480 lib/choose_repository.tcl:491 -#: lib/choose_repository.tcl:995 +#: lib/browser.tcl:278 lib/choose_repository.tcl:398 +#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497 +#: lib/choose_repository.tcl:1028 msgid "Browse" msgstr "Esplora" -#: lib/checkout_op.tcl:84 +#: lib/checkout_op.tcl:85 #, tcl-format msgid "Fetching %s from %s" msgstr "Recupero %s da %s" -#: lib/checkout_op.tcl:132 +#: lib/checkout_op.tcl:133 #, tcl-format msgid "fatal: Cannot resolve %s" msgstr "errore grave: impossibile risolvere %s" -#: lib/checkout_op.tcl:145 lib/console.tcl:81 lib/database.tcl:31 +#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31 #: lib/sshkey.tcl:53 msgid "Close" msgstr "Chiudi" -#: lib/checkout_op.tcl:174 +#: lib/checkout_op.tcl:175 #, tcl-format msgid "Branch '%s' does not exist." msgstr "Il ramo '%s' non esiste." -#: lib/checkout_op.tcl:193 +#: lib/checkout_op.tcl:194 #, tcl-format msgid "Failed to configure simplified git-pull for '%s'." msgstr "Impossibile configurare git-pull semplificato per '%s'." -#: lib/checkout_op.tcl:228 +#: lib/checkout_op.tcl:229 #, tcl-format msgid "" "Branch '%s' already exists.\n" @@ -882,22 +921,22 @@ msgstr "" "Non può effettuare un 'fast-forward' a %s.\n" "E' necessaria una fusione." -#: lib/checkout_op.tcl:242 +#: lib/checkout_op.tcl:243 #, tcl-format msgid "Merge strategy '%s' not supported." msgstr "La strategia di fusione '%s' non è supportata." -#: lib/checkout_op.tcl:261 +#: lib/checkout_op.tcl:262 #, tcl-format msgid "Failed to update '%s'." msgstr "Impossibile aggiornare '%s'." -#: lib/checkout_op.tcl:273 +#: lib/checkout_op.tcl:274 msgid "Staging area (index) is already locked." msgstr "" "L'area di preparazione per una nuova revisione (indice) è già bloccata." -#: lib/checkout_op.tcl:288 +#: lib/checkout_op.tcl:289 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -914,30 +953,30 @@ msgstr "" "\n" "La nuova analisi comincerà ora.\n" -#: lib/checkout_op.tcl:344 +#: lib/checkout_op.tcl:345 #, tcl-format msgid "Updating working directory to '%s'..." msgstr "Aggiornamento della directory di lavoro a '%s' in corso..." -#: lib/checkout_op.tcl:345 +#: lib/checkout_op.tcl:346 msgid "files checked out" msgstr "file presenti nella directory di lavoro" -#: lib/checkout_op.tcl:375 +#: lib/checkout_op.tcl:376 #, tcl-format msgid "Aborted checkout of '%s' (file level merging is required)." msgstr "Attivazione di '%s' fallita (richiesta una fusione a livello file)." -#: lib/checkout_op.tcl:376 +#: lib/checkout_op.tcl:377 msgid "File level merge required." msgstr "E' richiesta una fusione a livello file." -#: lib/checkout_op.tcl:380 +#: lib/checkout_op.tcl:381 #, tcl-format msgid "Staying on branch '%s'." msgstr "Si rimarrà sul ramo '%s'." -#: lib/checkout_op.tcl:451 +#: lib/checkout_op.tcl:452 msgid "" "You are no longer on a local branch.\n" "\n" @@ -949,31 +988,31 @@ msgstr "" "Se si vuole rimanere su un ramo, crearne uno ora a partire da 'Questa " "revisione attiva staccata'." -#: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472 +#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507 #, tcl-format msgid "Checked out '%s'." msgstr "Attivazione di '%s' completata." -#: lib/checkout_op.tcl:500 +#: lib/checkout_op.tcl:535 #, tcl-format msgid "Resetting '%s' to '%s' will lose the following commits:" msgstr "" "Ripristinare '%s' a '%s' comporterà la perdita delle seguenti revisioni:" -#: lib/checkout_op.tcl:522 +#: lib/checkout_op.tcl:557 msgid "Recovering lost commits may not be easy." msgstr "Ricomporre le revisioni perdute potrebbe non essere semplice." -#: lib/checkout_op.tcl:527 +#: lib/checkout_op.tcl:562 #, tcl-format msgid "Reset '%s'?" msgstr "Ripristinare '%s'?" -#: lib/checkout_op.tcl:532 lib/merge.tcl:164 lib/tools_dlg.tcl:343 +#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343 msgid "Visualize" msgstr "Visualizza" -#: lib/checkout_op.tcl:600 +#: lib/checkout_op.tcl:635 #, tcl-format msgid "" "Failed to set current branch.\n" @@ -1019,7 +1058,7 @@ msgstr "" msgid "Git Gui" msgstr "Git Gui" -#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:382 +#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386 msgid "Create New Repository" msgstr "Crea nuovo archivio" @@ -1027,7 +1066,7 @@ msgstr "Crea nuovo archivio" msgid "New..." msgstr "Nuovo..." -#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465 +#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471 msgid "Clone Existing Repository" msgstr "Clona archivio esistente" @@ -1035,7 +1074,7 @@ msgstr "Clona archivio esistente" msgid "Clone..." msgstr "Clona..." -#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983 +#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016 msgid "Open Existing Repository" msgstr "Apri archivio esistente" @@ -1051,198 +1090,198 @@ msgstr "Archivi recenti" msgid "Open Recent Repository:" msgstr "Apri archivio recente:" -#: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309 -#: lib/choose_repository.tcl:316 +#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313 +#: lib/choose_repository.tcl:320 #, tcl-format msgid "Failed to create repository %s:" msgstr "Impossibile creare l'archivio %s:" -#: lib/choose_repository.tcl:387 +#: lib/choose_repository.tcl:391 msgid "Directory:" msgstr "Directory:" -#: lib/choose_repository.tcl:417 lib/choose_repository.tcl:544 -#: lib/choose_repository.tcl:1017 +#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550 +#: lib/choose_repository.tcl:1052 msgid "Git Repository" msgstr "Archivio Git" -#: lib/choose_repository.tcl:442 +#: lib/choose_repository.tcl:448 #, tcl-format msgid "Directory %s already exists." msgstr "La directory %s esiste già." -#: lib/choose_repository.tcl:446 +#: lib/choose_repository.tcl:452 #, tcl-format msgid "File %s already exists." msgstr "Il file %s esiste già." -#: lib/choose_repository.tcl:460 +#: lib/choose_repository.tcl:466 msgid "Clone" msgstr "Clona" -#: lib/choose_repository.tcl:473 +#: lib/choose_repository.tcl:479 msgid "Source Location:" msgstr "Posizione sorgente:" -#: lib/choose_repository.tcl:484 +#: lib/choose_repository.tcl:490 msgid "Target Directory:" msgstr "Directory di destinazione:" -#: lib/choose_repository.tcl:496 +#: lib/choose_repository.tcl:502 msgid "Clone Type:" msgstr "Tipo di clone:" -#: lib/choose_repository.tcl:502 +#: lib/choose_repository.tcl:508 msgid "Standard (Fast, Semi-Redundant, Hardlinks)" msgstr "Standard (veloce, semi-ridondante, con hardlink)" -#: lib/choose_repository.tcl:508 +#: lib/choose_repository.tcl:514 msgid "Full Copy (Slower, Redundant Backup)" msgstr "Copia completa (più lento, backup ridondante)" -#: lib/choose_repository.tcl:514 +#: lib/choose_repository.tcl:520 msgid "Shared (Fastest, Not Recommended, No Backup)" msgstr "Shared (il più veloce, non raccomandato, nessun backup)" -#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597 -#: lib/choose_repository.tcl:743 lib/choose_repository.tcl:813 -#: lib/choose_repository.tcl:1023 lib/choose_repository.tcl:1031 +#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603 +#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819 +#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066 #, tcl-format msgid "Not a Git repository: %s" msgstr "%s non è un archivio Git." -#: lib/choose_repository.tcl:586 +#: lib/choose_repository.tcl:592 msgid "Standard only available for local repository." msgstr "Standard è disponibile solo per archivi locali." -#: lib/choose_repository.tcl:590 +#: lib/choose_repository.tcl:596 msgid "Shared only available for local repository." msgstr "Shared è disponibile solo per archivi locali." -#: lib/choose_repository.tcl:611 +#: lib/choose_repository.tcl:617 #, tcl-format msgid "Location %s already exists." msgstr "Il file/directory %s esiste già." -#: lib/choose_repository.tcl:622 +#: lib/choose_repository.tcl:628 msgid "Failed to configure origin" msgstr "Impossibile configurare origin" -#: lib/choose_repository.tcl:634 +#: lib/choose_repository.tcl:640 msgid "Counting objects" msgstr "Calcolo oggetti" -#: lib/choose_repository.tcl:635 +#: lib/choose_repository.tcl:641 msgid "buckets" msgstr "" -#: lib/choose_repository.tcl:659 +#: lib/choose_repository.tcl:665 #, tcl-format msgid "Unable to copy objects/info/alternates: %s" msgstr "Impossibile copiare oggetti/info/alternate: %s" -#: lib/choose_repository.tcl:695 +#: lib/choose_repository.tcl:701 #, tcl-format msgid "Nothing to clone from %s." msgstr "Niente da clonare da %s." -#: lib/choose_repository.tcl:697 lib/choose_repository.tcl:911 -#: lib/choose_repository.tcl:923 +#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917 +#: lib/choose_repository.tcl:929 msgid "The 'master' branch has not been initialized." msgstr "Il ramo 'master' non è stato inizializzato." -#: lib/choose_repository.tcl:710 +#: lib/choose_repository.tcl:716 msgid "Hardlinks are unavailable. Falling back to copying." msgstr "Impossibile utilizzare gli hardlink. Si ricorrerà alla copia." -#: lib/choose_repository.tcl:722 +#: lib/choose_repository.tcl:728 #, tcl-format msgid "Cloning from %s" msgstr "Clonazione da %s" -#: lib/choose_repository.tcl:753 +#: lib/choose_repository.tcl:759 msgid "Copying objects" msgstr "Copia degli oggetti" -#: lib/choose_repository.tcl:754 +#: lib/choose_repository.tcl:760 msgid "KiB" msgstr "KiB" -#: lib/choose_repository.tcl:778 +#: lib/choose_repository.tcl:784 #, tcl-format msgid "Unable to copy object: %s" msgstr "Impossibile copiare oggetto: %s" -#: lib/choose_repository.tcl:788 +#: lib/choose_repository.tcl:794 msgid "Linking objects" msgstr "Collegamento oggetti" -#: lib/choose_repository.tcl:789 +#: lib/choose_repository.tcl:795 msgid "objects" msgstr "oggetti" -#: lib/choose_repository.tcl:797 +#: lib/choose_repository.tcl:803 #, tcl-format msgid "Unable to hardlink object: %s" msgstr "Hardlink impossibile sull'oggetto: %s" -#: lib/choose_repository.tcl:852 +#: lib/choose_repository.tcl:858 msgid "Cannot fetch branches and objects. See console output for details." msgstr "" "Impossibile recuperare rami e oggetti. Controllare i dettagli forniti dalla " "console." -#: lib/choose_repository.tcl:863 +#: lib/choose_repository.tcl:869 msgid "Cannot fetch tags. See console output for details." msgstr "" "Impossibile recuperare le etichette. Controllare i dettagli forniti dalla " "console." -#: lib/choose_repository.tcl:887 +#: lib/choose_repository.tcl:893 msgid "Cannot determine HEAD. See console output for details." msgstr "" "Impossibile determinare HEAD. Controllare i dettagli forniti dalla console." -#: lib/choose_repository.tcl:896 +#: lib/choose_repository.tcl:902 #, tcl-format msgid "Unable to cleanup %s" msgstr "Impossibile ripulire %s" -#: lib/choose_repository.tcl:902 +#: lib/choose_repository.tcl:908 msgid "Clone failed." msgstr "Clonazione non riuscita." -#: lib/choose_repository.tcl:909 +#: lib/choose_repository.tcl:915 msgid "No default branch obtained." msgstr "Non è stato trovato un ramo predefinito." -#: lib/choose_repository.tcl:920 +#: lib/choose_repository.tcl:926 #, tcl-format msgid "Cannot resolve %s as a commit." msgstr "Impossibile risolvere %s come una revisione." -#: lib/choose_repository.tcl:932 +#: lib/choose_repository.tcl:938 msgid "Creating working directory" msgstr "Creazione directory di lavoro" -#: lib/choose_repository.tcl:933 lib/index.tcl:65 lib/index.tcl:128 -#: lib/index.tcl:196 +#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130 +#: lib/index.tcl:198 msgid "files" msgstr "file" -#: lib/choose_repository.tcl:962 +#: lib/choose_repository.tcl:968 msgid "Initial file checkout failed." msgstr "Attivazione iniziale non riuscita." -#: lib/choose_repository.tcl:978 +#: lib/choose_repository.tcl:1011 msgid "Open" msgstr "Apri" -#: lib/choose_repository.tcl:988 +#: lib/choose_repository.tcl:1021 msgid "Repository:" msgstr "Archivio:" -#: lib/choose_repository.tcl:1037 +#: lib/choose_repository.tcl:1072 #, tcl-format msgid "Failed to open repository %s:" msgstr "Impossibile accedere all'archivio %s:" @@ -1326,7 +1365,12 @@ msgstr "Impossibile ottenere la tua identità:" msgid "Invalid GIT_COMMITTER_IDENT:" msgstr "GIT_COMMITTER_IDENT non valida:" -#: lib/commit.tcl:132 +#: lib/commit.tcl:129 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "attenzione: Tcl non supporta la codifica '%s'." + +#: lib/commit.tcl:149 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -1343,7 +1387,7 @@ msgstr "" "\n" "La nuova analisi comincerà ora.\n" -#: lib/commit.tcl:155 +#: lib/commit.tcl:172 #, tcl-format msgid "" "Unmerged files cannot be committed.\n" @@ -1356,7 +1400,7 @@ msgstr "" "Il file %s presenta dei conflitti. Devi risolverli e preparare il file per " "creare una nuova revisione prima di effettuare questa azione.\n" -#: lib/commit.tcl:163 +#: lib/commit.tcl:180 #, tcl-format msgid "" "Unknown file state %s detected.\n" @@ -1367,7 +1411,7 @@ msgstr "" "\n" "Questo programma non può creare una revisione contenente il file %s.\n" -#: lib/commit.tcl:171 +#: lib/commit.tcl:188 msgid "" "No changes to commit.\n" "\n" @@ -1378,7 +1422,7 @@ msgstr "" "Devi preparare per una nuova revisione almeno 1 file prima di effettuare " "questa operazione.\n" -#: lib/commit.tcl:186 +#: lib/commit.tcl:203 msgid "" "Please supply a commit message.\n" "\n" @@ -1396,45 +1440,40 @@ msgstr "" "- Seconda linea: vuota.\n" "- Terza linea: spiega a cosa serve la tua modifica.\n" -#: lib/commit.tcl:210 -#, tcl-format -msgid "warning: Tcl does not support encoding '%s'." -msgstr "attenzione: Tcl non supporta la codifica '%s'." - -#: lib/commit.tcl:226 +#: lib/commit.tcl:234 msgid "Calling pre-commit hook..." msgstr "Avvio pre-commit hook..." -#: lib/commit.tcl:241 +#: lib/commit.tcl:249 msgid "Commit declined by pre-commit hook." msgstr "Revisione rifiutata dal pre-commit hook." -#: lib/commit.tcl:264 +#: lib/commit.tcl:272 msgid "Calling commit-msg hook..." msgstr "Avvio commit-msg hook..." -#: lib/commit.tcl:279 +#: lib/commit.tcl:287 msgid "Commit declined by commit-msg hook." msgstr "Revisione rifiutata dal commit-msg hook." -#: lib/commit.tcl:292 +#: lib/commit.tcl:300 msgid "Committing changes..." msgstr "Archiviazione modifiche..." -#: lib/commit.tcl:308 +#: lib/commit.tcl:316 msgid "write-tree failed:" msgstr "write-tree non riuscito:" -#: lib/commit.tcl:309 lib/commit.tcl:353 lib/commit.tcl:373 +#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382 msgid "Commit failed." msgstr "Impossibile creare una nuova revisione." -#: lib/commit.tcl:326 +#: lib/commit.tcl:334 #, tcl-format msgid "Commit %s appears to be corrupt" msgstr "La revisione %s sembra essere danneggiata" -#: lib/commit.tcl:331 +#: lib/commit.tcl:339 msgid "" "No changes to commit.\n" "\n" @@ -1448,19 +1487,19 @@ msgstr "" "\n" "Si procederà subito ad una nuova analisi.\n" -#: lib/commit.tcl:338 +#: lib/commit.tcl:346 msgid "No changes to commit." msgstr "Nessuna modifica per la nuova revisione." -#: lib/commit.tcl:352 +#: lib/commit.tcl:360 msgid "commit-tree failed:" msgstr "commit-tree non riuscito:" -#: lib/commit.tcl:372 +#: lib/commit.tcl:381 msgid "update-ref failed:" msgstr "update-ref non riuscito:" -#: lib/commit.tcl:460 +#: lib/commit.tcl:469 #, tcl-format msgid "Created commit %s: %s" msgstr "Creata revisione %s: %s" @@ -1513,20 +1552,19 @@ msgstr "Compressione dell'archivio in corso" msgid "Verifying the object database with fsck-objects" msgstr "Verifica dell'archivio con fsck-objects in corso" -#: lib/database.tcl:108 +#: lib/database.tcl:107 #, 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" +"the database.\n" "\n" "Compress the database now?" msgstr "" "Questo archivio attualmente ha circa %i oggetti slegati.\n" "\n" -"Per mantenere buone prestazioni si raccomanda di comprimere l'archivio " -"quando sono presenti più di %i oggetti slegati.\n" +"Per mantenere buone prestazioni si raccomanda di comprimere l'archivio.\n" "\n" "Comprimere l'archivio ora?" @@ -1535,7 +1573,7 @@ msgstr "" msgid "Invalid date from Git: %s" msgstr "Git ha restituito una data non valida: %s" -#: lib/diff.tcl:59 +#: lib/diff.tcl:64 #, tcl-format msgid "" "No differences detected.\n" @@ -1558,12 +1596,12 @@ msgstr "" "Si procederà automaticamente ad una nuova analisi per trovare altri file che " "potrebbero avere lo stesso stato." -#: lib/diff.tcl:99 +#: lib/diff.tcl:104 #, tcl-format msgid "Loading diff of %s..." msgstr "Caricamento delle differenze di %s..." -#: lib/diff.tcl:120 +#: lib/diff.tcl:125 msgid "" "LOCAL: deleted\n" "REMOTE:\n" @@ -1571,7 +1609,7 @@ msgstr "" "LOCALE: cancellato\n" "REMOTO:\n" -#: lib/diff.tcl:125 +#: lib/diff.tcl:130 msgid "" "REMOTE: deleted\n" "LOCAL:\n" @@ -1579,32 +1617,32 @@ msgstr "" "REMOTO: cancellato\n" "LOCALE:\n" -#: lib/diff.tcl:132 +#: lib/diff.tcl:137 msgid "LOCAL:\n" msgstr "LOCALE:\n" -#: lib/diff.tcl:135 +#: lib/diff.tcl:140 msgid "REMOTE:\n" msgstr "REMOTO:\n" -#: lib/diff.tcl:197 lib/diff.tcl:296 +#: lib/diff.tcl:202 lib/diff.tcl:319 #, tcl-format msgid "Unable to display %s" msgstr "Impossibile visualizzare %s" -#: lib/diff.tcl:198 +#: lib/diff.tcl:203 msgid "Error loading file:" msgstr "Errore nel caricamento del file:" -#: lib/diff.tcl:205 +#: lib/diff.tcl:210 msgid "Git Repository (subproject)" msgstr "Archivio Git (sottoprogetto)" -#: lib/diff.tcl:217 +#: lib/diff.tcl:222 msgid "* Binary file (not showing content)." msgstr "* File binario (il contenuto non sarà mostrato)." -#: lib/diff.tcl:222 +#: lib/diff.tcl:227 #, tcl-format msgid "" "* Untracked file is %d bytes.\n" @@ -1613,7 +1651,7 @@ msgstr "" "* Il file non tracciato è di %d byte.\n" "* Saranno visualizzati solo i primi %d byte.\n" -#: lib/diff.tcl:228 +#: lib/diff.tcl:233 #, tcl-format msgid "" "\n" @@ -1624,19 +1662,19 @@ msgstr "" "* %s non visualizza completamente questo file non tracciato.\n" "* Per visualizzare il file completo, usare un programma esterno.\n" -#: lib/diff.tcl:436 +#: lib/diff.tcl:482 msgid "Failed to unstage selected hunk." msgstr "Impossibile rimuovere la sezione scelta dalla nuova revisione." -#: lib/diff.tcl:443 +#: lib/diff.tcl:489 msgid "Failed to stage selected hunk." msgstr "Impossibile preparare la sezione scelta per una nuova revisione." -#: lib/diff.tcl:509 +#: lib/diff.tcl:568 msgid "Failed to unstage selected line." msgstr "Impossibile rimuovere la riga scelta dalla nuova revisione." -#: lib/diff.tcl:517 +#: lib/diff.tcl:576 msgid "Failed to stage selected line." msgstr "Impossibile preparare la riga scelta per una nuova revisione." @@ -1674,7 +1712,7 @@ msgstr "Impossibile sbloccare l'accesso all'indice" msgid "Index Error" msgstr "Errore nell'indice" -#: lib/index.tcl:21 +#: lib/index.tcl:17 msgid "" "Updating the Git index failed. A rescan will be automatically started to " "resynchronize git-gui." @@ -1682,7 +1720,7 @@ msgstr "" "Impossibile aggiornare l'indice. Ora sarà avviata una nuova analisi che " "aggiornerà git-gui." -#: lib/index.tcl:27 +#: lib/index.tcl:28 msgid "Continue" msgstr "Continua" @@ -1690,45 +1728,45 @@ msgstr "Continua" msgid "Unlock Index" msgstr "Sblocca l'accesso all'indice" -#: lib/index.tcl:287 +#: lib/index.tcl:289 #, tcl-format msgid "Unstaging %s from commit" msgstr "%s non farà parte della prossima revisione" -#: lib/index.tcl:326 +#: lib/index.tcl:328 msgid "Ready to commit." msgstr "Pronto per creare una nuova revisione." -#: lib/index.tcl:339 +#: lib/index.tcl:341 #, tcl-format msgid "Adding %s" msgstr "Aggiunta di %s in corso" -#: lib/index.tcl:396 +#: lib/index.tcl:398 #, tcl-format msgid "Revert changes in file %s?" msgstr "Annullare le modifiche nel file %s?" -#: lib/index.tcl:398 +#: lib/index.tcl:400 #, tcl-format msgid "Revert changes in these %i files?" msgstr "Annullare le modifiche in questi %i file?" -#: lib/index.tcl:406 +#: lib/index.tcl:408 msgid "Any unstaged changes will be permanently lost by the revert." msgstr "" "Tutte le modifiche non preparate per una nuova revisione saranno perse per " "sempre." -#: lib/index.tcl:409 +#: lib/index.tcl:411 msgid "Do Nothing" msgstr "Non fare niente" -#: lib/index.tcl:427 +#: lib/index.tcl:429 msgid "Reverting selected files" msgstr "Annullo le modifiche nei file selezionati" -#: lib/index.tcl:431 +#: lib/index.tcl:433 #, tcl-format msgid "Reverting %s" msgstr "Annullo le modifiche in %s" @@ -2215,10 +2253,6 @@ msgstr "Elimina solo se" msgid "Merged Into:" msgstr "Fuso in:" -#: lib/remote_branch_delete.tcl:119 -msgid "Always (Do not perform merge checks)" -msgstr "Sempre (non verificare le fusioni)" - #: lib/remote_branch_delete.tcl:152 msgid "A branch is required for 'Merged Into'." msgstr "Si richiede un ramo per 'Fuso in'." @@ -2247,26 +2281,16 @@ msgstr "" msgid "Please select one or more branches to delete." msgstr "Scegliere uno o più rami da cancellare." -#: lib/remote_branch_delete.tcl:216 -msgid "" -"Recovering deleted branches is difficult.\n" -"\n" -"Delete the selected branches?" -msgstr "" -"Ricomporre rami cancellati è difficile.\n" -"\n" -"Cancellare i rami selezionati?" - #: lib/remote_branch_delete.tcl:226 #, tcl-format msgid "Deleting branches from %s" msgstr "Cancellazione rami da %s" -#: lib/remote_branch_delete.tcl:286 +#: lib/remote_branch_delete.tcl:292 msgid "No repository selected." msgstr "Nessun archivio selezionato." -#: lib/remote_branch_delete.tcl:291 +#: lib/remote_branch_delete.tcl:297 #, tcl-format msgid "Scanning %s..." msgstr "Analisi in corso %s..." @@ -2287,11 +2311,11 @@ msgstr "Prec" msgid "Case-Sensitive" msgstr "Distingui maiuscole" -#: lib/shortcut.tcl:20 lib/shortcut.tcl:61 +#: lib/shortcut.tcl:21 lib/shortcut.tcl:62 msgid "Cannot write shortcut:" msgstr "Impossibile scrivere shortcut:" -#: lib/shortcut.tcl:136 +#: lib/shortcut.tcl:137 msgid "Cannot write icon:" msgstr "Impossibile scrivere icona:" @@ -2399,7 +2423,7 @@ msgstr "Vuoi davvero eseguire %s?" #: lib/tools.tcl:110 #, tcl-format msgid "Tool: %s" -msgstr "Strumento: %s" +msgstr "Accessorio: %s" #: lib/tools.tcl:111 #, tcl-format @@ -2418,7 +2442,7 @@ msgstr "Il programma esterno ha riportato un errore: %s" #: lib/tools_dlg.tcl:22 msgid "Add Tool" -msgstr "Aggiungi strumento" +msgstr "Aggiungi accessorio" #: lib/tools_dlg.tcl:28 msgid "Add New Tool Command" @@ -2430,7 +2454,7 @@ msgstr "Aggiungi per tutti gli archivi" #: lib/tools_dlg.tcl:45 msgid "Tool Details" -msgstr "Dettagli sullo strumento" +msgstr "Dettagli sull'accessorio" #: lib/tools_dlg.tcl:48 msgid "Use '/' separators to create a submenu tree:" @@ -2462,12 +2486,12 @@ msgstr "Avvia solo se è selezionata una differenza ($FILENAME non è vuoto)" #: lib/tools_dlg.tcl:121 msgid "Please supply a name for the tool." -msgstr "Bisogna dare un nome allo strumento." +msgstr "Bisogna dare un nome all'accessorio." #: lib/tools_dlg.tcl:129 #, tcl-format msgid "Tool '%s' already exists." -msgstr "Lo strumento '%s' esiste già." +msgstr "L'accessorio '%s' esiste già." #: lib/tools_dlg.tcl:151 #, tcl-format @@ -2475,17 +2499,17 @@ msgid "" "Could not add tool:\n" "%s" msgstr "" -"Impossibile aggiungere lo strumento:\n" +"Impossibile aggiungere l'accessorio:\n" "\n" "%s" #: lib/tools_dlg.tcl:190 msgid "Remove Tool" -msgstr "Rimuovi strumento" +msgstr "Rimuovi accessorio" #: lib/tools_dlg.tcl:196 msgid "Remove Tool Commands" -msgstr "Rimuovi i comandi dello strumento" +msgstr "Rimuovi i comandi accessori" #: lib/tools_dlg.tcl:200 msgid "Remove" @@ -2493,7 +2517,7 @@ msgstr "Rimuovi" #: lib/tools_dlg.tcl:236 msgid "(Blue denotes repository-local tools)" -msgstr "(Il colore blu indica strumenti per l'archivio locale)" +msgstr "(Il colore blu indica accessori per l'archivio locale)" #: lib/tools_dlg.tcl:297 #, tcl-format diff --git a/git-gui/po/ja.po b/git-gui/po/ja.po index 63c4695103..15017985e5 100644 --- a/git-gui/po/ja.po +++ b/git-gui/po/ja.po @@ -3,46 +3,45 @@ # This file is distributed under the same license as the git-gui package. # しらいし ななこ <nanako3@bluebottle.com>, 2007. # -#, fuzzy msgid "" msgstr "" "Project-Id-Version: git-gui\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-12-08 08:31-0800\n" -"PO-Revision-Date: 2008-12-09 06:27+0900\n" +"POT-Creation-Date: 2010-01-26 15:47-0800\n" +"PO-Revision-Date: 2010-02-02 19:03+0900\n" "Last-Translator: しらいし ななこ <nanako3@lavabit.com>\n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: git-gui.sh:41 git-gui.sh:737 git-gui.sh:751 git-gui.sh:764 git-gui.sh:847 -#: git-gui.sh:866 +#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903 +#: git-gui.sh:922 msgid "git-gui: fatal error" msgstr "git-gui: 致命的なエラー" -#: git-gui.sh:689 +#: git-gui.sh:743 #, tcl-format msgid "Invalid font specified in %s:" msgstr "%s に無効なフォントが指定されています:" -#: git-gui.sh:723 +#: git-gui.sh:779 msgid "Main Font" msgstr "主フォント" -#: git-gui.sh:724 +#: git-gui.sh:780 msgid "Diff/Console Font" msgstr "diff/コンソール・フォント" -#: git-gui.sh:738 +#: git-gui.sh:794 msgid "Cannot find git in PATH." msgstr "PATH 中に git が見つかりません" -#: git-gui.sh:765 +#: git-gui.sh:821 msgid "Cannot parse Git version string:" msgstr "Git バージョン名が理解できません:" -#: git-gui.sh:783 +#: git-gui.sh:839 #, tcl-format msgid "" "Git version cannot be determined.\n" @@ -61,446 +60,475 @@ msgstr "" "\n" "'%s' はバージョン 1.5.0 と思って良いですか?\n" -#: git-gui.sh:1062 +#: git-gui.sh:1128 msgid "Git directory not found:" msgstr "Git ディレクトリが見つかりません:" -#: git-gui.sh:1069 +#: git-gui.sh:1146 msgid "Cannot move to top of working directory:" msgstr "作業ディレクトリの最上位に移動できません" -#: git-gui.sh:1076 -msgid "Cannot use funny .git directory:" -msgstr "変な .git ディレクトリは使えません" +#: git-gui.sh:1154 +msgid "Cannot use bare repository:" +msgstr "裸のリポジトリは使えません:" -#: git-gui.sh:1081 +#: git-gui.sh:1162 msgid "No working directory" msgstr "作業ディレクトリがありません" -#: git-gui.sh:1247 lib/checkout_op.tcl:305 +#: git-gui.sh:1334 lib/checkout_op.tcl:306 msgid "Refreshing file status..." msgstr "ファイル状態を更新しています…" -#: git-gui.sh:1303 +#: git-gui.sh:1390 msgid "Scanning for modified files ..." msgstr "変更されたファイルをスキャンしています…" -#: git-gui.sh:1367 +#: git-gui.sh:1454 msgid "Calling prepare-commit-msg hook..." msgstr "prepare-commit-msg フックを実行中・・・" -#: git-gui.sh:1384 +#: git-gui.sh:1471 msgid "Commit declined by prepare-commit-msg hook." msgstr "prepare-commit-msg フックがコミットを拒否しました" -#: git-gui.sh:1542 lib/browser.tcl:246 +#: git-gui.sh:1629 lib/browser.tcl:246 msgid "Ready." msgstr "準備完了" -#: git-gui.sh:1819 +#: git-gui.sh:1787 +#, tcl-format +msgid "Displaying only %s of %s files." +msgstr "全体で%s個の内の%sファイルだけ表示しています" + +#: git-gui.sh:1913 msgid "Unmodified" msgstr "変更無し" -#: git-gui.sh:1821 +#: git-gui.sh:1915 msgid "Modified, not staged" msgstr "変更あり、コミット未予定" -#: git-gui.sh:1822 git-gui.sh:1830 +#: git-gui.sh:1916 git-gui.sh:1924 msgid "Staged for commit" msgstr "コミット予定済" -#: git-gui.sh:1823 git-gui.sh:1831 +#: git-gui.sh:1917 git-gui.sh:1925 msgid "Portions staged for commit" msgstr "部分的にコミット予定済" -#: git-gui.sh:1824 git-gui.sh:1832 +#: git-gui.sh:1918 git-gui.sh:1926 msgid "Staged for commit, missing" msgstr "コミット予定済、ファイル無し" -#: git-gui.sh:1826 +#: git-gui.sh:1920 msgid "File type changed, not staged" msgstr "ファイル型変更、コミット未予定" -#: git-gui.sh:1827 +#: git-gui.sh:1921 msgid "File type changed, staged" msgstr "ファイル型変更、コミット予定済" -#: git-gui.sh:1829 +#: git-gui.sh:1923 msgid "Untracked, not staged" msgstr "管理外、コミット未予定" -#: git-gui.sh:1834 +#: git-gui.sh:1928 msgid "Missing" msgstr "ファイル無し" -#: git-gui.sh:1835 +#: git-gui.sh:1929 msgid "Staged for removal" msgstr "削除予定済" -#: git-gui.sh:1836 +#: git-gui.sh:1930 msgid "Staged for removal, still present" msgstr "削除予定済、ファイル未削除" -#: git-gui.sh:1838 git-gui.sh:1839 git-gui.sh:1840 git-gui.sh:1841 -#: git-gui.sh:1842 git-gui.sh:1843 +#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935 +#: git-gui.sh:1936 git-gui.sh:1937 msgid "Requires merge resolution" msgstr "要マージ解決" -#: git-gui.sh:1878 +#: git-gui.sh:1972 msgid "Starting gitk... please wait..." msgstr "gitk を起動中…お待ち下さい…" -#: git-gui.sh:1887 +#: git-gui.sh:1984 msgid "Couldn't find gitk in PATH" msgstr "PATH 中に gitk が見つかりません" -#: git-gui.sh:2280 lib/choose_repository.tcl:36 +#: git-gui.sh:2043 +msgid "Couldn't find git gui in PATH" +msgstr "PATH 中に git gui が見つかりません" + +#: git-gui.sh:2455 lib/choose_repository.tcl:36 msgid "Repository" msgstr "リポジトリ" -#: git-gui.sh:2281 +#: git-gui.sh:2456 msgid "Edit" msgstr "編集" -#: git-gui.sh:2283 lib/choose_rev.tcl:561 +#: git-gui.sh:2458 lib/choose_rev.tcl:561 msgid "Branch" msgstr "ブランチ" -#: git-gui.sh:2286 lib/choose_rev.tcl:548 +#: git-gui.sh:2461 lib/choose_rev.tcl:548 msgid "Commit@@noun" msgstr "コミット" -#: git-gui.sh:2289 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 msgid "Merge" msgstr "マージ" -#: git-gui.sh:2290 lib/choose_rev.tcl:557 +#: git-gui.sh:2465 lib/choose_rev.tcl:557 msgid "Remote" msgstr "リモート" -#: git-gui.sh:2293 +#: git-gui.sh:2468 msgid "Tools" msgstr "ツール" -#: git-gui.sh:2302 +#: git-gui.sh:2477 msgid "Explore Working Copy" msgstr "ワーキングコピーをブラウズ" -#: git-gui.sh:2307 +#: git-gui.sh:2483 msgid "Browse Current Branch's Files" msgstr "現在のブランチのファイルを見る" -#: git-gui.sh:2311 +#: git-gui.sh:2487 msgid "Browse Branch Files..." msgstr "ブランチのファイルを見る…" -#: git-gui.sh:2316 +#: git-gui.sh:2492 msgid "Visualize Current Branch's History" msgstr "現在のブランチの履歴を見る" -#: git-gui.sh:2320 +#: git-gui.sh:2496 msgid "Visualize All Branch History" msgstr "全てのブランチの履歴を見る" -#: git-gui.sh:2327 +#: git-gui.sh:2503 #, tcl-format msgid "Browse %s's Files" msgstr "ブランチ %s のファイルを見る" -#: git-gui.sh:2329 +#: git-gui.sh:2505 #, tcl-format msgid "Visualize %s's History" msgstr "ブランチ %s の履歴を見る" -#: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67 +#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67 msgid "Database Statistics" msgstr "データベース統計" -#: git-gui.sh:2337 lib/database.tcl:34 +#: git-gui.sh:2513 lib/database.tcl:34 msgid "Compress Database" msgstr "データベース圧縮" -#: git-gui.sh:2340 +#: git-gui.sh:2516 msgid "Verify Database" msgstr "データベース検証" -#: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7 -#: lib/shortcut.tcl:39 lib/shortcut.tcl:71 +#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8 +#: lib/shortcut.tcl:40 lib/shortcut.tcl:72 msgid "Create Desktop Icon" msgstr "デスクトップ・アイコンを作る" -#: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 +#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 msgid "Quit" msgstr "終了" -#: git-gui.sh:2371 +#: git-gui.sh:2547 msgid "Undo" msgstr "元に戻す" -#: git-gui.sh:2374 +#: git-gui.sh:2550 msgid "Redo" msgstr "やり直し" -#: git-gui.sh:2378 git-gui.sh:2923 +#: git-gui.sh:2554 git-gui.sh:3109 msgid "Cut" msgstr "切り取り" -#: git-gui.sh:2381 git-gui.sh:2926 git-gui.sh:3000 git-gui.sh:3082 +#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259 #: lib/console.tcl:69 msgid "Copy" msgstr "コピー" -#: git-gui.sh:2384 git-gui.sh:2929 +#: git-gui.sh:2560 git-gui.sh:3115 msgid "Paste" msgstr "貼り付け" -#: git-gui.sh:2387 git-gui.sh:2932 lib/branch_delete.tcl:26 +#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26 #: lib/remote_branch_delete.tcl:38 msgid "Delete" msgstr "削除" -#: git-gui.sh:2391 git-gui.sh:2936 git-gui.sh:3086 lib/console.tcl:71 +#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71 msgid "Select All" msgstr "全て選択" -#: git-gui.sh:2400 +#: git-gui.sh:2576 msgid "Create..." msgstr "作成…" -#: git-gui.sh:2406 +#: git-gui.sh:2582 msgid "Checkout..." msgstr "チェックアウト" -#: git-gui.sh:2412 +#: git-gui.sh:2588 msgid "Rename..." msgstr "名前変更…" -#: git-gui.sh:2417 +#: git-gui.sh:2593 msgid "Delete..." msgstr "削除…" -#: git-gui.sh:2422 +#: git-gui.sh:2598 msgid "Reset..." msgstr "リセット…" -#: git-gui.sh:2432 +#: git-gui.sh:2608 msgid "Done" msgstr "完了" -#: git-gui.sh:2434 +#: git-gui.sh:2610 msgid "Commit@@verb" msgstr "コミット" -#: git-gui.sh:2443 git-gui.sh:2864 +#: git-gui.sh:2619 git-gui.sh:3050 msgid "New Commit" msgstr "新規コミット" -#: git-gui.sh:2451 git-gui.sh:2871 +#: git-gui.sh:2627 git-gui.sh:3057 msgid "Amend Last Commit" msgstr "最新コミットを訂正" -#: git-gui.sh:2461 git-gui.sh:2825 lib/remote_branch_delete.tcl:99 +#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99 msgid "Rescan" msgstr "再スキャン" -#: git-gui.sh:2467 +#: git-gui.sh:2643 msgid "Stage To Commit" msgstr "コミット予定する" -#: git-gui.sh:2473 +#: git-gui.sh:2649 msgid "Stage Changed Files To Commit" msgstr "変更されたファイルをコミット予定" -#: git-gui.sh:2479 +#: git-gui.sh:2655 msgid "Unstage From Commit" msgstr "コミットから降ろす" -#: git-gui.sh:2484 lib/index.tcl:410 +#: git-gui.sh:2661 lib/index.tcl:412 msgid "Revert Changes" msgstr "変更を元に戻す" -#: git-gui.sh:2491 git-gui.sh:3069 +#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341 msgid "Show Less Context" msgstr "文脈を少なく" -#: git-gui.sh:2495 git-gui.sh:3073 +#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345 msgid "Show More Context" msgstr "文脈を多く" -#: git-gui.sh:2502 git-gui.sh:2838 git-gui.sh:2947 +#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133 msgid "Sign Off" msgstr "署名" -#: git-gui.sh:2518 +#: git-gui.sh:2696 msgid "Local Merge..." msgstr "ローカル・マージ…" -#: git-gui.sh:2523 +#: git-gui.sh:2701 msgid "Abort Merge..." msgstr "マージ中止…" -#: git-gui.sh:2535 git-gui.sh:2575 +#: git-gui.sh:2713 git-gui.sh:2741 msgid "Add..." msgstr "追加" -#: git-gui.sh:2539 +#: git-gui.sh:2717 msgid "Push..." msgstr "プッシュ…" -#: git-gui.sh:2543 +#: git-gui.sh:2721 msgid "Delete Branch..." msgstr "ブランチ削除..." -#: git-gui.sh:2553 git-gui.sh:2589 lib/about.tcl:14 -#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 -#, tcl-format -msgid "About %s" -msgstr "%s について" - -#: git-gui.sh:2557 -msgid "Preferences..." -msgstr "設定…" - -#: git-gui.sh:2565 git-gui.sh:3115 +#: git-gui.sh:2731 git-gui.sh:3292 msgid "Options..." msgstr "オプション…" -#: git-gui.sh:2576 +#: git-gui.sh:2742 msgid "Remove..." msgstr "削除..." -#: git-gui.sh:2585 lib/choose_repository.tcl:50 +#: git-gui.sh:2751 lib/choose_repository.tcl:50 msgid "Help" msgstr "ヘルプ" -#: git-gui.sh:2611 +#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 +#, tcl-format +msgid "About %s" +msgstr "%s について" + +#: git-gui.sh:2783 msgid "Online Documentation" msgstr "オンライン・ドキュメント" -#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 +#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 msgid "Show SSH Key" msgstr "SSH キーを表示" -#: git-gui.sh:2707 +#: git-gui.sh:2893 #, tcl-format msgid "fatal: cannot stat path %s: No such file or directory" msgstr "" "致命的: パス %s が stat できません。そのようなファイルやディレクトリはありま" "せん" -#: git-gui.sh:2740 +#: git-gui.sh:2926 msgid "Current Branch:" msgstr "現在のブランチ" -#: git-gui.sh:2761 +#: git-gui.sh:2947 msgid "Staged Changes (Will Commit)" msgstr "ステージングされた(コミット予定済の)変更" -#: git-gui.sh:2781 +#: git-gui.sh:2967 msgid "Unstaged Changes" msgstr "コミット予定に入っていない変更" -#: git-gui.sh:2831 +#: git-gui.sh:3017 msgid "Stage Changed" msgstr "変更をコミット予定に入れる" -#: git-gui.sh:2850 lib/transport.tcl:93 lib/transport.tcl:182 +#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193 msgid "Push" msgstr "プッシュ" -#: git-gui.sh:2885 +#: git-gui.sh:3071 msgid "Initial Commit Message:" msgstr "最初のコミットメッセージ:" -#: git-gui.sh:2886 +#: git-gui.sh:3072 msgid "Amended Commit Message:" msgstr "訂正したコミットメッセージ:" -#: git-gui.sh:2887 +#: git-gui.sh:3073 msgid "Amended Initial Commit Message:" msgstr "訂正した最初のコミットメッセージ:" -#: git-gui.sh:2888 +#: git-gui.sh:3074 msgid "Amended Merge Commit Message:" msgstr "訂正したマージコミットメッセージ:" -#: git-gui.sh:2889 +#: git-gui.sh:3075 msgid "Merge Commit Message:" msgstr "マージコミットメッセージ:" -#: git-gui.sh:2890 +#: git-gui.sh:3076 msgid "Commit Message:" msgstr "コミットメッセージ:" -#: git-gui.sh:2939 git-gui.sh:3090 lib/console.tcl:73 +#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73 msgid "Copy All" msgstr "全てコピー" -#: git-gui.sh:2963 lib/blame.tcl:104 +#: git-gui.sh:3149 lib/blame.tcl:104 msgid "File:" msgstr "ファイル:" -#: git-gui.sh:3078 +#: git-gui.sh:3255 msgid "Refresh" msgstr "再読み込み" -#: git-gui.sh:3099 +#: git-gui.sh:3276 msgid "Decrease Font Size" msgstr "フォントを小さく" -#: git-gui.sh:3103 +#: git-gui.sh:3280 msgid "Increase Font Size" msgstr "フォントを大きく" -#: git-gui.sh:3111 lib/blame.tcl:281 +#: git-gui.sh:3288 lib/blame.tcl:281 msgid "Encoding" msgstr "エンコーディング" -#: git-gui.sh:3122 +#: git-gui.sh:3299 msgid "Apply/Reverse Hunk" msgstr "パッチを適用/取り消す" -#: git-gui.sh:3127 +#: git-gui.sh:3304 msgid "Apply/Reverse Line" msgstr "パッチ行を適用/取り消す" -#: git-gui.sh:3137 +#: git-gui.sh:3323 msgid "Run Merge Tool" msgstr "マージツールを起動" -#: git-gui.sh:3142 +#: git-gui.sh:3328 msgid "Use Remote Version" msgstr "リモートの方を採用" -#: git-gui.sh:3146 +#: git-gui.sh:3332 msgid "Use Local Version" msgstr "ローカルの方を採用" -#: git-gui.sh:3150 +#: git-gui.sh:3336 msgid "Revert To Base" msgstr "ベース版を採用" -#: git-gui.sh:3169 +#: git-gui.sh:3354 +msgid "Visualize These Changes In The Submodule" +msgstr "サブモジュール内のこれらの変更を見る" + +#: git-gui.sh:3358 +msgid "Visualize Current Branch History In The Submodule" +msgstr "サブモジュール内で現在のブランチの履歴を見る" + +#: git-gui.sh:3362 +msgid "Visualize All Branch History In The Submodule" +msgstr "サブモジュール内で全てのブランチの履歴を見る" + +#: git-gui.sh:3367 +msgid "Start git gui In The Submodule" +msgstr "サブモジュール内でgit guiを起動する" + +#: git-gui.sh:3389 msgid "Unstage Hunk From Commit" msgstr "パッチをコミット予定から外す" -#: git-gui.sh:3170 +#: git-gui.sh:3391 +msgid "Unstage Lines From Commit" +msgstr "コミット予定から行を外す" + +#: git-gui.sh:3393 msgid "Unstage Line From Commit" msgstr "コミット予定から行を外す" -#: git-gui.sh:3172 +#: git-gui.sh:3396 msgid "Stage Hunk For Commit" msgstr "パッチをコミット予定に加える" -#: git-gui.sh:3173 +#: git-gui.sh:3398 +msgid "Stage Lines For Commit" +msgstr "パッチ行をコミット予定に加える" + +#: git-gui.sh:3400 msgid "Stage Line For Commit" msgstr "パッチ行をコミット予定に加える" -#: git-gui.sh:3196 +#: git-gui.sh:3424 msgid "Initializing..." msgstr "初期化しています…" -#: git-gui.sh:3301 +#: git-gui.sh:3541 #, tcl-format msgid "" "Possible environment issues exist.\n" @@ -515,7 +543,7 @@ msgstr "" "以下の環境変数は %s が起動する Git サブプロセスによって無視されるでしょう:\n" "\n" -#: git-gui.sh:3331 +#: git-gui.sh:3570 msgid "" "\n" "This is due to a known issue with the\n" @@ -525,7 +553,7 @@ msgstr "" "これは Cygwin で配布されている Tcl バイナリに\n" "関しての既知の問題によります" -#: git-gui.sh:3336 +#: git-gui.sh:3575 #, tcl-format msgid "" "\n" @@ -609,43 +637,43 @@ msgstr "コピー検知を実行中…" msgid "Loading annotation..." msgstr "注釈を読み込んでいます…" -#: lib/blame.tcl:964 +#: lib/blame.tcl:963 msgid "Author:" msgstr "作者:" -#: lib/blame.tcl:968 +#: lib/blame.tcl:967 msgid "Committer:" msgstr "コミット者:" -#: lib/blame.tcl:973 +#: lib/blame.tcl:972 msgid "Original File:" msgstr "元ファイル" -#: lib/blame.tcl:1021 +#: lib/blame.tcl:1020 msgid "Cannot find HEAD commit:" msgstr "HEAD コミットが見つかりません" -#: lib/blame.tcl:1076 +#: lib/blame.tcl:1075 msgid "Cannot find parent commit:" msgstr "親コミットが見つかりません:" -#: lib/blame.tcl:1091 +#: lib/blame.tcl:1090 msgid "Unable to display parent" msgstr "親を表示できません" -#: lib/blame.tcl:1092 lib/diff.tcl:297 +#: lib/blame.tcl:1091 lib/diff.tcl:320 msgid "Error loading diff:" msgstr "diff を読む際のエラーです:" -#: lib/blame.tcl:1232 +#: lib/blame.tcl:1231 msgid "Originally By:" msgstr "原作者:" -#: lib/blame.tcl:1238 +#: lib/blame.tcl:1237 msgid "In File:" msgstr "ファイル:" -#: lib/blame.tcl:1243 +#: lib/blame.tcl:1242 msgid "Copied Or Moved Here By:" msgstr "複写・移動者:" @@ -659,10 +687,10 @@ 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:544 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172 #: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42 #: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352 -#: lib/transport.tcl:97 +#: lib/transport.tcl:108 msgid "Cancel" msgstr "中止" @@ -690,7 +718,7 @@ msgstr "ブランチを作成" msgid "Create New Branch" msgstr "ブランチを新規作成" -#: lib/branch_create.tcl:31 lib/choose_repository.tcl:377 +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381 msgid "Create" msgstr "作成" @@ -722,7 +750,7 @@ msgstr "いいえ" msgid "Fast Forward Only" msgstr "早送りのみ" -#: lib/branch_create.tcl:85 lib/checkout_op.tcl:536 +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571 msgid "Reset" msgstr "リセット" @@ -764,15 +792,25 @@ msgstr "ローカル・ブランチ" msgid "Delete Only If Merged Into" msgstr "マージ済みの時のみ削除" -#: lib/branch_delete.tcl:54 -msgid "Always (Do not perform merge test.)" -msgstr "無条件(マージテストしない)" +#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" +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 lib/remote_branch_delete.tcl:217 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"削除したブランチを回復するのは困難です。\n" +"\n" +"選択したブランチを削除して良いですか?" + #: lib/branch_delete.tcl:141 #, tcl-format msgid "" @@ -802,7 +840,7 @@ msgstr "新しい名前:" msgid "Please select a branch to rename." msgstr "名前を変更するブランチを選んで下さい。" -#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:201 +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202 #, tcl-format msgid "Branch '%s' already exists." msgstr "'%s'というブランチは既に存在します。" @@ -833,38 +871,38 @@ msgstr "[上位フォルダへ]" msgid "Browse Branch Files" msgstr "現在のブランチのファイルを見る" -#: lib/browser.tcl:278 lib/choose_repository.tcl:394 -#: lib/choose_repository.tcl:480 lib/choose_repository.tcl:491 -#: lib/choose_repository.tcl:995 +#: lib/browser.tcl:278 lib/choose_repository.tcl:398 +#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497 +#: lib/choose_repository.tcl:1028 msgid "Browse" msgstr "ブラウズ" -#: lib/checkout_op.tcl:84 +#: lib/checkout_op.tcl:85 #, tcl-format msgid "Fetching %s from %s" msgstr "%s から %s をフェッチしています" -#: lib/checkout_op.tcl:132 +#: lib/checkout_op.tcl:133 #, tcl-format msgid "fatal: Cannot resolve %s" msgstr "致命的エラー: %s を解決できません" -#: lib/checkout_op.tcl:145 lib/console.tcl:81 lib/database.tcl:31 +#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31 #: lib/sshkey.tcl:53 msgid "Close" msgstr "閉じる" -#: lib/checkout_op.tcl:174 +#: lib/checkout_op.tcl:175 #, tcl-format msgid "Branch '%s' does not exist." msgstr "ブランチ'%s'は存在しません。" -#: lib/checkout_op.tcl:193 +#: lib/checkout_op.tcl:194 #, tcl-format msgid "Failed to configure simplified git-pull for '%s'." msgstr "'%s' に簡易 git-pull を設定できませんでした" -#: lib/checkout_op.tcl:228 +#: lib/checkout_op.tcl:229 #, tcl-format msgid "" "Branch '%s' already exists.\n" @@ -877,21 +915,21 @@ msgstr "" "%s に早送りできません。\n" "マージが必要です。" -#: lib/checkout_op.tcl:242 +#: lib/checkout_op.tcl:243 #, tcl-format msgid "Merge strategy '%s' not supported." msgstr "'%s' マージ戦略はサポートされていません。" -#: lib/checkout_op.tcl:261 +#: lib/checkout_op.tcl:262 #, tcl-format msgid "Failed to update '%s'." msgstr "'%s' の更新に失敗しました。" -#: lib/checkout_op.tcl:273 +#: lib/checkout_op.tcl:274 msgid "Staging area (index) is already locked." msgstr "インデックスは既にロックされています。" -#: lib/checkout_op.tcl:288 +#: lib/checkout_op.tcl:289 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -907,30 +945,30 @@ msgstr "" "\n" "自動的に再スキャンを開始します。\n" -#: lib/checkout_op.tcl:344 +#: lib/checkout_op.tcl:345 #, tcl-format msgid "Updating working directory to '%s'..." msgstr "作業ディレクトリを '%s' に更新しています…" -#: lib/checkout_op.tcl:345 +#: lib/checkout_op.tcl:346 msgid "files checked out" msgstr "チェックアウトされたファイル" -#: lib/checkout_op.tcl:375 +#: lib/checkout_op.tcl:376 #, tcl-format msgid "Aborted checkout of '%s' (file level merging is required)." msgstr "'%s' のチェックアウトを中止しました(ファイル毎のマージが必要です)。" -#: lib/checkout_op.tcl:376 +#: lib/checkout_op.tcl:377 msgid "File level merge required." msgstr "ファイル毎のマージが必要です。" -#: lib/checkout_op.tcl:380 +#: lib/checkout_op.tcl:381 #, tcl-format msgid "Staying on branch '%s'." msgstr "ブランチ '%s' に滞まります。" -#: lib/checkout_op.tcl:451 +#: lib/checkout_op.tcl:452 msgid "" "You are no longer on a local branch.\n" "\n" @@ -942,30 +980,30 @@ msgstr "" "ブランチ上に滞まりたいときは、この「分離されたチェックアウト」から新規ブラン" "チを開始してください。" -#: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472 +#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507 #, tcl-format msgid "Checked out '%s'." msgstr "'%s' をチェックアウトしました" -#: lib/checkout_op.tcl:500 +#: lib/checkout_op.tcl:535 #, tcl-format msgid "Resetting '%s' to '%s' will lose the following commits:" msgstr "'%s' を '%s' にリセットすると、以下のコミットが失なわれます:" -#: lib/checkout_op.tcl:522 +#: lib/checkout_op.tcl:557 msgid "Recovering lost commits may not be easy." msgstr "失なわれたコミットを回復するのは簡単ではありません。" -#: lib/checkout_op.tcl:527 +#: lib/checkout_op.tcl:562 #, tcl-format msgid "Reset '%s'?" msgstr "'%s' をリセットしますか?" -#: lib/checkout_op.tcl:532 lib/merge.tcl:164 lib/tools_dlg.tcl:343 +#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343 msgid "Visualize" msgstr "可視化" -#: lib/checkout_op.tcl:600 +#: lib/checkout_op.tcl:635 #, tcl-format msgid "" "Failed to set current branch.\n" @@ -1009,7 +1047,7 @@ msgstr "" msgid "Git Gui" msgstr "Git GUI" -#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:382 +#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386 msgid "Create New Repository" msgstr "新しいリポジトリを作る" @@ -1017,7 +1055,7 @@ msgstr "新しいリポジトリを作る" msgid "New..." msgstr "新規…" -#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465 +#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471 msgid "Clone Existing Repository" msgstr "既存リポジトリを複製する" @@ -1025,7 +1063,7 @@ msgstr "既存リポジトリを複製する" msgid "Clone..." msgstr "複製…" -#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983 +#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016 msgid "Open Existing Repository" msgstr "既存リポジトリを開く" @@ -1041,193 +1079,193 @@ msgstr "最近使ったリポジトリ" msgid "Open Recent Repository:" msgstr "最近使ったリポジトリを開く" -#: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309 -#: lib/choose_repository.tcl:316 +#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313 +#: lib/choose_repository.tcl:320 #, tcl-format msgid "Failed to create repository %s:" msgstr "リポジトリ %s を作製できません:" -#: lib/choose_repository.tcl:387 +#: lib/choose_repository.tcl:391 msgid "Directory:" msgstr "ディレクトリ:" -#: lib/choose_repository.tcl:417 lib/choose_repository.tcl:544 -#: lib/choose_repository.tcl:1017 +#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550 +#: lib/choose_repository.tcl:1052 msgid "Git Repository" msgstr "GIT リポジトリ" -#: lib/choose_repository.tcl:442 +#: lib/choose_repository.tcl:448 #, tcl-format msgid "Directory %s already exists." msgstr "ディレクトリ '%s' は既に存在します。" -#: lib/choose_repository.tcl:446 +#: lib/choose_repository.tcl:452 #, tcl-format msgid "File %s already exists." msgstr "ファイル '%s' は既に存在します。" -#: lib/choose_repository.tcl:460 +#: lib/choose_repository.tcl:466 msgid "Clone" msgstr "複製" -#: lib/choose_repository.tcl:473 +#: lib/choose_repository.tcl:479 msgid "Source Location:" msgstr "ソースの位置" -#: lib/choose_repository.tcl:484 +#: lib/choose_repository.tcl:490 msgid "Target Directory:" msgstr "先ディレクトリ:" -#: lib/choose_repository.tcl:496 +#: lib/choose_repository.tcl:502 msgid "Clone Type:" msgstr "複製方式:" -#: lib/choose_repository.tcl:502 +#: lib/choose_repository.tcl:508 msgid "Standard (Fast, Semi-Redundant, Hardlinks)" msgstr "標準(高速・中冗長度・ハードリンク)" -#: lib/choose_repository.tcl:508 +#: lib/choose_repository.tcl:514 msgid "Full Copy (Slower, Redundant Backup)" msgstr "全複写(低速・冗長バックアップ)" -#: lib/choose_repository.tcl:514 +#: lib/choose_repository.tcl:520 msgid "Shared (Fastest, Not Recommended, No Backup)" msgstr "共有(最高速・非推奨・バックアップ無し)" -#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597 -#: lib/choose_repository.tcl:743 lib/choose_repository.tcl:813 -#: lib/choose_repository.tcl:1023 lib/choose_repository.tcl:1031 +#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603 +#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819 +#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066 #, tcl-format msgid "Not a Git repository: %s" msgstr "Git リポジトリではありません: %s" -#: lib/choose_repository.tcl:586 +#: lib/choose_repository.tcl:592 msgid "Standard only available for local repository." msgstr "標準方式は同一計算機上のリポジトリにのみ使えます。" -#: lib/choose_repository.tcl:590 +#: lib/choose_repository.tcl:596 msgid "Shared only available for local repository." msgstr "共有方式は同一計算機上のリポジトリにのみ使えます。" -#: lib/choose_repository.tcl:611 +#: lib/choose_repository.tcl:617 #, tcl-format msgid "Location %s already exists." msgstr "'%s' は既に存在します。" -#: lib/choose_repository.tcl:622 +#: lib/choose_repository.tcl:628 msgid "Failed to configure origin" msgstr "origin を設定できませんでした" -#: lib/choose_repository.tcl:634 +#: lib/choose_repository.tcl:640 msgid "Counting objects" msgstr "オブジェクトを数えています" -#: lib/choose_repository.tcl:635 +#: lib/choose_repository.tcl:641 msgid "buckets" msgstr "バケツ" -#: lib/choose_repository.tcl:659 +#: lib/choose_repository.tcl:665 #, tcl-format msgid "Unable to copy objects/info/alternates: %s" msgstr "objects/info/alternates を複写できません: %s" -#: lib/choose_repository.tcl:695 +#: lib/choose_repository.tcl:701 #, tcl-format msgid "Nothing to clone from %s." msgstr "%s から複製する内容はありません" -#: lib/choose_repository.tcl:697 lib/choose_repository.tcl:911 -#: lib/choose_repository.tcl:923 +#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917 +#: lib/choose_repository.tcl:929 msgid "The 'master' branch has not been initialized." msgstr "'master' ブランチが初期化されていません" -#: lib/choose_repository.tcl:710 +#: lib/choose_repository.tcl:716 msgid "Hardlinks are unavailable. Falling back to copying." msgstr "ハードリンクが作れないので、コピーします" -#: lib/choose_repository.tcl:722 +#: lib/choose_repository.tcl:728 #, tcl-format msgid "Cloning from %s" msgstr "%s から複製しています" -#: lib/choose_repository.tcl:753 +#: lib/choose_repository.tcl:759 msgid "Copying objects" msgstr "オブジェクトを複写しています" -#: lib/choose_repository.tcl:754 +#: lib/choose_repository.tcl:760 msgid "KiB" msgstr "KiB" -#: lib/choose_repository.tcl:778 +#: lib/choose_repository.tcl:784 #, tcl-format msgid "Unable to copy object: %s" msgstr "オブジェクトを複写できません: %s" -#: lib/choose_repository.tcl:788 +#: lib/choose_repository.tcl:794 msgid "Linking objects" msgstr "オブジェクトを連結しています" -#: lib/choose_repository.tcl:789 +#: lib/choose_repository.tcl:795 msgid "objects" msgstr "オブジェクト" -#: lib/choose_repository.tcl:797 +#: lib/choose_repository.tcl:803 #, tcl-format msgid "Unable to hardlink object: %s" msgstr "オブジェクトをハードリンクできません: %s" -#: lib/choose_repository.tcl:852 +#: lib/choose_repository.tcl:858 msgid "Cannot fetch branches and objects. See console output for details." msgstr "ブランチやオブジェクトを取得できません。コンソール出力を見て下さい" -#: lib/choose_repository.tcl:863 +#: lib/choose_repository.tcl:869 msgid "Cannot fetch tags. See console output for details." msgstr "タグを取得できません。コンソール出力を見て下さい" -#: lib/choose_repository.tcl:887 +#: lib/choose_repository.tcl:893 msgid "Cannot determine HEAD. See console output for details." msgstr "HEAD を確定できません。コンソール出力を見て下さい" -#: lib/choose_repository.tcl:896 +#: lib/choose_repository.tcl:902 #, tcl-format msgid "Unable to cleanup %s" msgstr "%s を掃除できません" -#: lib/choose_repository.tcl:902 +#: lib/choose_repository.tcl:908 msgid "Clone failed." msgstr "複写に失敗しました。" -#: lib/choose_repository.tcl:909 +#: lib/choose_repository.tcl:915 msgid "No default branch obtained." msgstr "デフォールト・ブランチが取得されませんでした" -#: lib/choose_repository.tcl:920 +#: lib/choose_repository.tcl:926 #, tcl-format msgid "Cannot resolve %s as a commit." msgstr "%s をコミットとして解釈できません" -#: lib/choose_repository.tcl:932 +#: lib/choose_repository.tcl:938 msgid "Creating working directory" msgstr "作業ディレクトリを作成しています" -#: lib/choose_repository.tcl:933 lib/index.tcl:65 lib/index.tcl:128 -#: lib/index.tcl:196 +#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130 +#: lib/index.tcl:198 msgid "files" msgstr "ファイル" -#: lib/choose_repository.tcl:962 +#: lib/choose_repository.tcl:968 msgid "Initial file checkout failed." msgstr "初期チェックアウトに失敗しました" -#: lib/choose_repository.tcl:978 +#: lib/choose_repository.tcl:1011 msgid "Open" msgstr "開く" -#: lib/choose_repository.tcl:988 +#: lib/choose_repository.tcl:1021 msgid "Repository:" msgstr "リポジトリ:" -#: lib/choose_repository.tcl:1037 +#: lib/choose_repository.tcl:1072 #, tcl-format msgid "Failed to open repository %s:" msgstr "リポジトリ %s を開けません:" @@ -1298,19 +1336,24 @@ msgstr "" "現在はまだマージの途中です。先にこのマージを中止しないと、前のコミットの訂正" "はできません\n" -#: lib/commit.tcl:49 +#: lib/commit.tcl:48 msgid "Error loading commit data for amend:" msgstr "訂正するコミットのデータを読めません:" -#: lib/commit.tcl:76 +#: lib/commit.tcl:75 msgid "Unable to obtain your identity:" msgstr "ユーザの正体を確認できません:" -#: lib/commit.tcl:81 +#: lib/commit.tcl:80 msgid "Invalid GIT_COMMITTER_IDENT:" msgstr "GIT_COMMITTER_IDENT が無効です:" -#: lib/commit.tcl:133 +#: lib/commit.tcl:129 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "警告: Tcl はエンコーディング '%s' をサポートしていません" + +#: lib/commit.tcl:149 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -1326,7 +1369,7 @@ msgstr "" "\n" "自動的に再スキャンを開始します。\n" -#: lib/commit.tcl:156 +#: lib/commit.tcl:172 #, tcl-format msgid "" "Unmerged files cannot be committed.\n" @@ -1339,7 +1382,7 @@ msgstr "" "ファイル %s にはマージ衝突が残っています。まず解決してコミット予定に加える必" "要があります。\n" -#: lib/commit.tcl:164 +#: lib/commit.tcl:180 #, tcl-format msgid "" "Unknown file state %s detected.\n" @@ -1350,7 +1393,7 @@ msgstr "" "\n" "ファイル %s は本プログラムではコミットできません。\n" -#: lib/commit.tcl:172 +#: lib/commit.tcl:188 msgid "" "No changes to commit.\n" "\n" @@ -1360,7 +1403,7 @@ msgstr "" "\n" "最低一つの変更をコミット予定に加えてからコミットして下さい。\n" -#: lib/commit.tcl:187 +#: lib/commit.tcl:203 msgid "" "Please supply a commit message.\n" "\n" @@ -1378,45 +1421,40 @@ msgstr "" "- 第2行: 空白\n" "- 残りの行: なぜ、この変更が良い変更か、の説明。\n" -#: lib/commit.tcl:211 -#, tcl-format -msgid "warning: Tcl does not support encoding '%s'." -msgstr "警告: Tcl はエンコーディング '%s' をサポートしていません" - -#: lib/commit.tcl:227 +#: lib/commit.tcl:234 msgid "Calling pre-commit hook..." msgstr "コミット前フックを実行中・・・" -#: lib/commit.tcl:242 +#: lib/commit.tcl:249 msgid "Commit declined by pre-commit hook." msgstr "コミット前フックがコミットを拒否しました" -#: lib/commit.tcl:265 +#: lib/commit.tcl:272 msgid "Calling commit-msg hook..." msgstr "コミット・メッセージ・フックを実行中・・・" -#: lib/commit.tcl:280 +#: lib/commit.tcl:287 msgid "Commit declined by commit-msg hook." msgstr "コミット・メッセージ・フックがコミットを拒否しました" -#: lib/commit.tcl:293 +#: lib/commit.tcl:300 msgid "Committing changes..." msgstr "変更点をコミット中・・・" -#: lib/commit.tcl:309 +#: lib/commit.tcl:316 msgid "write-tree failed:" msgstr "write-tree が失敗しました:" -#: lib/commit.tcl:310 lib/commit.tcl:354 lib/commit.tcl:374 +#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382 msgid "Commit failed." msgstr "コミットに失敗しました。" -#: lib/commit.tcl:327 +#: lib/commit.tcl:334 #, tcl-format msgid "Commit %s appears to be corrupt" msgstr "コミット %s は壊れています" -#: lib/commit.tcl:332 +#: lib/commit.tcl:339 msgid "" "No changes to commit.\n" "\n" @@ -1430,19 +1468,19 @@ msgstr "" "\n" "自動的に再スキャンを開始します。\n" -#: lib/commit.tcl:339 +#: lib/commit.tcl:346 msgid "No changes to commit." msgstr "コミットする変更がありません。" -#: lib/commit.tcl:353 +#: lib/commit.tcl:360 msgid "commit-tree failed:" msgstr "commit-tree が失敗しました:" -#: lib/commit.tcl:373 +#: lib/commit.tcl:381 msgid "update-ref failed:" msgstr "update-ref が失敗しました:" -#: lib/commit.tcl:461 +#: lib/commit.tcl:469 #, tcl-format msgid "Created commit %s: %s" msgstr "コミット %s を作成しました: %s" @@ -1495,20 +1533,19 @@ msgstr "データベース圧縮" msgid "Verifying the object database with fsck-objects" msgstr "fsck-objects でオブジェクト・データベースを検証しています" -#: lib/database.tcl:108 +#: lib/database.tcl:107 #, 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" +"the database.\n" "\n" "Compress the database now?" msgstr "" "このリポジトリにはおおよそ %i 個の個別オブジェクトがあります\n" "\n" -"最適な性能を保つために、%i 個以上の個別オブジェクトを作る毎にデータベースを圧" -"縮することを推奨します\n" +"最適な性能を保つために、データベースを圧縮することを推奨します\n" "\n" "データベースを圧縮しますか?" @@ -1517,7 +1554,7 @@ msgstr "" msgid "Invalid date from Git: %s" msgstr "Git から出た無効な日付: %s" -#: lib/diff.tcl:59 +#: lib/diff.tcl:64 #, tcl-format msgid "" "No differences detected.\n" @@ -1539,12 +1576,12 @@ msgstr "" "\n" "同様な状態のファイルを探すために、自動的に再スキャンを開始します。" -#: lib/diff.tcl:99 +#: lib/diff.tcl:104 #, tcl-format msgid "Loading diff of %s..." msgstr "%s の変更点をロード中…" -#: lib/diff.tcl:120 +#: lib/diff.tcl:125 msgid "" "LOCAL: deleted\n" "REMOTE:\n" @@ -1552,7 +1589,7 @@ msgstr "" "LOCAL: 削除\n" "Remote:\n" -#: lib/diff.tcl:125 +#: lib/diff.tcl:130 msgid "" "REMOTE: deleted\n" "LOCAL:\n" @@ -1560,32 +1597,32 @@ msgstr "" "REMOTE: 削除\n" "LOCAL:\n" -#: lib/diff.tcl:132 +#: lib/diff.tcl:137 msgid "LOCAL:\n" msgstr "LOCAL:\n" -#: lib/diff.tcl:135 +#: lib/diff.tcl:140 msgid "REMOTE:\n" msgstr "REMOTE\n" -#: lib/diff.tcl:197 lib/diff.tcl:296 +#: lib/diff.tcl:202 lib/diff.tcl:319 #, tcl-format msgid "Unable to display %s" msgstr "%s を表示できません" -#: lib/diff.tcl:198 +#: lib/diff.tcl:203 msgid "Error loading file:" msgstr "ファイルを読む際のエラーです:" -#: lib/diff.tcl:205 +#: lib/diff.tcl:210 msgid "Git Repository (subproject)" msgstr "Git リポジトリ(サブプロジェクト)" -#: lib/diff.tcl:217 +#: lib/diff.tcl:222 msgid "* Binary file (not showing content)." msgstr "* バイナリファイル(内容は表示しません)" -#: lib/diff.tcl:222 +#: lib/diff.tcl:227 #, tcl-format msgid "" "* Untracked file is %d bytes.\n" @@ -1594,7 +1631,7 @@ msgstr "" "* 管理外のファイルの大きさは %d バイトです。\n" "* 最初の %d バイトだけ表示しています。\n" -#: lib/diff.tcl:228 +#: lib/diff.tcl:233 #, tcl-format msgid "" "\n" @@ -1606,19 +1643,19 @@ msgstr "" "* %s は管理外のファイルをここで切りおとしました。\n" "* 全体を見るには外部エディタを使ってください。\n" -#: lib/diff.tcl:436 +#: lib/diff.tcl:482 msgid "Failed to unstage selected hunk." msgstr "選択されたパッチをコミット予定から外せません。" -#: lib/diff.tcl:443 +#: lib/diff.tcl:489 msgid "Failed to stage selected hunk." msgstr "選択されたパッチをコミット予定に加えられません。" -#: lib/diff.tcl:509 +#: lib/diff.tcl:568 msgid "Failed to unstage selected line." msgstr "選択されたパッチ行をコミット予定から外せません。" -#: lib/diff.tcl:517 +#: lib/diff.tcl:576 msgid "Failed to stage selected line." msgstr "選択されたパッチ行をコミット予定に加えられません。" @@ -1655,7 +1692,7 @@ msgstr "インデックスをロックできません" msgid "Index Error" msgstr "索引エラー" -#: lib/index.tcl:21 +#: lib/index.tcl:17 msgid "" "Updating the Git index failed. A rescan will be automatically started to " "resynchronize git-gui." @@ -1663,7 +1700,7 @@ msgstr "" "GIT インデックスの更新が失敗しました。git-gui と同期をとるために再スキャンし" "ます。" -#: lib/index.tcl:27 +#: lib/index.tcl:28 msgid "Continue" msgstr "続行" @@ -1671,43 +1708,43 @@ msgstr "続行" msgid "Unlock Index" msgstr "インデックスのロック解除" -#: lib/index.tcl:287 +#: lib/index.tcl:289 #, tcl-format msgid "Unstaging %s from commit" msgstr "コミットから '%s' を降ろす" -#: lib/index.tcl:326 +#: lib/index.tcl:328 msgid "Ready to commit." msgstr "コミット準備完了" -#: lib/index.tcl:339 +#: lib/index.tcl:341 #, tcl-format msgid "Adding %s" msgstr "コミットに %s を加えています" -#: lib/index.tcl:396 +#: lib/index.tcl:398 #, tcl-format msgid "Revert changes in file %s?" msgstr "ファイル %s にした変更を元に戻しますか?" -#: lib/index.tcl:398 +#: lib/index.tcl:400 #, tcl-format msgid "Revert changes in these %i files?" msgstr "これら %i 個のファイルにした変更を元に戻しますか?" -#: lib/index.tcl:406 +#: lib/index.tcl:408 msgid "Any unstaged changes will be permanently lost by the revert." msgstr "変更を元に戻すとコミット予定していない変更は全て失われます。" -#: lib/index.tcl:409 +#: lib/index.tcl:411 msgid "Do Nothing" msgstr "何もしない" -#: lib/index.tcl:427 +#: lib/index.tcl:429 msgid "Reverting selected files" msgstr "選択されたファイルにした変更を元に戻します" -#: lib/index.tcl:431 +#: lib/index.tcl:433 #, tcl-format msgid "Reverting %s" msgstr "%s にした変更を元に戻します" @@ -1883,7 +1920,8 @@ msgstr "" #: lib/mergetool.tcl:45 #, tcl-format msgid "File %s seems to have unresolved conflicts, still stage?" -msgstr "ファイル %s には解決していない競合部分がまだあるようですが、いいですか?" +msgstr "" +"ファイル %s には解決していない競合部分がまだあるようですが、いいですか?" #: lib/mergetool.tcl:60 #, tcl-format @@ -2141,7 +2179,8 @@ msgstr "%s からフェッチしています" msgid "Do not know how to initialize repository at location '%s'." msgstr "リポジトリ '%s' を初期化できません。" -#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:71 +#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63 +#: lib/transport.tcl:81 #, tcl-format msgid "push %s" msgstr "%s をプッシュ" @@ -2159,11 +2198,11 @@ msgstr "遠隔でブランチ削除" msgid "From Repository" msgstr "元のリポジトリ" -#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 +#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134 msgid "Remote:" msgstr "リモート:" -#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138 +#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149 msgid "Arbitrary Location:" msgstr "任意の位置:" @@ -2179,10 +2218,6 @@ msgstr "条件付で削除" 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 "'マージ先' にはブランチが必要です。" @@ -2211,26 +2246,16 @@ msgstr "" 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 +#: lib/remote_branch_delete.tcl:292 msgid "No repository selected." msgstr "リポジトリが選択されていません。" -#: lib/remote_branch_delete.tcl:291 +#: lib/remote_branch_delete.tcl:297 #, tcl-format msgid "Scanning %s..." msgstr "%s をスキャンしています…" @@ -2251,11 +2276,11 @@ msgstr "前" msgid "Case-Sensitive" msgstr "大文字小文字を区別" -#: lib/shortcut.tcl:20 lib/shortcut.tcl:61 +#: lib/shortcut.tcl:21 lib/shortcut.tcl:62 msgid "Cannot write shortcut:" msgstr "ショートカットが書けません:" -#: lib/shortcut.tcl:136 +#: lib/shortcut.tcl:137 msgid "Cannot write icon:" msgstr "アイコンが書けません:" @@ -2501,30 +2526,30 @@ msgstr "%s へミラーしています" msgid "Pushing %s %s to %s" msgstr "%3$s へ %1$s %2$s をプッシュしています" -#: lib/transport.tcl:89 +#: lib/transport.tcl:100 msgid "Push Branches" msgstr "ブランチをプッシュ" -#: lib/transport.tcl:103 +#: lib/transport.tcl:114 msgid "Source Branches" msgstr "元のブランチ" -#: lib/transport.tcl:120 +#: lib/transport.tcl:131 msgid "Destination Repository" msgstr "送り先リポジトリ" -#: lib/transport.tcl:158 +#: lib/transport.tcl:169 msgid "Transfer Options" msgstr "通信オプション" -#: lib/transport.tcl:160 +#: lib/transport.tcl:171 msgid "Force overwrite existing branch (may discard changes)" msgstr "既存ブランチを上書き(変更を破棄する可能性があります)" -#: lib/transport.tcl:164 +#: lib/transport.tcl:175 msgid "Use thin pack (for slow network connections)" msgstr "Thin Pack を使う(遅いネットワーク接続)" -#: lib/transport.tcl:168 +#: lib/transport.tcl:179 msgid "Include tags" 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-gui/po/sv.po b/git-gui/po/sv.po index c1535f94e8..d8d73acf2c 100644 --- a/git-gui/po/sv.po +++ b/git-gui/po/sv.po @@ -8,41 +8,41 @@ msgid "" msgstr "" "Project-Id-Version: sv\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-12-08 08:31-0800\n" -"PO-Revision-Date: 2008-12-10 09:49+0100\n" +"POT-Creation-Date: 2010-01-26 15:47-0800\n" +"PO-Revision-Date: 2010-01-28 13:57+0100\n" "Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n" "Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" +"Content-Transfer-Encoding: 8bit" -#: git-gui.sh:41 git-gui.sh:737 git-gui.sh:751 git-gui.sh:764 git-gui.sh:847 -#: git-gui.sh:866 +#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903 +#: git-gui.sh:922 msgid "git-gui: fatal error" msgstr "git-gui: ödesdigert fel" -#: git-gui.sh:689 +#: git-gui.sh:743 #, tcl-format msgid "Invalid font specified in %s:" msgstr "Ogiltigt teckensnitt angivet i %s:" -#: git-gui.sh:723 +#: git-gui.sh:779 msgid "Main Font" msgstr "Huvudteckensnitt" -#: git-gui.sh:724 +#: git-gui.sh:780 msgid "Diff/Console Font" msgstr "Diff/konsolteckensnitt" -#: git-gui.sh:738 +#: git-gui.sh:794 msgid "Cannot find git in PATH." msgstr "Hittar inte git i PATH." -#: git-gui.sh:765 +#: git-gui.sh:821 msgid "Cannot parse Git version string:" msgstr "Kan inte tolka versionssträng från Git:" -#: git-gui.sh:783 +#: git-gui.sh:839 #, tcl-format msgid "" "Git version cannot be determined.\n" @@ -61,449 +61,478 @@ msgstr "" "\n" "Anta att \"%s\" är version 1.5.0?\n" -#: git-gui.sh:1062 +#: git-gui.sh:1128 msgid "Git directory not found:" msgstr "Git-katalogen hittades inte:" -#: git-gui.sh:1069 +#: git-gui.sh:1146 msgid "Cannot move to top of working directory:" msgstr "Kan inte gå till början på arbetskatalogen:" -#: git-gui.sh:1076 -msgid "Cannot use funny .git directory:" -msgstr "Kan inte använda underlig .git-katalog:" +#: git-gui.sh:1154 +msgid "Cannot use bare repository:" +msgstr "Kan inte använda naket arkiv:" -#: git-gui.sh:1081 +#: git-gui.sh:1162 msgid "No working directory" msgstr "Ingen arbetskatalog" -#: git-gui.sh:1247 lib/checkout_op.tcl:305 +#: git-gui.sh:1334 lib/checkout_op.tcl:306 msgid "Refreshing file status..." msgstr "Uppdaterar filstatus..." -#: git-gui.sh:1303 +#: git-gui.sh:1390 msgid "Scanning for modified files ..." msgstr "Söker efter ändrade filer..." -#: git-gui.sh:1367 +#: git-gui.sh:1454 msgid "Calling prepare-commit-msg hook..." msgstr "" "Anropar kroken för förberedelse av incheckningsmeddelande (prepare-commit-" "msg)..." -#: git-gui.sh:1384 +#: git-gui.sh:1471 msgid "Commit declined by prepare-commit-msg hook." msgstr "" "Incheckningen avvisades av kroken för förberedelse av incheckningsmeddelande " "(prepare-commit-msg)." -#: git-gui.sh:1542 lib/browser.tcl:246 +#: git-gui.sh:1629 lib/browser.tcl:246 msgid "Ready." msgstr "Klar." -#: git-gui.sh:1819 +#: git-gui.sh:1787 +#, tcl-format +msgid "Displaying only %s of %s files." +msgstr "Visar endast %s av %s filer." + +#: git-gui.sh:1913 msgid "Unmodified" msgstr "Oförändrade" -#: git-gui.sh:1821 +#: git-gui.sh:1915 msgid "Modified, not staged" msgstr "Förändrade, ej köade" -#: git-gui.sh:1822 git-gui.sh:1830 +#: git-gui.sh:1916 git-gui.sh:1924 msgid "Staged for commit" msgstr "Köade för incheckning" -#: git-gui.sh:1823 git-gui.sh:1831 +#: git-gui.sh:1917 git-gui.sh:1925 msgid "Portions staged for commit" msgstr "Delar köade för incheckning" -#: git-gui.sh:1824 git-gui.sh:1832 +#: git-gui.sh:1918 git-gui.sh:1926 msgid "Staged for commit, missing" msgstr "Köade för incheckning, saknade" -#: git-gui.sh:1826 +#: git-gui.sh:1920 msgid "File type changed, not staged" msgstr "Filtyp ändrad, ej köade" -#: git-gui.sh:1827 +#: git-gui.sh:1921 msgid "File type changed, staged" msgstr "Filtyp ändrad, köade" -#: git-gui.sh:1829 +#: git-gui.sh:1923 msgid "Untracked, not staged" msgstr "Ej spårade, ej köade" -#: git-gui.sh:1834 +#: git-gui.sh:1928 msgid "Missing" msgstr "Saknade" -#: git-gui.sh:1835 +#: git-gui.sh:1929 msgid "Staged for removal" msgstr "Köade för borttagning" -#: git-gui.sh:1836 +#: git-gui.sh:1930 msgid "Staged for removal, still present" msgstr "Köade för borttagning, fortfarande närvarande" -#: git-gui.sh:1838 git-gui.sh:1839 git-gui.sh:1840 git-gui.sh:1841 -#: git-gui.sh:1842 git-gui.sh:1843 +#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935 +#: git-gui.sh:1936 git-gui.sh:1937 msgid "Requires merge resolution" msgstr "Kräver konflikthantering efter sammanslagning" -#: git-gui.sh:1878 +#: git-gui.sh:1972 msgid "Starting gitk... please wait..." msgstr "Startar gitk... vänta..." -#: git-gui.sh:1887 +#: git-gui.sh:1984 msgid "Couldn't find gitk in PATH" -msgstr "Hittar inte gitk i PATH." +msgstr "Hittade inte gitk i PATH." + +#: git-gui.sh:2043 +msgid "Couldn't find git gui in PATH" +msgstr "Hittade inte git gui i PATH." -#: git-gui.sh:2280 lib/choose_repository.tcl:36 +#: git-gui.sh:2455 lib/choose_repository.tcl:36 msgid "Repository" msgstr "Arkiv" -#: git-gui.sh:2281 +#: git-gui.sh:2456 msgid "Edit" msgstr "Redigera" -#: git-gui.sh:2283 lib/choose_rev.tcl:561 +#: git-gui.sh:2458 lib/choose_rev.tcl:561 msgid "Branch" msgstr "Gren" -#: git-gui.sh:2286 lib/choose_rev.tcl:548 +#: git-gui.sh:2461 lib/choose_rev.tcl:548 msgid "Commit@@noun" msgstr "Incheckning" -#: git-gui.sh:2289 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 msgid "Merge" msgstr "Slå ihop" -#: git-gui.sh:2290 lib/choose_rev.tcl:557 +#: git-gui.sh:2465 lib/choose_rev.tcl:557 msgid "Remote" msgstr "Fjärrarkiv" -#: git-gui.sh:2293 +#: git-gui.sh:2468 msgid "Tools" msgstr "Verktyg" -#: git-gui.sh:2302 +#: git-gui.sh:2477 msgid "Explore Working Copy" msgstr "Utforska arbetskopia" -#: git-gui.sh:2307 +#: git-gui.sh:2483 msgid "Browse Current Branch's Files" msgstr "Bläddra i grenens filer" -#: git-gui.sh:2311 +#: git-gui.sh:2487 msgid "Browse Branch Files..." msgstr "Bläddra filer på gren..." -#: git-gui.sh:2316 +#: git-gui.sh:2492 msgid "Visualize Current Branch's History" msgstr "Visualisera grenens historik" -#: git-gui.sh:2320 +#: git-gui.sh:2496 msgid "Visualize All Branch History" msgstr "Visualisera alla grenars historik" -#: git-gui.sh:2327 +#: git-gui.sh:2503 #, tcl-format msgid "Browse %s's Files" msgstr "Bläddra i filer för %s" -#: git-gui.sh:2329 +#: git-gui.sh:2505 #, tcl-format msgid "Visualize %s's History" msgstr "Visualisera historik för %s" -#: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67 +#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67 msgid "Database Statistics" msgstr "Databasstatistik" -#: git-gui.sh:2337 lib/database.tcl:34 +#: git-gui.sh:2513 lib/database.tcl:34 msgid "Compress Database" msgstr "Komprimera databas" -#: git-gui.sh:2340 +#: git-gui.sh:2516 msgid "Verify Database" msgstr "Verifiera databas" -#: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7 -#: lib/shortcut.tcl:39 lib/shortcut.tcl:71 +#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8 +#: lib/shortcut.tcl:40 lib/shortcut.tcl:72 msgid "Create Desktop Icon" msgstr "Skapa skrivbordsikon" -#: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 +#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 msgid "Quit" msgstr "Avsluta" -#: git-gui.sh:2371 +#: git-gui.sh:2547 msgid "Undo" msgstr "Ångra" -#: git-gui.sh:2374 +#: git-gui.sh:2550 msgid "Redo" msgstr "Gör om" -#: git-gui.sh:2378 git-gui.sh:2937 +#: git-gui.sh:2554 git-gui.sh:3109 msgid "Cut" msgstr "Klipp ut" -#: git-gui.sh:2381 git-gui.sh:2940 git-gui.sh:3014 git-gui.sh:3096 +#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259 #: lib/console.tcl:69 msgid "Copy" msgstr "Kopiera" -#: git-gui.sh:2384 git-gui.sh:2943 +#: git-gui.sh:2560 git-gui.sh:3115 msgid "Paste" msgstr "Klistra in" -#: git-gui.sh:2387 git-gui.sh:2946 lib/branch_delete.tcl:26 +#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26 #: lib/remote_branch_delete.tcl:38 msgid "Delete" msgstr "Ta bort" -#: git-gui.sh:2391 git-gui.sh:2950 git-gui.sh:3100 lib/console.tcl:71 +#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71 msgid "Select All" msgstr "Markera alla" -#: git-gui.sh:2400 +#: git-gui.sh:2576 msgid "Create..." msgstr "Skapa..." -#: git-gui.sh:2406 +#: git-gui.sh:2582 msgid "Checkout..." msgstr "Checka ut..." -#: git-gui.sh:2412 +#: git-gui.sh:2588 msgid "Rename..." msgstr "Byt namn..." -#: git-gui.sh:2417 +#: git-gui.sh:2593 msgid "Delete..." msgstr "Ta bort..." -#: git-gui.sh:2422 +#: git-gui.sh:2598 msgid "Reset..." msgstr "Återställ..." -#: git-gui.sh:2432 +#: git-gui.sh:2608 msgid "Done" msgstr "Färdig" -#: git-gui.sh:2434 +#: git-gui.sh:2610 msgid "Commit@@verb" msgstr "Checka in" -#: git-gui.sh:2443 git-gui.sh:2878 +#: git-gui.sh:2619 git-gui.sh:3050 msgid "New Commit" msgstr "Ny incheckning" -#: git-gui.sh:2451 git-gui.sh:2885 +#: git-gui.sh:2627 git-gui.sh:3057 msgid "Amend Last Commit" msgstr "Lägg till föregående incheckning" -#: git-gui.sh:2461 git-gui.sh:2839 lib/remote_branch_delete.tcl:99 +#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99 msgid "Rescan" msgstr "Sök på nytt" -#: git-gui.sh:2467 +#: git-gui.sh:2643 msgid "Stage To Commit" msgstr "Köa för incheckning" -#: git-gui.sh:2473 +#: git-gui.sh:2649 msgid "Stage Changed Files To Commit" msgstr "Köa ändrade filer för incheckning" -#: git-gui.sh:2479 +#: git-gui.sh:2655 msgid "Unstage From Commit" msgstr "Ta bort från incheckningskö" -#: git-gui.sh:2484 lib/index.tcl:410 +#: git-gui.sh:2661 lib/index.tcl:412 msgid "Revert Changes" msgstr "Återställ ändringar" -#: git-gui.sh:2491 git-gui.sh:3083 +#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341 msgid "Show Less Context" msgstr "Visa mindre sammanhang" -#: git-gui.sh:2495 git-gui.sh:3087 +#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345 msgid "Show More Context" msgstr "Visa mer sammanhang" -#: git-gui.sh:2502 git-gui.sh:2852 git-gui.sh:2961 +#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133 msgid "Sign Off" msgstr "Skriv under" -#: git-gui.sh:2518 +#: git-gui.sh:2696 msgid "Local Merge..." msgstr "Lokal sammanslagning..." -#: git-gui.sh:2523 +#: git-gui.sh:2701 msgid "Abort Merge..." msgstr "Avbryt sammanslagning..." -#: git-gui.sh:2535 git-gui.sh:2575 +#: git-gui.sh:2713 git-gui.sh:2741 msgid "Add..." msgstr "Lägg till..." -#: git-gui.sh:2539 +#: git-gui.sh:2717 msgid "Push..." msgstr "Sänd..." -#: git-gui.sh:2543 +#: git-gui.sh:2721 msgid "Delete Branch..." msgstr "Ta bort gren..." -#: git-gui.sh:2553 git-gui.sh:2589 lib/about.tcl:14 -#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 -#, tcl-format -msgid "About %s" -msgstr "Om %s" - -#: git-gui.sh:2557 -msgid "Preferences..." -msgstr "Inställningar..." - -#: git-gui.sh:2565 git-gui.sh:3129 +#: git-gui.sh:2731 git-gui.sh:3292 msgid "Options..." msgstr "Alternativ..." -#: git-gui.sh:2576 +#: git-gui.sh:2742 msgid "Remove..." msgstr "Ta bort..." -#: git-gui.sh:2585 lib/choose_repository.tcl:50 +#: git-gui.sh:2751 lib/choose_repository.tcl:50 msgid "Help" msgstr "Hjälp" -#: git-gui.sh:2611 +#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 +#, tcl-format +msgid "About %s" +msgstr "Om %s" + +#: git-gui.sh:2783 msgid "Online Documentation" msgstr "Webbdokumentation" -#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 +#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 msgid "Show SSH Key" msgstr "Visa SSH-nyckel" -#: git-gui.sh:2721 +#: git-gui.sh:2893 #, tcl-format msgid "fatal: cannot stat path %s: No such file or directory" msgstr "" "ödesdigert: kunde inte ta status på sökvägen %s: Fil eller katalog saknas" -#: git-gui.sh:2754 +#: git-gui.sh:2926 msgid "Current Branch:" msgstr "Aktuell gren:" -#: git-gui.sh:2775 +#: git-gui.sh:2947 msgid "Staged Changes (Will Commit)" msgstr "Köade ändringar (kommer att checkas in)" -#: git-gui.sh:2795 +#: git-gui.sh:2967 msgid "Unstaged Changes" msgstr "Oköade ändringar" -#: git-gui.sh:2845 +#: git-gui.sh:3017 msgid "Stage Changed" msgstr "Köa ändrade" -#: git-gui.sh:2864 lib/transport.tcl:104 lib/transport.tcl:193 +#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193 msgid "Push" msgstr "Sänd" -#: git-gui.sh:2899 +#: git-gui.sh:3071 msgid "Initial Commit Message:" msgstr "Inledande incheckningsmeddelande:" -#: git-gui.sh:2900 +#: git-gui.sh:3072 msgid "Amended Commit Message:" msgstr "Utökat incheckningsmeddelande:" -#: git-gui.sh:2901 +#: git-gui.sh:3073 msgid "Amended Initial Commit Message:" msgstr "Utökat inledande incheckningsmeddelande:" -#: git-gui.sh:2902 +#: git-gui.sh:3074 msgid "Amended Merge Commit Message:" msgstr "Utökat incheckningsmeddelande för sammanslagning:" -#: git-gui.sh:2903 +#: git-gui.sh:3075 msgid "Merge Commit Message:" msgstr "Incheckningsmeddelande för sammanslagning:" -#: git-gui.sh:2904 +#: git-gui.sh:3076 msgid "Commit Message:" msgstr "Incheckningsmeddelande:" -#: git-gui.sh:2953 git-gui.sh:3104 lib/console.tcl:73 +#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73 msgid "Copy All" msgstr "Kopiera alla" -#: git-gui.sh:2977 lib/blame.tcl:104 +#: git-gui.sh:3149 lib/blame.tcl:104 msgid "File:" msgstr "Fil:" -#: git-gui.sh:3092 +#: git-gui.sh:3255 msgid "Refresh" msgstr "Uppdatera" -#: git-gui.sh:3113 +#: git-gui.sh:3276 msgid "Decrease Font Size" msgstr "Minska teckensnittsstorlek" -#: git-gui.sh:3117 +#: git-gui.sh:3280 msgid "Increase Font Size" msgstr "Öka teckensnittsstorlek" -#: git-gui.sh:3125 lib/blame.tcl:281 +#: git-gui.sh:3288 lib/blame.tcl:281 msgid "Encoding" msgstr "Teckenkodning" -#: git-gui.sh:3136 +#: git-gui.sh:3299 msgid "Apply/Reverse Hunk" msgstr "Använd/återställ del" -#: git-gui.sh:3141 +#: git-gui.sh:3304 msgid "Apply/Reverse Line" msgstr "Använd/återställ rad" -#: git-gui.sh:3151 +#: git-gui.sh:3323 msgid "Run Merge Tool" msgstr "Starta verktyg för sammanslagning" -#: git-gui.sh:3156 +#: git-gui.sh:3328 msgid "Use Remote Version" msgstr "Använd versionen från fjärrarkivet" -#: git-gui.sh:3160 +#: git-gui.sh:3332 msgid "Use Local Version" msgstr "Använd lokala versionen" -#: git-gui.sh:3164 +#: git-gui.sh:3336 msgid "Revert To Base" msgstr "Återställ till basversionen" -#: git-gui.sh:3183 +#: git-gui.sh:3354 +msgid "Visualize These Changes In The Submodule" +msgstr "Visualisera ändringarna i undermodulen" + +#: git-gui.sh:3358 +msgid "Visualize Current Branch History In The Submodule" +msgstr "Visualisera grenens historik i undermodulen" + +#: git-gui.sh:3362 +msgid "Visualize All Branch History In The Submodule" +msgstr "Visualisera alla grenars historik i undermodulen" + +#: git-gui.sh:3367 +msgid "Start git gui In The Submodule" +msgstr "Starta git gui i undermodulen" + +#: git-gui.sh:3389 msgid "Unstage Hunk From Commit" msgstr "Ta bort del ur incheckningskö" -#: git-gui.sh:3184 +#: git-gui.sh:3391 +msgid "Unstage Lines From Commit" +msgstr "Ta bort rader ur incheckningskö" + +#: git-gui.sh:3393 msgid "Unstage Line From Commit" msgstr "Ta bort rad ur incheckningskö" -#: git-gui.sh:3186 +#: git-gui.sh:3396 msgid "Stage Hunk For Commit" msgstr "Ställ del i incheckningskö" -#: git-gui.sh:3187 +#: git-gui.sh:3398 +msgid "Stage Lines For Commit" +msgstr "Ställ rader i incheckningskö" + +#: git-gui.sh:3400 msgid "Stage Line For Commit" msgstr "Ställ rad i incheckningskö" -#: git-gui.sh:3210 +#: git-gui.sh:3424 msgid "Initializing..." msgstr "Initierar..." -#: git-gui.sh:3315 +#: git-gui.sh:3541 #, tcl-format msgid "" "Possible environment issues exist.\n" @@ -520,7 +549,7 @@ msgstr "" "av %s:\n" "\n" -#: git-gui.sh:3345 +#: git-gui.sh:3570 msgid "" "\n" "This is due to a known issue with the\n" @@ -530,7 +559,7 @@ msgstr "" "Detta beror på ett känt problem med\n" "Tcl-binären som följer med Cygwin." -#: git-gui.sh:3350 +#: git-gui.sh:3575 #, tcl-format msgid "" "\n" @@ -640,7 +669,7 @@ msgstr "Hittar inte föräldraincheckning:" msgid "Unable to display parent" msgstr "Kan inte visa förälder" -#: lib/blame.tcl:1091 lib/diff.tcl:297 +#: lib/blame.tcl:1091 lib/diff.tcl:320 msgid "Error loading diff:" msgstr "Fel vid inläsning av differens:" @@ -666,7 +695,7 @@ msgstr "Checka ut" #: 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:544 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172 #: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42 #: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352 #: lib/transport.tcl:108 @@ -697,7 +726,7 @@ msgstr "Skapa gren" msgid "Create New Branch" msgstr "Skapa ny gren" -#: lib/branch_create.tcl:31 lib/choose_repository.tcl:377 +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381 msgid "Create" msgstr "Skapa" @@ -729,7 +758,7 @@ msgstr "Nej" msgid "Fast Forward Only" msgstr "Endast snabbspolning" -#: lib/branch_create.tcl:85 lib/checkout_op.tcl:536 +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571 msgid "Reset" msgstr "Återställ" @@ -771,15 +800,25 @@ msgstr "Lokala grenar" msgid "Delete Only If Merged Into" msgstr "Ta bara bort om sammanslagen med" -#: lib/branch_delete.tcl:54 -msgid "Always (Do not perform merge test.)" -msgstr "Alltid (utför inte sammanslagningstest)." +#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" +msgstr "Alltid (utför inte sammanslagningstest)" #: lib/branch_delete.tcl:103 #, tcl-format msgid "The following branches are not completely merged into %s:" msgstr "Följande grenar är inte till fullo sammanslagna med %s:" +#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"Det kan vara svårt att återställa borttagna grenar.\n" +"\n" +"Ta bort de valda grenarna?" + #: lib/branch_delete.tcl:141 #, tcl-format msgid "" @@ -809,7 +848,7 @@ msgstr "Nytt namn:" msgid "Please select a branch to rename." msgstr "Välj en gren att byta namn på." -#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:201 +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202 #, tcl-format msgid "Branch '%s' already exists." msgstr "Grenen \"%s\" finns redan." @@ -840,38 +879,38 @@ msgstr "[Upp till förälder]" msgid "Browse Branch Files" msgstr "Bläddra filer på grenen" -#: lib/browser.tcl:278 lib/choose_repository.tcl:394 -#: lib/choose_repository.tcl:480 lib/choose_repository.tcl:491 -#: lib/choose_repository.tcl:995 +#: lib/browser.tcl:278 lib/choose_repository.tcl:398 +#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497 +#: lib/choose_repository.tcl:1028 msgid "Browse" msgstr "Bläddra" -#: lib/checkout_op.tcl:84 +#: lib/checkout_op.tcl:85 #, tcl-format msgid "Fetching %s from %s" msgstr "Hämtar %s från %s" -#: lib/checkout_op.tcl:132 +#: lib/checkout_op.tcl:133 #, tcl-format msgid "fatal: Cannot resolve %s" msgstr "ödesdigert: Kunde inte slå upp %s" -#: lib/checkout_op.tcl:145 lib/console.tcl:81 lib/database.tcl:31 +#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31 #: lib/sshkey.tcl:53 msgid "Close" msgstr "Stäng" -#: lib/checkout_op.tcl:174 +#: lib/checkout_op.tcl:175 #, tcl-format msgid "Branch '%s' does not exist." msgstr "Grenen \"%s\" finns inte." -#: lib/checkout_op.tcl:193 +#: lib/checkout_op.tcl:194 #, tcl-format msgid "Failed to configure simplified git-pull for '%s'." msgstr "Kunde inte konfigurera förenklad git-pull för '%s'." -#: lib/checkout_op.tcl:228 +#: lib/checkout_op.tcl:229 #, tcl-format msgid "" "Branch '%s' already exists.\n" @@ -884,21 +923,21 @@ msgstr "" "Den kan inte snabbspolas till %s.\n" "En sammanslagning krävs." -#: lib/checkout_op.tcl:242 +#: lib/checkout_op.tcl:243 #, tcl-format msgid "Merge strategy '%s' not supported." msgstr "Sammanslagningsstrategin \"%s\" stöds inte." -#: lib/checkout_op.tcl:261 +#: lib/checkout_op.tcl:262 #, tcl-format msgid "Failed to update '%s'." msgstr "Misslyckades med att uppdatera \"%s\"." -#: lib/checkout_op.tcl:273 +#: lib/checkout_op.tcl:274 msgid "Staging area (index) is already locked." msgstr "Köområdet (index) är redan låst." -#: lib/checkout_op.tcl:288 +#: lib/checkout_op.tcl:289 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -914,30 +953,30 @@ msgstr "" "\n" "Sökningen kommer att startas automatiskt nu.\n" -#: lib/checkout_op.tcl:344 +#: lib/checkout_op.tcl:345 #, tcl-format msgid "Updating working directory to '%s'..." msgstr "Uppdaterar arbetskatalogen till \"%s\"..." -#: lib/checkout_op.tcl:345 +#: lib/checkout_op.tcl:346 msgid "files checked out" msgstr "filer utcheckade" -#: lib/checkout_op.tcl:375 +#: lib/checkout_op.tcl:376 #, tcl-format msgid "Aborted checkout of '%s' (file level merging is required)." msgstr "Avbryter utcheckning av \"%s\" (sammanslagning på filnivå krävs)." -#: lib/checkout_op.tcl:376 +#: lib/checkout_op.tcl:377 msgid "File level merge required." msgstr "Sammanslagning på filnivå krävs." -#: lib/checkout_op.tcl:380 +#: lib/checkout_op.tcl:381 #, tcl-format msgid "Staying on branch '%s'." msgstr "Stannar på grenen \"%s\"." -#: lib/checkout_op.tcl:451 +#: lib/checkout_op.tcl:452 msgid "" "You are no longer on a local branch.\n" "\n" @@ -949,31 +988,31 @@ msgstr "" "Om du ville vara på en gren skapar du en nu, baserad på \"Denna frånkopplade " "utcheckning\"." -#: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472 +#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507 #, tcl-format msgid "Checked out '%s'." msgstr "Checkade ut \"%s\"." -#: lib/checkout_op.tcl:500 +#: lib/checkout_op.tcl:535 #, tcl-format msgid "Resetting '%s' to '%s' will lose the following commits:" msgstr "" "Om du återställer \"%s\" till \"%s\" går följande incheckningar förlorade:" -#: lib/checkout_op.tcl:522 +#: lib/checkout_op.tcl:557 msgid "Recovering lost commits may not be easy." msgstr "Det kanske inte är så enkelt att återskapa förlorade incheckningar." -#: lib/checkout_op.tcl:527 +#: lib/checkout_op.tcl:562 #, tcl-format msgid "Reset '%s'?" msgstr "Återställa \"%s\"?" -#: lib/checkout_op.tcl:532 lib/merge.tcl:164 lib/tools_dlg.tcl:343 +#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343 msgid "Visualize" msgstr "Visualisera" -#: lib/checkout_op.tcl:600 +#: lib/checkout_op.tcl:635 #, tcl-format msgid "" "Failed to set current branch.\n" @@ -1018,7 +1057,7 @@ msgstr "" msgid "Git Gui" msgstr "Git Gui" -#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:382 +#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386 msgid "Create New Repository" msgstr "Skapa nytt arkiv" @@ -1026,7 +1065,7 @@ msgstr "Skapa nytt arkiv" msgid "New..." msgstr "Nytt..." -#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465 +#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471 msgid "Clone Existing Repository" msgstr "Klona befintligt arkiv" @@ -1034,7 +1073,7 @@ msgstr "Klona befintligt arkiv" msgid "Clone..." msgstr "Klona..." -#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983 +#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016 msgid "Open Existing Repository" msgstr "Öppna befintligt arkiv" @@ -1050,193 +1089,193 @@ msgstr "Senaste arkiven" msgid "Open Recent Repository:" msgstr "Öppna tidigare arkiv:" -#: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309 -#: lib/choose_repository.tcl:316 +#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313 +#: lib/choose_repository.tcl:320 #, tcl-format msgid "Failed to create repository %s:" msgstr "Kunde inte skapa arkivet %s:" -#: lib/choose_repository.tcl:387 +#: lib/choose_repository.tcl:391 msgid "Directory:" msgstr "Katalog:" -#: lib/choose_repository.tcl:417 lib/choose_repository.tcl:544 -#: lib/choose_repository.tcl:1017 +#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550 +#: lib/choose_repository.tcl:1052 msgid "Git Repository" msgstr "Gitarkiv" -#: lib/choose_repository.tcl:442 +#: lib/choose_repository.tcl:448 #, tcl-format msgid "Directory %s already exists." msgstr "Katalogen %s finns redan." -#: lib/choose_repository.tcl:446 +#: lib/choose_repository.tcl:452 #, tcl-format msgid "File %s already exists." msgstr "Filen %s finns redan." -#: lib/choose_repository.tcl:460 +#: lib/choose_repository.tcl:466 msgid "Clone" msgstr "Klona" -#: lib/choose_repository.tcl:473 +#: lib/choose_repository.tcl:479 msgid "Source Location:" msgstr "Plats för källkod:" -#: lib/choose_repository.tcl:484 +#: lib/choose_repository.tcl:490 msgid "Target Directory:" msgstr "Målkatalog:" -#: lib/choose_repository.tcl:496 +#: lib/choose_repository.tcl:502 msgid "Clone Type:" msgstr "Typ av klon:" -#: lib/choose_repository.tcl:502 +#: lib/choose_repository.tcl:508 msgid "Standard (Fast, Semi-Redundant, Hardlinks)" msgstr "Standard (snabb, semiredundant, hårda länkar)" -#: lib/choose_repository.tcl:508 +#: lib/choose_repository.tcl:514 msgid "Full Copy (Slower, Redundant Backup)" msgstr "Full kopia (långsammare, redundant säkerhetskopia)" -#: lib/choose_repository.tcl:514 +#: lib/choose_repository.tcl:520 msgid "Shared (Fastest, Not Recommended, No Backup)" msgstr "Delad (snabbast, rekommenderas ej, ingen säkerhetskopia)" -#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597 -#: lib/choose_repository.tcl:743 lib/choose_repository.tcl:813 -#: lib/choose_repository.tcl:1023 lib/choose_repository.tcl:1031 +#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603 +#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819 +#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066 #, tcl-format msgid "Not a Git repository: %s" msgstr "Inte ett Gitarkiv: %s" -#: lib/choose_repository.tcl:586 +#: lib/choose_repository.tcl:592 msgid "Standard only available for local repository." msgstr "Standard är endast tillgängligt för lokala arkiv." -#: lib/choose_repository.tcl:590 +#: lib/choose_repository.tcl:596 msgid "Shared only available for local repository." msgstr "Delat är endast tillgängligt för lokala arkiv." -#: lib/choose_repository.tcl:611 +#: lib/choose_repository.tcl:617 #, tcl-format msgid "Location %s already exists." msgstr "Platsen %s finns redan." -#: lib/choose_repository.tcl:622 +#: lib/choose_repository.tcl:628 msgid "Failed to configure origin" msgstr "Kunde inte konfigurera ursprung" -#: lib/choose_repository.tcl:634 +#: lib/choose_repository.tcl:640 msgid "Counting objects" msgstr "Räknar objekt" -#: lib/choose_repository.tcl:635 +#: lib/choose_repository.tcl:641 msgid "buckets" msgstr "hinkar" -#: lib/choose_repository.tcl:659 +#: lib/choose_repository.tcl:665 #, tcl-format msgid "Unable to copy objects/info/alternates: %s" msgstr "Kunde inte kopiera objekt/info/alternativ: %s" -#: lib/choose_repository.tcl:695 +#: lib/choose_repository.tcl:701 #, tcl-format msgid "Nothing to clone from %s." msgstr "Ingenting att klona från %s." -#: lib/choose_repository.tcl:697 lib/choose_repository.tcl:911 -#: lib/choose_repository.tcl:923 +#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917 +#: lib/choose_repository.tcl:929 msgid "The 'master' branch has not been initialized." msgstr "Grenen \"master\" har inte initierats." -#: lib/choose_repository.tcl:710 +#: lib/choose_repository.tcl:716 msgid "Hardlinks are unavailable. Falling back to copying." msgstr "Hårda länkar är inte tillgängliga. Faller tillbaka på kopiering." -#: lib/choose_repository.tcl:722 +#: lib/choose_repository.tcl:728 #, tcl-format msgid "Cloning from %s" msgstr "Klonar från %s" -#: lib/choose_repository.tcl:753 +#: lib/choose_repository.tcl:759 msgid "Copying objects" msgstr "Kopierar objekt" -#: lib/choose_repository.tcl:754 +#: lib/choose_repository.tcl:760 msgid "KiB" msgstr "KiB" -#: lib/choose_repository.tcl:778 +#: lib/choose_repository.tcl:784 #, tcl-format msgid "Unable to copy object: %s" msgstr "Kunde inte kopiera objekt: %s" -#: lib/choose_repository.tcl:788 +#: lib/choose_repository.tcl:794 msgid "Linking objects" msgstr "Länkar objekt" -#: lib/choose_repository.tcl:789 +#: lib/choose_repository.tcl:795 msgid "objects" msgstr "objekt" -#: lib/choose_repository.tcl:797 +#: lib/choose_repository.tcl:803 #, tcl-format msgid "Unable to hardlink object: %s" msgstr "Kunde inte hårdlänka objekt: %s" -#: lib/choose_repository.tcl:852 +#: lib/choose_repository.tcl:858 msgid "Cannot fetch branches and objects. See console output for details." msgstr "Kunde inte hämta grenar och objekt. Se konsolutdata för detaljer." -#: lib/choose_repository.tcl:863 +#: lib/choose_repository.tcl:869 msgid "Cannot fetch tags. See console output for details." msgstr "Kunde inte hämta taggar. Se konsolutdata för detaljer." -#: lib/choose_repository.tcl:887 +#: lib/choose_repository.tcl:893 msgid "Cannot determine HEAD. See console output for details." msgstr "Kunde inte avgöra HEAD. Se konsolutdata för detaljer." -#: lib/choose_repository.tcl:896 +#: lib/choose_repository.tcl:902 #, tcl-format msgid "Unable to cleanup %s" msgstr "Kunde inte städa upp %s" -#: lib/choose_repository.tcl:902 +#: lib/choose_repository.tcl:908 msgid "Clone failed." msgstr "Kloning misslyckades." -#: lib/choose_repository.tcl:909 +#: lib/choose_repository.tcl:915 msgid "No default branch obtained." msgstr "Hämtade ingen standardgren." -#: lib/choose_repository.tcl:920 +#: lib/choose_repository.tcl:926 #, tcl-format msgid "Cannot resolve %s as a commit." msgstr "Kunde inte slå upp %s till någon incheckning." -#: lib/choose_repository.tcl:932 +#: lib/choose_repository.tcl:938 msgid "Creating working directory" msgstr "Skapar arbetskatalog" -#: lib/choose_repository.tcl:933 lib/index.tcl:65 lib/index.tcl:128 -#: lib/index.tcl:196 +#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130 +#: lib/index.tcl:198 msgid "files" msgstr "filer" -#: lib/choose_repository.tcl:962 +#: lib/choose_repository.tcl:968 msgid "Initial file checkout failed." msgstr "Inledande filutcheckning misslyckades." -#: lib/choose_repository.tcl:978 +#: lib/choose_repository.tcl:1011 msgid "Open" msgstr "Öppna" -#: lib/choose_repository.tcl:988 +#: lib/choose_repository.tcl:1021 msgid "Repository:" msgstr "Arkiv:" -#: lib/choose_repository.tcl:1037 +#: lib/choose_repository.tcl:1072 #, tcl-format msgid "Failed to open repository %s:" msgstr "Kunde inte öppna arkivet %s:" @@ -1320,7 +1359,12 @@ msgstr "Kunde inte hämta din identitet:" msgid "Invalid GIT_COMMITTER_IDENT:" msgstr "Felaktig GIT_COMMITTER_IDENT:" -#: lib/commit.tcl:132 +#: lib/commit.tcl:129 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "varning: Tcl stöder inte teckenkodningen \"%s\"." + +#: lib/commit.tcl:149 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -1336,7 +1380,7 @@ msgstr "" "\n" "Sökningen kommer att startas automatiskt nu.\n" -#: lib/commit.tcl:155 +#: lib/commit.tcl:172 #, tcl-format msgid "" "Unmerged files cannot be committed.\n" @@ -1349,7 +1393,7 @@ msgstr "" "Filen %s har sammanslagningskonflikter. Du måste lösa dem och köa filen " "innan du checkar in den.\n" -#: lib/commit.tcl:163 +#: lib/commit.tcl:180 #, tcl-format msgid "" "Unknown file state %s detected.\n" @@ -1360,7 +1404,7 @@ msgstr "" "\n" "Filen %s kan inte checkas in av programmet.\n" -#: lib/commit.tcl:171 +#: lib/commit.tcl:188 msgid "" "No changes to commit.\n" "\n" @@ -1370,7 +1414,7 @@ msgstr "" "\n" "Du måste köa åtminstone en fil innan du kan checka in.\n" -#: lib/commit.tcl:186 +#: lib/commit.tcl:203 msgid "" "Please supply a commit message.\n" "\n" @@ -1388,45 +1432,40 @@ msgstr "" "- Andra raden: Tom\n" "- Följande rader: Beskriv varför det här är en bra ändring.\n" -#: lib/commit.tcl:210 -#, tcl-format -msgid "warning: Tcl does not support encoding '%s'." -msgstr "varning: Tcl stöder inte teckenkodningen \"%s\"." - -#: lib/commit.tcl:226 +#: lib/commit.tcl:234 msgid "Calling pre-commit hook..." msgstr "Anropar kroken före incheckning (pre-commit)..." -#: lib/commit.tcl:241 +#: lib/commit.tcl:249 msgid "Commit declined by pre-commit hook." msgstr "Incheckningen avvisades av kroken före incheckning (pre-commit)." -#: lib/commit.tcl:264 +#: lib/commit.tcl:272 msgid "Calling commit-msg hook..." msgstr "Anropar kroken för incheckningsmeddelande (commit-msg)..." -#: lib/commit.tcl:279 +#: lib/commit.tcl:287 msgid "Commit declined by commit-msg hook." msgstr "Incheckning avvisad av kroken för incheckningsmeddelande (commit-msg)." -#: lib/commit.tcl:292 +#: lib/commit.tcl:300 msgid "Committing changes..." msgstr "Checkar in ändringar..." -#: lib/commit.tcl:308 +#: lib/commit.tcl:316 msgid "write-tree failed:" msgstr "write-tree misslyckades:" -#: lib/commit.tcl:309 lib/commit.tcl:353 lib/commit.tcl:373 +#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382 msgid "Commit failed." msgstr "Incheckningen misslyckades." -#: lib/commit.tcl:326 +#: lib/commit.tcl:334 #, tcl-format msgid "Commit %s appears to be corrupt" msgstr "Incheckningen %s verkar vara trasig" -#: lib/commit.tcl:331 +#: lib/commit.tcl:339 msgid "" "No changes to commit.\n" "\n" @@ -1440,19 +1479,19 @@ msgstr "" "\n" "En sökning kommer att startas automatiskt nu.\n" -#: lib/commit.tcl:338 +#: lib/commit.tcl:346 msgid "No changes to commit." msgstr "Inga ändringar att checka in." -#: lib/commit.tcl:352 +#: lib/commit.tcl:360 msgid "commit-tree failed:" msgstr "commit-tree misslyckades:" -#: lib/commit.tcl:372 +#: lib/commit.tcl:381 msgid "update-ref failed:" msgstr "update-ref misslyckades:" -#: lib/commit.tcl:460 +#: lib/commit.tcl:469 #, tcl-format msgid "Created commit %s: %s" msgstr "Skapade incheckningen %s: %s" @@ -1505,20 +1544,20 @@ msgstr "Komprimerar objektdatabasen" msgid "Verifying the object database with fsck-objects" msgstr "Verifierar objektdatabasen med fsck-objects" -#: lib/database.tcl:108 +#: lib/database.tcl:107 #, 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" +"the database.\n" "\n" "Compress the database now?" msgstr "" "Arkivet har för närvarande omkring %i lösa objekt.\n" "\n" "För att bibehålla optimal prestanda rekommenderas det å det bestämdaste att " -"du komprimerar databasen när den innehåller mer än %i lösa objekt.\n" +"du komprimerar databasen.\n" "\n" "Komprimera databasen nu?" @@ -1527,7 +1566,7 @@ msgstr "" msgid "Invalid date from Git: %s" msgstr "Ogiltigt datum från Git: %s" -#: lib/diff.tcl:59 +#: lib/diff.tcl:64 #, tcl-format msgid "" "No differences detected.\n" @@ -1550,12 +1589,12 @@ msgstr "" "En sökning kommer automatiskt att startas för att hitta andra filer som kan " "vara i samma tillstånd." -#: lib/diff.tcl:99 +#: lib/diff.tcl:104 #, tcl-format msgid "Loading diff of %s..." msgstr "Läser differens för %s..." -#: lib/diff.tcl:120 +#: lib/diff.tcl:125 msgid "" "LOCAL: deleted\n" "REMOTE:\n" @@ -1563,7 +1602,7 @@ msgstr "" "LOKAL: borttagen\n" "FJÄRR:\n" -#: lib/diff.tcl:125 +#: lib/diff.tcl:130 msgid "" "REMOTE: deleted\n" "LOCAL:\n" @@ -1571,32 +1610,32 @@ msgstr "" "FJÄRR: borttagen\n" "LOKAL:\n" -#: lib/diff.tcl:132 +#: lib/diff.tcl:137 msgid "LOCAL:\n" msgstr "LOKAL:\n" -#: lib/diff.tcl:135 +#: lib/diff.tcl:140 msgid "REMOTE:\n" msgstr "FJÄRR:\n" -#: lib/diff.tcl:197 lib/diff.tcl:296 +#: lib/diff.tcl:202 lib/diff.tcl:319 #, tcl-format msgid "Unable to display %s" msgstr "Kan inte visa %s" -#: lib/diff.tcl:198 +#: lib/diff.tcl:203 msgid "Error loading file:" msgstr "Fel vid läsning av fil:" -#: lib/diff.tcl:205 +#: lib/diff.tcl:210 msgid "Git Repository (subproject)" msgstr "Gitarkiv (underprojekt)" -#: lib/diff.tcl:217 +#: lib/diff.tcl:222 msgid "* Binary file (not showing content)." msgstr "* Binärfil (visar inte innehållet)." -#: lib/diff.tcl:222 +#: lib/diff.tcl:227 #, tcl-format msgid "" "* Untracked file is %d bytes.\n" @@ -1605,7 +1644,7 @@ msgstr "" "* Den ospårade filen är %d byte.\n" "* Visar endast inledande %d byte.\n" -#: lib/diff.tcl:228 +#: lib/diff.tcl:233 #, tcl-format msgid "" "\n" @@ -1616,19 +1655,19 @@ msgstr "" "* Den ospårade filen klipptes här av %s.\n" "* För att se hela filen, använd ett externt redigeringsprogram.\n" -#: lib/diff.tcl:436 +#: lib/diff.tcl:482 msgid "Failed to unstage selected hunk." msgstr "Kunde inte ta bort den valda delen från kön." -#: lib/diff.tcl:443 +#: lib/diff.tcl:489 msgid "Failed to stage selected hunk." msgstr "Kunde inte lägga till den valda delen till kön." -#: lib/diff.tcl:509 +#: lib/diff.tcl:568 msgid "Failed to unstage selected line." msgstr "Kunde inte ta bort den valda raden från kön." -#: lib/diff.tcl:517 +#: lib/diff.tcl:576 msgid "Failed to stage selected line." msgstr "Kunde inte lägga till den valda raden till kön." @@ -1665,7 +1704,7 @@ msgstr "Kunde inte låsa upp indexet." msgid "Index Error" msgstr "Indexfel" -#: lib/index.tcl:21 +#: lib/index.tcl:17 msgid "" "Updating the Git index failed. A rescan will be automatically started to " "resynchronize git-gui." @@ -1673,7 +1712,7 @@ msgstr "" "Misslyckades med att uppdatera Gitindexet. En omsökning kommer att startas " "automatiskt för att synkronisera om git-gui." -#: lib/index.tcl:27 +#: lib/index.tcl:28 msgid "Continue" msgstr "Forstätt" @@ -1681,44 +1720,44 @@ msgstr "Forstätt" msgid "Unlock Index" msgstr "Lås upp index" -#: lib/index.tcl:287 +#: lib/index.tcl:289 #, tcl-format msgid "Unstaging %s from commit" msgstr "Tar bort %s för incheckningskön" -#: lib/index.tcl:326 +#: lib/index.tcl:328 msgid "Ready to commit." msgstr "Redo att checka in." -#: lib/index.tcl:339 +#: lib/index.tcl:341 #, tcl-format msgid "Adding %s" msgstr "Lägger till %s" -#: lib/index.tcl:396 +#: lib/index.tcl:398 #, tcl-format msgid "Revert changes in file %s?" msgstr "Återställ ändringarna i filen %s?" -#: lib/index.tcl:398 +#: lib/index.tcl:400 #, tcl-format msgid "Revert changes in these %i files?" msgstr "Återställ ändringarna i dessa %i filer?" -#: lib/index.tcl:406 +#: lib/index.tcl:408 msgid "Any unstaged changes will be permanently lost by the revert." msgstr "" "Alla oköade ändringar kommer permanent gå förlorade vid återställningen." -#: lib/index.tcl:409 +#: lib/index.tcl:411 msgid "Do Nothing" msgstr "Gör ingenting" -#: lib/index.tcl:427 +#: lib/index.tcl:429 msgid "Reverting selected files" msgstr "Återställer valda filer" -#: lib/index.tcl:431 +#: lib/index.tcl:433 #, tcl-format msgid "Reverting %s" msgstr "Återställer %s" @@ -2195,10 +2234,6 @@ msgstr "Ta endast bort om" msgid "Merged Into:" msgstr "Sammanslagen i:" -#: lib/remote_branch_delete.tcl:119 -msgid "Always (Do not perform merge checks)" -msgstr "Alltid (utför inte sammanslagningstest)" - #: lib/remote_branch_delete.tcl:152 msgid "A branch is required for 'Merged Into'." msgstr "En gren krävs för \"Sammanslagen i\"." @@ -2227,26 +2262,16 @@ msgstr "" msgid "Please select one or more branches to delete." msgstr "Välj en eller flera grenar att ta bort." -#: lib/remote_branch_delete.tcl:216 -msgid "" -"Recovering deleted branches is difficult.\n" -"\n" -"Delete the selected branches?" -msgstr "" -"Det kan vara svårt att återställa borttagna grenar.\n" -"\n" -"Ta bort de valda grenarna?" - #: lib/remote_branch_delete.tcl:226 #, tcl-format msgid "Deleting branches from %s" msgstr "Tar bort grenar från %s" -#: lib/remote_branch_delete.tcl:286 +#: lib/remote_branch_delete.tcl:292 msgid "No repository selected." msgstr "Inget arkiv markerat." -#: lib/remote_branch_delete.tcl:291 +#: lib/remote_branch_delete.tcl:297 #, tcl-format msgid "Scanning %s..." msgstr "Söker %s..." @@ -2267,11 +2292,11 @@ msgstr "Föreg" msgid "Case-Sensitive" msgstr "Skilj på VERSALER/gemener" -#: lib/shortcut.tcl:20 lib/shortcut.tcl:61 +#: lib/shortcut.tcl:21 lib/shortcut.tcl:62 msgid "Cannot write shortcut:" msgstr "Kan inte skriva genväg:" -#: lib/shortcut.tcl:136 +#: lib/shortcut.tcl:137 msgid "Cannot write icon:" msgstr "Kan inte skriva ikon:" @@ -2545,6 +2570,15 @@ msgstr "Använd tunt paket (för långsamma nätverksanslutningar)" msgid "Include tags" msgstr "Ta med taggar" +#~ msgid "Cannot use funny .git directory:" +#~ msgstr "Kan inte använda underlig .git-katalog:" + +#~ msgid "Preferences..." +#~ msgstr "Inställningar..." + +#~ msgid "Always (Do not perform merge test.)" +#~ msgstr "Alltid (utför inte sammanslagningstest)." + #~ msgid "URL:" #~ msgstr "Webbadress:" diff --git a/git-instaweb.sh b/git-instaweb.sh index e9fb5aa67a..6a65f255cc 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -320,9 +320,23 @@ EOF else # plain-old CGI resolve_full_httpd - list_mods=$(echo "$full_httpd" | sed "s/-f$/-l/") + 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-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..51dd0d67ba 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -1,3 +1,4 @@ +#!/bin/sh # git-mergetool--lib is a library for common merge tool functions diff_mode() { test "$TOOL_MODE" = diff @@ -46,7 +47,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 +131,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 +337,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-parse-remote.sh b/git-parse-remote.sh index 5f47b18141..5f47b18141 100755..100644 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh diff --git a/git-pull.sh b/git-pull.sh index 2530f21861..38331a8611 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -13,10 +13,33 @@ set_reflog_action "pull $*" require_work_tree 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= +die_conflict () { + git diff-index --cached --name-status -r --ignore-submodules HEAD -- + if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then + die "Pull is not possible because you have unmerged files. +Please, fix them up in the work tree, and then use 'git add/rm <file>' +as appropriate to mark resolution, or use 'git commit -a'." + else + die "Pull is not possible because you have unmerged files." + fi +} + +die_merge () { + if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then + die "You have not concluded your merge (MERGE_HEAD exists). +Please, commit your changes before you can merge." + else + die "You have not concluded your merge (MERGE_HEAD exists)." + fi +} + +test -z "$(git ls-files -u)" || die_conflict +test -f "$GIT_DIR/MERGE_HEAD" && die_merge + +strategy_args= diffstat= no_commit= squash= no_ff= ff_only= +log_arg= verbosity= +merge_args= 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 +68,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) @@ -59,6 +84,18 @@ do esac strategy_args="${strategy_args}-s $strategy " ;; + -X*) + case "$#,$1" in + 1,-X) + usage ;; + *,-X) + xx="-X $(git rev-parse --sq-quote "$2")" + shift ;; + *,*) + xx=$(git rev-parse --sq-quote "$1") ;; + esac + merge_args="$merge_args$xx " + ;; -r|--r|--re|--reb|--reba|--rebas|--rebase) rebase=true ;; @@ -189,7 +226,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" || @@ -230,8 +267,15 @@ then fi 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 \ - "$merge_name" HEAD $merge_head $verbosity +case "$rebase" in +true) + eval="git-rebase $diffstat $strategy_args $merge_args" + eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}" + ;; +*) + eval="git-merge $diffstat $no_commit $squash $no_ff $ff_only" + eval="$eval $log_arg $strategy_args $merge_args" + eval="$eval \"\$merge_name\" HEAD $merge_head $verbosity" + ;; +esac +eval "exec $eval" diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 5014ae0579..3e4fd1456f 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -28,24 +28,81 @@ abort abort rebasing process and restore original branch skip skip current patch and continue rebasing process no-verify override pre-rebase hook from stopping the operation root rebase all reachable commmits up to the root(s) +autosquash move commits that begin with squash!/fixup! under -i " . git-sh-setup require_work_tree DOTEST="$GIT_DIR/rebase-merge" + +# The file containing rebase commands, comments, and empty lines. +# This file is created by "git rebase -i" then edited by the user. As +# the lines are processed, they are removed from the front of this +# file and written to the tail of $DONE. TODO="$DOTEST"/git-rebase-todo + +# The rebase command lines that have already been processed. A line +# is moved here when it is first handled, before any associated user +# actions. DONE="$DOTEST"/done + +# The commit message that is planned to be used for any changes that +# need to be committed following a user interaction. MSG="$DOTEST"/message + +# The file into which is accumulated the suggested commit message for +# squash/fixup commands. When the first of a series of squash/fixups +# is seen, the file is created and the commit message from the +# previous commit and from the first squash/fixup commit are written +# to it. The commit message for each subsequent squash/fixup commit +# is appended to the file as it is processed. +# +# The first line of the file is of the form +# # This is a combination of $COUNT commits. +# where $COUNT is the number of commits whose messages have been +# written to the file so far (including the initial "pick" commit). +# Each time that a commit message is processed, this line is read and +# updated. It is deleted just before the combined commit is made. SQUASH_MSG="$DOTEST"/message-squash + +# If the current series of squash/fixups has not yet included a squash +# command, then this file exists and holds the commit message of the +# original "pick" commit. (If the series ends without a "squash" +# command, then this can be used as the commit message of the combined +# commit without opening the editor.) +FIXUP_MSG="$DOTEST"/message-fixup + +# $REWRITTEN is the name of a directory containing files for each +# commit that is reachable by at least one merge base of $HEAD and +# $UPSTREAM. They are not necessarily rewritten, but their children +# might be. This ensures that commits on merged, but otherwise +# unrelated side branches are left alone. (Think "X" in the man page's +# example.) REWRITTEN="$DOTEST"/rewritten + DROPPED="$DOTEST"/dropped + +# A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and +# GIT_AUTHOR_DATE that will be used for the commit that is currently +# being rebased. +AUTHOR_SCRIPT="$DOTEST"/author-script + +# When an "edit" rebase command is being processed, the SHA1 of the +# commit to be edited is recorded in this file. When "git rebase +# --continue" is executed, if there are any staged changes then they +# will be amended to the HEAD commit, but only provided the HEAD +# commit is still the commit to be edited. When any other rebase +# command is processed, this file is deleted. +AMEND="$DOTEST"/amend + PRESERVE_MERGES= STRATEGY= ONTO= VERBOSE= OK_TO_SKIP_PRE_REBASE= REBASE_ROOT= +AUTOSQUASH= GIT_CHERRY_PICK_HELP=" After resolving the conflicts, mark the corrected paths with 'git add <paths>', and @@ -70,6 +127,11 @@ output () { esac } +# Output the commit message for the specified commit. +commit_message () { + git cat-file commit "$1" | sed "1,/^$/d" +} + run_pre_rebase_hook () { if test -z "$OK_TO_SKIP_PRE_REBASE" && test -x "$GIT_DIR/hooks/pre-rebase" @@ -129,10 +191,10 @@ make_patch () { echo "Root commit" ;; esac > "$DOTEST"/patch - test -f "$DOTEST"/message || - git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message - test -f "$DOTEST"/author-script || - get_author_ident_from_commit "$1" > "$DOTEST"/author-script + test -f "$MSG" || + commit_message "$1" > "$MSG" + test -f "$AUTHOR_SCRIPT" || + get_author_ident_from_commit "$1" > "$AUTHOR_SCRIPT" } die_with_patch () { @@ -150,13 +212,22 @@ has_action () { sane_grep '^[^#]' "$1" >/dev/null } +# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and +# GIT_AUTHOR_DATE exported from the current environment. +do_with_author () { + ( + export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE + "$@" + ) +} + pick_one () { no_ff= case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1" test -d "$REWRITTEN" && pick_one_preserving_merges "$@" && return - if test ! -z "$REBASE_ROOT" + if test -n "$REBASE_ROOT" then output git cherry-pick "$@" return @@ -164,11 +235,10 @@ pick_one () { parent_sha1=$(git rev-parse --verify $sha1^) || die "Could not get the parent of $sha1" current_sha1=$(git rev-parse --verify HEAD) - if test "$no_ff$current_sha1" = "$parent_sha1"; then + if test -z "$no_ff" && test "$current_sha1" = "$parent_sha1" + then 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 $(git rev-parse --short $sha1) else output git cherry-pick "$@" fi @@ -248,9 +318,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" : ' \([^ ]*\)') @@ -269,14 +339,11 @@ pick_one_preserving_merges () { # redo merge author_script=$(get_author_ident_from_commit $sha1) eval "$author_script" - msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')" + msg="$(commit_message $sha1)" # No point in merging the first parent, that's HEAD new_parents=${new_parents# $first_parent} - if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ - GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ - GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ - output git merge $STRATEGY -m "$msg" \ - $new_parents + if ! do_with_author output \ + git merge $STRATEGY -m "$msg" $new_parents then printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG die_with_patch $sha1 "Error redoing merge $sha1" @@ -300,34 +367,66 @@ nth_string () { esac } -make_squash_message () { +update_squash_messages () { if test -f "$SQUASH_MSG"; then - COUNT=$(($(sed -n "s/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p" \ - < "$SQUASH_MSG" | sed -ne '$p')+1)) - echo "# This is a combination of $COUNT commits." - sed -e 1d -e '2,/^./{ - /^$/d - }' <"$SQUASH_MSG" + mv "$SQUASH_MSG" "$SQUASH_MSG".bak || exit + COUNT=$(($(sed -n \ + -e "1s/^# This is a combination of \(.*\) commits\./\1/p" \ + -e "q" < "$SQUASH_MSG".bak)+1)) + { + echo "# This is a combination of $COUNT commits." + sed -e 1d -e '2,/^./{ + /^$/d + }' <"$SQUASH_MSG".bak + } >"$SQUASH_MSG" else + commit_message HEAD > "$FIXUP_MSG" || die "Cannot write $FIXUP_MSG" COUNT=2 - echo "# This is a combination of two commits." - echo "# The first commit's message is:" - echo - git cat-file commit HEAD | sed -e '1,/^$/d' + { + echo "# This is a combination of 2 commits." + echo "# The first commit's message is:" + echo + cat "$FIXUP_MSG" + } >"$SQUASH_MSG" fi - echo - echo "# This is the $(nth_string $COUNT) commit message:" - echo - git cat-file commit $1 | sed -e '1,/^$/d' + case $1 in + squash) + rm -f "$FIXUP_MSG" + echo + echo "# This is the $(nth_string $COUNT) commit message:" + echo + commit_message $2 + ;; + fixup) + echo + echo "# The $(nth_string $COUNT) commit message will be skipped:" + echo + commit_message $2 | sed -e 's/^/# /' + ;; + esac >>"$SQUASH_MSG" } peek_next_command () { - sed -n "1s/ .*$//p" < "$TODO" + sed -n -e "/^#/d" -e '/^$/d' -e "s/ .*//p" -e "q" < "$TODO" +} + +# A squash/fixup has failed. Prepare the long version of the squash +# commit message, then die_with_patch. This code path requires the +# user to edit the combined commit message for all commits that have +# been squashed/fixedup so far. So also erase the old squash +# messages, effectively causing the combined commit to be used as the +# new basis for any further squash/fixups. Args: sha1 rest +die_failed_squash() { + mv "$SQUASH_MSG" "$MSG" || exit + rm -f "$FIXUP_MSG" + cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit + warn + warn "Could not apply $1... $2" + die_with_patch $1 "" } do_next () { - rm -f "$DOTEST"/message "$DOTEST"/author-script \ - "$DOTEST"/amend || exit + rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit read command sha1 rest < "$TODO" case "$command" in '#'*|''|noop) @@ -340,6 +439,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 @@ -347,7 +454,7 @@ do_next () { pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" make_patch $sha1 - git rev-parse --verify HEAD > "$DOTEST"/amend + git rev-parse --verify HEAD > "$AMEND" warn "Stopped at $sha1... $rest" warn "You can amend the commit now, with" warn @@ -359,52 +466,49 @@ do_next () { warn exit 0 ;; - squash|s) - comment_for_reflog squash + squash|s|fixup|f) + case "$command" in + squash|s) + squash_style=squash + ;; + fixup|f) + squash_style=fixup + ;; + esac + comment_for_reflog $squash_style test -f "$DONE" && has_action "$DONE" || - die "Cannot 'squash' without a previous commit" + die "Cannot '$squash_style' without a previous commit" mark_action_done - make_squash_message $sha1 > "$MSG" - failed=f + update_squash_messages $squash_style $sha1 author_script=$(get_author_ident_from_commit HEAD) + echo "$author_script" > "$AUTHOR_SCRIPT" + eval "$author_script" output git reset --soft HEAD^ - pick_one -n $sha1 || failed=t + pick_one -n $sha1 || die_failed_squash $sha1 "$rest" case "$(peek_next_command)" in - squash|s) - USE_OUTPUT=output - MSG_OPT=-F - EDIT_OR_FILE="$MSG" - cp "$MSG" "$SQUASH_MSG" + squash|s|fixup|f) + # This is an intermediate commit; its message will only be + # used in case of trouble. So use the long version: + do_with_author output git commit --no-verify -F "$SQUASH_MSG" || + die_failed_squash $sha1 "$rest" ;; *) - USE_OUTPUT= - MSG_OPT= - EDIT_OR_FILE=-e - rm -f "$SQUASH_MSG" || exit - cp "$MSG" "$GIT_DIR"/SQUASH_MSG - rm -f "$GIT_DIR"/MERGE_MSG || exit + # This is the final command of this squash/fixup group + if test -f "$FIXUP_MSG" + then + do_with_author git commit --no-verify -F "$FIXUP_MSG" || + die_failed_squash $sha1 "$rest" + else + cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit + rm -f "$GIT_DIR"/MERGE_MSG + do_with_author git commit --no-verify -e || + die_failed_squash $sha1 "$rest" + fi + rm -f "$SQUASH_MSG" "$FIXUP_MSG" ;; esac - echo "$author_script" > "$DOTEST"/author-script - if test $failed = f - then - # This is like --amend, but with a different message - eval "$author_script" - GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ - GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ - GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ - $USE_OUTPUT git commit --no-verify \ - $MSG_OPT "$EDIT_OR_FILE" || failed=t - fi - if test $failed = t - then - cp "$MSG" "$GIT_DIR"/MERGE_MSG - warn - warn "Could not apply $sha1... $rest" - die_with_patch $sha1 "" - fi ;; *) warn "Unknown command: $command $sha1 $rest" @@ -487,6 +591,56 @@ get_saved_options () { test -f "$DOTEST"/rebase-root && REBASE_ROOT=t } +# Rearrange the todo list that has both "pick sha1 msg" and +# "pick sha1 fixup!/squash! msg" appears in it so that the latter +# comes immediately after the former, and change "pick" to +# "fixup"/"squash". +rearrange_squash () { + sed -n -e 's/^pick \([0-9a-f]*\) \(squash\)! /\1 \2 /p' \ + -e 's/^pick \([0-9a-f]*\) \(fixup\)! /\1 \2 /p' \ + "$1" >"$1.sq" + test -s "$1.sq" || return + + used= + while read pick sha1 message + do + case " $used" in + *" $sha1 "*) continue ;; + esac + echo "$pick $sha1 $message" + while read squash action msg + do + case "$message" in + "$msg"*) + echo "$action $squash $action! $msg" + used="$used$squash " + ;; + esac + done <"$1.sq" + done >"$1.rearranged" <"$1" + cat "$1.rearranged" >"$1" + rm -f "$1.sq" "$1.rearranged" +} + +LF=' +' +parse_onto () { + case "$1" in + *...*) + if left=${1%...*} right=${1#*...} && + onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD}) + then + case "$onto" in + ?*"$LF"?* | '') + exit 1 ;; + esac + echo "$onto" + exit 0 + fi + esac + git rev-parse --verify "$1^0" +} + while test $# != 0 do case "$1" in @@ -514,21 +668,20 @@ do then : Nothing to commit -- skip this else - . "$DOTEST"/author-script || + . "$AUTHOR_SCRIPT" || die "Cannot find the author identity" amend= - if test -f "$DOTEST"/amend + if test -f "$AMEND" then amend=$(git rev-parse --verify HEAD) - test "$amend" = $(cat "$DOTEST"/amend) || + test "$amend" = $(cat "$AMEND") || die "\ You have uncommitted changes in your working tree. Please, commit them first and then run 'git rebase --continue' again." git reset --soft HEAD^ || die "Cannot rewind the HEAD" fi - export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE && - git commit --no-verify -F "$DOTEST"/message -e || { + do_with_author git commit --no-verify -F "$MSG" -e || { test -n "$amend" && git reset --soft $amend die "Could not commit staged changes." } @@ -592,9 +745,12 @@ first and then run 'git rebase --continue' again." --root) REBASE_ROOT=t ;; + --autosquash) + AUTOSQUASH=t + ;; --onto) shift - ONTO=$(git rev-parse --verify "$1") || + ONTO=$(parse_onto "$1") || die "Does not point to a valid commit: $1" ;; --) @@ -652,13 +808,6 @@ first and then run 'git rebase --continue' again." test t = "$VERBOSE" && : > "$DOTEST"/verbose if test t = "$PRESERVE_MERGES" then - # $REWRITTEN contains files for each commit that is - # reachable by at least one merge base of $HEAD and - # $UPSTREAM. They are not necessarily rewritten, but - # their children might be. - # This ensures that commits on merged, but otherwise - # unrelated side branches are left alone. (Think "X" - # in the man page's example.) if test -z "$REBASE_ROOT" then mkdir "$REWRITTEN" && @@ -751,14 +900,17 @@ first and then run 'git rebase --continue' again." fi test -s "$TODO" || echo noop >> "$TODO" + test -n "$AUTOSQUASH" && rearrange_squash "$TODO" cat >> "$TODO" << EOF # Rebase $SHORTREVISIONS onto $SHORTONTO # # 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 +# f, fixup = like "squash", but discard this commit's log message # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. diff --git a/git-rebase.sh b/git-rebase.sh index 0ec435558f..fb4fef7b1d 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -34,6 +34,8 @@ set_reflog_action rebase require_work_tree cd_to_toplevel +LF=' +' OK_TO_SKIP_PRE_REBASE= RESOLVEMSG=" When you have resolved this problem run \"git rebase --continue\". @@ -50,6 +52,7 @@ diffstat=$(git config --bool rebase.stat) git_am_opt= rebase_root= force_rebase= +allow_rerere_autoupdate= continue_merge () { test -n "$prev_head" || die "prev_head must be defined" @@ -82,10 +85,8 @@ continue_merge () { printf "Already applied: %0${prec}d " $msgnum fi fi - if test -z "$GIT_QUIET" - then - git rev-list --pretty=oneline -1 "$cmt" | sed -e 's/^[^ ]* //' - fi + test -z "$GIT_QUIET" && + GIT_PAGER='' git log --format=%s -1 "$cmt" prev_head=`git rev-parse HEAD^0` # save the resulting commit so we can read-tree on it later @@ -118,7 +119,7 @@ call_merge () { return ;; 1) - git rerere + git rerere $allow_rerere_autoupdate die "$RESOLVEMSG" ;; 2) @@ -349,6 +350,9 @@ do -f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase) force_rebase=t ;; + --rerere-autoupdate|--no-rerere-autoupdate) + allow_rerere_autoupdate="$1" + ;; -*) usage ;; @@ -417,7 +421,27 @@ fi # Make sure the branch to rebase onto is valid. onto_name=${newbase-"$upstream_name"} -onto=$(git rev-parse --verify "${onto_name}^0") || exit +case "$onto_name" in +*...*) + if left=${onto_name%...*} right=${onto_name#*...} && + onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD}) + then + case "$onto" in + ?*"$LF"?*) + die "$onto_name: there are more than one merge bases" + ;; + '') + die "$onto_name: there is no merge base" + ;; + esac + else + die "$onto_name: there is no merge base" + fi + ;; +*) + onto=$(git rev-parse --verify "${onto_name}^0") || exit + ;; +esac # If a hook exists, give it a chance to interrupt run_pre_rebase_hook "$upstream_arg" "$@" @@ -496,7 +520,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-request-pull.sh b/git-request-pull.sh index 630ceddf03..8fd15f6df4 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -65,11 +65,11 @@ if [ -z "$branch" ]; then status=1 fi -echo "The following changes since commit $baserev:" -git shortlog --max-count=1 $baserev | sed -e 's/^\(.\)/ \1/' +git show -s --format='The following changes since commit %H: -echo "are available in the git repository at:" -echo + %s (%ci) + +are available in the git repository at:' $baserev echo " $url $branch" echo 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..d56426dd39 100755..100644 --- 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 () { @@ -127,20 +120,11 @@ is_bare_repository () { } cd_to_toplevel () { - cdup=$(git rev-parse --show-cdup) - if test ! -z "$cdup" - then - # The "-P" option says to follow "physical" directory - # structure instead of following symbolic links. When cdup is - # "../", this means following the ".." entry in the current - # directory instead textually removing a symlink path element - # from the PWD shell variable. The "-P" behavior is more - # consistent with the C-style chdir used by most of Git. - cd -P "$cdup" || { - echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree" - exit 1 - } - fi + cdup=$(git rev-parse --show-toplevel) && + cd "$cdup" || { + echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree" + exit 1 + } } require_work_tree () { diff --git a/git-stash.sh b/git-stash.sh index 79b2771099..2d69196393 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 () { @@ -387,11 +386,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..664f21721c 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 @@ -683,7 +688,11 @@ cmd_summary() { echo done | if test -n "$for_status"; then - echo "# Modified submodules:" + if [ -n "$files" ]; then + echo "# Submodules changed but not updated:" + else + echo "# Submodule changes to be committed:" + fi echo "#" sed -e 's|^|# |' -e 's|^# $|#|' else diff --git a/git-svn.perl b/git-svn.perl index eb4b75aff3..49dd649bc5 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -26,6 +26,7 @@ if (! exists $ENV{SVN_SSH}) { $ENV{SVN_SSH} = $ENV{GIT_SSH}; if ($^O eq 'msys') { $ENV{SVN_SSH} =~ s/\\/\\\\/g; + $ENV{SVN_SSH} =~ s/(.*)/"$1"/; } } } @@ -35,11 +36,13 @@ $ENV{TZ} = 'UTC'; $| = 1; # unbuffer STDOUT sub fatal (@) { print STDERR "@_\n"; exit 1 } -require SVN::Core; # use()-ing this causes segfaults for me... *shrug* -require SVN::Ra; -require SVN::Delta; -if ($SVN::Core::VERSION lt '1.1.0') { - fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)"; +sub _req_svn { + require SVN::Core; # use()-ing this causes segfaults for me... *shrug* + require SVN::Ra; + require SVN::Delta; + if ($SVN::Core::VERSION lt '1.1.0') { + fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)"; + } } my $can_compress = eval { require Compress::Zlib; 1}; push @Git::SVN::Ra::ISA, 'SVN::Ra'; @@ -115,6 +118,7 @@ my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared, 'use-svm-props' => sub { $icv{useSvmProps} = 1 }, 'use-svnsync-props' => sub { $icv{useSvnsyncProps} = 1 }, 'rewrite-root=s' => sub { $icv{rewriteRoot} = $_[1] }, + 'rewrite-uuid=s' => sub { $icv{rewriteUUID} = $_[1] }, %remote_opts ); my %cmt_opts = ( 'edit|e' => \$_edit, 'rmdir' => \$SVN::Git::Editor::_rmdir, @@ -155,12 +159,16 @@ my %cmd = ( { 'message|m=s' => \$_message, 'destination|d=s' => \$_branch_dest, 'dry-run|n' => \$_dry_run, - 'tag|t' => \$_tag } ], + 'tag|t' => \$_tag, + 'username=s' => \$Git::SVN::Prompt::_username, + 'commit-url=s' => \$_commit_url } ], tag => [ sub { $_tag = 1; cmd_branch(@_) }, 'Create a tag in the SVN repository', { 'message|m=s' => \$_message, 'destination|d=s' => \$_branch_dest, - 'dry-run|n' => \$_dry_run } ], + 'dry-run|n' => \$_dry_run, + 'username=s' => \$Git::SVN::Prompt::_username, + 'commit-url=s' => \$_commit_url } ], 'set-tree' => [ \&cmd_set_tree, "Set an SVN repository to a git tree-ish", { 'stdin' => \$_stdin, %cmt_opts, %fc_opts, } ], @@ -168,6 +176,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 +285,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 +400,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 +438,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 +671,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' }; @@ -701,7 +716,23 @@ sub cmd_branch { } } my ($lft, $rgt) = @{ $glob->{path} }{qw/left right/}; - my $dst = join '/', $remote->{url}, $lft, $branch_name, ($rgt || ()); + my $url; + if (defined $_commit_url) { + $url = $_commit_url; + } else { + $url = eval { command_oneline('config', '--get', + "svn-remote.$gs->{repo_id}.commiturl") }; + if (!$url) { + $url = $remote->{url}; + } + } + my $dst = join '/', $url, $lft, $branch_name, ($rgt || ()); + + if ($dst =~ /^https:/ && $src =~ /^http:/) { + $src=~s/^http:/https:/; + } + + ::_req_svn(); my $ctx = SVN::Client->new( auth => Git::SVN::Ra::_auth_providers(), @@ -769,6 +800,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 +862,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 +984,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 +1235,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 +1361,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 +1429,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 +1659,8 @@ 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 +use Memoize::Storable; my ($_gc_nr, $_gc_period); @@ -1728,7 +1768,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 +1809,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); @@ -1787,8 +1831,8 @@ sub read_all_remotes { my $rs = { t => $t, remote => $remote, - path => Git::SVN::GlobSpec->new($local_ref), - ref => Git::SVN::GlobSpec->new($remote_ref) }; + path => Git::SVN::GlobSpec->new($local_ref, 1), + ref => Git::SVN::GlobSpec->new($remote_ref, 0) }; if (length($rs->{ref}->{right}) != 0) { die "The '*' glob character must be the last ", "character of '$remote_ref'\n"; @@ -1979,7 +2023,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#^\./?#); @@ -2170,6 +2214,10 @@ sub svnsync { die "Can't have both 'useSvnsyncProps' and 'rewriteRoot' ", "options set!\n"; } + if ($self->rewrite_uuid) { + die "Can't have both 'useSvnsyncProps' and 'rewriteUUID' ", + "options set!\n"; + } my $svnsync; # see if we have it in our config, first: @@ -2433,12 +2481,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; } @@ -2457,6 +2499,20 @@ sub rewrite_root { $self->{-rewrite_root} = $rwr; } +sub rewrite_uuid { + my ($self) = @_; + return $self->{-rewrite_uuid} if exists $self->{-rewrite_uuid}; + my $k = "svn-remote.$self->{repo_id}.rewriteUUID"; + my $rwid = eval { command_oneline(qw/config --get/, $k) }; + if ($rwid) { + $rwid =~ s#/+$##; + if ($rwid !~ m#^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$#) { + die "$rwid is not a valid UUID (key: $k)\n"; + } + } + $self->{-rewrite_uuid} = $rwid; +} + sub metadata_url { my ($self) = @_; ($self->rewrite_root || $self->{url}) . @@ -2725,6 +2781,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 +2989,326 @@ 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)}; + } + for my $commit (keys %commits) { + if (has_no_changes($commit)) { + delete $commits{$commit}; + } + } + return (keys %commits); +} + +sub has_no_changes { + my $commit = shift; + + my @revs = split / /, command_oneline( + qw(rev-list --parents -1 -m), $commit); + + # Commits with no parents, e.g. the start of a partial branch, + # have changes by definition. + return 1 if (@revs < 2); + + # Commits with multiple parents, e.g a merge, have no changes + # by definition. + return 0 if (@revs > 2); + + return (command_oneline("rev-parse", "$commit^{tree}") eq + command_oneline("rev-parse", "$commit~1^{tree}")); +} + +# The GIT_DIR environment variable is not always set until after the command +# line arguments are processed, so we can't memoize in a BEGIN block. +{ + my $memoized = 0; + + sub memoize_svn_mergeinfo_functions { + return if $memoized; + $memoized = 1; + + my $cache_path = "$ENV{GIT_DIR}/svn/.caches/"; + mkpath([$cache_path]) unless -d $cache_path; + + tie my %lookup_svn_merge_cache => 'Memoize::Storable', + "$cache_path/lookup_svn_merge.db", 'nstore'; + memoize 'lookup_svn_merge', + SCALAR_CACHE => 'FAULT', + LIST_CACHE => ['HASH' => \%lookup_svn_merge_cache], + ; + + tie my %check_cherry_pick_cache => 'Memoize::Storable', + "$cache_path/check_cherry_pick.db", 'nstore'; + memoize 'check_cherry_pick', + SCALAR_CACHE => 'FAULT', + LIST_CACHE => ['HASH' => \%check_cherry_pick_cache], + ; + + tie my %has_no_changes_cache => 'Memoize::Storable', + "$cache_path/has_no_changes.db", 'nstore'; + memoize 'has_no_changes', + SCALAR_CACHE => ['HASH' => \%has_no_changes_cache], + LIST_CACHE => 'FAULT', + ; + } +} + +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... + + memoize_svn_mergeinfo_functions(); + + # 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; + eval { + $merge_base = command_oneline( + "merge-base", + @$parents, $merge_tip, + ); + }; + if ($@) { + die "An error occurred during merge-base" + unless $@->isa("Git::Error::Command"); + + warn "W: Cannot find common ancestor between ". + "@$parents and $merge_tip. Ignoring merge info.\n"; + next; + } + + # 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[$j]); + } + } + } + } + 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; @@ -2939,6 +3362,10 @@ sub make_log_entry { die "Can't have both 'useSvmProps' and 'rewriteRoot' ", "options set!\n"; } + if ($self->rewrite_uuid) { + die "Can't have both 'useSvmProps' and 'rewriteUUID' ", + "options set!\n"; + } my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}i; # we don't want "SVM: initializing mirror for junk" ... return undef if $r == 0; @@ -2969,10 +3396,10 @@ sub make_log_entry { } else { my $url = $self->metadata_url; remove_username($url); - $log_entry{metadata} = "$url\@$rev " . - $self->ra->get_uuid; - $email ||= "$author\@" . $self->ra->get_uuid; - $commit_email ||= "$author\@" . $self->ra->get_uuid; + my $uuid = $self->rewrite_uuid || $self->ra->get_uuid; + $log_entry{metadata} = "$url\@$rev " . $uuid; + $email ||= "$author\@" . $uuid; + $commit_email ||= "$author\@" . $uuid; } $log_entry{name} = $name; $log_entry{email} = $email; @@ -3054,7 +3481,7 @@ sub rebuild { '--'); my $metadata_url = $self->metadata_url; remove_username($metadata_url); - my $svn_uuid = $self->ra_uuid; + my $svn_uuid = $self->rewrite_uuid || $self->ra_uuid; my $c; while (<$log>) { if ( m{^commit ($::sha1)$} ) { @@ -3411,6 +3838,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 +4125,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; } @@ -4430,6 +4863,8 @@ sub new { $url =~ s!/+$!!; return $RA if ($RA && $RA->{url} eq $url); + ::_req_svn(); + SVN::_Core::svn_config_ensure($config_dir, undef); my ($baton, $callbacks) = SVN::Core::auth_open_helper(_auth_providers); my $config = SVN::Core::config_get_config($config_dir); @@ -4837,6 +5272,7 @@ sub match_globs { next if (length $g->{path}->{right} && ($self->check_path($p, $r) != $SVN::Node::dir)); + next unless $p =~ /$g->{path}->{regex}/; $exists->{$p} = Git::SVN->init($self->{url}, $p, undef, $g->{ref}->full_path($de), 1); } @@ -5029,10 +5465,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); @@ -5612,29 +6046,48 @@ use strict; use warnings; sub new { - my ($class, $glob) = @_; + my ($class, $glob, $pattern_ok) = @_; my $re = $glob; $re =~ s!/+$!!g; # no need for trailing slashes - $re =~ m!^([^*]*)(\*(?:/\*)*)(.*)$!; - my $temp = $re; - my ($left, $right) = ($1, $3); - $re = $2; - my $depth = $re =~ tr/*/*/; - if ($depth != $temp =~ tr/*/*/) { - die "Only one set of wildcard directories " . - "(e.g. '*' or '*/*/*') is supported: '$glob'\n"; + my (@left, @right, @patterns); + my $state = "left"; + my $die_msg = "Only one set of wildcard directories " . + "(e.g. '*' or '*/*/*') is supported: '$glob'\n"; + for my $part (split(m|/|, $glob)) { + if ($part =~ /\*/ && $part ne "*") { + die "Invalid pattern in '$glob': $part\n"; + } elsif ($pattern_ok && $part =~ /[{}]/ && + $part !~ /^\{[^{}]+\}/) { + die "Invalid pattern in '$glob': $part\n"; + } + if ($part eq "*") { + die $die_msg if $state eq "right"; + $state = "pattern"; + push(@patterns, "[^/]*"); + } elsif ($pattern_ok && $part =~ /^\{(.*)\}$/) { + die $die_msg if $state eq "right"; + $state = "pattern"; + my $p = quotemeta($1); + $p =~ s/\\,/|/g; + push(@patterns, "(?:$p)"); + } else { + if ($state eq "left") { + push(@left, $part); + } else { + push(@right, $part); + $state = "right"; + } + } } + my $depth = @patterns; if ($depth == 0) { - die "One '*' is needed for glob: '$glob'\n"; - } - $re =~ s!\*!\[^/\]*!g; - $re = quotemeta($left) . "($re)" . quotemeta($right); - if (length $left && !($left =~ s!/+$!!g)) { - die "Missing trailing '/' on left side of: '$glob' ($left)\n"; - } - if (length $right && !($right =~ s!^/+!!g)) { - die "Missing leading '/' on right side of: '$glob' ($right)\n"; + die "One '*' is needed in glob: '$glob'\n"; } + my $left = join('/', @left); + my $right = join('/', @right); + $re = join('/', @patterns); + $re = join('\/', + grep(length, quotemeta($left), "($re)", quotemeta($right))); my $left_re = qr/^\/\Q$left\E(\/|$)/; bless { left => $left, right => $right, left_regex => $left_re, regex => qr/$re/, glob => $glob, depth => $depth }, $class; @@ -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 }, @@ -310,8 +317,10 @@ static void handle_internal_command(int argc, const char **argv) { "fsck-objects", cmd_fsck, RUN_SETUP }, { "gc", cmd_gc, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id }, - { "grep", cmd_grep, RUN_SETUP | USE_PAGER }, + { "grep", cmd_grep, USE_PAGER }, + { "hash-object", cmd_hash_object }, { "help", cmd_help }, + { "index-pack", cmd_index_pack }, { "init", cmd_init_db }, { "init-db", cmd_init_db }, { "log", cmd_log, RUN_SETUP | USE_PAGER }, @@ -323,13 +332,20 @@ static void handle_internal_command(int argc, const char **argv) { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE }, { "merge-base", cmd_merge_base, RUN_SETUP }, { "merge-file", cmd_merge_file }, + { "merge-index", cmd_merge_index, RUN_SETUP }, { "merge-ours", cmd_merge_ours, RUN_SETUP }, { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE }, + { "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE }, + { "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE }, { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE }, + { "merge-tree", cmd_merge_tree, RUN_SETUP }, + { "mktag", cmd_mktag, RUN_SETUP }, { "mktree", cmd_mktree, RUN_SETUP }, { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, + { "pack-redundant", cmd_pack_redundant, RUN_SETUP }, + { "patch-id", cmd_patch_id }, { "peek-remote", cmd_ls_remote }, { "pickaxe", cmd_blame, RUN_SETUP }, { "prune", cmd_prune, RUN_SETUP }, @@ -356,11 +372,13 @@ static void handle_internal_command(int argc, const char **argv) { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, { "tag", cmd_tag, RUN_SETUP }, { "tar-tree", cmd_tar_tree }, + { "unpack-file", cmd_unpack_file, RUN_SETUP }, { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, { "update-index", cmd_update_index, RUN_SETUP }, { "update-ref", cmd_update_ref, RUN_SETUP }, { "update-server-info", cmd_update_server_info, RUN_SETUP }, { "upload-archive", cmd_upload_archive }, + { "var", cmd_var }, { "verify-tag", cmd_verify_tag, RUN_SETUP }, { "version", cmd_version }, { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, @@ -509,7 +527,7 @@ int main(int argc, const char **argv) break; if (was_alias) { fprintf(stderr, "Expansion of alias '%s' failed; " - "'%s' is not a git-command\n", + "'%s' is not a git command\n", cmd, argv[0]); exit(1); } diff --git a/git.spec.in b/git.spec.in index ab224f7eae..ee74a5eed7 100644 --- a/git.spec.in +++ b/git.spec.in @@ -98,6 +98,7 @@ BuildRequires: perl(Error) Perl interface to Git %define path_settings ETC_GITCONFIG=/etc/gitconfig prefix=%{_prefix} mandir=%{_mandir} htmldir=%{_docdir}/%{name}-%{version} +%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} %prep %setup -q @@ -112,6 +113,7 @@ rm -rf $RPM_BUILD_ROOT make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" DESTDIR=$RPM_BUILD_ROOT \ %{path_settings} \ INSTALLDIRS=vendor install %{!?_without_docs: install-doc} +test ! -d $RPM_BUILD_ROOT%{python_sitelib} || rm -fr $RPM_BUILD_ROOT%{python_sitelib} find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';' find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';' find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';' @@ -190,6 +192,12 @@ rm -rf $RPM_BUILD_ROOT # No files for you! %changelog +* Sun Jan 31 2010 Junio C Hamano <gitster@pobox.com> +- Do not use %define inside %{!?...} construct. + +* Sat Jan 30 2010 Junio C Hamano <gitster@pobox.com> +- We don't ship Python bits until a real foreign scm interface comes. + * Mon Feb 04 2009 David J. Mellor <dmellor@whistlingcat.com> - fixed broken git help -w after renaming the git-core package to git. 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..1f36a3e815 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)]} { @@ -128,7 +130,7 @@ proc unmerged_files {files} { } proc parseviewargs {n arglist} { - global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs + global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs env set vdatemode($n) 0 set vmergeonly($n) 0 @@ -208,6 +210,9 @@ proc parseviewargs {n arglist} { # git rev-parse doesn't understand --merge lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD } + "--no-replace-objects" { + set env(GIT_NO_REPLACE_OBJECTS) "1" + } "-*" { # Other flag arguments including -<n> if {[string is digit -strict [string range $arg 1 end]]} { @@ -265,7 +270,7 @@ proc parseviewrevs {view revs} { } lappend badrev $line } - } + } error_popup "[mc "Error parsing revisions:"] $err" return {} } @@ -987,6 +992,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 +1786,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 +1813,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 +1829,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 +1935,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 +1966,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 +2018,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 +2032,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,28 +2062,37 @@ 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 set sha1but .tf.bar.sha1label - button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \ + button $sha1but -text "[mc "SHA1 ID:"] " -state disabled -relief flat \ -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 +2112,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 +2157,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 +2174,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 +2193,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 +2237,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 +2248,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 +2289,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 +2314,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 +2349,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 +2579,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 +2619,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 +2646,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 +2662,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 +2686,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 +2715,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 +2746,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 +2770,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 © 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 +2807,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 +2851,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 +3504,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 +3969,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 +3997,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 +4011,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 +4050,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 +6917,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 +7068,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 +7093,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 +7140,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 +7155,7 @@ proc goforw {} { focus . if {$historyindex < [llength $history]} { + save_position set cmd [lindex $history $historyindex] incr historyindex godo $cmd @@ -7224,7 +7388,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 +7507,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 +7531,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 +7677,23 @@ 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]} { + set line [encoding convertfrom $diffencoding $line] + $ctext insert end "$line\n" dresult + } elseif {![string compare -length 3 " <" $line]} { + set line [encoding convertfrom $diffencoding $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 +7771,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 +8271,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 +8302,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 +8316,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 +8504,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 +8562,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 +8590,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 +8600,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 +8682,35 @@ 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}::label $top.op -text [mc "Tag message is optional"] + grid $top.op -columnspan 2 -sticky we + ${NS}::label $top.mlab -text [mc "Tag message:"] + ${NS}::entry $top.msg -width 60 + grid $top.mlab $top.msg -sticky w + ${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 @@ -8505,6 +8725,7 @@ proc domktag {} { set id [$mktagtop.sha1 get] set tag [$mktagtop.tag get] + set msg [$mktagtop.msg get] if {$tag == {}} { error_popup [mc "No tag name specified"] $mktagtop return 0 @@ -8514,7 +8735,11 @@ proc domktag {} { return 0 } if {[catch { - exec git tag $tag $id + if {$msg != {}} { + exec git tag -a -m $msg $tag $id + } else { + exec git tag $tag $id + } } err]} { error_popup "[mc "Error creating tag:"] $err" $mktagtop return 0 @@ -8573,34 +8798,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 +8856,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 +9019,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 +9099,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 +9194,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 +9204,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 +9215,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 +9425,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 +10487,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 @@ -10270,7 +10495,7 @@ proc showtag {tag isnew} { set linknum 0 if {![info exists tagcontents($tag)]} { catch { - set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)] + set tagcontents($tag) [exec git cat-file tag $tag] } } if {[info exists tagcontents($tag)]} { @@ -10279,6 +10504,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 +10523,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 +10549,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 +10581,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 +10619,7 @@ proc fontok {} { } set w $prefstop.$f $w conf -text $fontparam(family) -font $fontpref($f) - + fontcan } @@ -10407,6 +10634,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 +10672,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 +10685,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 +10843,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 +10880,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 +11291,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 +11356,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 +11365,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 +11424,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 +11503,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/de.po b/gitk-git/po/de.po index 53ef0d6359..c79aa9cbc8 100644 --- a/gitk-git/po/de.po +++ b/gitk-git/po/de.po @@ -8,33 +8,33 @@ msgid "" msgstr "" "Project-Id-Version: git-gui\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-05-12 21:55+0200\n" -"PO-Revision-Date: 2009-05-12 22:18+0200\n" +"POT-Creation-Date: 2010-01-27 20:21+0100\n" +"PO-Revision-Date: 2010-01-27 20:27+0100\n" "Last-Translator: Christian Stimming <stimming@tuhh.de>\n" "Language-Team: German\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: gitk:113 +#: gitk:115 msgid "Couldn't get list of unmerged files:" msgstr "Liste der nicht zusammengeführten Dateien nicht gefunden:" -#: gitk:268 +#: gitk:274 msgid "Error parsing revisions:" msgstr "Fehler beim Laden der Versionen:" -#: gitk:323 +#: gitk:329 msgid "Error executing --argscmd command:" msgstr "Fehler beim Ausführen des --argscmd-Kommandos:" -#: gitk:336 +#: gitk:342 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "Keine Dateien ausgewählt: Es wurde --merge angegeben, aber es existieren " "keine nicht zusammengeführten Dateien." -#: gitk:339 +#: gitk:345 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." @@ -42,719 +42,801 @@ msgstr "" "Keine Dateien ausgewählt: Es wurde --merge angegeben, aber es sind keine " "nicht zusammengeführten Dateien in der Dateiauswahl." -#: gitk:361 gitk:508 +#: gitk:367 gitk:514 msgid "Error executing git log:" msgstr "Fehler beim Ausführen von »git log«:" -#: gitk:379 gitk:524 +#: gitk:385 gitk:530 msgid "Reading" msgstr "Lesen" -#: gitk:439 gitk:4061 +#: gitk:445 gitk:4261 msgid "Reading commits..." msgstr "Versionen werden gelesen ..." -#: gitk:442 gitk:1560 gitk:4064 +#: gitk:448 gitk:1578 gitk:4264 msgid "No commits selected" msgstr "Keine Versionen ausgewählt" -#: gitk:1436 +#: gitk:1454 msgid "Can't parse git log output:" msgstr "Ausgabe von »git log« kann nicht erkannt werden:" -#: gitk:1656 +#: gitk:1674 msgid "No commit information available" msgstr "Keine Versionsinformation verfügbar" -#: gitk:1791 gitk:1815 gitk:3854 gitk:8714 gitk:10250 gitk:10422 +#: gitk:1816 +msgid "mc" +msgstr "mc" + +#: gitk:1851 gitk:4054 gitk:9044 gitk:10585 gitk:10804 msgid "OK" msgstr "Ok" -#: gitk:1817 gitk:3856 gitk:8311 gitk:8385 gitk:8495 gitk:8544 gitk:8716 -#: gitk:10251 gitk:10423 +#: gitk:1853 gitk:4056 gitk:8634 gitk:8713 gitk:8828 gitk:8877 gitk:9046 +#: gitk:10586 gitk:10805 msgid "Cancel" msgstr "Abbrechen" -#: gitk:1917 +#: gitk:1975 msgid "Update" msgstr "Aktualisieren" -#: gitk:1918 +#: gitk:1976 msgid "Reload" msgstr "Neu laden" -#: gitk:1919 +#: gitk:1977 msgid "Reread references" msgstr "Zweige neu laden" -#: gitk:1920 +#: gitk:1978 msgid "List references" msgstr "Zweige/Markierungen auflisten" -#: gitk:1922 +#: gitk:1980 msgid "Start git gui" msgstr "»git gui« starten" -#: gitk:1924 +#: gitk:1982 msgid "Quit" msgstr "Beenden" -#: gitk:1916 +#: gitk:1974 msgid "File" msgstr "Datei" -#: gitk:1928 +#: gitk:1986 msgid "Preferences" msgstr "Einstellungen" -#: gitk:1927 +#: gitk:1985 msgid "Edit" msgstr "Bearbeiten" -#: gitk:1932 +#: gitk:1990 msgid "New view..." msgstr "Neue Ansicht ..." -#: gitk:1933 +#: gitk:1991 msgid "Edit view..." msgstr "Ansicht bearbeiten ..." -#: gitk:1934 +#: gitk:1992 msgid "Delete view" msgstr "Ansicht entfernen" -#: gitk:1936 +#: gitk:1994 msgid "All files" msgstr "Alle Dateien" -#: gitk:1931 gitk:3666 +#: gitk:1989 gitk:3808 msgid "View" msgstr "Ansicht" -#: gitk:1941 gitk:1951 gitk:2650 +#: gitk:1999 gitk:2009 gitk:2780 msgid "About gitk" msgstr "Über gitk" -#: gitk:1942 gitk:1956 +#: gitk:2000 gitk:2014 msgid "Key bindings" msgstr "Tastenkürzel" -#: gitk:1940 gitk:1955 +#: gitk:1998 gitk:2013 msgid "Help" msgstr "Hilfe" -#: gitk:2016 -msgid "SHA1 ID: " -msgstr "SHA1:" +#: gitk:2091 gitk:8110 +msgid "SHA1 ID:" +msgstr "SHA1 ID:" -#: gitk:2047 +#: gitk:2122 msgid "Row" msgstr "Zeile" -#: gitk:2078 +#: gitk:2160 msgid "Find" msgstr "Suche" -#: gitk:2079 +#: gitk:2161 msgid "next" msgstr "nächste" -#: gitk:2080 +#: gitk:2162 msgid "prev" msgstr "vorige" -#: gitk:2081 +#: gitk:2163 msgid "commit" msgstr "Version nach" -#: gitk:2084 gitk:2086 gitk:4222 gitk:4245 gitk:4269 gitk:6210 gitk:6282 -#: gitk:6366 +#: gitk:2166 gitk:2168 gitk:4422 gitk:4445 gitk:4469 gitk:6410 gitk:6482 +#: gitk:6566 msgid "containing:" msgstr "Beschreibung:" -#: gitk:2087 gitk:3158 gitk:3163 gitk:4297 +#: gitk:2169 gitk:3290 gitk:3295 gitk:4497 msgid "touching paths:" msgstr "Dateien:" -#: gitk:2088 gitk:4302 +#: gitk:2170 gitk:4502 msgid "adding/removing string:" msgstr "Änderungen:" -#: gitk:2097 gitk:2099 +#: gitk:2179 gitk:2181 msgid "Exact" msgstr "Exakt" -#: gitk:2099 gitk:4377 gitk:6178 +#: gitk:2181 gitk:4577 gitk:6378 msgid "IgnCase" msgstr "Kein Groß/Klein" -#: gitk:2099 gitk:4271 gitk:4375 gitk:6174 +#: gitk:2181 gitk:4471 gitk:4575 gitk:6374 msgid "Regexp" msgstr "Regexp" -#: gitk:2101 gitk:2102 gitk:4396 gitk:4426 gitk:4433 gitk:6302 gitk:6370 +#: gitk:2183 gitk:2184 gitk:4596 gitk:4626 gitk:4633 gitk:6502 gitk:6570 msgid "All fields" msgstr "Alle Felder" -#: gitk:2102 gitk:4394 gitk:4426 gitk:6241 +#: gitk:2184 gitk:4594 gitk:4626 gitk:6441 msgid "Headline" msgstr "Überschrift" -#: gitk:2103 gitk:4394 gitk:6241 gitk:6370 gitk:6804 +#: gitk:2185 gitk:4594 gitk:6441 gitk:6570 gitk:7003 msgid "Comments" msgstr "Beschreibung" -#: gitk:2103 gitk:4394 gitk:4398 gitk:4433 gitk:6241 gitk:6739 gitk:7991 -#: gitk:8006 +#: gitk:2185 gitk:4594 gitk:4598 gitk:4633 gitk:6441 gitk:6938 gitk:8285 +#: gitk:8300 msgid "Author" msgstr "Autor" -#: gitk:2103 gitk:4394 gitk:6241 gitk:6741 +#: gitk:2185 gitk:4594 gitk:6441 gitk:6940 msgid "Committer" msgstr "Eintragender" -#: gitk:2132 +#: gitk:2216 msgid "Search" msgstr "Suchen" -#: gitk:2139 +#: gitk:2224 msgid "Diff" msgstr "Vergleich" -#: gitk:2141 +#: gitk:2226 msgid "Old version" msgstr "Alte Version" -#: gitk:2143 +#: gitk:2228 msgid "New version" msgstr "Neue Version" -#: gitk:2145 +#: gitk:2230 msgid "Lines of context" msgstr "Kontextzeilen" -#: gitk:2155 +#: gitk:2240 msgid "Ignore space change" msgstr "Leerzeichenänderungen ignorieren" -#: gitk:2213 +#: gitk:2299 msgid "Patch" msgstr "Patch" -#: gitk:2215 +#: gitk:2301 msgid "Tree" msgstr "Baum" -#: gitk:2359 gitk:2376 +#: gitk:2456 gitk:2473 msgid "Diff this -> selected" msgstr "Vergleich: diese -> gewählte" -#: gitk:2360 gitk:2377 +#: gitk:2457 gitk:2474 msgid "Diff selected -> this" msgstr "Vergleich: gewählte -> diese" -#: gitk:2361 gitk:2378 +#: gitk:2458 gitk:2475 msgid "Make patch" msgstr "Patch erstellen" -#: gitk:2362 gitk:8369 +#: gitk:2459 gitk:8692 msgid "Create tag" msgstr "Markierung erstellen" -#: gitk:2363 gitk:8475 +#: gitk:2460 gitk:8808 msgid "Write commit to file" msgstr "Version in Datei schreiben" -#: gitk:2364 gitk:8532 +#: gitk:2461 gitk:8865 msgid "Create new branch" msgstr "Neuen Zweig erstellen" -#: gitk:2365 +#: gitk:2462 msgid "Cherry-pick this commit" msgstr "Diese Version pflücken" -#: gitk:2366 +#: gitk:2463 msgid "Reset HEAD branch to here" msgstr "HEAD-Zweig auf diese Version zurücksetzen" -#: gitk:2367 +#: gitk:2464 msgid "Mark this commit" msgstr "Lesezeichen setzen" -#: gitk:2368 +#: gitk:2465 msgid "Return to mark" msgstr "Zum Lesezeichen" -#: gitk:2369 +#: gitk:2466 msgid "Find descendant of this and mark" msgstr "Abkömmling von Lesezeichen und dieser Version finden" -#: gitk:2370 +#: gitk:2467 msgid "Compare with marked commit" msgstr "Mit Lesezeichen vergleichen" -#: gitk:2384 +#: gitk:2481 msgid "Check out this branch" msgstr "Auf diesen Zweig umstellen" -#: gitk:2385 +#: gitk:2482 msgid "Remove this branch" msgstr "Zweig löschen" -#: gitk:2392 +#: gitk:2489 msgid "Highlight this too" msgstr "Diesen auch hervorheben" -#: gitk:2393 +#: gitk:2490 msgid "Highlight this only" msgstr "Nur diesen hervorheben" -#: gitk:2394 +#: gitk:2491 msgid "External diff" msgstr "Externes Diff-Programm" -#: gitk:2395 +#: gitk:2492 msgid "Blame parent commit" msgstr "Annotieren der Elternversion" -#: gitk:2402 +#: gitk:2499 msgid "Show origin of this line" msgstr "Herkunft dieser Zeile anzeigen" -#: gitk:2403 +#: gitk:2500 msgid "Run git gui blame on this line" msgstr "Diese Zeile annotieren (»git gui blame«)" -#: gitk:2652 +#: gitk:2782 msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2008 Paul Mackerras\n" +"Copyright ©9 2005-2009 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - eine Visualisierung der Git-Historie\n" "\n" -"Copyright © 2005-2008 Paul Mackerras\n" +"Copyright © 2005-2009 Paul Mackerras\n" "\n" -"Benutzung und Weiterverbreitung gemäß den Bedingungen der GNU General Public " -"License" +"Benutzung und Weiterverbreitung gemäß den Bedingungen der GNU General Public License" -#: gitk:2660 gitk:2722 gitk:8897 +#: gitk:2790 gitk:2854 gitk:9230 msgid "Close" msgstr "Schließen" -#: gitk:2679 +#: gitk:2811 msgid "Gitk key bindings" msgstr "Gitk-Tastaturbelegung" -#: gitk:2682 +#: gitk:2814 msgid "Gitk key bindings:" msgstr "Gitk-Tastaturbelegung:" -#: gitk:2684 +#: gitk:2816 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tBeenden" -#: gitk:2685 +#: gitk:2817 msgid "<Home>\t\tMove to first commit" msgstr "<Pos1>\t\tZur neuesten Version springen" -#: gitk:2686 +#: gitk:2818 msgid "<End>\t\tMove to last commit" msgstr "<Ende>\t\tZur ältesten Version springen" -#: gitk:2687 +#: gitk:2819 msgid "<Up>, p, i\tMove up one commit" msgstr "<Hoch>, p, i\tNächste neuere Version" -#: gitk:2688 +#: gitk:2820 msgid "<Down>, n, k\tMove down one commit" msgstr "<Runter>, n, k\tNächste ältere Version" -#: gitk:2689 +#: gitk:2821 msgid "<Left>, z, j\tGo back in history list" msgstr "<Links>, z, j\tEine Version zurückgehen" -#: gitk:2690 +#: gitk:2822 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Rechts>, x, l\tEine Version weitergehen" -#: gitk:2691 +#: gitk:2823 msgid "<PageUp>\tMove up one page in commit list" msgstr "<BildHoch>\tEine Seite nach oben blättern" -#: gitk:2692 +#: gitk:2824 msgid "<PageDown>\tMove down one page in commit list" msgstr "<BildRunter>\tEine Seite nach unten blättern" -#: gitk:2693 +#: gitk:2825 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Pos1>\tZum oberen Ende der Versionsliste blättern" -#: gitk:2694 +#: gitk:2826 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-Ende>\tZum unteren Ende der Versionsliste blättern" -#: gitk:2695 +#: gitk:2827 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Hoch>\tVersionsliste eine Zeile nach oben blättern" -#: gitk:2696 +#: gitk:2828 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Runter>\tVersionsliste eine Zeile nach unten blättern" -#: gitk:2697 +#: gitk:2829 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-BildHoch>\tVersionsliste eine Seite nach oben blättern" -#: gitk:2698 +#: gitk:2830 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-BildRunter>\tVersionsliste eine Seite nach unten blättern" -#: gitk:2699 +#: gitk:2831 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Umschalt-Hoch>\tRückwärts suchen (nach oben; neuere Versionen)" -#: gitk:2700 +#: gitk:2832 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Umschalt-Runter> Suchen (nach unten; ältere Versionen)" -#: gitk:2701 +#: gitk:2833 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Entf>, b\t\tVergleich eine Seite nach oben blättern" -#: gitk:2702 +#: gitk:2834 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Löschtaste>\tVergleich eine Seite nach oben blättern" -#: gitk:2703 +#: gitk:2835 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Leertaste>\tVergleich eine Seite nach unten blättern" -#: gitk:2704 +#: gitk:2836 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tVergleich um 18 Zeilen nach oben blättern" -#: gitk:2705 +#: gitk:2837 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tVergleich um 18 Zeilen nach unten blättern" -#: gitk:2706 +#: gitk:2838 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tSuchen" -#: gitk:2707 +#: gitk:2839 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tWeitersuchen" -#: gitk:2708 +#: gitk:2840 msgid "<Return>\tMove to next find hit" msgstr "<Eingabetaste>\tWeitersuchen" -#: gitk:2709 +#: gitk:2841 msgid "/\t\tFocus the search box" msgstr "/\t\tTastaturfokus ins Suchfeld" -#: gitk:2710 +#: gitk:2842 msgid "?\t\tMove to previous find hit" msgstr "?\t\tRückwärts weitersuchen" -#: gitk:2711 +#: gitk:2843 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tVergleich zur nächsten Datei blättern" -#: gitk:2712 +#: gitk:2844 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tWeitersuchen im Vergleich" -#: gitk:2713 +#: gitk:2845 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tRückwärts weitersuchen im Vergleich" -#: gitk:2714 +#: gitk:2846 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-Nummerblock-Plus>\tSchrift vergrößern" -#: gitk:2715 +#: gitk:2847 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-Plus>\tSchrift vergrößern" -#: gitk:2716 +#: gitk:2848 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-Nummernblock-Minus> Schrift verkleinern" -#: gitk:2717 +#: gitk:2849 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-Minus>\tSchrift verkleinern" -#: gitk:2718 +#: gitk:2850 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tAktualisieren" -#: gitk:3173 -#, tcl-format -msgid "Error getting \"%s\" from %s:" -msgstr "Fehler beim Holen von »%s« von »%s«:" - -#: gitk:3230 gitk:3239 +#: gitk:3305 gitk:3314 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Fehler beim Erzeugen des temporären Verzeichnisses »%s«:" -#: gitk:3251 +#: gitk:3327 +#, tcl-format +msgid "Error getting \"%s\" from %s:" +msgstr "Fehler beim Holen von »%s« von »%s«:" + +#: gitk:3390 msgid "command failed:" msgstr "Kommando fehlgeschlagen:" -#: gitk:3397 +#: gitk:3539 msgid "No such commit" msgstr "Version nicht gefunden" -#: gitk:3411 +#: gitk:3553 msgid "git gui blame: command failed:" msgstr "git gui blame: Kommando fehlgeschlagen:" -#: gitk:3442 +#: gitk:3584 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Zusammenführungs-Spitze konnte nicht gelesen werden: %s" -#: gitk:3450 +#: gitk:3592 #, tcl-format msgid "Error reading index: %s" msgstr "Fehler beim Lesen der Bereitstellung (»index«): %s" -#: gitk:3475 +#: gitk:3617 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "»git blame« konnte nicht gestartet werden: %s" -#: gitk:3478 gitk:6209 +#: gitk:3620 gitk:6409 msgid "Searching" msgstr "Suchen" -#: gitk:3510 +#: gitk:3652 #, tcl-format msgid "Error running git blame: %s" msgstr "Fehler beim Ausführen von »git blame«: %s" -#: gitk:3538 +#: gitk:3680 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "" "Diese Zeile stammt aus Version %s, die nicht in dieser Ansicht gezeigt wird" -#: gitk:3552 +#: gitk:3694 msgid "External diff viewer failed:" msgstr "Externes Diff-Programm fehlgeschlagen:" -#: gitk:3670 +#: gitk:3812 msgid "Gitk view definition" msgstr "Gitk-Ansichten" -#: gitk:3674 +#: gitk:3816 msgid "Remember this view" msgstr "Diese Ansicht speichern" -#: gitk:3675 -msgid "Commits to include (arguments to git log):" -msgstr "Versionen anzeigen (Argumente von git log):" +#: gitk:3817 +msgid "References (space separated list):" +msgstr "Zweige/Markierungen (durch Leerzeichen getrennte Liste):" -#: gitk:3676 -msgid "Use all refs" -msgstr "Alle Zweige verwenden" +#: gitk:3818 +msgid "Branches & tags:" +msgstr "Zweige/Markierungen:" -#: gitk:3677 -msgid "Strictly sort by date" -msgstr "Streng nach Datum sortieren" +#: gitk:3819 +msgid "All refs" +msgstr "Alle Markierungen und Zweige" -#: gitk:3678 -msgid "Mark branch sides" -msgstr "Zweig-Seiten markieren" +#: gitk:3820 +msgid "All (local) branches" +msgstr "Alle (lokalen) Zweige" -#: gitk:3679 -msgid "Since date:" -msgstr "Von Datum:" +#: gitk:3821 +msgid "All tags" +msgstr "Alle Markierungen" -#: gitk:3680 -msgid "Until date:" -msgstr "Bis Datum:" +#: gitk:3822 +msgid "All remote-tracking branches" +msgstr "Alle Übernahmezweige" + +#: gitk:3823 +msgid "Commit Info (regular expressions):" +msgstr "Versionsinformationen (reguläre Ausdrücke):" + +#: gitk:3824 +msgid "Author:" +msgstr "Autor:" + +#: gitk:3825 +msgid "Committer:" +msgstr "Eintragender:" + +#: gitk:3826 +msgid "Commit Message:" +msgstr "Versionsbeschreibung:" -#: gitk:3681 -msgid "Max count:" -msgstr "Max. Anzahl:" +#: gitk:3827 +msgid "Matches all Commit Info criteria" +msgstr "Alle Versionsinformationen-Kriterien erfüllen" -#: gitk:3682 -msgid "Skip:" +#: gitk:3828 +msgid "Changes to Files:" +msgstr "Dateien:" + +#: gitk:3829 +msgid "Fixed String" +msgstr "Zeichenkette" + +#: gitk:3830 +msgid "Regular Expression" +msgstr "Regulärer Ausdruck" + +#: gitk:3831 +msgid "Search string:" +msgstr "Suchausdruck:" + +#: gitk:3832 +msgid "" +"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" +msgstr "Datum (»2 weeks ago«, »2009-03-17 15:27:38«, »March 17, 2009 15:27:38«)" + +#: gitk:3833 +msgid "Since:" +msgstr "Von:" + +#: gitk:3834 +msgid "Until:" +msgstr "Bis:" + +#: gitk:3835 +msgid "Limit and/or skip a number of revisions (positive integer):" +msgstr "Versionsanzahl begrenzen oder einige überspringen (ganzzahliger Wert):" + +#: gitk:3836 +msgid "Number to show:" +msgstr "Anzeigen:" + +#: gitk:3837 +msgid "Number to skip:" msgstr "Überspringen:" -#: gitk:3683 +#: gitk:3838 +msgid "Miscellaneous options:" +msgstr "Sonstiges:" + +#: gitk:3839 +msgid "Strictly sort by date" +msgstr "Streng nach Datum sortieren" + +#: gitk:3840 +msgid "Mark branch sides" +msgstr "Zweig-Seiten markieren" + +#: gitk:3841 msgid "Limit to first parent" msgstr "Auf erste Elternversion beschränken" -#: gitk:3684 +#: gitk:3842 +msgid "Simple history" +msgstr "Einfache Historie" + +#: gitk:3843 +msgid "Additional arguments to git log:" +msgstr "Zusätzliche Argumente für »git log«:" + +#: gitk:3844 +msgid "Enter files and directories to include, one per line:" +msgstr "Folgende Dateien und Verzeichnisse anzeigen (eine pro Zeile):" + +#: gitk:3845 msgid "Command to generate more commits to include:" msgstr "Versionsliste durch folgendes Kommando erzeugen lassen:" -#: gitk:3780 +#: gitk:3967 msgid "Gitk: edit view" msgstr "Gitk: Ansicht bearbeiten" -#: gitk:3793 -msgid "Name" -msgstr "Name" +#: gitk:3975 +msgid "-- criteria for selecting revisions" +msgstr "-- Auswahl der angezeigten Versionen" -#: gitk:3841 -msgid "Enter files and directories to include, one per line:" -msgstr "Folgende Dateien und Verzeichnisse anzeigen (eine pro Zeile):" +#: gitk:3980 +msgid "View Name" +msgstr "Ansichtsname" -#: gitk:3855 +#: gitk:4055 msgid "Apply (F5)" msgstr "Anwenden (F5)" -#: gitk:3893 +#: gitk:4093 msgid "Error in commit selection arguments:" msgstr "Fehler in den ausgewählten Versionen:" -#: gitk:3946 gitk:3998 gitk:4446 gitk:4460 gitk:5721 gitk:11114 gitk:11115 +#: gitk:4146 gitk:4198 gitk:4646 gitk:4660 gitk:5921 gitk:11534 gitk:11535 msgid "None" msgstr "Keine" -#: gitk:4394 gitk:6241 gitk:7993 gitk:8008 +#: gitk:4594 gitk:6441 gitk:8287 gitk:8302 msgid "Date" msgstr "Datum" -#: gitk:4394 gitk:6241 +#: gitk:4594 gitk:6441 msgid "CDate" msgstr "Eintragedatum" -#: gitk:4543 gitk:4548 +#: gitk:4743 gitk:4748 msgid "Descendant" msgstr "Abkömmling" -#: gitk:4544 +#: gitk:4744 msgid "Not descendant" msgstr "Kein Abkömmling" -#: gitk:4551 gitk:4556 +#: gitk:4751 gitk:4756 msgid "Ancestor" msgstr "Vorgänger" -#: gitk:4552 +#: gitk:4752 msgid "Not ancestor" msgstr "Kein Vorgänger" -#: gitk:4842 +#: gitk:5042 msgid "Local changes checked in to index but not committed" msgstr "Lokale Änderungen bereitgestellt, aber nicht eingetragen" -#: gitk:4878 +#: gitk:5078 msgid "Local uncommitted changes, not checked in to index" msgstr "Lokale Änderungen, nicht bereitgestellt" -#: gitk:6559 +#: gitk:6759 msgid "many" msgstr "viele" -#: gitk:6743 +#: gitk:6942 msgid "Tags:" msgstr "Markierungen:" -#: gitk:6760 gitk:6766 gitk:7986 +#: gitk:6959 gitk:6965 gitk:8280 msgid "Parent" msgstr "Eltern" -#: gitk:6771 +#: gitk:6970 msgid "Child" msgstr "Kind" -#: gitk:6780 +#: gitk:6979 msgid "Branch" msgstr "Zweig" -#: gitk:6783 +#: gitk:6982 msgid "Follows" msgstr "Folgt auf" -#: gitk:6786 +#: gitk:6985 msgid "Precedes" msgstr "Vorgänger von" -#: gitk:7279 +#: gitk:7522 #, tcl-format msgid "Error getting diffs: %s" msgstr "Fehler beim Laden des Vergleichs: %s" -#: gitk:7819 +#: gitk:8108 msgid "Goto:" msgstr "Gehe zu:" -#: gitk:7821 -msgid "SHA1 ID:" -msgstr "SHA1-Hashwert:" - -#: gitk:7840 +#: gitk:8129 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "Kurzer SHA1-Hashwert »%s« ist mehrdeutig" -#: gitk:7852 +#: gitk:8136 +#, tcl-format +msgid "Revision %s is not known" +msgstr "Version »%s« ist unbekannt" + +#: gitk:8146 #, tcl-format msgid "SHA1 id %s is not known" msgstr "SHA1-Hashwert »%s« ist unbekannt" -#: gitk:7854 +#: gitk:8148 #, tcl-format -msgid "Tag/Head %s is not known" -msgstr "Markierung/Zweig »%s« ist unbekannt" +msgid "Revision %s is not in the current view" +msgstr "Version »%s« wird in der aktuellen Ansicht nicht angezeigt" -#: gitk:7996 +#: gitk:8290 msgid "Children" msgstr "Kinder" -#: gitk:8053 +#: gitk:8348 #, tcl-format msgid "Reset %s branch to here" msgstr "Zweig »%s« hierher zurücksetzen" -#: gitk:8055 +#: gitk:8350 msgid "Detached head: can't reset" msgstr "Zweigspitze ist abgetrennt: Zurücksetzen nicht möglich" -#: gitk:8164 gitk:8170 +#: gitk:8459 gitk:8465 msgid "Skipping merge commit " msgstr "Überspringe Zusammenführungs-Version " -#: gitk:8179 gitk:8184 +#: gitk:8474 gitk:8479 msgid "Error getting patch ID for " msgstr "Fehler beim Holen der Patch-ID für " -#: gitk:8180 gitk:8185 +#: gitk:8475 gitk:8480 msgid " - stopping\n" msgstr " - Abbruch.\n" -#: gitk:8190 gitk:8193 gitk:8201 gitk:8211 gitk:8220 +#: gitk:8485 gitk:8488 gitk:8496 gitk:8510 gitk:8519 msgid "Commit " msgstr "Version " -#: gitk:8194 +#: gitk:8489 msgid "" " is the same patch as\n" " " @@ -762,7 +844,7 @@ msgstr "" " ist das gleiche Patch wie\n" " " -#: gitk:8202 +#: gitk:8497 msgid "" " differs from\n" " " @@ -770,117 +852,138 @@ msgstr "" " ist unterschiedlich von\n" " " -#: gitk:8204 -msgid "- stopping\n" -msgstr "- Abbruch.\n" +#: gitk:8499 +msgid "" +"Diff of commits:\n" +"\n" +msgstr "" +"Vergleich der Versionen:\n" +"\n" -#: gitk:8212 gitk:8221 +#: gitk:8511 gitk:8520 #, tcl-format msgid " has %s children - stopping\n" msgstr " hat %s Kinder. Abbruch\n" -#: gitk:8252 +#: gitk:8539 +#, tcl-format +msgid "Error writing commit to file: %s" +msgstr "Fehler beim Schreiben der Version in Datei: %s" + +#: gitk:8545 +#, tcl-format +msgid "Error diffing commits: %s" +msgstr "Fehler beim Vergleichen der Versionen: %s" + +#: gitk:8575 msgid "Top" msgstr "Oben" -#: gitk:8253 +#: gitk:8576 msgid "From" msgstr "Von" -#: gitk:8258 +#: gitk:8581 msgid "To" msgstr "bis" -#: gitk:8282 +#: gitk:8605 msgid "Generate patch" msgstr "Patch erstellen" -#: gitk:8284 +#: gitk:8607 msgid "From:" msgstr "Von:" -#: gitk:8293 +#: gitk:8616 msgid "To:" msgstr "bis:" -#: gitk:8302 +#: gitk:8625 msgid "Reverse" msgstr "Umgekehrt" -#: gitk:8304 gitk:8489 +#: gitk:8627 gitk:8822 msgid "Output file:" msgstr "Ausgabedatei:" -#: gitk:8310 +#: gitk:8633 msgid "Generate" msgstr "Erzeugen" -#: gitk:8348 +#: gitk:8671 msgid "Error creating patch:" msgstr "Fehler beim Erzeugen des Patches:" -#: gitk:8371 gitk:8477 gitk:8534 +#: gitk:8694 gitk:8810 gitk:8867 msgid "ID:" msgstr "ID:" -#: gitk:8380 +#: gitk:8703 msgid "Tag name:" msgstr "Markierungsname:" -#: gitk:8384 gitk:8543 +#: gitk:8706 +msgid "Tag message is optional" +msgstr "Eine Markierungsbeschreibung ist optional" + +#: gitk:8708 +msgid "Tag message:" +msgstr "Markierungsbeschreibung:" + +#: gitk:8712 gitk:8876 msgid "Create" msgstr "Erstellen" -#: gitk:8401 +#: gitk:8730 msgid "No tag name specified" msgstr "Kein Markierungsname angegeben" -#: gitk:8405 +#: gitk:8734 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "Markierung »%s« existiert bereits." -#: gitk:8411 +#: gitk:8744 msgid "Error creating tag:" msgstr "Fehler beim Erstellen der Markierung:" -#: gitk:8486 +#: gitk:8819 msgid "Command:" msgstr "Kommando:" -#: gitk:8494 +#: gitk:8827 msgid "Write" msgstr "Schreiben" -#: gitk:8512 +#: gitk:8845 msgid "Error writing commit:" msgstr "Fehler beim Schreiben der Version:" -#: gitk:8539 +#: gitk:8872 msgid "Name:" msgstr "Name:" -#: gitk:8562 +#: gitk:8895 msgid "Please specify a name for the new branch" msgstr "Bitte geben Sie einen Namen für den neuen Zweig an." -#: gitk:8567 +#: gitk:8900 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "Zweig »%s« existiert bereits. Soll er überschrieben werden?" -#: gitk:8633 +#: gitk:8966 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "" -"Version »%s« ist bereits im Zweig »%s« enthalten -- trotzdem erneut " -"eintragen?" +"Version »%s« ist bereits im Zweig »%s« enthalten -- trotzdem erneut eintragen?" -#: gitk:8638 +#: gitk:8971 msgid "Cherry-picking" msgstr "Version pflücken" -#: gitk:8647 +#: gitk:8980 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -890,7 +993,7 @@ msgstr "" "vorliegen. Bitte diese Änderungen eintragen, zurücksetzen oder\n" "zwischenspeichern (»git stash«) und dann erneut versuchen." -#: gitk:8653 +#: gitk:8986 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -899,34 +1002,34 @@ msgstr "" "ist. Soll das Zusammenführungs-Werkzeug (»git citool«) aufgerufen\n" "werden, um diesen Konflikt aufzulösen?" -#: gitk:8669 +#: gitk:9002 msgid "No changes committed" msgstr "Keine Änderungen eingetragen" -#: gitk:8695 +#: gitk:9028 msgid "Confirm reset" msgstr "Zurücksetzen bestätigen" -#: gitk:8697 +#: gitk:9030 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Zweig »%s« auf »%s« zurücksetzen?" -#: gitk:8701 +#: gitk:9032 msgid "Reset type:" msgstr "Art des Zurücksetzens:" -#: gitk:8705 +#: gitk:9035 msgid "Soft: Leave working tree and index untouched" msgstr "Harmlos: Arbeitskopie und Bereitstellung unverändert" -#: gitk:8708 +#: gitk:9038 msgid "Mixed: Leave working tree untouched, reset index" msgstr "" "Gemischt: Arbeitskopie unverändert,\n" "Bereitstellung zurückgesetzt" -#: gitk:8711 +#: gitk:9041 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -934,21 +1037,21 @@ msgstr "" "Hart: Arbeitskopie und Bereitstellung\n" "(Alle lokalen Änderungen werden gelöscht)" -#: gitk:8728 +#: gitk:9058 msgid "Resetting" msgstr "Zurücksetzen" -#: gitk:8785 +#: gitk:9118 msgid "Checking out" msgstr "Umstellen" -#: gitk:8838 +#: gitk:9171 msgid "Cannot delete the currently checked-out branch" msgstr "" "Der Zweig, auf den die Arbeitskopie momentan umgestellt ist, kann nicht " "gelöscht werden." -#: gitk:8844 +#: gitk:9177 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -957,16 +1060,16 @@ msgstr "" "Die Versionen auf Zweig »%s« existieren auf keinem anderen Zweig.\n" "Zweig »%s« trotzdem löschen?" -#: gitk:8875 +#: gitk:9208 #, tcl-format msgid "Tags and heads: %s" msgstr "Markierungen und Zweige: %s" -#: gitk:8890 +#: gitk:9223 msgid "Filter" msgstr "Filtern" -#: gitk:9185 +#: gitk:9518 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -974,182 +1077,202 @@ msgstr "" "Fehler beim Lesen der Strukturinformationen; Zweige und Informationen zu " "Vorgänger/Nachfolger werden unvollständig sein." -#: gitk:10171 +#: gitk:10504 msgid "Tag" msgstr "Markierung" -#: gitk:10171 +#: gitk:10504 msgid "Id" msgstr "Id" -#: gitk:10219 +#: gitk:10554 msgid "Gitk font chooser" msgstr "Gitk-Schriften wählen" -#: gitk:10236 +#: gitk:10571 msgid "B" msgstr "F" -#: gitk:10239 +#: gitk:10574 msgid "I" msgstr "K" -#: gitk:10334 +#: gitk:10692 msgid "Gitk preferences" msgstr "Gitk-Einstellungen" -#: gitk:10336 +#: gitk:10694 msgid "Commit list display options" msgstr "Anzeige der Versionsliste" -#: gitk:10339 +#: gitk:10697 msgid "Maximum graph width (lines)" msgstr "Maximale Graphenbreite (Zeilen)" -#: gitk:10343 +#: gitk:10700 #, tcl-format msgid "Maximum graph width (% of pane)" msgstr "Maximale Graphenbreite (% des Fensters)" -#: gitk:10347 +#: gitk:10703 msgid "Show local changes" msgstr "Lokale Änderungen anzeigen" -#: gitk:10350 +#: gitk:10706 msgid "Auto-select SHA1" msgstr "SHA1-Hashwert automatisch auswählen" -#: gitk:10354 +#: gitk:10709 +msgid "Hide remote refs" +msgstr "Entfernte Zweige/Markierungen ausblenden" + +#: gitk:10713 msgid "Diff display options" msgstr "Anzeige des Vergleichs" -#: gitk:10356 +#: gitk:10715 msgid "Tab spacing" msgstr "Tabulatorbreite" -#: gitk:10359 +#: gitk:10718 msgid "Display nearby tags" msgstr "Naheliegende Markierungen anzeigen" -#: gitk:10362 +#: gitk:10721 msgid "Limit diffs to listed paths" msgstr "Vergleich nur für angezeigte Pfade" -#: gitk:10365 +#: gitk:10724 msgid "Support per-file encodings" msgstr "Zeichenkodierung pro Datei ermitteln" -#: gitk:10371 gitk:10436 +#: gitk:10730 gitk:10819 msgid "External diff tool" msgstr "Externes Diff-Programm" -#: gitk:10373 +#: gitk:10731 msgid "Choose..." msgstr "Wählen ..." -#: gitk:10378 +#: gitk:10736 +msgid "General options" +msgstr "Allgemeine Optionen" + +#: gitk:10739 +msgid "Use themed widgets" +msgstr "Aussehen der Benutzeroberfläche durch Thema bestimmen" + +#: gitk:10741 +msgid "(change requires restart)" +msgstr "(Änderungen werden erst nach Neustart wirksam)" + +#: gitk:10743 +msgid "(currently unavailable)" +msgstr "(Momentan nicht verfügbar)" + +#: gitk:10747 msgid "Colors: press to choose" msgstr "Farben: Klicken zum Wählen" -#: gitk:10381 +#: gitk:10750 +msgid "Interface" +msgstr "Benutzeroberfläche" + +#: gitk:10751 +msgid "interface" +msgstr "Benutzeroberfläche" + +#: gitk:10754 msgid "Background" msgstr "Hintergrund" -#: gitk:10382 gitk:10412 +#: gitk:10755 gitk:10785 msgid "background" msgstr "Hintergrund" -#: gitk:10385 +#: gitk:10758 msgid "Foreground" msgstr "Vordergrund" -#: gitk:10386 +#: gitk:10759 msgid "foreground" msgstr "Vordergrund" -#: gitk:10389 +#: gitk:10762 msgid "Diff: old lines" msgstr "Vergleich: Alte Zeilen" -#: gitk:10390 +#: gitk:10763 msgid "diff old lines" msgstr "Vergleich - Alte Zeilen" -#: gitk:10394 +#: gitk:10767 msgid "Diff: new lines" msgstr "Vergleich: Neue Zeilen" -#: gitk:10395 +#: gitk:10768 msgid "diff new lines" msgstr "Vergleich - Neue Zeilen" -#: gitk:10399 +#: gitk:10772 msgid "Diff: hunk header" msgstr "Vergleich: Änderungstitel" -#: gitk:10401 +#: gitk:10774 msgid "diff hunk header" msgstr "Vergleich - Änderungstitel" -#: gitk:10405 +#: gitk:10778 msgid "Marked line bg" msgstr "Hintergrund für markierte Zeile" -#: gitk:10407 +#: gitk:10780 msgid "marked line background" msgstr "Hintergrund für markierte Zeile" -#: gitk:10411 +#: gitk:10784 msgid "Select bg" msgstr "Hintergrundfarbe auswählen" -#: gitk:10415 +#: gitk:10788 msgid "Fonts: press to choose" msgstr "Schriftart: Klicken zum Wählen" -#: gitk:10417 +#: gitk:10790 msgid "Main font" msgstr "Programmschriftart" -#: gitk:10418 +#: gitk:10791 msgid "Diff display font" msgstr "Schriftart für Vergleich" -#: gitk:10419 +#: gitk:10792 msgid "User interface font" msgstr "Beschriftungen" -#: gitk:10446 +#: gitk:10829 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: Farbe wählen für %s" -#: gitk:10893 -msgid "" -"Sorry, gitk cannot run with this version of Tcl/Tk.\n" -" Gitk requires at least Tcl/Tk 8.4." -msgstr "" -"Gitk läuft nicht mit dieser Version von Tcl/Tk.\n" -"Gitk benötigt mindestens Tcl/Tk 8.4." - -#: gitk:11020 +#: gitk:11433 msgid "Cannot find a git repository here." msgstr "Kein Git-Projektarchiv gefunden." -#: gitk:11024 +#: gitk:11437 #, tcl-format msgid "Cannot find the git directory \"%s\"." msgstr "Git-Verzeichnis »%s« wurde nicht gefunden." -#: gitk:11071 +#: gitk:11484 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Mehrdeutige Angabe »%s«: Sowohl Version als auch Dateiname existiert." -#: gitk:11083 +#: gitk:11496 msgid "Bad arguments to gitk:" msgstr "Falsche Kommandozeilen-Parameter für gitk:" -#: gitk:11167 +#: gitk:11587 msgid "Command line" msgstr "Kommandozeile" diff --git a/gitk-git/po/fr.po b/gitk-git/po/fr.po new file mode 100644 index 0000000000..cb0e1edc63 --- /dev/null +++ b/gitk-git/po/fr.po @@ -0,0 +1,1254 @@ +# French translation of the gitk package +# Copyright (C) 2005-2008 Paul Mackerras. All rights reserved. +# This file is distributed under the same license as the gitk package. +# Translators: +# Emmanuel Trillaud <etrillaud@gmail.com> +# +msgid "" +msgstr "" +"Project-Id-Version: gitk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-10-05 15:16+0200\n" +"PO-Revision-Date: 2009-11-19 22:13+0100\n" +"Last-Translator: Emmanuel Trillaud <etrillaud@gmail.com>\n" +"Language-Team: git@vger.kernel.org\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: French\n" +"X-Poedit-Country: FRANCE\n" + +#: gitk:113 +msgid "Couldn't get list of unmerged files:" +msgstr "Impossible de récupérer la liste des fichiers non fusionnés :" + +#: gitk:269 +msgid "Error parsing revisions:" +msgstr "Erreur lors du parcours des révisions :" + +#: gitk:324 +msgid "Error executing --argscmd command:" +msgstr "Erreur à l'exécution de la commande --argscmd :" + +#: gitk:337 +msgid "No files selected: --merge specified but no files are unmerged." +msgstr "" +"Aucun fichier sélectionné : --merge précisé, mais tous les fichiers sont " +"fusionnés." + +# FIXME : améliorer la traduction de 'file limite' +#: gitk:340 +#, fuzzy +msgid "" +"No files selected: --merge specified but no unmerged files are within file " +"limit." +msgstr "" +"Aucun fichier sélectionné : --merge précisé mais aucun fichier non fusionné " +"n'est dans la limite des fichiers." + +#: gitk:362 gitk:509 +msgid "Error executing git log:" +msgstr "Erreur à l'exécution de git log :" + +#: gitk:380 gitk:525 +msgid "Reading" +msgstr "Lecture en cours" + +#: gitk:440 gitk:4123 +msgid "Reading commits..." +msgstr "Lecture des commits..." + +#: gitk:443 gitk:1561 gitk:4126 +msgid "No commits selected" +msgstr "Aucun commit sélectionné" + +#: gitk:1437 +msgid "Can't parse git log output:" +msgstr "Impossible de lire la sortie de git log :" + +#: gitk:1657 +msgid "No commit information available" +msgstr "Aucune information disponible sur le commit" + +#: gitk:1793 gitk:1817 gitk:3916 gitk:8786 gitk:10322 gitk:10498 +msgid "OK" +msgstr "OK" + +#: gitk:1819 gitk:3918 gitk:8383 gitk:8457 gitk:8567 gitk:8616 gitk:8788 +#: gitk:10323 gitk:10499 +msgid "Cancel" +msgstr "Annuler" + +#: gitk:1919 +msgid "Update" +msgstr "Mise à jour" + +#: gitk:1920 +msgid "Reload" +msgstr "Recharger" + +#: gitk:1921 +msgid "Reread references" +msgstr "Relire les références" + +#: gitk:1922 +msgid "List references" +msgstr "Lister les références" + +#: gitk:1924 +msgid "Start git gui" +msgstr "Démarrer git gui" + +#: gitk:1926 +msgid "Quit" +msgstr "Quitter" + +#: gitk:1918 +msgid "File" +msgstr "Fichier" + +#: gitk:1930 +msgid "Preferences" +msgstr "Préférences" + +#: gitk:1929 +msgid "Edit" +msgstr "Éditer" + +#: gitk:1934 +msgid "New view..." +msgstr "Nouvelle vue..." + +#: gitk:1935 +msgid "Edit view..." +msgstr "Éditer la vue..." + +#: gitk:1936 +msgid "Delete view" +msgstr "Supprimer la vue" + +#: gitk:1938 +msgid "All files" +msgstr "Tous les fichiers" + +#: gitk:1933 gitk:3670 +msgid "View" +msgstr "Vue" + +#: gitk:1943 gitk:1953 gitk:2654 +msgid "About gitk" +msgstr "À propos de gitk" + +#: gitk:1944 gitk:1958 +msgid "Key bindings" +msgstr "Raccourcis clavier" + +#: gitk:1942 gitk:1957 +msgid "Help" +msgstr "Aide" + +#: gitk:2018 +msgid "SHA1 ID: " +msgstr "ID SHA1 :" + +#: gitk:2049 +msgid "Row" +msgstr "Colonne" + +#: gitk:2080 +msgid "Find" +msgstr "Recherche" + +#: gitk:2081 +msgid "next" +msgstr "suivant" + +#: gitk:2082 +msgid "prev" +msgstr "précédent" + +#: gitk:2083 +msgid "commit" +msgstr "commit" + +#: gitk:2086 gitk:2088 gitk:4284 gitk:4307 gitk:4331 gitk:6272 gitk:6344 +#: gitk:6428 +msgid "containing:" +msgstr "contient :" + +#: gitk:2089 gitk:3162 gitk:3167 gitk:4359 +msgid "touching paths:" +msgstr "chemins modifiés :" + +#: gitk:2090 gitk:4364 +msgid "adding/removing string:" +msgstr "ajoute/supprime la chaîne :" + +#: gitk:2099 gitk:2101 +msgid "Exact" +msgstr "Exact" + +#: gitk:2101 gitk:4439 gitk:6240 +msgid "IgnCase" +msgstr "Ignorer la casse" + +#: gitk:2101 gitk:4333 gitk:4437 gitk:6236 +msgid "Regexp" +msgstr "Expression régulière" + +#: gitk:2103 gitk:2104 gitk:4458 gitk:4488 gitk:4495 gitk:6364 gitk:6432 +msgid "All fields" +msgstr "Tous les champs" + +#: gitk:2104 gitk:4456 gitk:4488 gitk:6303 +msgid "Headline" +msgstr "Surligner" + +#: gitk:2105 gitk:4456 gitk:6303 gitk:6432 gitk:6866 +msgid "Comments" +msgstr "Commentaires" + +#: gitk:2105 gitk:4456 gitk:4460 gitk:4495 gitk:6303 gitk:6801 gitk:8063 +#: gitk:8078 +msgid "Author" +msgstr "Auteur" + +#: gitk:2105 gitk:4456 gitk:6303 gitk:6803 +msgid "Committer" +msgstr "Auteur du commit" + +#: gitk:2134 +msgid "Search" +msgstr "Rechercher" + +#: gitk:2141 +msgid "Diff" +msgstr "Diff" + +#: gitk:2143 +msgid "Old version" +msgstr "Ancienne version" + +#: gitk:2145 +msgid "New version" +msgstr "Nouvelle version" + +#: gitk:2147 +msgid "Lines of context" +msgstr "Lignes de contexte" + +#: gitk:2157 +msgid "Ignore space change" +msgstr "Ignorer les modifications d'espace" + +#: gitk:2215 +msgid "Patch" +msgstr "Patch" + +#: gitk:2217 +msgid "Tree" +msgstr "Arbre" + +#: gitk:2361 gitk:2378 +msgid "Diff this -> selected" +msgstr "Diff entre ceci et la sélection" + +#: gitk:2362 gitk:2379 +msgid "Diff selected -> this" +msgstr "Diff entre sélection et ceci" + +#: gitk:2363 gitk:2380 +msgid "Make patch" +msgstr "Créer patch" + +#: gitk:2364 gitk:8441 +msgid "Create tag" +msgstr "Créer tag" + +#: gitk:2365 gitk:8547 +msgid "Write commit to file" +msgstr "Écrire le commit dans un fichier" + +#: gitk:2366 gitk:8604 +msgid "Create new branch" +msgstr "Créer une nouvelle branche" + +#: gitk:2367 +msgid "Cherry-pick this commit" +msgstr "Cueillir (cherry-pick) ce commit" + +#: gitk:2368 +msgid "Reset HEAD branch to here" +msgstr "Réinitialiser la branche HEAD vers cet état" + +#: gitk:2369 +msgid "Mark this commit" +msgstr "Marquer ce commit" + +#: gitk:2370 +msgid "Return to mark" +msgstr "Retourner à la marque" + +#: gitk:2371 +msgid "Find descendant of this and mark" +msgstr "Chercher le descendant de ceci et le marquer" + +#: gitk:2372 +msgid "Compare with marked commit" +msgstr "Comparer avec le commit marqué" + +#: gitk:2386 +msgid "Check out this branch" +msgstr "Récupérer cette branche" + +#: gitk:2387 +msgid "Remove this branch" +msgstr "Supprimer cette branche" + +#: gitk:2394 +msgid "Highlight this too" +msgstr "Surligner également ceci" + +#: gitk:2395 +msgid "Highlight this only" +msgstr "Surligner seulement ceci" + +#: gitk:2396 +msgid "External diff" +msgstr "Diff externe" + +#: gitk:2397 +msgid "Blame parent commit" +msgstr "Blâmer le commit parent" + +#: gitk:2404 +msgid "Show origin of this line" +msgstr "Montrer l'origine de cette ligne" + +#: gitk:2405 +msgid "Run git gui blame on this line" +msgstr "Exécuter git gui blame sur cette ligne" + +#: gitk:2656 +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 - visualisateur de commit pour git\n" +"\n" +"Copyright © 2005-2008 Paul Mackerras\n" +"\n" +"Utilisation et redistribution soumises aux termes de la GNU General Public " +"License" + +#: gitk:2664 gitk:2726 gitk:8969 +msgid "Close" +msgstr "Fermer" + +#: gitk:2683 +msgid "Gitk key bindings" +msgstr "Raccourcis clavier de Gitk" + +#: gitk:2686 +msgid "Gitk key bindings:" +msgstr "Raccourcis clavier de Gitk :" + +#: gitk:2688 +#, tcl-format +msgid "<%s-Q>\t\tQuit" +msgstr "<%s-Q>\t\tQuitter" + +#: gitk:2689 +msgid "<Home>\t\tMove to first commit" +msgstr "<Début>\t\tAller au premier commit" + +#: gitk:2690 +msgid "<End>\t\tMove to last commit" +msgstr "<Fin>\t\tAller au dernier commit" + +#: gitk:2691 +msgid "<Up>, p, i\tMove up one commit" +msgstr "<Haut>, p, i\t Aller au commit suivant" + +#: gitk:2692 +msgid "<Down>, n, k\tMove down one commit" +msgstr "<Bas>, n, k\t Aller au commit précédent" + +#: gitk:2693 +msgid "<Left>, z, j\tGo back in history list" +msgstr "<Gauche>, z, j\tReculer dans l'historique" + +#: gitk:2694 +msgid "<Right>, x, l\tGo forward in history list" +msgstr "<Droite>, x, l\tAvancer dans l'historique" + +#: gitk:2695 +msgid "<PageUp>\tMove up one page in commit list" +msgstr "<PageUp>\tMonter d'une page dans la liste des commits" + +#: gitk:2696 +msgid "<PageDown>\tMove down one page in commit list" +msgstr "<PageDown>\tDescendre d'une page dans la liste des commits" + +#: gitk:2697 +#, tcl-format +msgid "<%s-Home>\tScroll to top of commit list" +msgstr "<%s-Début>\tAller en haut de la liste des commits" + +#: gitk:2698 +#, tcl-format +msgid "<%s-End>\tScroll to bottom of commit list" +msgstr "<%s-End>\tAller en bas de la liste des commits" + +#: gitk:2699 +#, tcl-format +msgid "<%s-Up>\tScroll commit list up one line" +msgstr "<%s-Up>\tMonter d'une ligne dans la liste des commits" + +#: gitk:2700 +#, tcl-format +msgid "<%s-Down>\tScroll commit list down one line" +msgstr "<%s-Down>\tDescendre d'une ligne dans la liste des commits" + +#: gitk:2701 +#, tcl-format +msgid "<%s-PageUp>\tScroll commit list up one page" +msgstr "<%s-PageUp>\tMonter d'une page dans la liste des commits" + +#: gitk:2702 +#, tcl-format +msgid "<%s-PageDown>\tScroll commit list down one page" +msgstr "<%s-PageDown>\tDescendre d'une page dans la liste des commits" + +#: gitk:2703 +msgid "<Shift-Up>\tFind backwards (upwards, later commits)" +msgstr "" +"<Shift-Up>\tRecherche en arrière (vers l'avant, commits les plus anciens)" + +#: gitk:2704 +msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" +msgstr "" +"<Shift-Down>\tRecherche en avant (vers l'arrière, commit les plus récents)" + +#: gitk:2705 +msgid "<Delete>, b\tScroll diff view up one page" +msgstr "<Supprimer>, b\tMonter d'une page dans la vue des diff" + +#: gitk:2706 +msgid "<Backspace>\tScroll diff view up one page" +msgstr "<Backspace>\tMonter d'une page dans la vue des diff" + +#: gitk:2707 +msgid "<Space>\t\tScroll diff view down one page" +msgstr "<Espace>\t\tDescendre d'une page dans la vue des diff" + +#: gitk:2708 +msgid "u\t\tScroll diff view up 18 lines" +msgstr "u\t\tMonter de 18 lignes dans la vue des diff" + +#: gitk:2709 +msgid "d\t\tScroll diff view down 18 lines" +msgstr "d\t\tDescendre de 18 lignes dans la vue des diff" + +#: gitk:2710 +#, tcl-format +msgid "<%s-F>\t\tFind" +msgstr "<%s-F>\t\tRechercher" + +#: gitk:2711 +#, tcl-format +msgid "<%s-G>\t\tMove to next find hit" +msgstr "<%s-G>\t\tAller au résultat de recherche suivant" + +#: gitk:2712 +msgid "<Return>\tMove to next find hit" +msgstr "<Return>\t\tAller au résultat de recherche suivant" + +#: gitk:2713 +msgid "/\t\tFocus the search box" +msgstr "/\t\tFocus sur la zone de recherche" + +#: gitk:2714 +msgid "?\t\tMove to previous find hit" +msgstr "?\t\tAller au résultat de recherche précédent" + +#: gitk:2715 +msgid "f\t\tScroll diff view to next file" +msgstr "f\t\tAller au prochain fichier dans la vue des diff" + +#: gitk:2716 +#, tcl-format +msgid "<%s-S>\t\tSearch for next hit in diff view" +msgstr "<%s-S>\t\tAller au résultat suivant dans la vue des diff" + +#: gitk:2717 +#, tcl-format +msgid "<%s-R>\t\tSearch for previous hit in diff view" +msgstr "<%s-R>\t\tAller au résultat précédent dans la vue des diff" + +#: gitk:2718 +#, tcl-format +msgid "<%s-KP+>\tIncrease font size" +msgstr "<%s-KP+>\tAugmenter la taille de la police" + +#: gitk:2719 +#, tcl-format +msgid "<%s-plus>\tIncrease font size" +msgstr "<%s-plus>\tAugmenter la taille de la police" + +#: gitk:2720 +#, tcl-format +msgid "<%s-KP->\tDecrease font size" +msgstr "<%s-KP->\tDiminuer la taille de la police" + +#: gitk:2721 +#, tcl-format +msgid "<%s-minus>\tDecrease font size" +msgstr "<%s-minus>\tDiminuer la taille de la police" + +#: gitk:2722 +msgid "<F5>\t\tUpdate" +msgstr "<F5>\t\tMise à jour" + +#: gitk:3177 +#, tcl-format +msgid "Error getting \"%s\" from %s:" +msgstr "Erreur en obtenant \"%s\" de %s:" + +#: gitk:3234 gitk:3243 +#, tcl-format +msgid "Error creating temporary directory %s:" +msgstr "Erreur lors de la création du répertoire temporaire %s :" + +#: gitk:3255 +msgid "command failed:" +msgstr "échec de la commande :" + +#: gitk:3401 +msgid "No such commit" +msgstr "Commit inexistant" + +#: gitk:3415 +msgid "git gui blame: command failed:" +msgstr "git gui blame : échec de la commande :" + +#: gitk:3446 +#, tcl-format +msgid "Couldn't read merge head: %s" +msgstr "Impossible de lire le head de la fusion : %s" + +#: gitk:3454 +#, tcl-format +msgid "Error reading index: %s" +msgstr "Erreur à la lecture de l'index : %s" + +#: gitk:3479 +#, tcl-format +msgid "Couldn't start git blame: %s" +msgstr "Impossible de démarrer git blame : %s" + +#: gitk:3482 gitk:6271 +msgid "Searching" +msgstr "Recherche en cours" + +#: gitk:3514 +#, tcl-format +msgid "Error running git blame: %s" +msgstr "Erreur à l'exécution de git blame : %s" + +#: gitk:3542 +#, tcl-format +msgid "That line comes from commit %s, which is not in this view" +msgstr "Cette ligne est issue du commit %s, qui n'est pas dans cette vue" + +#: gitk:3556 +msgid "External diff viewer failed:" +msgstr "Échec de l'outil externe de visualisation des diff" + +#: gitk:3674 +msgid "Gitk view definition" +msgstr "Définition des vues de Gitk" + +#: gitk:3678 +msgid "Remember this view" +msgstr "Se souvenir de cette vue" + +#: gitk:3679 +msgid "References (space separated list):" +msgstr "Références (liste d'éléments séparés par des espaces) :" + +#: gitk:3680 +msgid "Branches & tags:" +msgstr "Branches & tags :" + +#: gitk:3681 +msgid "All refs" +msgstr "Toutes les références" + +#: gitk:3682 +msgid "All (local) branches" +msgstr "Toutes les branches (locales)" + +#: gitk:3683 +msgid "All tags" +msgstr "Tous les tags" + +#: gitk:3684 +msgid "All remote-tracking branches" +msgstr "Toutes les branches de suivi à distance" + +#: gitk:3685 +msgid "Commit Info (regular expressions):" +msgstr "Info sur les commits (expressions régulières) :" + +#: gitk:3686 +msgid "Author:" +msgstr "Auteur :" + +#: gitk:3687 +msgid "Committer:" +msgstr "Commiteur :" + +#: gitk:3688 +msgid "Commit Message:" +msgstr "Message de commit :" + +#: gitk:3689 +msgid "Matches all Commit Info criteria" +msgstr "Correspond à tous les critères d'Info sur les commits" + +#: gitk:3690 +msgid "Changes to Files:" +msgstr "Changements des fichiers :" + +#: gitk:3691 +msgid "Fixed String" +msgstr "Chaîne Figée" + +#: gitk:3692 +msgid "Regular Expression" +msgstr "Expression Régulière" + +#: gitk:3693 +msgid "Search string:" +msgstr "Recherche de la chaîne :" + +#: gitk:3694 +msgid "" +"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" +msgstr "" +"Dates des commits (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, " +"2009 15:27:38\") :" + +#: gitk:3695 +msgid "Since:" +msgstr "De :" + +#: gitk:3696 +msgid "Until:" +msgstr "Jusqu'au :" + +#: gitk:3697 +msgid "Limit and/or skip a number of revisions (positive integer):" +msgstr "Limiter et/ou sauter un certain nombre (entier positif) de révisions :" + +#: gitk:3698 +msgid "Number to show:" +msgstr "Nombre à afficher :" + +#: gitk:3699 +msgid "Number to skip:" +msgstr "Nombre à sauter :" + +#: gitk:3700 +msgid "Miscellaneous options:" +msgstr "Options diverses :" + +#: gitk:3701 +msgid "Strictly sort by date" +msgstr "Trier par date" + +# FIXME : traduction de "branch sides" +#: gitk:3702 +#, fuzzy +msgid "Mark branch sides" +msgstr "Marquer les extrémités des branches" + +#: gitk:3703 +msgid "Limit to first parent" +msgstr "Limiter au premier ancêtre" + +#: gitk:3704 +msgid "Simple history" +msgstr "Historique simple" + +#: gitk:3705 +msgid "Additional arguments to git log:" +msgstr "Arguments supplémentaires de git log :" + +#: gitk:3706 +msgid "Enter files and directories to include, one per line:" +msgstr "Saisir les fichiers et répertoires à inclure, un par ligne :" + +#: gitk:3707 +msgid "Command to generate more commits to include:" +msgstr "Commande pour générer plus de commits à inclure :" + +#: gitk:3829 +msgid "Gitk: edit view" +msgstr "Gitk : éditer la vue" + +#: gitk:3837 +msgid "-- criteria for selecting revisions" +msgstr "-- critère pour la sélection des révisions" + +#: gitk:3842 +msgid "View Name:" +msgstr "Nom de la vue :" + +#: gitk:3917 +msgid "Apply (F5)" +msgstr "Appliquer (F5)" + +#: gitk:3955 +msgid "Error in commit selection arguments:" +msgstr "Erreur dans les arguments de sélection des commits :" + +#: gitk:4008 gitk:4060 gitk:4508 gitk:4522 gitk:5783 gitk:11196 gitk:11197 +msgid "None" +msgstr "Aucun" + +#: gitk:4456 gitk:6303 gitk:8065 gitk:8080 +msgid "Date" +msgstr "Date" + +#: gitk:4456 gitk:6303 +msgid "CDate" +msgstr "CDate" + +#: gitk:4605 gitk:4610 +msgid "Descendant" +msgstr "Descendant" + +#: gitk:4606 +msgid "Not descendant" +msgstr "Pas un descendant" + +#: gitk:4613 gitk:4618 +msgid "Ancestor" +msgstr "Ancêtre" + +#: gitk:4614 +msgid "Not ancestor" +msgstr "Pas un ancêtre" + +#: gitk:4904 +msgid "Local changes checked in to index but not committed" +msgstr "Modifications locales enregistrées dans l'index mais non commitées" + +#: gitk:4940 +msgid "Local uncommitted changes, not checked in to index" +msgstr "Modifications locales non enregistrées dans l'index et non commitées" + +#: gitk:6621 +msgid "many" +msgstr "nombreux" + +#: gitk:6805 +msgid "Tags:" +msgstr "Tags :" + +#: gitk:6822 gitk:6828 gitk:8058 +msgid "Parent" +msgstr "Parent" + +#: gitk:6833 +msgid "Child" +msgstr "Enfant" + +#: gitk:6842 +msgid "Branch" +msgstr "Branche" + +#: gitk:6845 +msgid "Follows" +msgstr "Suit" + +#: gitk:6848 +msgid "Precedes" +msgstr "Précède" + +#: gitk:7346 +#, tcl-format +msgid "Error getting diffs: %s" +msgstr "Erreur lors de la récupération des diff : %s" + +#: gitk:7886 +msgid "Goto:" +msgstr "Aller à :" + +#: gitk:7888 +msgid "SHA1 ID:" +msgstr "Id SHA1 :" + +#: gitk:7907 +#, tcl-format +msgid "Short SHA1 id %s is ambiguous" +msgstr "Id SHA1 court %s est ambigu" + +#: gitk:7914 +#, tcl-format +msgid "Revision %s is not known" +msgstr "Id SHA1 %s est inconnu" + +#: gitk:7924 +#, tcl-format +msgid "SHA1 id %s is not known" +msgstr "Id SHA1 %s est inconnu" + +#: gitk:7926 +#, tcl-format +msgid "Revision %s is not in the current view" +msgstr "La révision %s n'est pas dans la vue courante" + +#: gitk:8068 +msgid "Children" +msgstr "Enfants" + +#: gitk:8125 +#, tcl-format +msgid "Reset %s branch to here" +msgstr "Réinitialiser la branche %s vers cet état" + +#: gitk:8127 +msgid "Detached head: can't reset" +msgstr "Head détaché : impossible de réinitialiser" + +#: gitk:8236 gitk:8242 +msgid "Skipping merge commit " +msgstr "Éviter le commit de la fusion " + +#: gitk:8251 gitk:8256 +msgid "Error getting patch ID for " +msgstr "Erreur à l'obtention de l'ID du patch pour " + +#: gitk:8252 gitk:8257 +msgid " - stopping\n" +msgstr " - arrêt en cours\n" + +#: gitk:8262 gitk:8265 gitk:8273 gitk:8283 gitk:8292 +msgid "Commit " +msgstr "Commit " + +#: gitk:8266 +msgid "" +" is the same patch as\n" +" " +msgstr "" +"est le même patch que \n" +" " + +#: gitk:8274 +msgid "" +" differs from\n" +" " +msgstr "" +" diffère de\n" +" " + +#: gitk:8276 +msgid "- stopping\n" +msgstr "- arrêt en cours\n" + +#: gitk:8284 gitk:8293 +#, tcl-format +msgid " has %s children - stopping\n" +msgstr "a %s enfants - arrêt en cours\n" + +#: gitk:8324 +msgid "Top" +msgstr "Haut" + +#: gitk:8325 +msgid "From" +msgstr "De" + +#: gitk:8330 +msgid "To" +msgstr "À" + +#: gitk:8354 +msgid "Generate patch" +msgstr "Générer le patch" + +#: gitk:8356 +msgid "From:" +msgstr "De :" + +#: gitk:8365 +msgid "To:" +msgstr "À :" + +#: gitk:8374 +msgid "Reverse" +msgstr "Inverser" + +#: gitk:8376 gitk:8561 +msgid "Output file:" +msgstr "Fichier de sortie :" + +#: gitk:8382 +msgid "Generate" +msgstr "Générer" + +#: gitk:8420 +msgid "Error creating patch:" +msgstr "Erreur à la création du patch :" + +#: gitk:8443 gitk:8549 gitk:8606 +msgid "ID:" +msgstr "ID :" + +#: gitk:8452 +msgid "Tag name:" +msgstr "Nom du Tag :" + +#: gitk:8456 gitk:8615 +msgid "Create" +msgstr "Créer" + +#: gitk:8473 +msgid "No tag name specified" +msgstr "Aucun nom de tag spécifié" + +#: gitk:8477 +#, tcl-format +msgid "Tag \"%s\" already exists" +msgstr "Le tag \"%s\" existe déjà" + +#: gitk:8483 +msgid "Error creating tag:" +msgstr "Erreur à la création du tag :" + +#: gitk:8558 +msgid "Command:" +msgstr "Commande :" + +#: gitk:8566 +msgid "Write" +msgstr "Écrire" + +#: gitk:8584 +msgid "Error writing commit:" +msgstr "Erreur à l'ecriture du commit :" + +#: gitk:8611 +msgid "Name:" +msgstr "Nom :" + +#: gitk:8634 +msgid "Please specify a name for the new branch" +msgstr "Veuillez spécifier un nom pour la nouvelle branche" + +#: gitk:8639 +#, tcl-format +msgid "Branch '%s' already exists. Overwrite?" +msgstr "La branche '%s' existe déjà. Écraser?" + +#: gitk:8705 +#, tcl-format +msgid "Commit %s is already included in branch %s -- really re-apply it?" +msgstr "" +"Le Commit %s est déjà inclus dans la branche %s -- le ré-appliquer malgré " +"tout?" + +#: gitk:8710 +msgid "Cherry-picking" +msgstr "Cueillir (Cherry-picking)" + +#: gitk:8719 +#, tcl-format +msgid "" +"Cherry-pick failed because of local changes to file '%s'.\n" +"Please commit, reset or stash your changes and try again." +msgstr "" +"La cueillette (cherry-pick) a échouée à cause de modifications locales du " +"fichier '%s'.\n" +"Veuillez commiter, réinitialiser ou stasher vos changements et essayer de " +"nouveau." + +#: gitk:8725 +msgid "" +"Cherry-pick failed because of merge conflict.\n" +"Do you wish to run git citool to resolve it?" +msgstr "" +"La cueillette (cherry-pick) a échouée à cause d'un conflit lors d'une " +"fusion.\n" +"Souhaitez-vous exécuter git citool pour le résoudre ?" + +#: gitk:8741 +msgid "No changes committed" +msgstr "Aucun changement commité" + +#: gitk:8767 +msgid "Confirm reset" +msgstr "Confirmer la réinitialisation" + +#: gitk:8769 +#, tcl-format +msgid "Reset branch %s to %s?" +msgstr "Réinitialiser la branche %s à %s?" + +#: gitk:8773 +msgid "Reset type:" +msgstr "Type de réinitialisation :" + +#: gitk:8777 +msgid "Soft: Leave working tree and index untouched" +msgstr "Douce : Laisse le répertoire de travail et l'index intacts" + +#: gitk:8780 +msgid "Mixed: Leave working tree untouched, reset index" +msgstr "" +"Hybride : Laisse le répertoire de travail dans son état courant, " +"réinitialise l'index" + +#: gitk:8783 +msgid "" +"Hard: Reset working tree and index\n" +"(discard ALL local changes)" +msgstr "" +"Dure : Réinitialise le répertoire de travail et l'index\n" +"(abandonne TOUS les changements locaux)" + +#: gitk:8800 +msgid "Resetting" +msgstr "Réinitialisation" + +# Fixme: Récupération est-il vraiment une mauvaise traduction? +#: gitk:8857 +#, fuzzy +msgid "Checking out" +msgstr "Récupération" + +#: gitk:8910 +msgid "Cannot delete the currently checked-out branch" +msgstr "Impossible de supprimer la branche en cours" + +#: gitk:8916 +#, tcl-format +msgid "" +"The commits on branch %s aren't on any other branch.\n" +"Really delete branch %s?" +msgstr "" +"Les commits de la branche %s ne sont dans aucune autre branche.\n" +"Voulez-vous vraiment supprimer cette branche %s ?" + +#: gitk:8947 +#, tcl-format +msgid "Tags and heads: %s" +msgstr "Tags et heads : %s" + +#: gitk:8962 +msgid "Filter" +msgstr "Filtrer" + +#: gitk:9257 +msgid "" +"Error reading commit topology information; branch and preceding/following " +"tag information will be incomplete." +msgstr "" +"Erreur à la lecture des informations sur la topologie des commits, les " +"informations sur les branches et les tags précédents/suivants seront " +"incomplètes." + +#: gitk:10243 +msgid "Tag" +msgstr "Tag" + +#: gitk:10243 +msgid "Id" +msgstr "Id" + +#: gitk:10291 +msgid "Gitk font chooser" +msgstr "Sélecteur de police de Gitk" + +#: gitk:10308 +msgid "B" +msgstr "B" + +#: gitk:10311 +msgid "I" +msgstr "I" + +#: gitk:10407 +msgid "Gitk preferences" +msgstr "Préférences de Gitk" + +#: gitk:10409 +msgid "Commit list display options" +msgstr "Options d'affichage de la liste des commits" + +#: gitk:10412 +msgid "Maximum graph width (lines)" +msgstr "Longueur maximum du graphe (lignes)" + +# FIXME : Traduction standard de "pane"? +#: gitk:10416 +#, fuzzy, tcl-format +msgid "Maximum graph width (% of pane)" +msgstr "Longueur maximum du graphe (% du panneau)" + +#: gitk:10420 +msgid "Show local changes" +msgstr "Montrer les changements locaux" + +#: gitk:10423 +msgid "Auto-select SHA1" +msgstr "Sélection auto. du SHA1" + +#: gitk:10427 +msgid "Diff display options" +msgstr "Options d'affichage des diff" + +#: gitk:10429 +msgid "Tab spacing" +msgstr "Taille des tabulations" + +#: gitk:10432 +msgid "Display nearby tags" +msgstr "Afficher les tags les plus proches" + +#: gitk:10435 +msgid "Hide remote refs" +msgstr "Cacher les refs distantes" + +#: gitk:10438 +msgid "Limit diffs to listed paths" +msgstr "Limiter les différences aux chemins listés" + +#: gitk:10441 +msgid "Support per-file encodings" +msgstr "Support pour un encodage des caractères par fichier" + +#: gitk:10447 gitk:10512 +msgid "External diff tool" +msgstr "Outil diff externe" + +#: gitk:10449 +msgid "Choose..." +msgstr "Choisir..." + +#: gitk:10454 +msgid "Colors: press to choose" +msgstr "Couleurs : cliquer pour choisir" + +#: gitk:10457 +msgid "Background" +msgstr "Arrière-plan" + +#: gitk:10458 gitk:10488 +msgid "background" +msgstr "arrière-plan" + +#: gitk:10461 +msgid "Foreground" +msgstr "Premier plan" + +#: gitk:10462 +msgid "foreground" +msgstr "premier plan" + +#: gitk:10465 +msgid "Diff: old lines" +msgstr "Diff : anciennes lignes" + +#: gitk:10466 +msgid "diff old lines" +msgstr "diff anciennes lignes" + +#: gitk:10470 +msgid "Diff: new lines" +msgstr "Diff : nouvelles lignes" + +#: gitk:10471 +msgid "diff new lines" +msgstr "diff nouvelles lignes" + +#: gitk:10475 +msgid "Diff: hunk header" +msgstr "Diff : entête du hunk" + +#: gitk:10477 +msgid "diff hunk header" +msgstr "diff : entête du hunk" + +#: gitk:10481 +msgid "Marked line bg" +msgstr "Arrière-plan de la ligne marquée" + +#: gitk:10483 +msgid "marked line background" +msgstr "Arrière-plan de la ligne marquée" + +#: gitk:10487 +msgid "Select bg" +msgstr "Sélectionner l'arrière-plan" + +#: gitk:10491 +msgid "Fonts: press to choose" +msgstr "Polices : cliquer pour choisir" + +#: gitk:10493 +msgid "Main font" +msgstr "Police principale" + +#: gitk:10494 +msgid "Diff display font" +msgstr "Police d'affichage des diff" + +#: gitk:10495 +msgid "User interface font" +msgstr "Police de l'interface utilisateur" + +#: gitk:10522 +#, tcl-format +msgid "Gitk: choose color for %s" +msgstr "Gitk : choisir la couleur de %s" + +#: gitk:10973 +msgid "" +"Sorry, gitk cannot run with this version of Tcl/Tk.\n" +" Gitk requires at least Tcl/Tk 8.4." +msgstr "" +"Désolé, gitk ne peut être exécuté avec cette version de Tcl/Tk.\n" +" Gitk requiert Tcl/Tk version 8.4 ou supérieur." + +#: gitk:11101 +msgid "Cannot find a git repository here." +msgstr "Impossible de trouver un dépôt git ici." + +#: gitk:11105 +#, tcl-format +msgid "Cannot find the git directory \"%s\"." +msgstr "Impossible de trouver le répertoire git \"%s\"." + +#: gitk:11152 +#, tcl-format +msgid "Ambiguous argument '%s': both revision and filename" +msgstr "Argument '%s' ambigu : à la fois une révision et un nom de fichier" + +#: gitk:11164 +msgid "Bad arguments to gitk:" +msgstr "Arguments invalides pour gitk :" + +#: gitk:11249 +msgid "Command line" +msgstr "Ligne de commande" diff --git a/gitk-git/po/hu.po b/gitk-git/po/hu.po new file mode 100644 index 0000000000..1df212e881 --- /dev/null +++ b/gitk-git/po/hu.po @@ -0,0 +1,1295 @@ +# Translation of gitk to Hungarian. +# Copyright (C) 2007 Paul Mackerras. +# This file is distributed under the same license as the gitk package. +# +# Laszlo Papp <laszlo.papp@arhungary.hu>, 2009. +# +msgid "" +msgstr "" +"Project-Id-Version: git-gui\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-12-14 13:33+0100\n" +"PO-Revision-Date: 2009-12-14 14:04+0100\n" +"Last-Translator: Laszlo Papp <djszapi@archlinux.us>\n" +"Language-Team: Hungarian\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: gitk:115 +msgid "Couldn't get list of unmerged files:" +msgstr "Nem sikerült letölteni az unmerged fájl listát:" + +#: gitk:271 +msgid "Error parsing revisions:" +msgstr "Hiba történt értelmezés közben:" + +#: gitk:326 +msgid "Error executing --argscmd command:" +msgstr "Hiba történt a végrehajtáskor --argscmd parancs:" + +#: gitk:339 +msgid "No files selected: --merge specified but no files are unmerged." +msgstr "" +"Nincsen fájl kiválasztva: --merge megadve, de egyetlen fájl sem unmerged." + +#: gitk:342 +msgid "" +"No files selected: --merge specified but no unmerged files are within file " +"limit." +msgstr "" +"Nincsen fájl kiválasztva: --merge megadva, de nincsenek unmerged fájlok a " +"fájlon belül limit." + +#: gitk:364 gitk:511 +msgid "Error executing git log:" +msgstr "Hiba történt a git log végrehajtása közben:" + +#: gitk:382 gitk:527 +msgid "Reading" +msgstr "Olvasás" + +#: gitk:442 gitk:4258 +msgid "Reading commits..." +msgstr "Commitok olvasása ..." + +#: gitk:445 gitk:1575 gitk:4261 +msgid "No commits selected" +msgstr "Nincsen commit kiválasztva" + +#: gitk:1451 +msgid "Can't parse git log output:" +msgstr "Nem lehet értelmezni a git log kimenetét:" + +#: gitk:1671 +msgid "No commit information available" +msgstr "Nincsen elérhető commit információ" + +#: gitk:1813 +msgid "mc" +msgstr "mc" + +#: gitk:1848 gitk:4051 gitk:9029 gitk:10570 gitk:10789 +msgid "OK" +msgstr "OK" + +#: gitk:1850 gitk:4053 gitk:8629 gitk:8703 gitk:8813 gitk:8862 gitk:9031 +#: gitk:10571 gitk:10790 +msgid "Cancel" +msgstr "Visszavonás" + +#: gitk:1972 +msgid "Update" +msgstr "Frissités" + +#: gitk:1973 +msgid "Reload" +msgstr "Újratöltés" + +#: gitk:1974 +msgid "Reread references" +msgstr "Referenciák újraolvasása" + +#: gitk:1975 +msgid "List references" +msgstr "Referenciák listázása" + +#: gitk:1977 +msgid "Start git gui" +msgstr "Git gui indítása" + +#: gitk:1979 +msgid "Quit" +msgstr "Kilépés" + +#: gitk:1971 +msgid "File" +msgstr "Fájl" + +#: gitk:1983 +msgid "Preferences" +msgstr "Beállítások" + +#: gitk:1982 +msgid "Edit" +msgstr "Szerkesztés" + +#: gitk:1987 +msgid "New view..." +msgstr "Új nézet ..." + +#: gitk:1988 +msgid "Edit view..." +msgstr "Nézet szerkesztése ..." + +#: gitk:1989 +msgid "Delete view" +msgstr "Nézet törlése" + +#: gitk:1991 +msgid "All files" +msgstr "Minden fájl" + +#: gitk:1986 gitk:3805 +msgid "View" +msgstr "Nézet" + +#: gitk:1996 gitk:2006 gitk:2777 +msgid "About gitk" +msgstr "Gitk névjegy" + +#: gitk:1997 gitk:2011 +msgid "Key bindings" +msgstr "Billentyűkombináció" + +#: gitk:1995 gitk:2010 +msgid "Help" +msgstr "Segítség" + +#: gitk:2088 +msgid "SHA1 ID: " +msgstr "SHA1 ID: " + +#: gitk:2119 +msgid "Row" +msgstr "Sor" + +#: gitk:2157 +msgid "Find" +msgstr "Keresés" + +#: gitk:2158 +msgid "next" +msgstr "következő" + +#: gitk:2159 +msgid "prev" +msgstr "előző" + +#: gitk:2160 +msgid "commit" +msgstr "commit" + +#: gitk:2163 gitk:2165 gitk:4419 gitk:4442 gitk:4466 gitk:6407 gitk:6479 +#: gitk:6563 +msgid "containing:" +msgstr "tartalmazás:" + +#: gitk:2166 gitk:3287 gitk:3292 gitk:4494 +msgid "touching paths:" +msgstr "érintendő útvonalak:" + +#: gitk:2167 gitk:4499 +msgid "adding/removing string:" +msgstr "string hozzáadása/törlése:" + +#: gitk:2176 gitk:2178 +msgid "Exact" +msgstr "Pontos" + +#: gitk:2178 gitk:4574 gitk:6375 +msgid "IgnCase" +msgstr "Kis/nagy betű nem számít" + +#: gitk:2178 gitk:4468 gitk:4572 gitk:6371 +msgid "Regexp" +msgstr "Regexp" + +#: gitk:2180 gitk:2181 gitk:4593 gitk:4623 gitk:4630 gitk:6499 gitk:6567 +msgid "All fields" +msgstr "Minden mező" + +#: gitk:2181 gitk:4591 gitk:4623 gitk:6438 +msgid "Headline" +msgstr "Főcím" + +#: gitk:2182 gitk:4591 gitk:6438 gitk:6567 gitk:7000 +msgid "Comments" +msgstr "Megjegyzések" + +#: gitk:2182 gitk:4591 gitk:4595 gitk:4630 gitk:6438 gitk:6935 gitk:8280 +#: gitk:8295 +msgid "Author" +msgstr "Szerző" + +#: gitk:2182 gitk:4591 gitk:6438 gitk:6937 +msgid "Committer" +msgstr "Commitoló" + +#: gitk:2213 +msgid "Search" +msgstr "Keresés" + +#: gitk:2221 +msgid "Diff" +msgstr "Diff" + +#: gitk:2223 +msgid "Old version" +msgstr "Régi verzió" + +#: gitk:2225 +msgid "New version" +msgstr "Új verzió" + +#: gitk:2227 +msgid "Lines of context" +msgstr "Tartalmi sorok" + +#: gitk:2237 +msgid "Ignore space change" +msgstr "Space váltás mellőzése" + +#: gitk:2296 +msgid "Patch" +msgstr "Patch" + +#: gitk:2298 +msgid "Tree" +msgstr "Tree" + +#: gitk:2453 gitk:2470 +msgid "Diff this -> selected" +msgstr "Diff ezeket -> kiválasztott" + +#: gitk:2454 gitk:2471 +msgid "Diff selected -> this" +msgstr "Diff kiválasztottakat -> ezt" + +#: gitk:2455 gitk:2472 +msgid "Make patch" +msgstr "Patch készítése" + +#: gitk:2456 gitk:8687 +msgid "Create tag" +msgstr "Tag készítése" + +#: gitk:2457 gitk:8793 +msgid "Write commit to file" +msgstr "Commit fáljba írása" + +#: gitk:2458 gitk:8850 +msgid "Create new branch" +msgstr "Új branch készítése" + +#: gitk:2459 +msgid "Cherry-pick this commit" +msgstr "Cherry-pick erre a commitra" + +#: gitk:2460 +msgid "Reset HEAD branch to here" +msgstr "HEAD branch újraindítása ide" + +#: gitk:2461 +msgid "Mark this commit" +msgstr "Ezen commit megjelölése" + +#: gitk:2462 +msgid "Return to mark" +msgstr "Visszatérés a megjelöléshez" + +#: gitk:2463 +msgid "Find descendant of this and mark" +msgstr "Találd meg ezen utódokat és jelöld meg" + +#: gitk:2464 +msgid "Compare with marked commit" +msgstr "Összehasonlítás a megjelölt commit-tal" + +#: gitk:2478 +msgid "Check out this branch" +msgstr "Check out ezt a branchot" + +#: gitk:2479 +msgid "Remove this branch" +msgstr "Töröld ezt a branch-ot" + +#: gitk:2486 +msgid "Highlight this too" +msgstr "Emeld ki ezt is" + +#: gitk:2487 +msgid "Highlight this only" +msgstr "Csak ezt emeld ki" + +#: gitk:2488 +msgid "External diff" +msgstr "Külső diff" + +#: gitk:2489 +msgid "Blame parent commit" +msgstr "Blame szülő kommitra" + +#: gitk:2496 +msgid "Show origin of this line" +msgstr "Mutasd meg ennek a sornak az eredetét" + +#: gitk:2497 +msgid "Run git gui blame on this line" +msgstr "Futtasd a git gui blame-t ezen a soron" + +#: gitk:2779 +msgid "" +"\n" +"Gitk - a commit viewer for git\n" +"\n" +"Copyright ©9 2005-2009 Paul Mackerras\n" +"\n" +"Use and redistribute under the terms of the GNU General Public License" +msgstr "" +"\n" +"Gitk - commit nézegető a githez\n" +"\n" +"Szerzői jog ©9 2005-2009 Paul Mackerras\n" +"\n" +"Használd és terjeszd a GNU General Public License feltételei mellett" + +#: gitk:2787 gitk:2851 gitk:9215 +msgid "Close" +msgstr "Bezárás" + +#: gitk:2808 +msgid "Gitk key bindings" +msgstr "Gitk-billentyű hozzárendelés" + +#: gitk:2811 +msgid "Gitk key bindings:" +msgstr "Gitk-billentyű hozzaárendelés:" + +#: gitk:2813 +#, tcl-format +msgid "<%s-Q>\t\tQuit" +msgstr "<%s-Q>\t\tKilépés" + +#: gitk:2814 +msgid "<Home>\t\tMove to first commit" +msgstr "<Pos1>\t\tElső commithoz" + +#: gitk:2815 +msgid "<End>\t\tMove to last commit" +msgstr "<Ende>\t\tUtolsó commithoz" + +#: gitk:2816 +msgid "<Up>, p, i\tMove up one commit" +msgstr "<Hoch>, p, i\tEgy committal feljebb" + +#: gitk:2817 +msgid "<Down>, n, k\tMove down one commit" +msgstr "<Runter>, n, k\tEgy committal lejjebb" + +#: gitk:2818 +msgid "<Left>, z, j\tGo back in history list" +msgstr "<Links>, z, j\tVissza a history listába" + +#: gitk:2819 +msgid "<Right>, x, l\tGo forward in history list" +msgstr "<Rechts>, x, l\tElőre a history listába" + +#: gitk:2820 +msgid "<PageUp>\tMove up one page in commit list" +msgstr "<BildHoch>\tEgy lappal feljebb a commit listába" + +#: gitk:2821 +msgid "<PageDown>\tMove down one page in commit list" +msgstr "<BildRunter>\tEgy lappal lejjebb a commit listába" + +#: gitk:2822 +#, tcl-format +msgid "<%s-Home>\tScroll to top of commit list" +msgstr "<%s-Pos1>\tGörgetés a commit lista tetejéhez" + +#: gitk:2823 +#, tcl-format +msgid "<%s-End>\tScroll to bottom of commit list" +msgstr "<%s-Ende>\tGörgetés a commit lista aljához" + +#: gitk:2824 +#, tcl-format +msgid "<%s-Up>\tScroll commit list up one line" +msgstr "<%s-Hoch>\tEgy sorral feljebb görgetés a commit listában" + +#: gitk:2825 +#, tcl-format +msgid "<%s-Down>\tScroll commit list down one line" +msgstr "<%s-Runter>\tEgy sorral lejjebb görgetés a commit listában" + +#: gitk:2826 +#, tcl-format +msgid "<%s-PageUp>\tScroll commit list up one page" +msgstr "<%s-BildHoch>\tEgy lappal feljebb görgetés a commit listában" + +#: gitk:2827 +#, tcl-format +msgid "<%s-PageDown>\tScroll commit list down one page" +msgstr "<%s-BildRunter>\tEgy sorral lejjebb görgetés a commit listában" + +#: gitk:2828 +msgid "<Shift-Up>\tFind backwards (upwards, later commits)" +msgstr "<Umschalt-Hoch>\tKeresés visszafele (felfele, utolsó commitok)" + +#: gitk:2829 +msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" +msgstr "<Umschalt-Runter>\tKeresés előre (lefelé; korábbi commitok)" + +#: gitk:2830 +msgid "<Delete>, b\tScroll diff view up one page" +msgstr "<Entf>, b\t\tEgy lappal feljebb görgetés a diff nézetben" + +#: gitk:2831 +msgid "<Backspace>\tScroll diff view up one page" +msgstr "<Löschtaste>\tEgy lappal feljebb görgetés a diff nézetben" + +#: gitk:2832 +msgid "<Space>\t\tScroll diff view down one page" +msgstr "<Leertaste>\tEgy lappal lejjebb görgetés a diff nézetben" + +#: gitk:2833 +msgid "u\t\tScroll diff view up 18 lines" +msgstr "u\t\t18 sorral felfelé görgetés diff nézetben" + +#: gitk:2834 +msgid "d\t\tScroll diff view down 18 lines" +msgstr "d\t\t18 sorral lejjebb görgetés a diff nézetben" + +#: gitk:2835 +#, tcl-format +msgid "<%s-F>\t\tFind" +msgstr "<%s-F>\t\tKeresés" + +#: gitk:2836 +#, tcl-format +msgid "<%s-G>\t\tMove to next find hit" +msgstr "<%s-G>\t\tKövetkező találathoz" + +#: gitk:2837 +msgid "<Return>\tMove to next find hit" +msgstr "<Eingabetaste>\tKövetkező találathoz" + +#: gitk:2838 +msgid "/\t\tFocus the search box" +msgstr "/\t\tLépj a keresési mezőre" + +#: gitk:2839 +msgid "?\t\tMove to previous find hit" +msgstr "?\t\tElőző találathoz" + +#: gitk:2840 +msgid "f\t\tScroll diff view to next file" +msgstr "f\t\tKövetkező fájlra görgetés diff nézetben" + +#: gitk:2841 +#, tcl-format +msgid "<%s-S>\t\tSearch for next hit in diff view" +msgstr "<%s-S>\t\tKövetkező találatra keresés diff nézetben" + +#: gitk:2842 +#, tcl-format +msgid "<%s-R>\t\tSearch for previous hit in diff view" +msgstr "<%s-R>\t\tElőző találatra keresés diff nézetben" + +#: gitk:2843 +#, tcl-format +msgid "<%s-KP+>\tIncrease font size" +msgstr "<%s-Nummerblock-Plus>\tBetűméret növelése" + +#: gitk:2844 +#, tcl-format +msgid "<%s-plus>\tIncrease font size" +msgstr "<%s-Plus>\tBetűméret növelése" + +#: gitk:2845 +#, tcl-format +msgid "<%s-KP->\tDecrease font size" +msgstr "<%s-Nummernblock-Minus> Betűméret csökkentése" + +#: gitk:2846 +#, tcl-format +msgid "<%s-minus>\tDecrease font size" +msgstr "<%s-Minus>\tBetűméret csökkentése" + +#: gitk:2847 +msgid "<F5>\t\tUpdate" +msgstr "<F5>\t\tFrissítés" + +#: gitk:3302 gitk:3311 +#, tcl-format +msgid "Error creating temporary directory %s:" +msgstr "Hiba történt az ideiglenes könyvtár létrehozása közben %s:" + +#: gitk:3324 +#, tcl-format +msgid "Error getting \"%s\" from %s:" +msgstr "Hiba történt \"%s\" letöltése közben %s-ről:" + +#: gitk:3387 +msgid "command failed:" +msgstr "parancs hiba:" + +#: gitk:3536 +msgid "No such commit" +msgstr "Nincs ilyen commit" + +#: gitk:3550 +msgid "git gui blame: command failed:" +msgstr "git gui blame: parancs hiba:" + +#: gitk:3581 +#, tcl-format +msgid "Couldn't read merge head: %s" +msgstr "Nem sikerült a Merge head olvasása: %s" + +#: gitk:3589 +#, tcl-format +msgid "Error reading index: %s" +msgstr "Hiba történt az index olvasása közben: %s" + +#: gitk:3614 +#, tcl-format +msgid "Couldn't start git blame: %s" +msgstr "Nem sikerült a git blame indítása: %s" + +#: gitk:3617 gitk:6406 +msgid "Searching" +msgstr "Keresés" + +#: gitk:3649 +#, tcl-format +msgid "Error running git blame: %s" +msgstr "Hiba történt a git blame futtatása közben: %s" + +#: gitk:3677 +#, tcl-format +msgid "That line comes from commit %s, which is not in this view" +msgstr "" +"A %s commitból származik az a sor, amelyik nem található ebben a nézetben" + +#: gitk:3691 +msgid "External diff viewer failed:" +msgstr "Külső diff nézegető hiba:" + +#: gitk:3809 +msgid "Gitk view definition" +msgstr "Gitk nézet meghatározása" + +#: gitk:3813 +msgid "Remember this view" +msgstr "Maradj ennél a nézetnél" + +#: gitk:3814 +msgid "References (space separated list):" +msgstr "Referenciák (szóközzel tagolt lista" + +#: gitk:3815 +msgid "Branches & tags:" +msgstr "Branch-ek & tagek:" + +#: gitk:3816 +msgid "All refs" +msgstr "Minden ref" + +#: gitk:3817 +msgid "All (local) branches" +msgstr "Minden (helyi) branch" + +#: gitk:3818 +msgid "All tags" +msgstr "Minden tag" + +#: gitk:3819 +msgid "All remote-tracking branches" +msgstr "Minden távoli követő branch" + +#: gitk:3820 +msgid "Commit Info (regular expressions):" +msgstr "Commit Infó (reguláris kifejezés):" + +#: gitk:3821 +msgid "Author:" +msgstr "Szerző:" + +#: gitk:3822 +msgid "Committer:" +msgstr "Commitoló:" + +#: gitk:3823 +msgid "Commit Message:" +msgstr "Commit üzenet:" + +#: gitk:3824 +msgid "Matches all Commit Info criteria" +msgstr "Egyezik minen Commit Infó feltétellel" + +#: gitk:3825 +msgid "Changes to Files:" +msgstr "Fájl változások:" + +#: gitk:3826 +msgid "Fixed String" +msgstr "Fix String" + +#: gitk:3827 +msgid "Regular Expression" +msgstr "Reguláris kifejezés" + +#: gitk:3828 +msgid "Search string:" +msgstr "Keresés szöveg:" + +#: gitk:3829 +msgid "" +"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" +msgstr "" +"Commit Dátumok (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" + +#: gitk:3830 +msgid "Since:" +msgstr "Ettől:" + +#: gitk:3831 +msgid "Until:" +msgstr "Eddig:" + +#: gitk:3832 +msgid "Limit and/or skip a number of revisions (positive integer):" +msgstr "Limitálva és/vagy kihagyva egy adott számú revíziót (pozitív egész):" + +#: gitk:3833 +msgid "Number to show:" +msgstr "Mutatandó szám:" + +#: gitk:3834 +msgid "Number to skip:" +msgstr "Kihagyandó szám:" + +#: gitk:3835 +msgid "Miscellaneous options:" +msgstr "Különféle opciók:" + +#: gitk:3836 +msgid "Strictly sort by date" +msgstr "Szigorú rendezás dátum alapján" + +#: gitk:3837 +msgid "Mark branch sides" +msgstr "Jelölje meg az ágakat" + +#: gitk:3838 +msgid "Limit to first parent" +msgstr "Korlátozás az első szülőre" + +#: gitk:3839 +msgid "Simple history" +msgstr "Egyszerű history" + +#: gitk:3840 +msgid "Additional arguments to git log:" +msgstr "További argumentok a git log-hoz:" + +#: gitk:3841 +msgid "Enter files and directories to include, one per line:" +msgstr "Fájlok és könyvtárak bejegyzése amiket tartalmaz, soronként:" + +#: gitk:3842 +msgid "Command to generate more commits to include:" +msgstr "Parancs több tartalmazó commit generálására:" + +#: gitk:3964 +msgid "Gitk: edit view" +msgstr "Gitk: szerkesztés nézet" + +#: gitk:3972 +msgid "-- criteria for selecting revisions" +msgstr "-- kritériumok a revíziók kiválasztásához" + +#: gitk:3977 +msgid "View Name" +msgstr "Nézet neve" + +#: gitk:4052 +msgid "Apply (F5)" +msgstr "Alkalmaz (F5)" + +#: gitk:4090 +msgid "Error in commit selection arguments:" +msgstr "Hiba történt a commit argumentumok kiválasztása közben:" + +#: gitk:4143 gitk:4195 gitk:4643 gitk:4657 gitk:5918 gitk:11519 gitk:11520 +msgid "None" +msgstr "Keine" + +#: gitk:4591 gitk:6438 gitk:8282 gitk:8297 +msgid "Date" +msgstr "Dátum" + +#: gitk:4591 gitk:6438 +msgid "CDate" +msgstr "Dátum" + +#: gitk:4740 gitk:4745 +msgid "Descendant" +msgstr "Leszármazott" + +#: gitk:4741 +msgid "Not descendant" +msgstr "Nem leszármazott" + +#: gitk:4748 gitk:4753 +msgid "Ancestor" +msgstr "Előd" + +#: gitk:4749 +msgid "Not ancestor" +msgstr "Nem előd" + +#: gitk:5039 +msgid "Local changes checked in to index but not committed" +msgstr "" +"Lokális változtatások, melyek be vannak téve az indexbe, de még nincsenek " +"commitolva" + +#: gitk:5075 +msgid "Local uncommitted changes, not checked in to index" +msgstr "Lokális nem commitolt változások, nincsenek betéve az indexbe" + +#: gitk:6756 +msgid "many" +msgstr "sok" + +#: gitk:6939 +msgid "Tags:" +msgstr "Tagek:" + +#: gitk:6956 gitk:6962 gitk:8275 +msgid "Parent" +msgstr "Eltern" + +#: gitk:6967 +msgid "Child" +msgstr "Gyerek" + +#: gitk:6976 +msgid "Branch" +msgstr "Ág" + +#: gitk:6979 +msgid "Follows" +msgstr "Következők" + +#: gitk:6982 +msgid "Precedes" +msgstr "Megelőzők" + +#: gitk:7519 +#, tcl-format +msgid "Error getting diffs: %s" +msgstr "Hiba történt a diff-ek letöltése közben: %s" + +#: gitk:8103 +msgid "Goto:" +msgstr "Menj:" + +#: gitk:8105 +msgid "SHA1 ID:" +msgstr "SHA1 ID:" + +#: gitk:8124 +#, tcl-format +msgid "Short SHA1 id %s is ambiguous" +msgstr "Rövid SHA1 id %s félreérthető" + +#: gitk:8131 +msgid "Revision %s is not known" +msgstr "A(z) %s revízió nem ismert" + +#: gitk:8141 +#, tcl-format +msgid "SHA1 id %s is not known" +msgstr "SHA1 id %s nem ismert" + +#: gitk:8143 +#, tcl-format +msgid "Revision %s is not in the current view" +msgstr "A(z) %s revízió nincs a jelenlegi nézetben" + +#: gitk:8285 +msgid "Children" +msgstr "Gyerekek" + +#: gitk:8343 +#, tcl-format +msgid "Reset %s branch to here" +msgstr "Állítsd vissza a %s branch-ot ide" + +#: gitk:8345 +msgid "Detached head: can't reset" +msgstr "Elkülönített head: nem lehet visszaállítani" + +#: gitk:8454 gitk:8460 +msgid "Skipping merge commit " +msgstr "Merge commit kihagyása " + +#: gitk:8469 gitk:8474 +msgid "Error getting patch ID for " +msgstr "Hiba történt a patch ID megszerzése közben a következőnél " + +#: gitk:8470 gitk:8475 +msgid " - stopping\n" +msgstr " - abbahagyás\n" + +#: gitk:8480 gitk:8483 gitk:8491 gitk:8505 gitk:8514 +msgid "Commit " +msgstr "Commit " + +#: gitk:8484 +msgid "" +" is the same patch as\n" +" " +msgstr "" +" Ugyanaz a patch mint\n" +" " + +#: gitk:8492 +msgid "" +" differs from\n" +" " +msgstr "" +" különbözik innentől\n" +" " + +#: gitk:8494 +msgid "" +"Diff of commits:\n" +"\n" +msgstr "" +"A commitok diffje:\n" +"\n" + +#: gitk:8506 gitk:8515 +#, tcl-format +msgid " has %s children - stopping\n" +msgstr " %s gyereke van. abbahagyás\n" + +#: gitk:8534 +msgid "Error writing commit to file: %s" +msgstr "Hiba történt a commit fájlba írása közben: %s" + +#: gitk:8540 +msgid "Error diffing commits: %s" +msgstr "Hiba történt a commitok diffelése közben: %s" + +#: gitk:8570 +msgid "Top" +msgstr "Teteje" + +#: gitk:8571 +msgid "From" +msgstr "Innen" + +#: gitk:8576 +msgid "To" +msgstr "Ide" + +#: gitk:8600 +msgid "Generate patch" +msgstr "Patch generálása" + +#: gitk:8602 +msgid "From:" +msgstr "Innen:" + +#: gitk:8611 +msgid "To:" +msgstr "Ide:" + +#: gitk:8620 +msgid "Reverse" +msgstr "Visszafele" + +#: gitk:8622 gitk:8807 +msgid "Output file:" +msgstr "Kimeneti fájl:" + +#: gitk:8628 +msgid "Generate" +msgstr "Generálás" + +#: gitk:8666 +msgid "Error creating patch:" +msgstr "Hiba törtét a patch készítése közben:" + +#: gitk:8689 gitk:8795 gitk:8852 +msgid "ID:" +msgstr "ID:" + +#: gitk:8698 +msgid "Tag name:" +msgstr "Tag név:" + +#: gitk:8702 gitk:8861 +msgid "Create" +msgstr "Létrehozás" + +#: gitk:8719 +msgid "No tag name specified" +msgstr "A tag neve nincsen megadva" + +#: gitk:8723 +#, tcl-format +msgid "Tag \"%s\" already exists" +msgstr "%s Tag már létezik" + +#: gitk:8729 +msgid "Error creating tag:" +msgstr "Hiba történt a tag létrehozása közben:" + +#: gitk:8804 +msgid "Command:" +msgstr "Parancs:" + +#: gitk:8812 +msgid "Write" +msgstr "Írás" + +#: gitk:8830 +msgid "Error writing commit:" +msgstr "Hiba történt a commit írása közben:" + +#: gitk:8857 +msgid "Name:" +msgstr "Név:" + +#: gitk:8880 +msgid "Please specify a name for the new branch" +msgstr "Kérem adja meg a nevét az új branchhoz" + +#: gitk:8885 +#, tcl-format +msgid "Branch '%s' already exists. Overwrite?" +msgstr "%s branch már létezik. Felülírja?" + +#: gitk:8951 +#, tcl-format +msgid "Commit %s is already included in branch %s -- really re-apply it?" +msgstr "" +"%s commit már benne van a %s branchban -- biztos hogy újra csinálja ?" +"eintragen?" + +#: gitk:8956 +msgid "Cherry-picking" +msgstr "Cherry-picking" + +#: gitk:8965 +#, tcl-format +msgid "" +"Cherry-pick failed because of local changes to file '%s'.\n" +"Please commit, reset or stash your changes and try again." +msgstr "" +"Cherry-pick hiba történt lokális váltotások miatt a '%s' fájlban.\n" +"Kérem commitolja, indítsa újra vagy rejtse el a változtatásait és próbálja " +"újra." + +#: gitk:8971 +msgid "" +"Cherry-pick failed because of merge conflict.\n" +"Do you wish to run git citool to resolve it?" +msgstr "" +"Cherry-pick hiba történt merge konfliktus miatt.\n" +"Kívánja futtatni a git citool-t a probléma megoldásához?" + +#: gitk:8987 +msgid "No changes committed" +msgstr "Nincsen változás commitolva" + +#: gitk:9013 +msgid "Confirm reset" +msgstr "Újraindítás megerősítése" + +#: gitk:9015 +#, tcl-format +msgid "Reset branch %s to %s?" +msgstr "Újraindítja a %s branchot %s-ig?" + +#: gitk:9017 +msgid "Reset type:" +msgstr "Újraindítás típusa:" + +#: gitk:9020 +msgid "Soft: Leave working tree and index untouched" +msgstr "Soft: Hagyd a working tree-t és az indexet érintetlenül" + +#: gitk:9023 +msgid "Mixed: Leave working tree untouched, reset index" +msgstr "Kevert: Hagyd a working tree-t érintetlenül, töröld az indexet" + +#: gitk:9026 +msgid "" +"Hard: Reset working tree and index\n" +"(discard ALL local changes)" +msgstr "" +"Hard: Indítsd újra a working tree-t és az indexet\n" +"(MINDEN lokális változás eldobása)" + +#: gitk:9043 +msgid "Resetting" +msgstr "Újraindítás" + +#: gitk:9103 +msgid "Checking out" +msgstr "Kivesz" + +#: gitk:9156 +msgid "Cannot delete the currently checked-out branch" +msgstr "Nem lehet a jelenleg kivett branch-ot törölni" + +#: gitk:9162 +#, tcl-format +msgid "" +"The commits on branch %s aren't on any other branch.\n" +"Really delete branch %s?" +msgstr "" +"A %s branchon található commit nem található meg semelyik másik branchon.\n" +"Tényleg törli a %s branchot?" + +#: gitk:9193 +#, tcl-format +msgid "Tags and heads: %s" +msgstr "Tagek és headek: %s" + +#: gitk:9208 +msgid "Filter" +msgstr "Szűrő" + +#: gitk:9503 +msgid "" +"Error reading commit topology information; branch and preceding/following " +"tag information will be incomplete." +msgstr "" +"Hiba történt a commit topológiai információ olvasása közben; branch ésa " +"megelőző/következő információ nem lesz teljes." + +#: gitk:10489 +msgid "Tag" +msgstr "Tag" + +#: gitk:10489 +msgid "Id" +msgstr "Id" + +#: gitk:10539 +msgid "Gitk font chooser" +msgstr "Gitk-betű kiválasztó" + +#: gitk:10556 +msgid "B" +msgstr "F" + +#: gitk:10559 +msgid "I" +msgstr "K" + +#: gitk:10677 +msgid "Gitk preferences" +msgstr "Gitk beállítások" + +#: gitk:10679 +msgid "Commit list display options" +msgstr "Commit lista kijelzési opciók" + +#: gitk:10682 +msgid "Maximum graph width (lines)" +msgstr "Maximális grafikon szélesség (sorok)" + +#: gitk:10685 +#, tcl-format +msgid "Maximum graph width (% of pane)" +msgstr "Maximális grafikon szélesség (táble %-je)" + +#: gitk:10688 +msgid "Show local changes" +msgstr "Mutasd a lokális változtatásokat" + +#: gitk:10691 +msgid "Auto-select SHA1" +msgstr "SHA1 Automatikus kiválasztása" + +#: gitk:10694 +msgid "Hide remote refs" +msgstr "A távoli refek elrejtése" + +#: gitk:10698 +msgid "Diff display options" +msgstr "Diff kijelző opciók" + +#: gitk:10700 +msgid "Tab spacing" +msgstr "Tab sorköz" + +#: gitk:10703 +msgid "Display nearby tags" +msgstr "Szomszédos tagek kijelzése" + +#: gitk:10706 +msgid "Limit diffs to listed paths" +msgstr "Korlátozott diffek a kilistázott útvonalakhoz" + +#: gitk:10709 +msgid "Support per-file encodings" +msgstr "Fájlonkénti kódolás támgatása" + +#: gitk:10715 gitk:10804 +msgid "External diff tool" +msgstr "Külső diff alkalmazás" + +#: gitk:10716 +msgid "Choose..." +msgstr "Válaszd ..." + +#: gitk:10721 +msgid "General options" +msgstr "Általános opciók" + +#: gitk:10724 +msgid "Use themed widgets" +msgstr "Témázott vezérlők használata" + +#: gitk:10726 +msgid "(change requires restart)" +msgstr "(a változás újraindítást igényel)" + +#: gitk:10728 +msgid "(currently unavailable)" +msgstr "(jelenleg nem elérhető)" + +#: gitk:10732 +msgid "Colors: press to choose" +msgstr "Színek: nyomja meg a kiválasztáshoz" + +#: gitk:10735 +msgid "Interface" +msgstr "Interfész" + +#: gitk:10736 +msgid "interface" +msgstr "interfész" + +#: gitk:10739 +msgid "Background" +msgstr "Háttér" + +#: gitk:10740 gitk:10770 +msgid "background" +msgstr "háttér" + +#: gitk:10743 +msgid "Foreground" +msgstr "Előtér" + +#: gitk:10744 +msgid "foreground" +msgstr "előtér" + +#: gitk:10747 +msgid "Diff: old lines" +msgstr "Diff: régi sorok" + +#: gitk:10748 +msgid "diff old lines" +msgstr "diff régi sorok" + +#: gitk:10752 +msgid "Diff: new lines" +msgstr "Diff: új sorok" + +#: gitk:10753 +msgid "diff new lines" +msgstr "diff - új sorok" + +#: gitk:10757 +msgid "Diff: hunk header" +msgstr "Diff: nagy headerök" + +#: gitk:10759 +msgid "diff hunk header" +msgstr "diff - nagy headerök" + +#: gitk:10763 +msgid "Marked line bg" +msgstr "Megjelölt sor háttér" + +#: gitk:10765 +msgid "marked line background" +msgstr "megjelölt sor háttér" + +#: gitk:10769 +msgid "Select bg" +msgstr "Válasszon hátteret" + +#: gitk:10773 +msgid "Fonts: press to choose" +msgstr "Betű: nyomja meg a kiválasztáshoz" + +#: gitk:10775 +msgid "Main font" +msgstr "Fő betű" + +#: gitk:10776 +msgid "Diff display font" +msgstr "Diff kijelző betű" + +#: gitk:10777 +msgid "User interface font" +msgstr "Felhasználói interfész betű" + +#: gitk:10814 +#, tcl-format +msgid "Gitk: choose color for %s" +msgstr "Gitk: válasszon színt a %s-ra" + +#: gitk:11418 +msgid "Cannot find a git repository here." +msgstr "Nem találhatü git repository itt." + +#: gitk:11422 +#, tcl-format +msgid "Cannot find the git directory \"%s\"." +msgstr "Nem található a \"%s\" könyvtár." + +#: gitk:11469 +#, tcl-format +msgid "Ambiguous argument '%s': both revision and filename" +msgstr "Félreérthető argumentum '%s': revízió és fájlnév is" + +#: gitk:11481 +msgid "Bad arguments to gitk:" +msgstr "Rossz gitk argumentumok:" + +#: gitk:11572 +msgid "Command line" +msgstr "Parancs sor" + +#~ msgid "Use all refs" +#~ msgstr "Használd az összes referenciát" + +#~ msgid "Max count:" +#~ msgstr "Max. szám:" + +#~ msgid "Skip:" +#~ msgstr "Kihagy:" + +#~ msgid "Name" +#~ msgstr "Név" + +#~ msgid "Tag/Head %s is not known" +#~ msgstr "Tag/Head %s nem ismert" + +#~ msgid "- stopping\n" +#~ msgstr "- abbahagyás.\n" + +#~ msgid "" +#~ "Sorry, gitk cannot run with this version of Tcl/Tk.\n" +#~ " Gitk requires at least Tcl/Tk 8.4." +#~ msgstr "" +#~ "Sajnáljuk, de a gitk nem futtatható ezzel a Tcl/Tk verzióval.\n" +#~ "Gitk futtatásához legalább Tcl/Tk 8.4 szükséges." diff --git a/gitk-git/po/it.po b/gitk-git/po/it.po index e89c95702c..4818652309 100644 --- a/gitk-git/po/it.po +++ b/gitk-git/po/it.po @@ -8,25 +8,33 @@ msgid "" msgstr "" "Project-Id-Version: gitk\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-10-18 22:03+1100\n" -"PO-Revision-Date: 2008-03-13 17:34+0100\n" +"POT-Creation-Date: 2010-01-28 18:40+0100\n" +"PO-Revision-Date: 2010-01-28 18:41+0100\n" "Last-Translator: Michele Ballabio <barra_cuda@katamail.com>\n" "Language-Team: Italian\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: gitk:113 +#: gitk:115 msgid "Couldn't get list of unmerged files:" msgstr "Impossibile ottenere l'elenco dei file in attesa di fusione:" -#: gitk:340 +#: gitk:274 +msgid "Error parsing revisions:" +msgstr "Errore nella lettura delle revisioni:" + +#: gitk:329 +msgid "Error executing --argscmd command:" +msgstr "Errore nell'esecuzione del comando specificato con --argscmd:" + +#: gitk:342 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "Nessun file selezionato: è stata specificata l'opzione --merge ma non ci " "sono file in attesa di fusione." -#: gitk:343 +#: gitk:345 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." @@ -34,688 +42,994 @@ msgstr "" "Nessun file selezionato: è stata specificata l'opzione --merge ma i file " "specificati non sono in attesa di fusione." -#: gitk:365 gitk:503 +#: gitk:367 gitk:514 msgid "Error executing git log:" msgstr "Errore nell'esecuzione di git log:" -#: gitk:378 +#: gitk:385 gitk:530 msgid "Reading" msgstr "Lettura in corso" -#: gitk:438 gitk:3462 +#: gitk:445 gitk:4261 msgid "Reading commits..." msgstr "Lettura delle revisioni in corso..." -#: gitk:441 gitk:1528 gitk:3465 +#: gitk:448 gitk:1578 gitk:4264 msgid "No commits selected" msgstr "Nessuna revisione selezionata" -#: gitk:1399 +#: gitk:1454 msgid "Can't parse git log output:" msgstr "Impossibile elaborare i dati di git log:" -#: gitk:1605 +#: gitk:1674 msgid "No commit information available" msgstr "Nessuna informazione disponibile sulle revisioni" -#: gitk:1709 gitk:1731 gitk:3259 gitk:7764 gitk:9293 gitk:9466 +#: gitk:1816 +msgid "mc" +msgstr "" + +#: gitk:1851 gitk:4054 gitk:9044 gitk:10585 gitk:10804 msgid "OK" msgstr "OK" -#: gitk:1733 gitk:3260 gitk:7439 gitk:7510 gitk:7613 gitk:7660 gitk:7766 -#: gitk:9294 gitk:9467 +#: gitk:1853 gitk:4056 gitk:8634 gitk:8713 gitk:8828 gitk:8877 gitk:9046 +#: gitk:10586 gitk:10805 msgid "Cancel" msgstr "Annulla" -#: gitk:1811 +#: gitk:1975 msgid "Update" msgstr "Aggiorna" -#: gitk:1813 +#: gitk:1976 +msgid "Reload" +msgstr "Ricarica" + +#: gitk:1977 msgid "Reread references" msgstr "Rileggi riferimenti" -#: gitk:1814 +#: gitk:1978 msgid "List references" msgstr "Elenca riferimenti" -#: gitk:1815 +#: gitk:1980 +msgid "Start git gui" +msgstr "Avvia git gui" + +#: gitk:1982 msgid "Quit" msgstr "Esci" -#: gitk:1810 +#: gitk:1974 msgid "File" msgstr "File" -#: gitk:1818 +#: gitk:1986 msgid "Preferences" msgstr "Preferenze" -#: gitk:1817 +#: gitk:1985 msgid "Edit" msgstr "Modifica" -#: gitk:1821 +#: gitk:1990 msgid "New view..." msgstr "Nuova vista..." -#: gitk:1822 +#: gitk:1991 msgid "Edit view..." msgstr "Modifica vista..." -#: gitk:1823 +#: gitk:1992 msgid "Delete view" msgstr "Elimina vista" -#: gitk:1825 +#: gitk:1994 msgid "All files" msgstr "Tutti i file" -#: gitk:1820 gitk:3196 +#: gitk:1989 gitk:3808 msgid "View" msgstr "Vista" -#: gitk:1828 gitk:2487 +#: gitk:1999 gitk:2009 gitk:2780 msgid "About gitk" msgstr "Informazioni su gitk" -#: gitk:1829 +#: gitk:2000 gitk:2014 msgid "Key bindings" msgstr "Scorciatoie da tastiera" -#: gitk:1827 +#: gitk:1998 gitk:2013 msgid "Help" msgstr "Aiuto" -#: gitk:1887 -msgid "SHA1 ID: " -msgstr "SHA1 ID: " +#: gitk:2091 gitk:8110 +msgid "SHA1 ID:" +msgstr "SHA1 ID:" -#: gitk:1918 +#: gitk:2122 msgid "Row" -msgstr "" +msgstr "Riga" -#: gitk:1949 +#: gitk:2160 msgid "Find" msgstr "Trova" -#: gitk:1950 +#: gitk:2161 msgid "next" msgstr "succ" -#: gitk:1951 +#: gitk:2162 msgid "prev" msgstr "prec" -#: gitk:1952 +#: gitk:2163 msgid "commit" msgstr "revisione" -#: gitk:1955 gitk:1957 gitk:3617 gitk:3640 gitk:3664 gitk:5550 gitk:5621 +#: gitk:2166 gitk:2168 gitk:4422 gitk:4445 gitk:4469 gitk:6410 gitk:6482 +#: gitk:6566 msgid "containing:" msgstr "contenente:" -#: gitk:1958 gitk:2954 gitk:2959 gitk:3692 +#: gitk:2169 gitk:3290 gitk:3295 gitk:4497 msgid "touching paths:" msgstr "che riguarda i percorsi:" -#: gitk:1959 gitk:3697 +#: gitk:2170 gitk:4502 msgid "adding/removing string:" msgstr "che aggiunge/rimuove la stringa:" -#: gitk:1968 gitk:1970 +#: gitk:2179 gitk:2181 msgid "Exact" msgstr "Esatto" -#: gitk:1970 gitk:3773 gitk:5518 +#: gitk:2181 gitk:4577 gitk:6378 msgid "IgnCase" msgstr "" -#: gitk:1970 gitk:3666 gitk:3771 gitk:5514 +#: gitk:2181 gitk:4471 gitk:4575 gitk:6374 msgid "Regexp" msgstr "" -#: gitk:1972 gitk:1973 gitk:3792 gitk:3822 gitk:3829 gitk:5641 gitk:5708 +#: gitk:2183 gitk:2184 gitk:4596 gitk:4626 gitk:4633 gitk:6502 gitk:6570 msgid "All fields" msgstr "Tutti i campi" -#: gitk:1973 gitk:3790 gitk:3822 gitk:5580 +#: gitk:2184 gitk:4594 gitk:4626 gitk:6441 msgid "Headline" msgstr "Titolo" -#: gitk:1974 gitk:3790 gitk:5580 gitk:5708 gitk:6109 +#: gitk:2185 gitk:4594 gitk:6441 gitk:6570 gitk:7003 msgid "Comments" msgstr "Commenti" -#: gitk:1974 gitk:3790 gitk:3794 gitk:3829 gitk:5580 gitk:6045 gitk:7285 -#: gitk:7300 +#: gitk:2185 gitk:4594 gitk:4598 gitk:4633 gitk:6441 gitk:6938 gitk:8285 +#: gitk:8300 msgid "Author" msgstr "Autore" -#: gitk:1974 gitk:3790 gitk:5580 gitk:6047 +#: gitk:2185 gitk:4594 gitk:6441 gitk:6940 msgid "Committer" msgstr "Revisione creata da" -#: gitk:2003 +#: gitk:2216 msgid "Search" msgstr "Cerca" -#: gitk:2010 +#: gitk:2224 msgid "Diff" msgstr "" -#: gitk:2012 +#: gitk:2226 msgid "Old version" msgstr "Vecchia versione" -#: gitk:2014 +#: gitk:2228 msgid "New version" msgstr "Nuova versione" -#: gitk:2016 +#: gitk:2230 msgid "Lines of context" msgstr "Linee di contesto" -#: gitk:2026 +#: gitk:2240 msgid "Ignore space change" msgstr "Ignora modifiche agli spazi" -#: gitk:2084 +#: gitk:2299 msgid "Patch" msgstr "Modifiche" -#: gitk:2086 +#: gitk:2301 msgid "Tree" msgstr "Directory" -#: gitk:2213 gitk:2226 +#: gitk:2456 gitk:2473 msgid "Diff this -> selected" msgstr "Diff questo -> selezionato" -#: gitk:2214 gitk:2227 +#: gitk:2457 gitk:2474 msgid "Diff selected -> this" msgstr "Diff selezionato -> questo" -#: gitk:2215 gitk:2228 +#: gitk:2458 gitk:2475 msgid "Make patch" msgstr "Crea patch" -#: gitk:2216 gitk:7494 +#: gitk:2459 gitk:8692 msgid "Create tag" msgstr "Crea etichetta" -#: gitk:2217 gitk:7593 +#: gitk:2460 gitk:8808 msgid "Write commit to file" msgstr "Scrivi revisione in un file" -#: gitk:2218 gitk:7647 +#: gitk:2461 gitk:8865 msgid "Create new branch" msgstr "Crea un nuovo ramo" -#: gitk:2219 +#: gitk:2462 msgid "Cherry-pick this commit" msgstr "Porta questa revisione in cima al ramo attuale" -#: gitk:2220 +#: gitk:2463 msgid "Reset HEAD branch to here" msgstr "Aggiorna il ramo HEAD a questa revisione" -#: gitk:2234 +#: gitk:2464 +msgid "Mark this commit" +msgstr "Segna questa revisione" + +#: gitk:2465 +msgid "Return to mark" +msgstr "Torna alla revisione segnata" + +#: gitk:2466 +msgid "Find descendant of this and mark" +msgstr "Trova il discendente di questa revisione e di quella segnata" + +#: gitk:2467 +msgid "Compare with marked commit" +msgstr "Confronta con la revisione segnata" + +#: gitk:2481 msgid "Check out this branch" msgstr "Attiva questo ramo" -#: gitk:2235 +#: gitk:2482 msgid "Remove this branch" msgstr "Elimina questo ramo" -#: gitk:2242 +#: gitk:2489 msgid "Highlight this too" msgstr "Evidenzia anche questo" -#: gitk:2243 +#: gitk:2490 msgid "Highlight this only" msgstr "Evidenzia solo questo" -#: gitk:2245 +#: gitk:2491 +msgid "External diff" +msgstr "Visualizza differenze in un altro programma" + +#: gitk:2492 msgid "Blame parent commit" -msgstr "" +msgstr "Annota la revisione precedente" + +#: gitk:2499 +msgid "Show origin of this line" +msgstr "Mostra la provenienza di questa riga" -#: gitk:2488 +#: gitk:2500 +msgid "Run git gui blame on this line" +msgstr "Esegui git gui blame su questa riga" + +#: gitk:2782 msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2008 Paul Mackerras\n" +"Copyright © 2005-2009 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - un visualizzatore di revisioni per git\n" "\n" -"Copyright © 2005-2008 Paul Mackerras\n" +"Copyright © 2005-2009 Paul Mackerras\n" "\n" "Utilizzo e redistribuzione permessi sotto i termini della GNU General Public " "License" -#: gitk:2496 gitk:2557 gitk:7943 +#: gitk:2790 gitk:2854 gitk:9230 msgid "Close" msgstr "Chiudi" -#: gitk:2515 +#: gitk:2811 msgid "Gitk key bindings" msgstr "Scorciatoie da tastiera di Gitk" -#: gitk:2517 +#: gitk:2814 msgid "Gitk key bindings:" msgstr "Scorciatoie da tastiera di Gitk:" -#: gitk:2519 +#: gitk:2816 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tEsci" -#: gitk:2520 +#: gitk:2817 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\tVai alla prima revisione" -#: gitk:2521 +#: gitk:2818 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\tVai all'ultima revisione" -#: gitk:2522 +#: gitk:2819 msgid "<Up>, p, i\tMove up one commit" -msgstr "<Up>, p, i\tVai più in alto di una revisione" +msgstr "<Su>, p, i\tVai più in alto di una revisione" -#: gitk:2523 +#: gitk:2820 msgid "<Down>, n, k\tMove down one commit" -msgstr "<Down>, n, k\tVai più in basso di una revisione" +msgstr "<Giù>, n, k\tVai più in basso di una revisione" -#: gitk:2524 +#: gitk:2821 msgid "<Left>, z, j\tGo back in history list" -msgstr "<Left>, z, j\tTorna indietro nella cronologia" +msgstr "<Sinistra>, z, j\tTorna indietro nella cronologia" -#: gitk:2525 +#: gitk:2822 msgid "<Right>, x, l\tGo forward in history list" -msgstr "<Right>, x, l\tVai avanti nella cronologia" +msgstr "<Destra>, x, l\tVai avanti nella cronologia" -#: gitk:2526 +#: gitk:2823 msgid "<PageUp>\tMove up one page in commit list" -msgstr "<PageUp>\tVai più in alto di una pagina nella lista delle revisioni" +msgstr "<PaginaSu>\tVai più in alto di una pagina nella lista delle revisioni" -#: gitk:2527 +#: gitk:2824 msgid "<PageDown>\tMove down one page in commit list" -msgstr "<PageDown>\tVai più in basso di una pagina nella lista delle revisioni" +msgstr "" +"<PaginaGiù>\tVai più in basso di una pagina nella lista delle revisioni" -#: gitk:2528 +#: gitk:2825 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tScorri alla cima della lista delle revisioni" -#: gitk:2529 +#: gitk:2826 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tScorri alla fine della lista delle revisioni" -#: gitk:2530 +#: gitk:2827 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" -msgstr "<%s-Up>\tScorri la lista delle revisioni in alto di una riga" +msgstr "<%s-Su>\tScorri la lista delle revisioni in alto di una riga" -#: gitk:2531 +#: gitk:2828 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" -msgstr "<%s-Down>\tScorri la lista delle revisioni in basso di una riga" +msgstr "<%s-Giù>\tScorri la lista delle revisioni in basso di una riga" -#: gitk:2532 +#: gitk:2829 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" -msgstr "<%s-PageUp>\tScorri la lista delle revisioni in alto di una pagina" +msgstr "<%s-PaginaSu>\tScorri la lista delle revisioni in alto di una pagina" -#: gitk:2533 +#: gitk:2830 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" -msgstr "<%s-PageDown>\tScorri la lista delle revisioni in basso di una pagina" +msgstr "<%s-PaginaGiù>\tScorri la lista delle revisioni in basso di una pagina" -#: gitk:2534 +#: gitk:2831 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" -msgstr "<Shift-Up>\tTrova all'indietro (verso l'alto, revisioni successive)" +msgstr "<Shift-Su>\tTrova all'indietro (verso l'alto, revisioni successive)" -#: gitk:2535 +#: gitk:2832 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" -msgstr "<Shift-Down>\tTrova in avanti (verso il basso, revisioni precedenti)" +msgstr "<Shift-Giù>\tTrova in avanti (verso il basso, revisioni precedenti)" -#: gitk:2536 +#: gitk:2833 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Delete>, b\tScorri la vista delle differenze in alto di una pagina" -#: gitk:2537 +#: gitk:2834 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Backspace>\tScorri la vista delle differenze in alto di una pagina" -#: gitk:2538 +#: gitk:2835 msgid "<Space>\t\tScroll diff view down one page" -msgstr "<Space>\t\tScorri la vista delle differenze in basso di una pagina" +msgstr "<Spazio>\t\tScorri la vista delle differenze in basso di una pagina" -#: gitk:2539 +#: gitk:2836 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tScorri la vista delle differenze in alto di 18 linee" -#: gitk:2540 +#: gitk:2837 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tScorri la vista delle differenze in basso di 18 linee" -#: gitk:2541 +#: gitk:2838 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tTrova" -#: gitk:2542 +#: gitk:2839 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tTrova in avanti" -#: gitk:2543 +#: gitk:2840 msgid "<Return>\tMove to next find hit" -msgstr "<Return>\tTrova in avanti" +msgstr "<Invio>\tTrova in avanti" -#: gitk:2544 -msgid "/\t\tMove to next find hit, or redo find" -msgstr "/\t\tTrova in avanti, o cerca di nuovo" +#: gitk:2841 +msgid "/\t\tFocus the search box" +msgstr "/\t\tCursore nel box di ricerca" -#: gitk:2545 +#: gitk:2842 msgid "?\t\tMove to previous find hit" msgstr "?\t\tTrova all'indietro" -#: gitk:2546 +#: gitk:2843 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tScorri la vista delle differenze al file successivo" -#: gitk:2547 +#: gitk:2844 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tCerca in avanti nella vista delle differenze" -#: gitk:2548 +#: gitk:2845 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tCerca all'indietro nella vista delle differenze" -#: gitk:2549 +#: gitk:2846 #, tcl-format msgid "<%s-KP+>\tIncrease font size" -msgstr "<%s-KP+>\tAumenta grandezza carattere" +msgstr "<%s-KP+>\tAumenta dimensione carattere" -#: gitk:2550 +#: gitk:2847 #, tcl-format msgid "<%s-plus>\tIncrease font size" -msgstr "<%s-plus>\tAumenta grandezza carattere" +msgstr "<%s-più>\tAumenta dimensione carattere" -#: gitk:2551 +#: gitk:2848 #, tcl-format msgid "<%s-KP->\tDecrease font size" -msgstr "<%s-KP->\tDiminuisci grandezza carattere" +msgstr "<%s-KP->\tDiminuisci dimensione carattere" -#: gitk:2552 +#: gitk:2849 #, tcl-format msgid "<%s-minus>\tDecrease font size" -msgstr "<%s-minus>\tDiminuisci grandezza carattere" +msgstr "<%s-meno>\tDiminuisci dimensione carattere" -#: gitk:2553 +#: gitk:2850 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tAggiorna" -#: gitk:3200 +#: gitk:3305 gitk:3314 +#, tcl-format +msgid "Error creating temporary directory %s:" +msgstr "Errore durante la creazione della directory temporanea %s:" + +#: gitk:3327 +#, tcl-format +msgid "Error getting \"%s\" from %s:" +msgstr "Errore nella lettura di \"%s\" da %s:" + +#: gitk:3390 +msgid "command failed:" +msgstr "impossibile eseguire il comando:" + +#: gitk:3539 +msgid "No such commit" +msgstr "Revisione inesistente" + +#: gitk:3553 +msgid "git gui blame: command failed:" +msgstr "git gui blame: impossibile eseguire il comando:" + +#: gitk:3584 +#, tcl-format +msgid "Couldn't read merge head: %s" +msgstr "Impossibile leggere merge head: %s" + +#: gitk:3592 +#, tcl-format +msgid "Error reading index: %s" +msgstr "Errore nella lettura dell'indice: %s" + +#: gitk:3617 +#, tcl-format +msgid "Couldn't start git blame: %s" +msgstr "Impossibile eseguire git blame: %s" + +#: gitk:3620 gitk:6409 +msgid "Searching" +msgstr "Ricerca in corso" + +#: gitk:3652 +#, tcl-format +msgid "Error running git blame: %s" +msgstr "Errore nell'esecuzione di git blame: %s" + +#: gitk:3680 +#, tcl-format +msgid "That line comes from commit %s, which is not in this view" +msgstr "Quella riga proviene dalla revisione %s, non presente in questa vista" + +#: gitk:3694 +msgid "External diff viewer failed:" +msgstr "Impossibile eseguire il visualizzatore di differenze:" + +#: gitk:3812 msgid "Gitk view definition" msgstr "Scelta vista Gitk" -#: gitk:3225 -msgid "Name" -msgstr "Nome" - -#: gitk:3228 +#: gitk:3816 msgid "Remember this view" msgstr "Ricorda questa vista" -#: gitk:3232 -msgid "Commits to include (arguments to git log):" -msgstr "Revisioni da includere (argomenti di git log):" +#: gitk:3817 +msgid "References (space separated list):" +msgstr "Riferimenti (lista di elementi separati da spazi)" -#: gitk:3239 -msgid "Command to generate more commits to include:" -msgstr "Comando che genera altre revisioni da visualizzare:" +#: gitk:3818 +msgid "Branches & tags:" +msgstr "Rami ed etichette" + +#: gitk:3819 +msgid "All refs" +msgstr "Tutti i riferimenti" + +#: gitk:3820 +msgid "All (local) branches" +msgstr "Tutti i rami (locali)" + +#: gitk:3821 +msgid "All tags" +msgstr "Tutte le etichette" + +#: gitk:3822 +msgid "All remote-tracking branches" +msgstr "Tutti i rami remoti" + +#: gitk:3823 +msgid "Commit Info (regular expressions):" +msgstr "Informazioni sulla revisione (espressioni regolari):" + +#: gitk:3824 +msgid "Author:" +msgstr "Autore:" + +#: gitk:3825 +msgid "Committer:" +msgstr "Revisione creata da:" + +#: gitk:3826 +msgid "Commit Message:" +msgstr "Messaggio di revisione:" + +#: gitk:3827 +msgid "Matches all Commit Info criteria" +msgstr "Risponde a tutti i criteri di ricerca sulle revisioni" + +#: gitk:3828 +msgid "Changes to Files:" +msgstr "Modifiche ai file:" + +#: gitk:3829 +msgid "Fixed String" +msgstr "Stringa fissa" + +#: gitk:3830 +msgid "Regular Expression" +msgstr "Espressione regolare" + +#: gitk:3831 +msgid "Search string:" +msgstr "Cerca stringa:" + +#: gitk:3832 +msgid "" +"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" +msgstr "" +"Date di revisione (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, " +"2009 15:27:38\"):" + +#: gitk:3833 +msgid "Since:" +msgstr "Da:" + +#: gitk:3834 +msgid "Until:" +msgstr "A:" + +#: gitk:3835 +msgid "Limit and/or skip a number of revisions (positive integer):" +msgstr "Limita e/o salta N revisioni (intero positivo):" + +#: gitk:3836 +msgid "Number to show:" +msgstr "Numero di revisioni da mostrare:" + +#: gitk:3837 +msgid "Number to skip:" +msgstr "Numero di revisioni da saltare:" + +#: gitk:3838 +msgid "Miscellaneous options:" +msgstr "Altre opzioni:" + +#: gitk:3839 +msgid "Strictly sort by date" +msgstr "Ordina solo per data" + +#: gitk:3840 +msgid "Mark branch sides" +msgstr "Segna i lati del ramo" + +#: gitk:3841 +msgid "Limit to first parent" +msgstr "Limita al primo genitore" -#: gitk:3246 +#: gitk:3842 +msgid "Simple history" +msgstr "Cronologia semplificata" + +#: gitk:3843 +msgid "Additional arguments to git log:" +msgstr "Ulteriori argomenti da passare a git log:" + +#: gitk:3844 msgid "Enter files and directories to include, one per line:" msgstr "Inserire file e directory da includere, uno per riga:" -#: gitk:3293 +#: gitk:3845 +msgid "Command to generate more commits to include:" +msgstr "Comando che genera altre revisioni da visualizzare:" + +#: gitk:3967 +msgid "Gitk: edit view" +msgstr "Gitk: modifica vista" + +#: gitk:3975 +msgid "-- criteria for selecting revisions" +msgstr "-- criteri per la scelta delle revisioni" + +#: gitk:3980 +msgid "View Name" +msgstr "Nome vista" + +#: gitk:4055 +msgid "Apply (F5)" +msgstr "Applica (F5)" + +#: gitk:4093 msgid "Error in commit selection arguments:" msgstr "Errore negli argomenti di selezione delle revisioni:" -#: gitk:3347 gitk:3399 gitk:3842 gitk:3856 gitk:5060 gitk:10141 gitk:10142 +#: gitk:4146 gitk:4198 gitk:4646 gitk:4660 gitk:5921 gitk:11534 gitk:11535 msgid "None" msgstr "Nessuno" -#: gitk:3790 gitk:5580 gitk:7287 gitk:7302 +#: gitk:4594 gitk:6441 gitk:8287 gitk:8302 msgid "Date" msgstr "Data" -#: gitk:3790 gitk:5580 +#: gitk:4594 gitk:6441 msgid "CDate" msgstr "" -#: gitk:3939 gitk:3944 +#: gitk:4743 gitk:4748 msgid "Descendant" msgstr "Discendente" -#: gitk:3940 +#: gitk:4744 msgid "Not descendant" msgstr "Non discendente" -#: gitk:3947 gitk:3952 +#: gitk:4751 gitk:4756 msgid "Ancestor" msgstr "Ascendente" -#: gitk:3948 +#: gitk:4752 msgid "Not ancestor" msgstr "Non ascendente" -#: gitk:4187 +#: gitk:5042 msgid "Local changes checked in to index but not committed" msgstr "Modifiche locali presenti nell'indice ma non nell'archivio" -#: gitk:4220 +#: gitk:5078 msgid "Local uncommitted changes, not checked in to index" msgstr "Modifiche locali non presenti né nell'archivio né nell'indice" -#: gitk:5549 -msgid "Searching" -msgstr "Ricerca in corso" +#: gitk:6759 +msgid "many" +msgstr "molti" -#: gitk:6049 +#: gitk:6942 msgid "Tags:" msgstr "Etichette:" -#: gitk:6066 gitk:6072 gitk:7280 +#: gitk:6959 gitk:6965 gitk:8280 msgid "Parent" msgstr "Genitore" -#: gitk:6077 +#: gitk:6970 msgid "Child" msgstr "Figlio" -#: gitk:6086 +#: gitk:6979 msgid "Branch" msgstr "Ramo" -#: gitk:6089 +#: gitk:6982 msgid "Follows" msgstr "Segue" -#: gitk:6092 +#: gitk:6985 msgid "Precedes" msgstr "Precede" -#: gitk:6378 -msgid "Error getting merge diffs:" -msgstr "Errore nella lettura delle differenze di fusione:" +#: gitk:7522 +#, tcl-format +msgid "Error getting diffs: %s" +msgstr "Errore nella lettura delle differenze:" -#: gitk:7113 +#: gitk:8108 msgid "Goto:" msgstr "Vai a:" -#: gitk:7115 -msgid "SHA1 ID:" -msgstr "SHA1 ID:" - -#: gitk:7134 +#: gitk:8129 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "La SHA1 id abbreviata %s è ambigua" -#: gitk:7146 +#: gitk:8136 +#, tcl-format +msgid "Revision %s is not known" +msgstr "La revisione %s è sconosciuta" + +#: gitk:8146 #, tcl-format msgid "SHA1 id %s is not known" msgstr "La SHA1 id %s è sconosciuta" -#: gitk:7148 +#: gitk:8148 #, tcl-format -msgid "Tag/Head %s is not known" -msgstr "L'etichetta/ramo %s è sconosciuto" +msgid "Revision %s is not in the current view" +msgstr "La revisione %s non è presente nella vista attuale" -#: gitk:7290 +#: gitk:8290 msgid "Children" msgstr "Figli" -#: gitk:7347 +#: gitk:8348 #, tcl-format msgid "Reset %s branch to here" msgstr "Aggiorna il ramo %s a questa revisione" -#: gitk:7349 +#: gitk:8350 msgid "Detached head: can't reset" +msgstr "Nessun ramo attivo: reset impossibile" + +#: gitk:8459 gitk:8465 +msgid "Skipping merge commit " +msgstr "Salto la revisione di fusione " + +#: gitk:8474 gitk:8479 +msgid "Error getting patch ID for " +msgstr "Errore nella identificazione della patch per " + +#: gitk:8475 gitk:8480 +msgid " - stopping\n" +msgstr " - fine\n" + +#: gitk:8485 gitk:8488 gitk:8496 gitk:8510 gitk:8519 +msgid "Commit " +msgstr "La revisione " + +#: gitk:8489 +msgid "" +" is the same patch as\n" +" " msgstr "" +" ha le stesse differenze di\n" +" " + +#: gitk:8497 +msgid "" +" differs from\n" +" " +msgstr "" +" è diversa da\n" +" " + +#: gitk:8499 +msgid "" +"Diff of commits:\n" +"\n" +msgstr "" +"Differenze tra le revisioni:\n" +"\n" + +#: gitk:8511 gitk:8520 +#, tcl-format +msgid " has %s children - stopping\n" +msgstr " ha %s figli - fine\n" + +#: gitk:8539 +#, tcl-format +msgid "Error writing commit to file: %s" +msgstr "Errore nella scrittura della revisione nel file: %s" -#: gitk:7381 +#: gitk:8545 +#, tcl-format +msgid "Error diffing commits: %s" +msgstr "Errore nelle differenze tra le revisioni: %s" + +#: gitk:8575 msgid "Top" msgstr "Inizio" -#: gitk:7382 +#: gitk:8576 msgid "From" msgstr "Da" -#: gitk:7387 +#: gitk:8581 msgid "To" msgstr "A" -#: gitk:7410 +#: gitk:8605 msgid "Generate patch" msgstr "Genera patch" -#: gitk:7412 +#: gitk:8607 msgid "From:" msgstr "Da:" -#: gitk:7421 +#: gitk:8616 msgid "To:" msgstr "A:" -#: gitk:7430 +#: gitk:8625 msgid "Reverse" msgstr "Inverti" -#: gitk:7432 gitk:7607 +#: gitk:8627 gitk:8822 msgid "Output file:" msgstr "Scrivi sul file:" -#: gitk:7438 +#: gitk:8633 msgid "Generate" msgstr "Genera" -#: gitk:7474 +#: gitk:8671 msgid "Error creating patch:" msgstr "Errore nella creazione della patch:" -#: gitk:7496 gitk:7595 gitk:7649 +#: gitk:8694 gitk:8810 gitk:8867 msgid "ID:" msgstr "ID:" -#: gitk:7505 +#: gitk:8703 msgid "Tag name:" msgstr "Nome etichetta:" -#: gitk:7509 gitk:7659 +#: gitk:8706 +msgid "Tag message is optional" +msgstr "Il messaggio dell'etichetta è opzionale" + +#: gitk:8708 +msgid "Tag message:" +msgstr "Messaggio dell'etichetta:" + +#: gitk:8712 gitk:8876 msgid "Create" msgstr "Crea" -#: gitk:7524 +#: gitk:8730 msgid "No tag name specified" msgstr "Nessuna etichetta specificata" -#: gitk:7528 +#: gitk:8734 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "L'etichetta \"%s\" esiste già" -#: gitk:7534 +#: gitk:8744 msgid "Error creating tag:" msgstr "Errore nella creazione dell'etichetta:" -#: gitk:7604 +#: gitk:8819 msgid "Command:" msgstr "Comando:" -#: gitk:7612 +#: gitk:8827 msgid "Write" msgstr "Scrivi" -#: gitk:7628 +#: gitk:8845 msgid "Error writing commit:" msgstr "Errore nella scrittura della revisione:" -#: gitk:7654 +#: gitk:8872 msgid "Name:" msgstr "Nome:" -#: gitk:7674 +#: gitk:8895 msgid "Please specify a name for the new branch" msgstr "Specificare un nome per il nuovo ramo" -#: gitk:7703 +#: gitk:8900 +#, tcl-format +msgid "Branch '%s' already exists. Overwrite?" +msgstr "Il ramo '%s' esiste già. Sovrascrivere?" + +#: gitk:8966 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "La revisione %s è già inclusa nel ramo %s -- applicarla di nuovo?" -#: gitk:7708 +#: gitk:8971 msgid "Cherry-picking" msgstr "" -#: gitk:7720 +#: gitk:8980 +#, 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 "" +"Impossibile eseguire cherry-pick perché il file '%s' è stato modificato " +"nella directory di lavoro.\n" +"Prima di riprovare, bisogna creare una nuova revisione, annullare le " +"modifiche o usare 'git stash'." + +#: gitk:8986 +msgid "" +"Cherry-pick failed because of merge conflict.\n" +"Do you wish to run git citool to resolve it?" +msgstr "" +"Impossibile eseguire cherry-pick a causa di un conflitto nella fusione.\n" +"Vuoi avviare git citool per risolverlo?" + +#: gitk:9002 msgid "No changes committed" msgstr "Nessuna modifica archiviata" -#: gitk:7745 +#: gitk:9028 msgid "Confirm reset" msgstr "Conferma git reset" -#: gitk:7747 +#: gitk:9030 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Aggiornare il ramo %s a %s?" -#: gitk:7751 +#: gitk:9032 msgid "Reset type:" msgstr "Tipo di aggiornamento:" -#: gitk:7755 +#: gitk:9035 msgid "Soft: Leave working tree and index untouched" msgstr "Soft: Lascia la direcory di lavoro e l'indice come sono" -#: gitk:7758 +#: gitk:9038 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Mixed: Lascia la directory di lavoro come è, aggiorna l'indice" -#: gitk:7761 +#: gitk:9041 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -723,19 +1037,19 @@ msgstr "" "Hard: Aggiorna la directory di lavoro e l'indice\n" "(abbandona TUTTE le modifiche locali)" -#: gitk:7777 +#: gitk:9058 msgid "Resetting" msgstr "git reset in corso" -#: gitk:7834 +#: gitk:9118 msgid "Checking out" msgstr "Attivazione in corso" -#: gitk:7885 +#: gitk:9171 msgid "Cannot delete the currently checked-out branch" msgstr "Impossibile cancellare il ramo attualmente attivo" -#: gitk:7891 +#: gitk:9177 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -744,16 +1058,16 @@ msgstr "" "Le revisioni nel ramo %s non sono presenti su altri rami.\n" "Cancellare il ramo %s?" -#: gitk:7922 +#: gitk:9208 #, tcl-format msgid "Tags and heads: %s" msgstr "Etichette e rami: %s" -#: gitk:7936 +#: gitk:9223 msgid "Filter" msgstr "Filtro" -#: gitk:8230 +#: gitk:9518 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -761,154 +1075,202 @@ msgstr "" "Errore nella lettura della topologia delle revisioni: le informazioni sul " "ramo e le etichette precedenti e seguenti saranno incomplete." -#: gitk:9216 +#: gitk:10504 msgid "Tag" msgstr "Etichetta" -#: gitk:9216 +#: gitk:10504 msgid "Id" msgstr "Id" -#: gitk:9262 +#: gitk:10554 msgid "Gitk font chooser" msgstr "Scelta caratteri gitk" -#: gitk:9279 +#: gitk:10571 msgid "B" msgstr "B" -#: gitk:9282 +#: gitk:10574 msgid "I" msgstr "I" -#: gitk:9375 +#: gitk:10692 msgid "Gitk preferences" msgstr "Preferenze gitk" -#: gitk:9376 +#: gitk:10694 msgid "Commit list display options" msgstr "Opzioni visualizzazione dell'elenco revisioni" -#: gitk:9379 +#: gitk:10697 msgid "Maximum graph width (lines)" msgstr "Larghezza massima del grafico (in linee)" -#: gitk:9383 +#: gitk:10700 #, tcl-format msgid "Maximum graph width (% of pane)" msgstr "Larghezza massima del grafico (% del pannello)" -#: gitk:9388 +#: gitk:10703 msgid "Show local changes" msgstr "Mostra modifiche locali" -#: gitk:9393 +#: gitk:10706 msgid "Auto-select SHA1" msgstr "Seleziona automaticamente SHA1 hash" -#: gitk:9398 +#: gitk:10709 +msgid "Hide remote refs" +msgstr "Nascondi i riferimenti remoti" + +#: gitk:10713 msgid "Diff display options" msgstr "Opzioni di visualizzazione delle differenze" -#: gitk:9400 +#: gitk:10715 msgid "Tab spacing" msgstr "Spaziatura tabulazioni" -#: gitk:9404 +#: gitk:10718 msgid "Display nearby tags" msgstr "Mostra etichette vicine" -#: gitk:9409 +#: gitk:10721 msgid "Limit diffs to listed paths" msgstr "Limita le differenze ai percorsi elencati" -#: gitk:9414 +#: gitk:10724 msgid "Support per-file encodings" -msgstr "" +msgstr "Attiva codifica file per file" -#: gitk:9421 +#: gitk:10730 gitk:10819 msgid "External diff tool" -msgstr "" +msgstr "Visualizzatore di differenze" -#: gitk:9423 +#: gitk:10731 msgid "Choose..." -msgstr "" +msgstr "Scegli..." -#: gitk:9428 +#: gitk:10736 +msgid "General options" +msgstr "Opzioni generali" + +#: gitk:10739 +msgid "Use themed widgets" +msgstr "Utilizza interfaccia a tema" + +#: gitk:10741 +msgid "(change requires restart)" +msgstr "(una modifica richiede il riavvio)" + +#: gitk:10743 +msgid "(currently unavailable)" +msgstr "(momentaneamente non disponibile)" + +#: gitk:10747 msgid "Colors: press to choose" msgstr "Colori: premere per scegliere" -#: gitk:9431 +#: gitk:10750 +msgid "Interface" +msgstr "Interfaccia" + +#: gitk:10751 +msgid "interface" +msgstr "interfaccia" + +#: gitk:10754 msgid "Background" msgstr "Sfondo" -#: gitk:9435 +#: gitk:10755 gitk:10785 +msgid "background" +msgstr "sfondo" + +#: gitk:10758 msgid "Foreground" msgstr "Primo piano" -#: gitk:9439 +#: gitk:10759 +msgid "foreground" +msgstr "primo piano" + +#: gitk:10762 msgid "Diff: old lines" msgstr "Diff: vecchie linee" -#: gitk:9444 +#: gitk:10763 +msgid "diff old lines" +msgstr "vecchie linee" + +#: gitk:10767 msgid "Diff: new lines" msgstr "Diff: nuove linee" -#: gitk:9449 +#: gitk:10768 +msgid "diff new lines" +msgstr "nuove linee" + +#: gitk:10772 msgid "Diff: hunk header" msgstr "Diff: intestazione della sezione" -#: gitk:9455 +#: gitk:10774 +msgid "diff hunk header" +msgstr "intestazione della sezione" + +#: gitk:10778 +msgid "Marked line bg" +msgstr "Sfondo riga selezionata" + +#: gitk:10780 +msgid "marked line background" +msgstr "sfondo riga selezionata" + +#: gitk:10784 msgid "Select bg" -msgstr "Sfondo selezione" +msgstr "Sfondo" -#: gitk:9459 +#: gitk:10788 msgid "Fonts: press to choose" msgstr "Carattere: premere per scegliere" -#: gitk:9461 +#: gitk:10790 msgid "Main font" msgstr "Carattere principale" -#: gitk:9462 +#: gitk:10791 msgid "Diff display font" msgstr "Carattere per differenze" -#: gitk:9463 +#: gitk:10792 msgid "User interface font" msgstr "Carattere per interfaccia utente" -#: gitk:9488 +#: gitk:10829 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: scegliere un colore per %s" -#: gitk:9934 -msgid "" -"Sorry, gitk cannot run with this version of Tcl/Tk.\n" -" Gitk requires at least Tcl/Tk 8.4." -msgstr "" -"Questa versione di Tcl/Tk non può avviare gitk.\n" -" Gitk richiede Tcl/Tk versione 8.4 o superiore." - -#: gitk:10047 +#: gitk:11433 msgid "Cannot find a git repository here." msgstr "Archivio git non trovato." -#: gitk:10051 +#: gitk:11437 #, tcl-format msgid "Cannot find the git directory \"%s\"." msgstr "Directory git \"%s\" non trovata." -#: gitk:10098 +#: gitk:11484 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Argomento ambiguo: '%s' è sia revisione che nome di file" -#: gitk:10110 +#: gitk:11496 msgid "Bad arguments to gitk:" msgstr "Gitk: argomenti errati:" -#: gitk:10170 +#: gitk:11587 msgid "Command line" msgstr "Linea di comando" 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/gitk-git/po/sv.po b/gitk-git/po/sv.po index 624eb2281e..0f5e2fd8d7 100644 --- a/gitk-git/po/sv.po +++ b/gitk-git/po/sv.po @@ -2,39 +2,39 @@ # Copyright (C) 2005-2009 Paul Mackerras # This file is distributed under the same license as the gitk package. # -# Peter Krefting <peter@softwolves.pp.se>, 2008-2009. +# Peter Krefting <peter@softwolves.pp.se>, 2008-2010. # Mikael Magnusson <mikachu@gmail.com>, 2008. msgid "" msgstr "" "Project-Id-Version: sv\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-08-13 13:38+0100\n" -"PO-Revision-Date: 2009-08-13 13:40+0100\n" +"POT-Creation-Date: 2010-01-28 13:16+0100\n" +"PO-Revision-Date: 2010-01-28 13:48+0100\n" "Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n" "Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit" -#: gitk:113 +#: gitk:115 msgid "Couldn't get list of unmerged files:" msgstr "Kunde inte hämta lista över ej sammanslagna filer:" -#: gitk:269 +#: gitk:274 msgid "Error parsing revisions:" msgstr "Fel vid tolkning av revisioner:" -#: gitk:324 +#: gitk:329 msgid "Error executing --argscmd command:" msgstr "Fel vid körning av --argscmd-kommando:" -#: gitk:337 +#: gitk:342 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "Inga filer valdes: --merge angavs men det finns inga filer som inte har " "slagits samman." -#: gitk:340 +#: gitk:345 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." @@ -42,596 +42,600 @@ msgstr "" "Inga filer valdes: --merge angavs men det finns inga filer inom " "filbegränsningen." -#: gitk:362 gitk:509 +#: gitk:367 gitk:514 msgid "Error executing git log:" msgstr "Fel vid körning av git log:" -#: gitk:380 gitk:525 +#: gitk:385 gitk:530 msgid "Reading" msgstr "Läser" -#: gitk:440 gitk:4123 +#: gitk:445 gitk:4261 msgid "Reading commits..." msgstr "Läser incheckningar..." -#: gitk:443 gitk:1561 gitk:4126 +#: gitk:448 gitk:1578 gitk:4264 msgid "No commits selected" msgstr "Inga incheckningar markerade" -#: gitk:1437 +#: gitk:1454 msgid "Can't parse git log output:" msgstr "Kan inte tolka utdata från git log:" -#: gitk:1657 +#: gitk:1674 msgid "No commit information available" msgstr "Ingen incheckningsinformation är tillgänglig" -#: gitk:1793 gitk:1817 gitk:3916 gitk:8786 gitk:10322 gitk:10498 +#: gitk:1816 +msgid "mc" +msgstr "mc" + +#: gitk:1851 gitk:4054 gitk:9044 gitk:10585 gitk:10804 msgid "OK" msgstr "OK" -#: gitk:1819 gitk:3918 gitk:8383 gitk:8457 gitk:8567 gitk:8616 gitk:8788 -#: gitk:10323 gitk:10499 +#: gitk:1853 gitk:4056 gitk:8634 gitk:8713 gitk:8828 gitk:8877 gitk:9046 +#: gitk:10586 gitk:10805 msgid "Cancel" msgstr "Avbryt" -#: gitk:1919 +#: gitk:1975 msgid "Update" msgstr "Uppdatera" -#: gitk:1920 +#: gitk:1976 msgid "Reload" msgstr "Ladda om" -#: gitk:1921 +#: gitk:1977 msgid "Reread references" msgstr "Läs om referenser" -#: gitk:1922 +#: gitk:1978 msgid "List references" msgstr "Visa referenser" -#: gitk:1924 +#: gitk:1980 msgid "Start git gui" msgstr "Starta git gui" -#: gitk:1926 +#: gitk:1982 msgid "Quit" msgstr "Avsluta" -#: gitk:1918 +#: gitk:1974 msgid "File" msgstr "Arkiv" -#: gitk:1930 +#: gitk:1986 msgid "Preferences" msgstr "Inställningar" -#: gitk:1929 +#: gitk:1985 msgid "Edit" msgstr "Redigera" -#: gitk:1934 +#: gitk:1990 msgid "New view..." msgstr "Ny vy..." -#: gitk:1935 +#: gitk:1991 msgid "Edit view..." msgstr "Ändra vy..." -#: gitk:1936 +#: gitk:1992 msgid "Delete view" msgstr "Ta bort vy" -#: gitk:1938 +#: gitk:1994 msgid "All files" msgstr "Alla filer" -#: gitk:1933 gitk:3670 +#: gitk:1989 gitk:3808 msgid "View" msgstr "Visa" -#: gitk:1943 gitk:1953 gitk:2654 +#: gitk:1999 gitk:2009 gitk:2780 msgid "About gitk" msgstr "Om gitk" -#: gitk:1944 gitk:1958 +#: gitk:2000 gitk:2014 msgid "Key bindings" msgstr "Tangentbordsbindningar" -#: gitk:1942 gitk:1957 +#: gitk:1998 gitk:2013 msgid "Help" msgstr "Hjälp" -#: gitk:2018 -msgid "SHA1 ID: " -msgstr "SHA1-id: " +#: gitk:2091 gitk:8110 +msgid "SHA1 ID:" +msgstr "SHA1-id:" -#: gitk:2049 +#: gitk:2122 msgid "Row" msgstr "Rad" -#: gitk:2080 +#: gitk:2160 msgid "Find" msgstr "Sök" -#: gitk:2081 +#: gitk:2161 msgid "next" msgstr "nästa" -#: gitk:2082 +#: gitk:2162 msgid "prev" msgstr "föreg" -#: gitk:2083 +#: gitk:2163 msgid "commit" msgstr "incheckning" -#: gitk:2086 gitk:2088 gitk:4284 gitk:4307 gitk:4331 gitk:6272 gitk:6344 -#: gitk:6428 +#: gitk:2166 gitk:2168 gitk:4422 gitk:4445 gitk:4469 gitk:6410 gitk:6482 +#: gitk:6566 msgid "containing:" msgstr "som innehåller:" -#: gitk:2089 gitk:3162 gitk:3167 gitk:4359 +#: gitk:2169 gitk:3290 gitk:3295 gitk:4497 msgid "touching paths:" msgstr "som rör sökväg:" -#: gitk:2090 gitk:4364 +#: gitk:2170 gitk:4502 msgid "adding/removing string:" msgstr "som lägger/till tar bort sträng:" -#: gitk:2099 gitk:2101 +#: gitk:2179 gitk:2181 msgid "Exact" msgstr "Exakt" -#: gitk:2101 gitk:4439 gitk:6240 +#: gitk:2181 gitk:4577 gitk:6378 msgid "IgnCase" msgstr "IgnVersaler" -#: gitk:2101 gitk:4333 gitk:4437 gitk:6236 +#: gitk:2181 gitk:4471 gitk:4575 gitk:6374 msgid "Regexp" msgstr "Reg.uttr." -#: gitk:2103 gitk:2104 gitk:4458 gitk:4488 gitk:4495 gitk:6364 gitk:6432 +#: gitk:2183 gitk:2184 gitk:4596 gitk:4626 gitk:4633 gitk:6502 gitk:6570 msgid "All fields" msgstr "Alla fält" -#: gitk:2104 gitk:4456 gitk:4488 gitk:6303 +#: gitk:2184 gitk:4594 gitk:4626 gitk:6441 msgid "Headline" msgstr "Rubrik" -#: gitk:2105 gitk:4456 gitk:6303 gitk:6432 gitk:6866 +#: gitk:2185 gitk:4594 gitk:6441 gitk:6570 gitk:7003 msgid "Comments" msgstr "Kommentarer" -#: gitk:2105 gitk:4456 gitk:4460 gitk:4495 gitk:6303 gitk:6801 gitk:8063 -#: gitk:8078 +#: gitk:2185 gitk:4594 gitk:4598 gitk:4633 gitk:6441 gitk:6938 gitk:8285 +#: gitk:8300 msgid "Author" msgstr "Författare" -#: gitk:2105 gitk:4456 gitk:6303 gitk:6803 +#: gitk:2185 gitk:4594 gitk:6441 gitk:6940 msgid "Committer" msgstr "Incheckare" -#: gitk:2134 +#: gitk:2216 msgid "Search" msgstr "Sök" -#: gitk:2141 +#: gitk:2224 msgid "Diff" msgstr "Diff" -#: gitk:2143 +#: gitk:2226 msgid "Old version" msgstr "Gammal version" -#: gitk:2145 +#: gitk:2228 msgid "New version" msgstr "Ny version" -#: gitk:2147 +#: gitk:2230 msgid "Lines of context" msgstr "Rader sammanhang" -#: gitk:2157 +#: gitk:2240 msgid "Ignore space change" msgstr "Ignorera ändringar i blanksteg" -#: gitk:2215 +#: gitk:2299 msgid "Patch" msgstr "Patch" -#: gitk:2217 +#: gitk:2301 msgid "Tree" msgstr "Träd" -#: gitk:2361 gitk:2378 +#: gitk:2456 gitk:2473 msgid "Diff this -> selected" msgstr "Diff denna -> markerad" -#: gitk:2362 gitk:2379 +#: gitk:2457 gitk:2474 msgid "Diff selected -> this" msgstr "Diff markerad -> denna" -#: gitk:2363 gitk:2380 +#: gitk:2458 gitk:2475 msgid "Make patch" msgstr "Skapa patch" -#: gitk:2364 gitk:8441 +#: gitk:2459 gitk:8692 msgid "Create tag" msgstr "Skapa tagg" -#: gitk:2365 gitk:8547 +#: gitk:2460 gitk:8808 msgid "Write commit to file" msgstr "Skriv incheckning till fil" -#: gitk:2366 gitk:8604 +#: gitk:2461 gitk:8865 msgid "Create new branch" msgstr "Skapa ny gren" -#: gitk:2367 +#: gitk:2462 msgid "Cherry-pick this commit" msgstr "Plocka denna incheckning" -#: gitk:2368 +#: gitk:2463 msgid "Reset HEAD branch to here" msgstr "Återställ HEAD-grenen hit" -#: gitk:2369 +#: gitk:2464 msgid "Mark this commit" msgstr "Markera denna incheckning" -#: gitk:2370 +#: gitk:2465 msgid "Return to mark" msgstr "Återgå till markering" -#: gitk:2371 +#: gitk:2466 msgid "Find descendant of this and mark" msgstr "Hitta efterföljare till denna och markera" -#: gitk:2372 +#: gitk:2467 msgid "Compare with marked commit" msgstr "Jämför med markerad incheckning" -#: gitk:2386 +#: gitk:2481 msgid "Check out this branch" msgstr "Checka ut denna gren" -#: gitk:2387 +#: gitk:2482 msgid "Remove this branch" msgstr "Ta bort denna gren" -#: gitk:2394 +#: gitk:2489 msgid "Highlight this too" msgstr "Markera även detta" -#: gitk:2395 +#: gitk:2490 msgid "Highlight this only" msgstr "Markera bara detta" -#: gitk:2396 +#: gitk:2491 msgid "External diff" msgstr "Extern diff" -#: gitk:2397 +#: gitk:2492 msgid "Blame parent commit" msgstr "Klandra föräldraincheckning" -#: gitk:2404 +#: gitk:2499 msgid "Show origin of this line" msgstr "Visa ursprunget för den här raden" -#: gitk:2405 +#: gitk:2500 msgid "Run git gui blame on this line" msgstr "Kör git gui blame på den här raden" -#: gitk:2656 +#: gitk:2782 msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2008 Paul Mackerras\n" +"Copyright ©9 2005-2009 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - en incheckningsvisare för git\n" "\n" -"Copyright © 2005-2008 Paul Mackerras\n" +"Copyright © 2005-2009 Paul Mackerras\n" "\n" "Använd och vidareförmedla enligt villkoren i GNU General Public License" -#: gitk:2664 gitk:2726 gitk:8969 +#: gitk:2790 gitk:2854 gitk:9230 msgid "Close" msgstr "Stäng" -#: gitk:2683 +#: gitk:2811 msgid "Gitk key bindings" msgstr "Tangentbordsbindningar för Gitk" -#: gitk:2686 +#: gitk:2814 msgid "Gitk key bindings:" msgstr "Tangentbordsbindningar för Gitk:" -#: gitk:2688 +#: gitk:2816 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tAvsluta" -#: gitk:2689 +#: gitk:2817 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\tGå till första incheckning" -#: gitk:2690 +#: gitk:2818 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\tGå till sista incheckning" -#: gitk:2691 +#: gitk:2819 msgid "<Up>, p, i\tMove up one commit" msgstr "<Upp>, p, i\tGå en incheckning upp" -#: gitk:2692 +#: gitk:2820 msgid "<Down>, n, k\tMove down one commit" msgstr "<Ned>, n, k\tGå en incheckning ned" -#: gitk:2693 +#: gitk:2821 msgid "<Left>, z, j\tGo back in history list" msgstr "<Vänster>, z, j\tGå bakåt i historiken" -#: gitk:2694 +#: gitk:2822 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Höger>, x, l\tGå framåt i historiken" -#: gitk:2695 +#: gitk:2823 msgid "<PageUp>\tMove up one page in commit list" msgstr "<PageUp>\tGå upp en sida i incheckningslistan" -#: gitk:2696 +#: gitk:2824 msgid "<PageDown>\tMove down one page in commit list" msgstr "<PageDown>\tGå ned en sida i incheckningslistan" -#: gitk:2697 +#: gitk:2825 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tRulla till början av incheckningslistan" -#: gitk:2698 +#: gitk:2826 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tRulla till slutet av incheckningslistan" -#: gitk:2699 +#: gitk:2827 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Upp>\tRulla incheckningslistan upp ett steg" -#: gitk:2700 +#: gitk:2828 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Ned>\tRulla incheckningslistan ned ett steg" -#: gitk:2701 +#: gitk:2829 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PageUp>\tRulla incheckningslistan upp en sida" -#: gitk:2702 +#: gitk:2830 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PageDown>\tRulla incheckningslistan ned en sida" -#: gitk:2703 +#: gitk:2831 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Skift-Upp>\tSök bakåt (uppåt, senare incheckningar)" -#: gitk:2704 +#: gitk:2832 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Skift-Ned>\tSök framåt (nedåt, tidigare incheckningar)" -#: gitk:2705 +#: gitk:2833 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Delete>, b\tRulla diffvisningen upp en sida" -#: gitk:2706 +#: gitk:2834 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Baksteg>\tRulla diffvisningen upp en sida" -#: gitk:2707 +#: gitk:2835 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Blanksteg>\tRulla diffvisningen ned en sida" -#: gitk:2708 +#: gitk:2836 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tRulla diffvisningen upp 18 rader" -#: gitk:2709 +#: gitk:2837 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tRulla diffvisningen ned 18 rader" -#: gitk:2710 +#: gitk:2838 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tSök" -#: gitk:2711 +#: gitk:2839 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tGå till nästa sökträff" -#: gitk:2712 +#: gitk:2840 msgid "<Return>\tMove to next find hit" msgstr "<Return>\t\tGå till nästa sökträff" -#: gitk:2713 +#: gitk:2841 msgid "/\t\tFocus the search box" msgstr "/\t\tFokusera sökrutan" -#: gitk:2714 +#: gitk:2842 msgid "?\t\tMove to previous find hit" msgstr "?\t\tGå till föregående sökträff" -#: gitk:2715 +#: gitk:2843 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tRulla diffvisningen till nästa fil" -#: gitk:2716 +#: gitk:2844 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tGå till nästa sökträff i diffvisningen" -#: gitk:2717 +#: gitk:2845 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tGå till föregående sökträff i diffvisningen" -#: gitk:2718 +#: gitk:2846 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-Num+>\tÖka teckenstorlek" -#: gitk:2719 +#: gitk:2847 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\tÖka teckenstorlek" -#: gitk:2720 +#: gitk:2848 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-Num->\tMinska teckenstorlek" -#: gitk:2721 +#: gitk:2849 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\tMinska teckenstorlek" -#: gitk:2722 +#: gitk:2850 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tUppdatera" -#: gitk:3177 -#, tcl-format -msgid "Error getting \"%s\" from %s:" -msgstr "Fel vid hämtning av \"%s\" från %s:" - -#: gitk:3234 gitk:3243 +#: gitk:3305 gitk:3314 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Fel vid skapande av temporär katalog %s:" -#: gitk:3255 +#: gitk:3327 +#, tcl-format +msgid "Error getting \"%s\" from %s:" +msgstr "Fel vid hämtning av \"%s\" från %s:" + +#: gitk:3390 msgid "command failed:" msgstr "kommando misslyckades:" -#: gitk:3401 +#: gitk:3539 msgid "No such commit" msgstr "Incheckning saknas" -#: gitk:3415 +#: gitk:3553 msgid "git gui blame: command failed:" msgstr "git gui blame: kommando misslyckades:" -#: gitk:3446 +#: gitk:3584 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Kunde inte läsa sammanslagningshuvud: %s" -#: gitk:3454 +#: gitk:3592 #, tcl-format msgid "Error reading index: %s" msgstr "Fel vid läsning av index: %s" -#: gitk:3479 +#: gitk:3617 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "Kunde inte starta git blame: %s" -#: gitk:3482 gitk:6271 +#: gitk:3620 gitk:6409 msgid "Searching" msgstr "Söker" -#: gitk:3514 +#: gitk:3652 #, tcl-format msgid "Error running git blame: %s" msgstr "Fel vid körning av git blame: %s" -#: gitk:3542 +#: gitk:3680 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "Raden kommer från incheckningen %s, som inte finns i denna vy" -#: gitk:3556 +#: gitk:3694 msgid "External diff viewer failed:" msgstr "Externt diff-verktyg misslyckades:" -#: gitk:3674 +#: gitk:3812 msgid "Gitk view definition" msgstr "Definition av Gitk-vy" -#: gitk:3678 +#: gitk:3816 msgid "Remember this view" msgstr "Spara denna vy" -#: gitk:3679 +#: gitk:3817 msgid "References (space separated list):" msgstr "Referenser (blankstegsavdelad lista):" -#: gitk:3680 +#: gitk:3818 msgid "Branches & tags:" msgstr "Grenar & taggar:" -#: gitk:3681 +#: gitk:3819 msgid "All refs" msgstr "Alla referenser" -#: gitk:3682 +#: gitk:3820 msgid "All (local) branches" msgstr "Alla (lokala) grenar" -#: gitk:3683 +#: gitk:3821 msgid "All tags" msgstr "Alla taggar" -#: gitk:3684 +#: gitk:3822 msgid "All remote-tracking branches" msgstr "Alla fjärrspårande grenar" -#: gitk:3685 +#: gitk:3823 msgid "Commit Info (regular expressions):" msgstr "Incheckningsinfo (reguljära uttryck):" -#: gitk:3686 +#: gitk:3824 msgid "Author:" msgstr "Författare:" -#: gitk:3687 +#: gitk:3825 msgid "Committer:" msgstr "Incheckare:" -#: gitk:3688 +#: gitk:3826 msgid "Commit Message:" msgstr "Incheckningsmeddelande:" -#: gitk:3689 +#: gitk:3827 msgid "Matches all Commit Info criteria" msgstr "Motsvarar alla kriterier för incheckningsinfo" -#: gitk:3690 +#: gitk:3828 msgid "Changes to Files:" msgstr "Ändringar av filer:" -#: gitk:3691 +#: gitk:3829 msgid "Fixed String" msgstr "Fast sträng" -#: gitk:3692 +#: gitk:3830 msgid "Regular Expression" msgstr "Reguljärt uttryck" -#: gitk:3693 +#: gitk:3831 msgid "Search string:" msgstr "Söksträng:" -#: gitk:3694 +#: gitk:3832 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" @@ -639,205 +643,201 @@ msgstr "" "Incheckingsdatum (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" -#: gitk:3695 +#: gitk:3833 msgid "Since:" msgstr "Från:" -#: gitk:3696 +#: gitk:3834 msgid "Until:" msgstr "Till:" -#: gitk:3697 +#: gitk:3835 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "Begränsa och/eller hoppa över ett antal revisioner (positivt heltal):" -#: gitk:3698 +#: gitk:3836 msgid "Number to show:" msgstr "Antal att visa:" -#: gitk:3699 +#: gitk:3837 msgid "Number to skip:" msgstr "Antal att hoppa över:" -#: gitk:3700 +#: gitk:3838 msgid "Miscellaneous options:" msgstr "Diverse alternativ:" -#: gitk:3701 +#: gitk:3839 msgid "Strictly sort by date" msgstr "Strikt datumsortering" -#: gitk:3702 +#: gitk:3840 msgid "Mark branch sides" msgstr "Markera sidogrenar" -#: gitk:3703 +#: gitk:3841 msgid "Limit to first parent" msgstr "Begränsa till första förälder" -#: gitk:3704 +#: gitk:3842 msgid "Simple history" msgstr "Enkel historik" -#: gitk:3705 +#: gitk:3843 msgid "Additional arguments to git log:" msgstr "Ytterligare argument till git log:" -#: gitk:3706 +#: gitk:3844 msgid "Enter files and directories to include, one per line:" msgstr "Ange filer och kataloger att ta med, en per rad:" -#: gitk:3707 +#: gitk:3845 msgid "Command to generate more commits to include:" msgstr "Kommando för att generera fler incheckningar att ta med:" -#: gitk:3829 +#: gitk:3967 msgid "Gitk: edit view" msgstr "Gitk: redigera vy" -#: gitk:3837 +#: gitk:3975 msgid "-- criteria for selecting revisions" msgstr " - kriterier för val av revisioner" -#: gitk:3842 -msgid "View Name:" -msgstr "Namn på vy:" +#: gitk:3980 +msgid "View Name" +msgstr "Namn på vy" -#: gitk:3917 +#: gitk:4055 msgid "Apply (F5)" msgstr "Använd (F5)" -#: gitk:3955 +#: gitk:4093 msgid "Error in commit selection arguments:" msgstr "Fel i argument för val av incheckningar:" -#: gitk:4008 gitk:4060 gitk:4508 gitk:4522 gitk:5783 gitk:11196 gitk:11197 +#: gitk:4146 gitk:4198 gitk:4646 gitk:4660 gitk:5921 gitk:11534 gitk:11535 msgid "None" msgstr "Inget" -#: gitk:4456 gitk:6303 gitk:8065 gitk:8080 +#: gitk:4594 gitk:6441 gitk:8287 gitk:8302 msgid "Date" msgstr "Datum" -#: gitk:4456 gitk:6303 +#: gitk:4594 gitk:6441 msgid "CDate" msgstr "Skapat datum" -#: gitk:4605 gitk:4610 +#: gitk:4743 gitk:4748 msgid "Descendant" msgstr "Avkomling" -#: gitk:4606 +#: gitk:4744 msgid "Not descendant" msgstr "Inte avkomling" -#: gitk:4613 gitk:4618 +#: gitk:4751 gitk:4756 msgid "Ancestor" msgstr "Förfader" -#: gitk:4614 +#: gitk:4752 msgid "Not ancestor" msgstr "Inte förfader" -#: gitk:4904 +#: gitk:5042 msgid "Local changes checked in to index but not committed" msgstr "Lokala ändringar sparade i indexet men inte incheckade" -#: gitk:4940 +#: gitk:5078 msgid "Local uncommitted changes, not checked in to index" msgstr "Lokala ändringar, ej sparade i indexet" -#: gitk:6621 +#: gitk:6759 msgid "many" msgstr "många" -#: gitk:6805 +#: gitk:6942 msgid "Tags:" msgstr "Taggar:" -#: gitk:6822 gitk:6828 gitk:8058 +#: gitk:6959 gitk:6965 gitk:8280 msgid "Parent" msgstr "Förälder" -#: gitk:6833 +#: gitk:6970 msgid "Child" msgstr "Barn" -#: gitk:6842 +#: gitk:6979 msgid "Branch" msgstr "Gren" -#: gitk:6845 +#: gitk:6982 msgid "Follows" msgstr "Följer" -#: gitk:6848 +#: gitk:6985 msgid "Precedes" msgstr "Föregår" -#: gitk:7346 +#: gitk:7522 #, tcl-format msgid "Error getting diffs: %s" msgstr "Fel vid hämtning av diff: %s" -#: gitk:7886 +#: gitk:8108 msgid "Goto:" msgstr "Gå till:" -#: gitk:7888 -msgid "SHA1 ID:" -msgstr "SHA1-id:" - -#: gitk:7907 +#: gitk:8129 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "Förkortat SHA1-id %s är tvetydigt" -#: gitk:7914 +#: gitk:8136 #, tcl-format msgid "Revision %s is not known" msgstr "Revisionen %s är inte känd" -#: gitk:7924 +#: gitk:8146 #, tcl-format msgid "SHA1 id %s is not known" msgstr "SHA-id:t %s är inte känt" -#: gitk:7926 +#: gitk:8148 #, tcl-format msgid "Revision %s is not in the current view" msgstr "Revisionen %s finns inte i den nuvarande vyn" -#: gitk:8068 +#: gitk:8290 msgid "Children" msgstr "Barn" -#: gitk:8125 +#: gitk:8348 #, tcl-format msgid "Reset %s branch to here" msgstr "Återställ grenen %s hit" -#: gitk:8127 +#: gitk:8350 msgid "Detached head: can't reset" msgstr "Frånkopplad head: kan inte återställa" -#: gitk:8236 gitk:8242 +#: gitk:8459 gitk:8465 msgid "Skipping merge commit " msgstr "Hoppar över sammanslagningsincheckning " -#: gitk:8251 gitk:8256 +#: gitk:8474 gitk:8479 msgid "Error getting patch ID for " msgstr "Fel vid hämtning av patch-id för " -#: gitk:8252 gitk:8257 +#: gitk:8475 gitk:8480 msgid " - stopping\n" msgstr " - stannar\n" -#: gitk:8262 gitk:8265 gitk:8273 gitk:8283 gitk:8292 +#: gitk:8485 gitk:8488 gitk:8496 gitk:8510 gitk:8519 msgid "Commit " msgstr "Incheckning " -#: gitk:8266 +#: gitk:8489 msgid "" " is the same patch as\n" " " @@ -845,7 +845,7 @@ msgstr "" " är samma patch som\n" " " -#: gitk:8274 +#: gitk:8497 msgid "" " differs from\n" " " @@ -853,117 +853,139 @@ msgstr "" " skiljer sig från\n" " " -#: gitk:8276 -msgid "- stopping\n" -msgstr "- stannar\n" +#: gitk:8499 +msgid "" +"Diff of commits:\n" +"\n" +msgstr "Skillnad mellan incheckningar:\n" +"\n" +"" -#: gitk:8284 gitk:8293 +#: gitk:8511 gitk:8520 #, tcl-format msgid " has %s children - stopping\n" msgstr " har %s barn - stannar\n" -#: gitk:8324 +#: gitk:8539 +#, tcl-format +msgid "Error writing commit to file: %s" +msgstr "Fel vid skrivning av incheckning till fil: %s" + +#: gitk:8545 +#, tcl-format +msgid "Error diffing commits: %s" +msgstr "Fel vid jämförelse av incheckningar: %s" + +#: gitk:8575 msgid "Top" msgstr "Topp" -#: gitk:8325 +#: gitk:8576 msgid "From" msgstr "Från" -#: gitk:8330 +#: gitk:8581 msgid "To" msgstr "Till" -#: gitk:8354 +#: gitk:8605 msgid "Generate patch" msgstr "Generera patch" -#: gitk:8356 +#: gitk:8607 msgid "From:" msgstr "Från:" -#: gitk:8365 +#: gitk:8616 msgid "To:" msgstr "Till:" -#: gitk:8374 +#: gitk:8625 msgid "Reverse" msgstr "Vänd" -#: gitk:8376 gitk:8561 +#: gitk:8627 gitk:8822 msgid "Output file:" msgstr "Utdatafil:" -#: gitk:8382 +#: gitk:8633 msgid "Generate" msgstr "Generera" -#: gitk:8420 +#: gitk:8671 msgid "Error creating patch:" msgstr "Fel vid generering av patch:" -#: gitk:8443 gitk:8549 gitk:8606 +#: gitk:8694 gitk:8810 gitk:8867 msgid "ID:" msgstr "Id:" -#: gitk:8452 +#: gitk:8703 msgid "Tag name:" msgstr "Taggnamn:" -#: gitk:8456 gitk:8615 +#: gitk:8706 +msgid "Tag message is optional" +msgstr "Taggmeddelandet är valfritt" + +#: gitk:8708 +msgid "Tag message:" +msgstr "Taggmeddelande:" + +#: gitk:8712 gitk:8876 msgid "Create" msgstr "Skapa" -#: gitk:8473 +#: gitk:8730 msgid "No tag name specified" msgstr "Inget taggnamn angavs" -#: gitk:8477 +#: gitk:8734 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "Taggen \"%s\" finns redan" -#: gitk:8483 +#: gitk:8744 msgid "Error creating tag:" msgstr "Fel vid skapande av tagg:" -#: gitk:8558 +#: gitk:8819 msgid "Command:" msgstr "Kommando:" -#: gitk:8566 +#: gitk:8827 msgid "Write" msgstr "Skriv" -#: gitk:8584 +#: gitk:8845 msgid "Error writing commit:" msgstr "Fel vid skrivning av incheckning:" -#: gitk:8611 +#: gitk:8872 msgid "Name:" msgstr "Namn:" -#: gitk:8634 +#: gitk:8895 msgid "Please specify a name for the new branch" msgstr "Ange ett namn för den nya grenen" -#: gitk:8639 +#: gitk:8900 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "Grenen \"%s\" finns redan. Skriva över?" -#: gitk:8705 +#: gitk:8966 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "" "Incheckningen %s finns redan på grenen %s -- skall den verkligen appliceras " "på nytt?" -#: gitk:8710 +#: gitk:8971 msgid "Cherry-picking" msgstr "Plockar" -#: gitk:8719 +#: gitk:8980 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -973,7 +995,7 @@ msgstr "" "Checka in, återställ eller spara undan (stash) dina ändringar och försök " "igen." -#: gitk:8725 +#: gitk:8986 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -981,32 +1003,32 @@ msgstr "" "Cherry-pick misslyckades på grund av en sammanslagningskonflikt.\n" "Vill du köra git citool för att lösa den?" -#: gitk:8741 +#: gitk:9002 msgid "No changes committed" msgstr "Inga ändringar incheckade" -#: gitk:8767 +#: gitk:9028 msgid "Confirm reset" msgstr "Bekräfta återställning" -#: gitk:8769 +#: gitk:9030 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Återställa grenen %s till %s?" -#: gitk:8773 +#: gitk:9032 msgid "Reset type:" msgstr "Typ av återställning:" -#: gitk:8777 +#: gitk:9035 msgid "Soft: Leave working tree and index untouched" msgstr "Mjuk: Rör inte utcheckning och index" -#: gitk:8780 +#: gitk:9038 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Blandad: Rör inte utcheckning, återställ index" -#: gitk:8783 +#: gitk:9041 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1014,19 +1036,19 @@ msgstr "" "Hård: Återställ utcheckning och index\n" "(förkastar ALLA lokala ändringar)" -#: gitk:8800 +#: gitk:9058 msgid "Resetting" msgstr "Återställer" -#: gitk:8857 +#: gitk:9118 msgid "Checking out" msgstr "Checkar ut" -#: gitk:8910 +#: gitk:9171 msgid "Cannot delete the currently checked-out branch" msgstr "Kan inte ta bort den just nu utcheckade grenen" -#: gitk:8916 +#: gitk:9177 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -1035,16 +1057,16 @@ msgstr "" "Incheckningarna på grenen %s existerar inte på någon annan gren.\n" "Vill du verkligen ta bort grenen %s?" -#: gitk:8947 +#: gitk:9208 #, tcl-format msgid "Tags and heads: %s" msgstr "Taggar och huvuden: %s" -#: gitk:8962 +#: gitk:9223 msgid "Filter" msgstr "Filter" -#: gitk:9257 +#: gitk:9518 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -1052,190 +1074,219 @@ msgstr "" "Fel vid läsning av information om incheckningstopologi; information om " "grenar och föregående/senare taggar kommer inte vara komplett." -#: gitk:10243 +#: gitk:10504 msgid "Tag" msgstr "Tagg" -#: gitk:10243 +#: gitk:10504 msgid "Id" msgstr "Id" -#: gitk:10291 +#: gitk:10554 msgid "Gitk font chooser" msgstr "Teckensnittsväljare för Gitk" -#: gitk:10308 +#: gitk:10571 msgid "B" msgstr "F" -#: gitk:10311 +#: gitk:10574 msgid "I" msgstr "K" -#: gitk:10407 +#: gitk:10692 msgid "Gitk preferences" msgstr "Inställningar för Gitk" -#: gitk:10409 +#: gitk:10694 msgid "Commit list display options" msgstr "Alternativ för incheckningslistvy" -#: gitk:10412 +#: gitk:10697 msgid "Maximum graph width (lines)" msgstr "Maximal grafbredd (rader)" -#: gitk:10416 +#: gitk:10700 #, tcl-format msgid "Maximum graph width (% of pane)" msgstr "Maximal grafbredd (% av ruta)" -#: gitk:10420 +#: gitk:10703 msgid "Show local changes" msgstr "Visa lokala ändringar" -#: gitk:10423 +#: gitk:10706 msgid "Auto-select SHA1" msgstr "Välj SHA1 automatiskt" -#: gitk:10427 +#: gitk:10709 +msgid "Hide remote refs" +msgstr "Dölj fjärr-referenser" + +#: gitk:10713 msgid "Diff display options" msgstr "Alternativ för diffvy" -#: gitk:10429 +#: gitk:10715 msgid "Tab spacing" msgstr "Blanksteg för tabulatortecken" -#: gitk:10432 +#: gitk:10718 msgid "Display nearby tags" msgstr "Visa närliggande taggar" -#: gitk:10435 -msgid "Hide remote refs" -msgstr "Dölj fjärr-referenser" - -#: gitk:10438 +#: gitk:10721 msgid "Limit diffs to listed paths" msgstr "Begränsa diff till listade sökvägar" -#: gitk:10441 +#: gitk:10724 msgid "Support per-file encodings" msgstr "Stöd för filspecifika teckenkodningar" -#: gitk:10447 gitk:10512 +#: gitk:10730 gitk:10819 msgid "External diff tool" msgstr "Externt diff-verktyg" -#: gitk:10449 +#: gitk:10731 msgid "Choose..." msgstr "Välj..." -#: gitk:10454 +#: gitk:10736 +msgid "General options" +msgstr "Allmänna inställningar" + +#: gitk:10739 +msgid "Use themed widgets" +msgstr "Använd tema på fönsterelement" + +#: gitk:10741 +msgid "(change requires restart)" +msgstr "(ändringen kräver omstart)" + +#: gitk:10743 +msgid "(currently unavailable)" +msgstr "(för närvarande inte tillgängligt)" + +#: gitk:10747 msgid "Colors: press to choose" msgstr "Färger: tryck för att välja" -#: gitk:10457 +#: gitk:10750 +msgid "Interface" +msgstr "Gränssnitt" + +#: gitk:10751 +msgid "interface" +msgstr "gränssnitt" + +#: gitk:10754 msgid "Background" msgstr "Bakgrund" -#: gitk:10458 gitk:10488 +#: gitk:10755 gitk:10785 msgid "background" msgstr "bakgrund" -#: gitk:10461 +#: gitk:10758 msgid "Foreground" msgstr "Förgrund" -#: gitk:10462 +#: gitk:10759 msgid "foreground" msgstr "förgrund" -#: gitk:10465 +#: gitk:10762 msgid "Diff: old lines" msgstr "Diff: gamla rader" -#: gitk:10466 +#: gitk:10763 msgid "diff old lines" msgstr "diff gamla rader" -#: gitk:10470 +#: gitk:10767 msgid "Diff: new lines" msgstr "Diff: nya rader" -#: gitk:10471 +#: gitk:10768 msgid "diff new lines" msgstr "diff nya rader" -#: gitk:10475 +#: gitk:10772 msgid "Diff: hunk header" msgstr "Diff: delhuvud" -#: gitk:10477 +#: gitk:10774 msgid "diff hunk header" msgstr "diff delhuvud" -#: gitk:10481 +#: gitk:10778 msgid "Marked line bg" msgstr "Markerad rad bakgrund" -#: gitk:10483 +#: gitk:10780 msgid "marked line background" msgstr "markerad rad bakgrund" -#: gitk:10487 +#: gitk:10784 msgid "Select bg" msgstr "Markerad bakgrund" -#: gitk:10491 +#: gitk:10788 msgid "Fonts: press to choose" msgstr "Teckensnitt: tryck för att välja" -#: gitk:10493 +#: gitk:10790 msgid "Main font" msgstr "Huvudteckensnitt" -#: gitk:10494 +#: gitk:10791 msgid "Diff display font" msgstr "Teckensnitt för diffvisning" -#: gitk:10495 +#: gitk:10792 msgid "User interface font" msgstr "Teckensnitt för användargränssnitt" -#: gitk:10522 +#: gitk:10829 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: välj färg för %s" -#: gitk:10973 -msgid "" -"Sorry, gitk cannot run with this version of Tcl/Tk.\n" -" Gitk requires at least Tcl/Tk 8.4." -msgstr "" -"Gitk kan tyvärr inte köra med denna version av Tcl/Tk.\n" -" Gitk kräver åtminstone Tcl/Tk 8.4." - -#: gitk:11101 +#: gitk:11433 msgid "Cannot find a git repository here." msgstr "Hittar inget gitk-arkiv här." -#: gitk:11105 +#: gitk:11437 #, tcl-format msgid "Cannot find the git directory \"%s\"." msgstr "Hittar inte git-katalogen \"%s\"." -#: gitk:11152 +#: gitk:11484 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Tvetydigt argument \"%s\": både revision och filnamn" -#: gitk:11164 +#: gitk:11496 msgid "Bad arguments to gitk:" msgstr "Felaktiga argument till gitk:" -#: gitk:11249 +#: gitk:11587 msgid "Command line" msgstr "Kommandorad" +#~ msgid "SHA1 ID: " +#~ msgstr "SHA1-id: " + +#~ msgid "- stopping\n" +#~ msgstr "- stannar\n" + +#~ msgid "" +#~ "Sorry, gitk cannot run with this version of Tcl/Tk.\n" +#~ " Gitk requires at least Tcl/Tk 8.4." +#~ msgstr "" +#~ "Gitk kan tyvärr inte köra med denna version av Tcl/Tk.\n" +#~ " Gitk kräver åtminstone Tcl/Tk 8.4." + #~ msgid "Tag/Head %s is not known" #~ msgstr "Tagg/huvud %s är okänt" diff --git a/gitweb/Makefile b/gitweb/Makefile new file mode 100644 index 0000000000..c9eb1ee667 --- /dev/null +++ b/gitweb/Makefile @@ -0,0 +1,129 @@ +# The default target of this Makefile is... +all:: + +# Define V=1 to have a more verbose compile. +# +# Define JSMIN to point to JavaScript minifier that functions as +# a filter to have gitweb.js minified. +# + +prefix ?= $(HOME) +bindir ?= $(prefix)/bin +RM ?= rm -f + +# JavaScript minifier invocation that can function as filter +JSMIN ?= + +# default configuration for gitweb +GITWEB_CONFIG = gitweb_config.perl +GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf +GITWEB_HOME_LINK_STR = projects +GITWEB_SITENAME = +GITWEB_PROJECTROOT = /pub/git +GITWEB_PROJECT_MAXDEPTH = 2007 +GITWEB_EXPORT_OK = +GITWEB_STRICT_EXPORT = +GITWEB_BASE_URL = +GITWEB_LIST = +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 = + +# include user config +-include ../config.mak.autogen +-include ../config.mak + +# determine version +../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE + $(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) GIT-VERSION-FILE + +-include ../GIT-VERSION-FILE + +### Build rules + +SHELL_PATH ?= $(SHELL) +PERL_PATH ?= /usr/bin/perl + +# Shell quote; +bindir_SQ = $(subst ','\'',$(bindir)) #' +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) #' +PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) #' + +# Quiet generation (unless V=1) +QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir +QUIET_SUBDIR1 = + +ifneq ($(findstring $(MAKEFLAGS),w),w) +PRINT_DIR = --no-print-directory +else # "make -w" +NO_SUBDIR = : +endif + +ifneq ($(findstring $(MAKEFLAGS),s),s) +ifndef V + QUIET = @ + QUIET_GEN = $(QUIET)echo ' ' GEN $@; + QUIET_SUBDIR0 = +@subdir= + QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ + $(MAKE) $(PRINT_DIR) -C $$subdir + export V + export QUIET + export QUIET_GEN + export QUIET_SUBDIR0 + export QUIET_SUBDIR1 +endif +endif + +all:: gitweb.cgi + +ifdef JSMIN +FILES=gitweb.cgi gitweb.min.js +gitweb.cgi: gitweb.perl gitweb.min.js +else # !JSMIN +FILES=gitweb.cgi +gitweb.cgi: gitweb.perl +endif # JSMIN + +gitweb.cgi: + $(QUIET_GEN)$(RM) $@ $@+ && \ + sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ + -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ + -e 's|++GIT_BINDIR++|$(bindir)|g' \ + -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \ + -e 's|++GITWEB_CONFIG_SYSTEM++|$(GITWEB_CONFIG_SYSTEM)|g' \ + -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \ + -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ + -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ + -e 's|"++GITWEB_PROJECT_MAXDEPTH++"|$(GITWEB_PROJECT_MAXDEPTH)|g' \ + -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \ + -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \ + -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \ + -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \ + -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \ + -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 $@+ $@ + +ifdef JSMIN +gitweb.min.js: gitweb.js + $(QUIET_GEN)$(JSMIN) <$< >$@ +endif # JSMIN + +clean: + $(RM) $(FILES) + +.PHONY: all clean .FORCE-GIT-VERSION-FILE diff --git a/gitweb/README b/gitweb/README index 66c6a9391d..6c2c8e1259 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 @@ -169,7 +174,7 @@ not include variables usually directly set during build): Base URL for relative URLs in pages generated by gitweb, (e.g. $logo, $favicon, @stylesheets if they are relative URLs), needed and used only for URLs with nonempty PATH_INFO via - <base href="$base_url>. Usually gitweb sets its value correctly, + <base href="$base_url">. Usually gitweb sets its value correctly, and there is no need to set this variable, e.g. to $my_uri or "/". * $home_link Target of the home link on top of all pages (the first part of view @@ -223,6 +228,11 @@ not include variables usually directly set during build): repositories from launching cross-site scripting (XSS) attacks. Set this to true if you don't trust the content of your repositories. The default is false. + * $maxload + Used to set the maximum load that we will still respond to gitweb queries. + If server load exceed this value then return "503 Service Unavaliable" error. + Server load is taken to be 0 if gitweb cannot determine its value. Set it to + undefined value to turn it off. The default is 300. Projects list file format 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..9c66928c4a --- /dev/null +++ b/gitweb/gitweb.js @@ -0,0 +1,875 @@ +// 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 + // try ... catch block is to work around bug in IE8 + try { + if (xhr.readyState === 3 && xhr.status !== 200) { + return; + } + } catch (e) { + 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..3c879b88fe 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/"; @@ -213,6 +221,12 @@ our %avatar_size = ( 'double' => 32 ); +# Used to set the maximum load that we will still respond to gitweb queries. +# If server load exceed this value then return "503 server busy" error. +# If gitweb cannot determined server load, it is taken to be 0. +# Leave it undefined (or set to 'undef') to turn off load checking. +our $maxload = 300; + # You define site-wide feature defaults here; override them with # $GITWEB_CONFIG as necessary. our %feature = ( @@ -297,6 +311,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 +431,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 { @@ -516,11 +557,38 @@ if (-e $GITWEB_CONFIG) { do $GITWEB_CONFIG_SYSTEM if -e $GITWEB_CONFIG_SYSTEM; } +# Get loadavg of system, to compare against $maxload. +# Currently it requires '/proc/loadavg' present to get loadavg; +# if it is not present it returns 0, which means no load checking. +sub get_loadavg { + if( -e '/proc/loadavg' ){ + open my $fd, '<', '/proc/loadavg' + or return 0; + my @load = split(/\s+/, scalar <$fd>); + close $fd; + + # The first three columns measure CPU and IO utilization of the last one, + # five, and 10 minute periods. The fourth column shows the number of + # currently running processes and the total number of processes in the m/n + # format. The last column displays the last process ID used. + return $load[0] || 0; + } + # additional checks for load average should go here for things that don't export + # /proc/loadavg + + return 0; +} + # version of the core git binary our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown"; +$number_of_git_cmds++; $projects_list ||= $projectroot; +if (defined $maxload && get_loadavg() > $maxload) { + die_error(503, "The load average on the server is too high"); +} + # ====================================================================== # input validation and dispatch @@ -555,12 +623,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, @@ -1071,6 +1143,7 @@ sub validate_refname { # in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning sub to_utf8 { my $str = shift; + return undef unless defined $str; if (utf8::valid($str)) { utf8::decode($str); return $str; @@ -1083,6 +1156,7 @@ sub to_utf8 { # correct, but quoted slashes look too horrible in bookmarks sub esc_param { my $str = shift; + return undef unless defined $str; $str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg; $str =~ s/ /\+/g; return $str; @@ -1091,6 +1165,7 @@ sub esc_param { # quote unsafe chars in whole URL, so some charactrs cannot be quoted sub esc_url { my $str = shift; + return undef unless defined $str; $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg; $str =~ s/\+/%2B/g; $str =~ s/ /\+/g; @@ -1102,6 +1177,8 @@ sub esc_html { my $str = shift; my %opts = @_; + return undef unless defined $str; + $str = to_utf8($str); $str = $cgi->escapeHTML($str); if ($opts{'-nbsp'}) { @@ -1116,6 +1193,8 @@ sub esc_path { my $str = shift; my %opts = @_; + return undef unless defined $str; + $str = to_utf8($str); $str = $cgi->escapeHTML($str); if ($opts{'-nbsp'}) { @@ -1258,7 +1337,6 @@ sub chop_str { $str =~ m/^(.*?)($begre)$/; my ($lead, $body) = ($1, $2); if (length($lead) > 4) { - $body =~ s/^[^;]*;// if ($lead =~ m/&[^;]*$/); $lead = " ..."; } return "$lead$body"; @@ -1269,8 +1347,6 @@ sub chop_str { $str =~ m/^(.*?)($begre)$/; my ($mid, $right) = ($1, $2); if (length($mid) > 5) { - $left =~ s/&[^;]*$//; - $right =~ s/^[^;]*;// if ($mid =~ m/&[^;]*$/); $mid = " ... "; } return "$left$mid$right"; @@ -1280,7 +1356,6 @@ sub chop_str { my $body = $1; my $tail = $2; if (length($tail) > 4) { - $body =~ s/&[^;]*$//; $tail = "... "; } return "$body$tail"; @@ -1594,6 +1669,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 +1700,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 +2068,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 +2083,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 +2875,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; @@ -3130,7 +3257,7 @@ EOF print "</head>\n" . "<body>\n"; - if (-f $site_header) { + if (defined $site_header && -f $site_header) { insert_file($site_header); } @@ -3217,15 +3344,42 @@ sub git_footer_html { } print "</div>\n"; # class="page_footer" - if (-f $site_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 (defined $site_footer && -f $site_footer) { insert_file($site_footer); } + print qq!<script type="text/javascript" src="$javascript"></script>\n!; + if (defined $action && + $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>"; } -# die_error(<http_status_code>, <error_message>) +# die_error(<http_status_code>, <error_message>[, <detailed_html_description>]) # Example: die_error(404, 'Hash not found') # By convention, use the following status codes (as defined in RFC 2616): # 400: Invalid or missing CGI parameters, or @@ -3236,22 +3390,33 @@ sub git_footer_html { # 500: The server isn't configured properly, or # an internal error occurred (e.g. failed assertions caused by bugs), or # an unknown error occurred (e.g. the git binary died unexpectedly). +# 503: The server is currently unavailable (because it is overloaded, +# or down for maintenance). Generally, this is a temporary state. sub die_error { my $status = shift || 500; - my $error = shift || "Internal server error"; - - my %http_responses = (400 => '400 Bad Request', - 403 => '403 Forbidden', - 404 => '404 Not Found', - 500 => '500 Internal Server Error'); + my $error = esc_html(shift) || "Internal Server Error"; + my $extra = shift; + + my %http_responses = ( + 400 => '400 Bad Request', + 403 => '403 Forbidden', + 404 => '404 Not Found', + 500 => '500 Internal Server Error', + 503 => '503 Service Unavailable', + ); git_header_html($http_responses{$status}); print <<EOF; <div class="page_body"> <br /><br /> $status - $error <br /> -</div> EOF + if (defined $extra) { + print "<hr />\n" . + "$extra\n"; + } + print "</div>\n"; + git_footer_html(); exit; } @@ -3310,22 +3475,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) { @@ -3357,14 +3518,21 @@ sub git_print_header_div { } sub print_local_time { + print format_local_time(@_); +} + +sub format_local_time { + my $localtime = ''; my %date = @_; if ($date{'hour_local'} < 6) { - printf(" (<span class=\"atnight\">%02d:%02d</span> %s)", + $localtime .= sprintf(" (<span class=\"atnight\">%02d:%02d</span> %s)", $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'}); } else { - printf(" (%02d:%02d %s)", + $localtime .= sprintf(" (%02d:%02d %s)", $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'}); } + + return $localtime; } # Outputs the author name and date in long form @@ -3372,10 +3540,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 +3563,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 +3736,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 +3784,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 " | " . @@ -4178,17 +4356,24 @@ sub fill_project_list_info { # print 'sort by' <th> element, generating 'sort by $name' replay link # if that order is not selected sub print_sort_th { + print format_sort_th(@_); +} + +sub format_sort_th { my ($name, $order, $header) = @_; + my $sort_th = ""; $header ||= ucfirst($name); if ($order eq $name) { - print "<th>$header</th>\n"; + $sort_th .= "<th>$header</th>\n"; } else { - print "<th>" . - $cgi->a({-href => href(-replay=>1, order=>$name), - -class => "header"}, $header) . - "</th>\n"; + $sort_th .= "<th>" . + $cgi->a({-href => href(-replay=>1, order=>$name), + -class => "header"}, $header) . + "</th>\n"; } + + return $sort_th; } sub git_project_list_body { @@ -4298,6 +4483,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 +4569,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 +4604,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) { @@ -4578,7 +4804,7 @@ sub git_project_list { } git_header_html(); - if (-f $home_text) { + if (defined $home_text && -f $home_text) { print "<div class=\"index_include\">\n"; insert_file($home_text); print "</div>\n"; @@ -4770,7 +4996,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 +5024,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 +5417,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 +5435,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 +5450,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 +5484,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 +5496,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 +5514,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 +5568,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 +5599,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 +5658,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 +6214,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 +6480,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); } ## ...................................................................... @@ -29,11 +29,26 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat, p->next = NULL; } -static int is_fixed(const char *s) +struct grep_opt *grep_opt_dup(const struct grep_opt *opt) { - while (*s && !is_regex_special(*s)) - s++; - return !*s; + struct grep_pat *pat; + struct grep_opt *ret = xmalloc(sizeof(struct grep_opt)); + *ret = *opt; + + ret->pattern_list = NULL; + ret->pattern_tail = &ret->pattern_list; + + for(pat = opt->pattern_list; pat != NULL; pat = pat->next) + { + if(pat->token == GREP_PATTERN_HEAD) + append_header_grep_pattern(ret, pat->field, + pat->pattern); + else + append_grep_pattern(ret, pat->pattern, pat->origin, + pat->no, pat->token); + } + + return ret; } static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) @@ -42,11 +57,8 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) p->word_regexp = opt->word_regexp; p->ignore_case = opt->ignore_case; + p->fixed = opt->fixed; - if (opt->fixed || is_fixed(p->pattern)) - p->fixed = 1; - if (opt->regflags & REG_ICASE) - p->fixed = 0; if (p->fixed) return; @@ -260,7 +272,8 @@ static int word_char(char ch) static void show_name(struct grep_opt *opt, const char *name) { - printf("%s%c", name, opt->null_following_name ? '\0' : '\n'); + opt->output(opt, name, strlen(name)); + opt->output(opt, opt->null_following_name ? "\0" : "\n", 1); } @@ -497,24 +510,32 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, const char *name, unsigned lno, char sign) { int rest = eol - bol; + char sign_str[1]; + sign_str[0] = sign; if (opt->pre_context || opt->post_context) { if (opt->last_shown == 0) { if (opt->show_hunk_mark) - fputs("--\n", stdout); + opt->output(opt, "--\n", 3); else opt->show_hunk_mark = 1; } else if (lno > opt->last_shown + 1) - fputs("--\n", stdout); + opt->output(opt, "--\n", 3); } opt->last_shown = lno; if (opt->null_following_name) - sign = '\0'; - if (opt->pathname) - printf("%s%c", name, sign); - if (opt->linenum) - printf("%d%c", lno, sign); + sign_str[0] = '\0'; + if (opt->pathname) { + opt->output(opt, name, strlen(name)); + opt->output(opt, sign_str, 1); + } + if (opt->linenum) { + char buf[32]; + snprintf(buf, sizeof(buf), "%d", lno); + opt->output(opt, buf, strlen(buf)); + opt->output(opt, sign_str, 1); + } if (opt->color) { regmatch_t match; enum grep_context ctx = GREP_CONTEXT_BODY; @@ -525,18 +546,22 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, while (next_match(opt, bol, eol, ctx, &match, eflags)) { if (match.rm_so == match.rm_eo) break; - printf("%.*s%s%.*s%s", - (int)match.rm_so, bol, - opt->color_match, - (int)(match.rm_eo - match.rm_so), bol + match.rm_so, - GIT_COLOR_RESET); + + opt->output(opt, bol, match.rm_so); + opt->output(opt, opt->color_match, + strlen(opt->color_match)); + opt->output(opt, bol + match.rm_so, + (int)(match.rm_eo - match.rm_so)); + opt->output(opt, GIT_COLOR_RESET, + strlen(GIT_COLOR_RESET)); bol += match.rm_eo; rest -= match.rm_eo; eflags = REG_NOTBOL; } *eol = ch; } - printf("%.*s\n", rest, bol); + opt->output(opt, bol, rest); + opt->output(opt, "\n", 1); } static int match_funcname(struct grep_opt *opt, char *bol, char *eol) @@ -615,6 +640,98 @@ static void show_pre_context(struct grep_opt *opt, const char *name, char *buf, } } +static int should_lookahead(struct grep_opt *opt) +{ + struct grep_pat *p; + + if (opt->extended) + return 0; /* punt for too complex stuff */ + if (opt->invert) + return 0; + for (p = opt->pattern_list; p; p = p->next) { + if (p->token != GREP_PATTERN) + return 0; /* punt for "header only" and stuff */ + } + return 1; +} + +static int look_ahead(struct grep_opt *opt, + unsigned long *left_p, + unsigned *lno_p, + char **bol_p) +{ + unsigned lno = *lno_p; + char *bol = *bol_p; + struct grep_pat *p; + char *sp, *last_bol; + regoff_t earliest = -1; + + for (p = opt->pattern_list; p; p = p->next) { + int hit; + regmatch_t m; + + if (p->fixed) + hit = !fixmatch(p->pattern, bol, p->ignore_case, &m); + else { +#ifdef REG_STARTEND + m.rm_so = 0; + m.rm_eo = *left_p; + hit = !regexec(&p->regexp, bol, 1, &m, REG_STARTEND); +#else + hit = !regexec(&p->regexp, bol, 1, &m, 0); +#endif + } + if (!hit || m.rm_so < 0 || m.rm_eo < 0) + continue; + if (earliest < 0 || m.rm_so < earliest) + earliest = m.rm_so; + } + + if (earliest < 0) { + *bol_p = bol + *left_p; + *left_p = 0; + return 1; + } + for (sp = bol + earliest; bol < sp && sp[-1] != '\n'; sp--) + ; /* find the beginning of the line */ + last_bol = sp; + + for (sp = bol; sp < last_bol; sp++) { + if (*sp == '\n') + lno++; + } + *left_p -= last_bol - bol; + *bol_p = last_bol; + *lno_p = lno; + return 0; +} + +int grep_threads_ok(const struct grep_opt *opt) +{ + /* If this condition is true, then we may use the attribute + * machinery in grep_buffer_1. The attribute code is not + * thread safe, so we disable the use of threads. + */ + if (opt->funcname && !opt->unmatch_name_only && !opt->status_only && + !opt->name_only) + return 0; + + /* If we are showing hunk marks, we should not do it for the + * first match. The synchronization problem we get for this + * constraint is not yet solved, so we disable threading in + * this case. + */ + if (opt->pre_context || opt->post_context) + return 0; + + return 1; +} + +static void std_output(struct grep_opt *opt, const void *buf, size_t size) +{ + fwrite(buf, size, 1, stdout); +} + static int grep_buffer_1(struct grep_opt *opt, const char *name, char *buf, unsigned long size, int collect_hits) { @@ -624,11 +741,15 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name, unsigned last_hit = 0; int binary_match_only = 0; unsigned count = 0; + int try_lookahead = 0; enum grep_context ctx = GREP_CONTEXT_HEAD; xdemitconf_t xecfg; opt->last_shown = 0; + if (!opt->output) + opt->output = std_output; + if (buffer_is_binary(buf, size)) { switch (opt->binary) { case GREP_BINARY_DEFAULT: @@ -652,11 +773,26 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name, opt->priv = &xecfg; } } + try_lookahead = should_lookahead(opt); while (left) { char *eol, ch; int hit; + /* + * look_ahead() skips quicly to the line that possibly + * has the next hit; don't call it if we need to do + * something more than just skipping the current line + * in response to an unmatch for the current line. E.g. + * inside a post-context window, we will show the current + * line as a context around the previous hit when it + * doesn't hit. + */ + if (try_lookahead + && !(last_hit + && lno <= last_hit + opt->post_context) + && look_ahead(opt, &left, &lno, &bol)) + break; eol = end_of_line(bol, &left); ch = *eol; *eol = 0; @@ -686,7 +822,9 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name, if (opt->status_only) return 1; if (binary_match_only) { - printf("Binary file %s matches\n", name); + opt->output(opt, "Binary file ", 12); + opt->output(opt, name, strlen(name)); + opt->output(opt, " matches\n", 9); return 1; } if (opt->name_only) { @@ -742,9 +880,13 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name, * which feels mostly useless but sometimes useful. Maybe * make it another option? For now suppress them. */ - if (opt->count && count) - printf("%s%c%u\n", name, - opt->null_following_name ? '\0' : ':', count); + if (opt->count && count) { + char buf[32]; + opt->output(opt, name, strlen(name)); + snprintf(buf, sizeof(buf), "%c%u\n", + opt->null_following_name ? '\0' : ':', count); + opt->output(opt, buf, strlen(buf)); + } return !!last_hit; } @@ -85,13 +85,15 @@ struct grep_opt { int max_depth; int funcname; char color_match[COLOR_MAXLEN]; - const char *color_external; int regflags; unsigned pre_context; unsigned post_context; unsigned last_shown; int show_hunk_mark; void *priv; + + void (*output)(struct grep_opt *opt, const void *data, size_t size); + void *output_priv; }; extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t); @@ -100,4 +102,7 @@ extern void compile_grep_patterns(struct grep_opt *opt); extern void free_grep_patterns(struct grep_opt *opt); extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size); +extern struct grep_opt *grep_opt_dup(const struct grep_opt *opt); +extern int grep_threads_ok(const struct grep_opt *opt); + #endif @@ -350,7 +350,7 @@ const char *help_unknown_cmd(const char *cmd) return assumed; } - fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd); + fprintf(stderr, "git: '%s' is not a git command. See 'git --help'.\n", cmd); if (SIMILAR_ENOUGH(best_similarity)) { fprintf(stderr, "\nDid you mean %s?\n", @@ -0,0 +1,67 @@ +#include "cache.h" + +const signed char hexval_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ + 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ + 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ +}; + +int get_sha1_hex(const char *hex, unsigned char *sha1) +{ + int i; + for (i = 0; i < 20; i++) { + unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]); + if (val & ~0xff) + return -1; + *sha1++ = val; + hex += 2; + } + return 0; +} + +char *sha1_to_hex(const unsigned char *sha1) +{ + static int bufno; + static char hexbuffer[4][50]; + static const char hex[] = "0123456789abcdef"; + char *buffer = hexbuffer[3 & ++bufno], *buf = buffer; + int i; + + for (i = 0; i < 20; i++) { + unsigned int val = *sha1++; + *buf++ = hex[val >> 4]; + *buf++ = hex[val & 0xf]; + } + *buf = '\0'; + + return buffer; +} diff --git a/http-backend.c b/http-backend.c new file mode 100644 index 0000000000..345c12b790 --- /dev/null +++ b/http-backend.c @@ -0,0 +1,658 @@ +#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); + if (!getenv("GIT_HTTP_EXPORT_ALL") && + access("git-daemon-export-ok", F_OK) ) + not_found("Repository not exported: '%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,10 +1,18 @@ #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; +#if LIBCURL_VERSION_NUM >= 0x070a06 +#define LIBCURL_CAN_HANDLE_AUTH_ANY +#endif + +static int min_curl_sessions = 1; +static int curl_session_count; #ifdef USE_CURL_MULTI static int max_requests = -1; static CURLM *curlm; @@ -97,8 +105,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) { @@ -152,6 +158,14 @@ static int http_options(const char *var, const char *value, void *cb) ssl_cert_password_required = 1; return 0; } + if (!strcmp("http.minsessions", var)) { + min_curl_sessions = git_config_int(var, value); +#ifndef USE_CURL_MULTI + if (min_curl_sessions > 1) + min_curl_sessions = 1; +#endif + return 0; + } #ifdef USE_CURL_MULTI if (!strcmp("http.maxrequests", var)) { max_requests = git_config_int(var, value); @@ -174,6 +188,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); } @@ -223,6 +244,9 @@ static CURL *get_curl_handle(void) #if LIBCURL_VERSION_NUM >= 0x070907 curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL); #endif +#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY + curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY); +#endif init_curl_http_auth(result); @@ -365,6 +389,7 @@ void http_init(struct remote *remote) if (curl_ssl_verify == -1) curl_ssl_verify = 1; + curl_session_count = 0; #ifdef USE_CURL_MULTI if (max_requests < 1) max_requests = DEFAULT_MAX_REQUESTS; @@ -473,6 +498,7 @@ struct active_request_slot *get_active_slot(void) #else slot->curl = curl_easy_duphandle(curl_default); #endif + curl_session_count++; } active_requests++; @@ -551,9 +577,11 @@ void fill_active_slots(void) } while (slot != NULL) { - if (!slot->in_use && slot->curl != NULL) { + if (!slot->in_use && slot->curl != NULL + && curl_session_count > min_curl_sessions) { curl_easy_cleanup(slot->curl); slot->curl = NULL; + curl_session_count--; } slot = slot->next; } @@ -623,22 +651,23 @@ static void closedown_active_slot(struct active_request_slot *slot) slot->in_use = 0; } -void release_active_slot(struct active_request_slot *slot) +static void release_active_slot(struct active_request_slot *slot) { closedown_active_slot(slot); - if (slot->curl) { + if (slot->curl && curl_session_count > min_curl_sessions) { #ifdef USE_CURL_MULTI curl_multi_remove_handle(curlm, slot->curl); #endif curl_easy_cleanup(slot->curl); slot->curl = NULL; + curl_session_count--; } #ifdef USE_CURL_MULTI fill_active_slots(); #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); @@ -805,7 +834,13 @@ int http_get_strbuf(const char *url, struct strbuf *result, int options) return http_request(url, result, HTTP_REQUEST_STRBUF, options); } -int http_get_file(const char *url, const char *filename, int options) +/* + * Downloads an url and stores the result in the given file. + * + * If a previous interrupted download is detected (i.e. a previous temporary + * file is still around) the download is resumed. + */ +static int http_get_file(const char *url, const char *filename, int options) { int ret; struct strbuf tmpfile = STRBUF_INIT; @@ -1237,7 +1272,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,8 +79,8 @@ 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); #ifdef USE_CURL_MULTI extern void fill_active_slots(void); @@ -94,6 +94,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]; @@ -134,14 +135,6 @@ extern char *get_remote_object_url(const char *url, const char *hex, int http_get_strbuf(const char *url, struct strbuf *result, int options); /* - * Downloads an url and stores the result in the given file. - * - * If a previous interrupted download is detected (i.e. a previous temporary - * file is still around) the download is resumed. - */ -int http_get_file(const char *url, const char *filename, int options); - -/* * Prints an error message using error() containing url and curl_errorstr, * and returns ret. */ @@ -85,10 +85,11 @@ static void setup_ident(void) if (!git_default_email[0]) { const char *email = getenv("EMAIL"); - if (email && email[0]) + if (email && email[0]) { strlcpy(git_default_email, email, sizeof(git_default_email)); - else { + user_ident_explicitly_given |= IDENT_MAIL_GIVEN; + } else { if (!pw) pw = getpwuid(getuid()); if (!pw) @@ -168,8 +169,6 @@ static int copy(char *buf, size_t size, int offset, const char *src) return offset; } -static const char au_env[] = "GIT_AUTHOR_NAME"; -static const char co_env[] = "GIT_COMMITTER_NAME"; static const char *env_hint = "\n" "*** Please tell me who you are.\n" @@ -204,8 +203,8 @@ 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" */ + fputs(env_hint, stderr); + env_hint = NULL; /* warn only once */ } if (error_on_no_name) die("empty ident %s <%s> not allowed", name, email); @@ -251,11 +250,21 @@ const char *git_author_info(int flag) const char *git_committer_info(int flag) { - if (getenv("GIT_COMMITTER_NAME") && - getenv("GIT_COMMITTER_EMAIL")) - user_ident_explicitly_given = 1; + if (getenv("GIT_COMMITTER_NAME")) + user_ident_explicitly_given |= IDENT_NAME_GIVEN; + if (getenv("GIT_COMMITTER_EMAIL")) + user_ident_explicitly_given |= IDENT_MAIL_GIVEN; return fmt_ident(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"), getenv("GIT_COMMITTER_DATE"), flag); } + +int user_ident_sufficiently_given(void) +{ +#ifndef WINDOWS + return (user_ident_explicitly_given & IDENT_MAIL_GIVEN); +#else + return (user_ident_explicitly_given == IDENT_ALL_GIVEN); +#endif +} diff --git a/imap-send.c b/imap-send.c index f805c6ed81..5631930bc3 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 @@ -90,9 +91,11 @@ struct msg_data { char *data; int len; unsigned char flags; - 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 +103,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 +129,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 +157,7 @@ struct imap_list { }; struct imap_socket { - int fd; + int fd[2]; SSL *ssl; }; @@ -308,8 +311,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 +338,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 +356,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 +374,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 +503,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 +564,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 +578,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 +594,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 +885,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 +931,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 +953,31 @@ 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); - - if (socketpair(PF_UNIX, SOCK_STREAM, 0, a)) { - perror("socketpair"); - exit(1); - } + const char *argv[] = { srvc->tunnel, NULL }; + struct child_process tunnel = {0}; - 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); - } + imap_info("Starting tunnel '%s'... ", srvc->tunnel); - close(a[0]); + tunnel.argv = argv; + tunnel.use_shell = 1; + 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 +1054,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 +1161,59 @@ static int imap_make_flags(int flags, char *buf) return d; } -#define TUIDL 8 +static void lf_to_crlf(struct msg_data *msg) +{ + char *new; + int i, j, lfnum = 0; + + if (msg->data[0] == '\n') + lfnum++; + for (i = 1; i < msg->len; i++) { + if (msg->data[i - 1] != '\r' && msg->data[i] == '\n') + lfnum++; + } -static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid) + new = xmalloc(msg->len + lfnum); + if (msg->data[0] == '\n') { + new[0] = '\r'; + new[1] = '\n'; + i = 1; + j = 2; + } else { + new[0] = msg->data[0]; + i = 1; + j = 1; + } + for ( ; i < msg->len; i++) { + if (msg->data[i] != '\n') { + new[j++] = msg->data[i]; + continue; + } + if (msg->data[i - 1] != '\r') + new[j++] = '\r'; + /* otherwise it already had CR before */ + new[j++] = '\n'; + } + msg->len += lfnum; + free(msg->data); + msg->data = new; +} + +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]; + lf_to_crlf(data); 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 +1222,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; } @@ -1451,11 +1369,16 @@ static int git_imap_config(const char *key, const char *val, void *cb) if (strncmp(key, imap_key, sizeof imap_key - 1)) return 0; - if (!val) - return config_error_nonbool(key); - key += sizeof imap_key - 1; + /* check booleans first, and barf on others */ + if (!strcmp("sslverify", key)) + server.ssl_verify = git_config_bool(key, val); + else if (!strcmp("preformattedhtml", key)) + server.use_html = git_config_bool(key, val); + else if (!val) + return config_error_nonbool(key); + if (!strcmp("folder", key)) { imap_folder = xstrdup(val); } else if (!strcmp("host", key)) { @@ -1476,10 +1399,6 @@ static int git_imap_config(const char *key, const char *val, void *cb) server.port = git_config_int(key, val); else if (!strcmp("tunnel", key)) server.tunnel = xstrdup(val); - else if (!strcmp("sslverify", key)) - server.ssl_verify = git_config_bool(key, val); - else if (!strcmp("preformattedHTML", key)) - server.use_html = git_config_bool(key, val); return 0; } @@ -1487,7 +1406,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 +1413,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 +1462,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/levenshtein.h b/levenshtein.h index 0173abeef5..4105bf3549 100644 --- a/levenshtein.h +++ b/levenshtein.h @@ -2,7 +2,7 @@ #define LEVENSHTEIN_H int levenshtein(const char *string1, const char *string2, - int swap_penalty, int substition_penalty, + int swap_penalty, int substitution_penalty, int insertion_penalty, int deletion_penalty); #endif diff --git a/ll-merge.c b/ll-merge.c index 2d6b6d6cb1..4c7f11ba84 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -18,7 +18,8 @@ typedef int (*ll_merge_fn)(const struct ll_merge_driver *, mmfile_t *orig, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, - int virtual_ancestor); + int flag, + int marker_size); struct ll_merge_driver { const char *name; @@ -38,14 +39,14 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused, mmfile_t *orig, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, - int virtual_ancestor) + int flag, int marker_size) { /* * The tentative merge result is "ours" for the final round, * or common ancestor for an internal merge. Still return * "conflicted merge" status. */ - mmfile_t *stolen = virtual_ancestor ? orig : src1; + mmfile_t *stolen = (flag & 01) ? orig : src1; result->ptr = stolen->ptr; result->size = stolen->size; @@ -59,10 +60,11 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, mmfile_t *orig, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, - int virtual_ancestor) + int flag, int marker_size) { - xpparam_t xpp; + xmparam_t xmp; int style = 0; + int favor = (flag >> 1) & 03; if (buffer_is_binary(orig->ptr, orig->size) || buffer_is_binary(src1->ptr, src1->size) || @@ -73,16 +75,18 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, path, orig, src1, name1, src2, name2, - virtual_ancestor); + flag, marker_size); } - memset(&xpp, 0, sizeof(xpp)); + memset(&xmp, 0, sizeof(xmp)); if (git_xmerge_style >= 0) style = git_xmerge_style; + if (marker_size > 0) + xmp.marker_size = marker_size; return xdl_merge(orig, src1, name1, src2, name2, - &xpp, XDL_MERGE_ZEALOUS | style, + &xmp, XDL_MERGE_FLAGS(XDL_MERGE_ZEALOUS, style, favor), result); } @@ -92,11 +96,10 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused, mmfile_t *orig, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, - int virtual_ancestor) + int flag, int marker_size) { char *src, *dst; long size; - const int marker_size = 7; int status, saved_style; /* We have to force the RCS "merge" style */ @@ -104,7 +107,7 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused, git_xmerge_style = 0; status = ll_xdl_merge(drv_unused, result, path_unused, orig, src1, NULL, src2, NULL, - virtual_ancestor); + flag, marker_size); git_xmerge_style = saved_style; if (status <= 0) return status; @@ -165,17 +168,18 @@ static int ll_ext_merge(const struct ll_merge_driver *fn, mmfile_t *orig, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, - int virtual_ancestor) + int flag, int marker_size) { - char temp[3][50]; + char temp[4][50]; struct strbuf cmd = STRBUF_INIT; struct strbuf_expand_dict_entry dict[] = { { "O", temp[0] }, { "A", temp[1] }, { "B", temp[2] }, + { "L", temp[3] }, { NULL } }; - const char *args[] = { "sh", "-c", NULL, NULL }; + const char *args[] = { NULL, NULL }; int status, fd, i; struct stat st; @@ -187,11 +191,12 @@ static int ll_ext_merge(const struct ll_merge_driver *fn, create_temp(orig, temp[0]); create_temp(src1, temp[1]); create_temp(src2, temp[2]); + sprintf(temp[3], "%d", marker_size); strbuf_expand(&cmd, fn->cmdline, strbuf_expand_dict_cb, &dict); - args[2] = cmd.buf; - status = run_command_v_opt(args, 0); + args[0] = cmd.buf; + status = run_command_v_opt(args, RUN_USING_SHELL); fd = open(temp[1], O_RDONLY); if (fd < 0) goto bad; @@ -279,6 +284,7 @@ static int read_merge_config(const char *var, const char *value, void *cb) * %O - temporary file name for the merge base. * %A - temporary file name for our version. * %B - temporary file name for the other branches' version. + * %L - conflict marker length * * The external merge driver should write the results in the * file named by %A, and signal that it has done with zero exit @@ -339,16 +345,13 @@ static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr return &ll_merge_drv[LL_TEXT_MERGE]; } -static const char *git_path_check_merge(const char *path) +static int git_path_check_merge(const char *path, struct git_attr_check check[2]) { - static struct git_attr_check attr_merge_check; - - if (!attr_merge_check.attr) - attr_merge_check.attr = git_attr("merge", 5); - - if (git_checkattr(path, 1, &attr_merge_check)) - return NULL; - return attr_merge_check.value; + if (!check[0].attr) { + check[0].attr = git_attr("merge"); + check[1].attr = git_attr("conflict-marker-size"); + } + return git_checkattr(path, 2, check); } int ll_merge(mmbuffer_t *result_buf, @@ -356,18 +359,41 @@ int ll_merge(mmbuffer_t *result_buf, mmfile_t *ancestor, mmfile_t *ours, const char *our_label, mmfile_t *theirs, const char *their_label, - int virtual_ancestor) + int flag) { - const char *ll_driver_name; + static struct git_attr_check check[2]; + const char *ll_driver_name = NULL; + int marker_size = DEFAULT_CONFLICT_MARKER_SIZE; const struct ll_merge_driver *driver; - - ll_driver_name = git_path_check_merge(path); + int virtual_ancestor = flag & 01; + + if (!git_path_check_merge(path, check)) { + ll_driver_name = check[0].value; + if (check[1].value) { + marker_size = atoi(check[1].value); + if (marker_size <= 0) + marker_size = DEFAULT_CONFLICT_MARKER_SIZE; + } + } driver = find_ll_merge_driver(ll_driver_name); - if (virtual_ancestor && driver->recursive) driver = find_ll_merge_driver(driver->recursive); - return driver->fn(driver, result_buf, path, - ancestor, - ours, our_label, - theirs, their_label, virtual_ancestor); + return driver->fn(driver, result_buf, path, ancestor, + ours, our_label, theirs, their_label, + flag, marker_size); +} + +int ll_merge_marker_size(const char *path) +{ + static struct git_attr_check check; + int marker_size = DEFAULT_CONFLICT_MARKER_SIZE; + + if (!check.attr) + check.attr = git_attr("conflict-marker-size"); + if (!git_checkattr(path, 1, &check) && check.value) { + marker_size = atoi(check.value); + if (marker_size <= 0) + marker_size = DEFAULT_CONFLICT_MARKER_SIZE; + } + return marker_size; } diff --git a/ll-merge.h b/ll-merge.h index 5388422d09..57889227b1 100644 --- a/ll-merge.h +++ b/ll-merge.h @@ -10,6 +10,8 @@ int ll_merge(mmbuffer_t *result_buf, mmfile_t *ancestor, mmfile_t *ours, const char *our_label, mmfile_t *theirs, const char *their_label, - int virtual_ancestor); + int flag); + +int ll_merge_marker_size(const char *path); #endif diff --git a/lockfile.c b/lockfile.c index 6851fa55a5..b0d74cddde 100644 --- a/lockfile.c +++ b/lockfile.c @@ -164,9 +164,10 @@ static char *unable_to_lock_message(const char *path, int err) "If no other git process is currently running, this probably means a\n" "git process crashed in this repository earlier. Make sure no other git\n" "process is running and remove the file manually to continue.", - path, strerror(err)); + make_nonrelative_path(path), strerror(err)); } else - strbuf_addf(&buf, "Unable to create '%s.lock': %s", path, strerror(err)); + strbuf_addf(&buf, "Unable to create '%s.lock': %s", + make_nonrelative_path(path), strerror(err)); return strbuf_detach(&buf, NULL); } diff --git a/log-tree.c b/log-tree.c index 1618f3c79a..27afcf6972 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,12 +279,12 @@ 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; + ctx.show_notes = opt->show_notes; if (!opt->verbose_header) { graph_show_commit(opt->graph); @@ -346,8 +349,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 +407,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); @@ -243,8 +243,3 @@ int map_user(struct string_list *map, debug_mm("map_user: --\n"); return 0; } - -int map_email(struct string_list *map, const char *email, char *name, int maxlen) -{ - return map_user(map, (char *)email, 0, name, maxlen); -} @@ -4,7 +4,6 @@ int read_mailmap(struct string_list *map, char **repo_abbrev); void clear_mailmap(struct string_list *map); -int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen); int map_user(struct string_list *mailmap, char *email, int maxlen_email, char *name, int maxlen_name); diff --git a/match-trees.c b/match-trees.c index 0fd6df7d6e..26f7ed143e 100644 --- a/match-trees.c +++ b/match-trees.c @@ -185,7 +185,7 @@ static void match_trees(const unsigned char *hash1, * tree object by replacing it with another tree "hash2". */ static int splice_tree(const unsigned char *hash1, - char *prefix, + const char *prefix, const unsigned char *hash2, unsigned char *result) { @@ -264,6 +264,13 @@ void shift_tree(const unsigned char *hash1, char *del_prefix; int add_score, del_score; + /* + * NEEDSWORK: this limits the recursion depth to hardcoded + * value '2' to avoid excessive overhead. + */ + if (!depth_limit) + depth_limit = 2; + add_score = del_score = score_trees(hash1, hash2); add_prefix = xcalloc(1, 1); del_prefix = xcalloc(1, 1); @@ -301,3 +308,63 @@ void shift_tree(const unsigned char *hash1, splice_tree(hash1, add_prefix, hash2, shifted); } + +/* + * The user says the trees will be shifted by this much. + * Unfortunately we cannot fundamentally tell which one to + * be prefixed, as recursive merge can work in either direction. + */ +void shift_tree_by(const unsigned char *hash1, + const unsigned char *hash2, + unsigned char *shifted, + const char *shift_prefix) +{ + unsigned char sub1[20], sub2[20]; + unsigned mode1, mode2; + unsigned candidate = 0; + + /* Can hash2 be a tree at shift_prefix in tree hash1? */ + if (!get_tree_entry(hash1, shift_prefix, sub1, &mode1) && + S_ISDIR(mode1)) + candidate |= 1; + + /* Can hash1 be a tree at shift_prefix in tree hash2? */ + if (!get_tree_entry(hash2, shift_prefix, sub2, &mode2) && + S_ISDIR(mode2)) + candidate |= 2; + + if (candidate == 3) { + /* Both are plausible -- we need to evaluate the score */ + int best_score = score_trees(hash1, hash2); + int score; + + candidate = 0; + score = score_trees(sub1, hash2); + if (score > best_score) { + candidate = 1; + best_score = score; + } + score = score_trees(sub2, hash1); + if (score > best_score) + candidate = 2; + } + + if (!candidate) { + /* Neither is plausible -- do not shift */ + hashcpy(shifted, hash2); + return; + } + + if (candidate == 1) + /* + * shift tree2 down by adding shift_prefix above it + * to match tree1. + */ + splice_tree(hash1, shift_prefix, hash2, shifted); + else + /* + * shift tree2 up by removing shift_prefix from it + * to match tree1. + */ + hashcpy(shifted, sub2); +} diff --git a/merge-file.c b/merge-file.c index 3120a95f78..fd34d76e15 100644 --- a/merge-file.c +++ b/merge-file.c @@ -1,6 +1,7 @@ #include "cache.h" #include "run-command.h" #include "xdiff-interface.h" +#include "ll-merge.h" #include "blob.h" static int fill_mmfile_blob(mmfile_t *f, struct blob *obj) @@ -24,16 +25,13 @@ static void free_mmfile(mmfile_t *f) free(f->ptr); } -static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size) +static void *three_way_filemerge(const char *path, mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size) { - mmbuffer_t res; - xpparam_t xpp; int merge_status; + mmbuffer_t res; - memset(&xpp, 0, sizeof(xpp)); - merge_status = xdl_merge(base, our, ".our", their, ".their", - &xpp, XDL_MERGE_ZEALOUS, &res); - + merge_status = ll_merge(&res, path, base, + our, ".our", their, ".their", 0); if (merge_status < 0) return NULL; @@ -75,7 +73,7 @@ static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2) return xdi_diff(f1, f2, &xpp, &xecfg, &ecb); } -void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsigned long *size) +void *merge_file(const char *path, struct blob *base, struct blob *our, struct blob *their, unsigned long *size) { void *res = NULL; mmfile_t f1, f2, common; @@ -108,7 +106,7 @@ void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsign if (generate_common_file(&common, &f1, &f2) < 0) goto out_free_f2_f1; } - res = three_way_filemerge(&common, &f1, &f2, size); + res = three_way_filemerge(path, &common, &f1, &f2, size); free_mmfile(&common); out_free_f2_f1: free_mmfile(&f2); diff --git a/merge-recursive.c b/merge-recursive.c index cd3628c4d9..cb53b01c19 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -21,15 +21,17 @@ #include "merge-recursive.h" #include "dir.h" -static struct tree *shift_tree_object(struct tree *one, struct tree *two) +static struct tree *shift_tree_object(struct tree *one, struct tree *two, + const char *subtree_shift) { unsigned char shifted[20]; - /* - * NEEDSWORK: this limits the recursion depth to hardcoded - * value '2' to avoid excessive overhead. - */ - shift_tree(one->object.sha1, two->object.sha1, shifted, 2); + if (!*subtree_shift) { + shift_tree(one->object.sha1, two->object.sha1, shifted, 0); + } else { + shift_tree_by(one->object.sha1, two->object.sha1, shifted, + subtree_shift); + } if (!hashcmp(two->object.sha1, shifted)) return two; return lookup_tree(shifted); @@ -87,6 +89,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; @@ -199,13 +202,14 @@ struct tree *write_tree_from_memory(struct merge_options *o) if (unmerged_cache()) { int i; - output(o, 0, "There are unmerged index entries:"); + fprintf(stderr, "BUG: There are unmerged index entries:\n"); 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); + fprintf(stderr, "BUG: %d %.*s", ce_stage(ce), + (int)ce_namelen(ce), ce->name); } - return NULL; + die("Bug in merge-recursive.c"); } if (!active_cache_tree) @@ -623,6 +627,23 @@ static int merge_3way(struct merge_options *o, mmfile_t orig, src1, src2; char *name1, *name2; int merge_status; + int favor; + + if (o->call_depth) + favor = 0; + else { + switch (o->recursive_variant) { + case MERGE_RECURSIVE_OURS: + favor = XDL_MERGE_FAVOR_OURS; + break; + case MERGE_RECURSIVE_THEIRS: + favor = XDL_MERGE_FAVOR_THEIRS; + break; + default: + favor = 0; + break; + } + } if (strcmp(a->path, b->path)) { name1 = xstrdup(mkpath("%s:%s", branch1, a->path)); @@ -638,7 +659,7 @@ static int merge_3way(struct merge_options *o, merge_status = ll_merge(result_buf, a->path, &orig, &src1, name1, &src2, name2, - o->call_depth); + (!!o->call_depth) | (favor << 1)); free(name1); free(name2); @@ -1199,9 +1220,9 @@ int merge_trees(struct merge_options *o, { int code, clean; - if (o->subtree_merge) { - merge = shift_tree_object(head, merge); - common = shift_tree_object(head, common); + if (o->subtree_shift) { + merge = shift_tree_object(head, merge, o->subtree_shift); + common = shift_tree_object(head, common, o->subtree_shift); } if (sha_eq(common->object.sha1, merge->object.sha1)) { diff --git a/merge-recursive.h b/merge-recursive.h index d8bc7299ee..be8410ad18 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -6,7 +6,12 @@ struct merge_options { const char *branch1; const char *branch2; - unsigned subtree_merge : 1; + enum { + MERGE_RECURSIVE_NORMAL = 0, + MERGE_RECURSIVE_OURS, + MERGE_RECURSIVE_THEIRS, + } recursive_variant; + const char *subtree_shift; unsigned buffer_output : 1; int verbosity; int diff_rename_limit; 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 @@ -217,27 +217,6 @@ struct object_list *object_list_insert(struct object *item, return new_list; } -void object_list_append(struct object *item, - struct object_list **list_p) -{ - while (*list_p) { - list_p = &((*list_p)->next); - } - *list_p = xmalloc(sizeof(struct object_list)); - (*list_p)->next = NULL; - (*list_p)->item = item; -} - -unsigned object_list_length(struct object_list *list) -{ - unsigned ret = 0; - while (list) { - list = list->next; - ret++; - } - return ret; -} - int object_list_contains(struct object_list *list, struct object *obj) { while (list) { @@ -72,11 +72,6 @@ struct object *lookup_unknown_object(const unsigned char *sha1); struct object_list *object_list_insert(struct object *item, struct object_list **list_p); -void object_list_append(struct object *item, - struct object_list **list_p); - -unsigned object_list_length(struct object_list *list); - int object_list_contains(struct object_list *list, struct object *obj); /* Object array handling .. */ diff --git a/pack-write.c b/pack-write.c index 741efcd93b..9f47cf9961 100644 --- a/pack-write.c +++ b/pack-write.c @@ -17,8 +17,8 @@ static int sha1_compare(const void *_a, const void *_b) * the SHA1 hash of sorted object names. The objects array passed in * will be sorted by SHA1 on exit. */ -char *write_idx_file(char *index_name, struct pack_idx_entry **objects, - int nr_objects, unsigned char *sha1) +const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, + int nr_objects, unsigned char *sha1) { struct sha1file *f; struct pack_idx_entry **sorted_by_sha, **list, **last; @@ -55,7 +55,7 @@ struct pack_idx_entry { off_t offset; }; -extern char *write_idx_file(char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1); +extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1); extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr); extern int verify_pack(struct packed_git *); extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t); @@ -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. @@ -24,7 +28,7 @@ static void pager_preexec(void) } #endif -static const char *pager_argv[] = { "sh", "-c", NULL, NULL }; +static const char *pager_argv[] = { NULL, NULL }; static struct child_process pager_process; static void wait_for_pager(void) @@ -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,14 +64,25 @@ 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 */ /* spawn the pager */ - pager_argv[2] = pager; + pager_argv[0] = pager; + pager_process.use_shell = 1; pager_process.argv = pager_argv; pager_process.in = -1; if (!getenv("LESS")) { diff --git a/parse-options.c b/parse-options.c index f5594114ed..d218122af5 100644 --- a/parse-options.c +++ b/parse-options.c @@ -3,6 +3,9 @@ #include "cache.h" #include "commit.h" +static int parse_options_usage(const char * const *usagestr, + const struct option *opts); + #define OPT_SHORT 1 #define OPT_UNSET 2 @@ -560,8 +563,8 @@ void usage_msg_opt(const char *msg, usage_with_options(usagestr, options); } -int parse_options_usage(const char * const *usagestr, - const struct option *opts) +static int parse_options_usage(const char * const *usagestr, + const struct option *opts) { return usage_with_options_internal(usagestr, opts, 0); } @@ -633,3 +636,10 @@ int parse_opt_with_commit(const struct option *opt, const char *arg, int unset) commit_list_insert(commit, opt->value); return 0; } + +int parse_opt_tertiary(const struct option *opt, const char *arg, int unset) +{ + int *target = opt->value; + *target = unset ? 2 : 1; + return 0; +} diff --git a/parse-options.h b/parse-options.h index f295a2cf85..0c996916b6 100644 --- a/parse-options.h +++ b/parse-options.h @@ -123,6 +123,8 @@ struct option { (h), PARSE_OPT_NOARG, NULL, (p) } #define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), "n", (h) } #define OPT_STRING(s, l, v, a, h) { OPTION_STRING, (s), (l), (v), (a), (h) } +#define OPT_UYN(s, l, v, h) { OPTION_CALLBACK, (s), (l), (v), NULL, \ + (h), PARSE_OPT_NOARG, &parse_opt_tertiary } #define OPT_DATE(s, l, v, h) \ { OPTION_CALLBACK, (s), (l), (v), "time",(h), 0, \ parse_opt_approxidate_cb } @@ -171,9 +173,6 @@ struct parse_opt_ctx_t { const char *prefix; }; -extern int parse_options_usage(const char * const *usagestr, - const struct option *opts); - extern void parse_options_start(struct parse_opt_ctx_t *ctx, int argc, const char **argv, const char *prefix, int flags); @@ -190,6 +189,7 @@ extern int parse_opt_abbrev_cb(const struct option *, const char *, int); extern int parse_opt_approxidate_cb(const struct option *, const char *, int); extern int parse_opt_verbosity_cb(const struct option *, const char *, int); extern int parse_opt_with_commit(const struct option *, const char *, int); +extern int parse_opt_tertiary(const struct option *, const char *, int); #define OPT__VERBOSE(var) OPT_BOOLEAN('v', "verbose", (var), "be verbose") #define OPT__QUIET(var) OPT_BOOLEAN('q', "quiet", (var), "be quiet") diff --git a/patch-delta.c b/patch-delta.c index e02e13bd4e..d218faa02b 100644 --- a/patch-delta.c +++ b/patch-delta.c @@ -33,8 +33,7 @@ void *patch_delta(const void *src_buf, unsigned long src_size, /* now the result size */ size = get_delta_hdr_size(&data, top); - dst_buf = xmalloc(size + 1); - dst_buf[size] = 0; + dst_buf = xmallocz(size); out = dst_buf; while (data < top) { @@ -336,7 +336,7 @@ char *enter_repo(char *path, int strict) if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 && validate_headref("HEAD") == 0) { - setenv(GIT_DIR_ENVIRONMENT, ".", 1); + set_git_dir("."); check_repository_format(); return path; } @@ -394,17 +394,38 @@ int set_shared_perm(const char *path, int mode) const char *make_relative_path(const char *abs, const char *base) { static char buf[PATH_MAX + 1]; - int baselen; - if (!base) - return abs; - baselen = strlen(base); - if (prefixcmp(abs, base)) + int i = 0, j = 0; + + if (!base || !base[0]) return abs; - if (abs[baselen] == '/') - baselen++; - else if (base[baselen - 1] != '/') + while (base[i]) { + if (is_dir_sep(base[i])) { + if (!is_dir_sep(abs[j])) + return abs; + while (is_dir_sep(base[i])) + i++; + while (is_dir_sep(abs[j])) + j++; + continue; + } else if (abs[j] != base[i]) { + return abs; + } + i++; + j++; + } + if ( + /* "/foo" is a prefix of "/foo" */ + abs[j] && + /* "/foo" is not a prefix of "/foobar" */ + !is_dir_sep(base[i-1]) && !is_dir_sep(abs[j]) + ) return abs; - strcpy(buf, abs + baselen); + while (is_dir_sep(abs[j])) + j++; + if (!abs[j]) + strcpy(buf, "."); + else + strcpy(buf, abs + j); return buf; } @@ -581,3 +602,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 canonicalization. + * + * sl becomes true immediately after seeing '/' and continues to + * be true as long as dots continue after that without intervening + * non-dot character. + */ + if (!p || (*p != '/' && *p != '~')) + return -1; + sl = 1; ndot = 0; + p++; + + while (1) { + char ch = *p++; + if (sl) { + if (ch == '.') + ndot++; + else if (ch == '/') { + if (ndot < 3) + /* reject //, /./ and /../ */ + return -1; + ndot = 0; + } + else if (ch == 0) { + if (0 < ndot && ndot < 3) + /* reject /.$ and /..$ */ + return -1; + return 0; + } + else + sl = ndot = 0; + } + else if (ch == 0) + return 0; + else if (ch == '/') { + sl = 1; + ndot = 0; + } + } +} diff --git a/perl/Git.pm b/perl/Git.pm index e8df55d2f2..970fe434ed 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -204,14 +204,14 @@ sub repository { $dir = $opts{Directory}; unless (-d "$dir/refs" and -d "$dir/objects" and -e "$dir/HEAD") { - # Mimick git-rev-parse --git-dir error message: + # Mimic git-rev-parse --git-dir error message: throw Error::Simple("fatal: Not a git repository: $dir"); } my $search = Git->repository(Repository => $dir); try { $search->command('symbolic-ref', 'HEAD'); } catch Git::Error::Command with { - # Mimick git-rev-parse --git-dir error message: + # Mimic git-rev-parse --git-dir error message: throw Error::Simple("fatal: Not a git repository: $dir"); } 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 diff --git a/preload-index.c b/preload-index.c index 92899333c2..e3d0bda31a 100644 --- a/preload-index.c +++ b/preload-index.c @@ -47,6 +47,8 @@ static void *preload_thread(void *_data) if (ce_stage(ce)) continue; + if (S_ISGITLINK(ce->ce_mode)) + continue; if (ce_uptodate(ce)) continue; if (!ce_path_match(ce, p->pathspec)) @@ -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; @@ -81,7 +83,7 @@ static int get_one_line(const char *msg) } /* High bit set, or ISO-2022-INT */ -int non_ascii(int ch) +static int non_ascii(int ch) { return !isascii(ch) || ch == '\033'; } @@ -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 (context->show_notes) + get_commit_notes(commit, sb, encoding, + NOTES_SHOW_HEADER | NOTES_INDENT); + free(reencoded); } @@ -72,7 +72,7 @@ void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen) } } -char *sq_dequote_step(char *arg, char **next) +static char *sq_dequote_step(char *arg, char **next) { char *dst = arg; char *src = arg; @@ -213,7 +213,7 @@ static size_t quote_c_style_counted(const char *name, ssize_t maxlen, int ch; len = next_quote_pos(p, maxlen); - if (len == maxlen || !p[len]) + if (len == maxlen || (maxlen < 0 && !p[len])) break; if (!no_dq && p == name) @@ -223,6 +223,8 @@ static size_t quote_c_style_counted(const char *name, ssize_t maxlen, EMIT('\\'); p += len; ch = (unsigned char)*p++; + if (maxlen >= 0) + maxlen -= len + 1; if (sq_lookup[ch] >= ' ') { EMIT(sq_lookup[ch]); } else { @@ -45,7 +45,6 @@ extern char *sq_dequote(char *); * next argument that should be passed as first parameter. When there * is no more argument to be dequoted, "next" is updated to point to NULL. */ -extern char *sq_dequote_step(char *arg, char **next); extern int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc); extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp); diff --git a/read-cache.c b/read-cache.c index 1bbaf1cffb..f1f789b7b8 100644 --- a/read-cache.c +++ b/read-cache.c @@ -10,10 +10,10 @@ #include "dir.h" #include "tree.h" #include "commit.h" -#include "diff.h" -#include "diffcore.h" -#include "revision.h" #include "blob.h" +#include "resolve-undo.h" + +static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really); /* Index extensions. * @@ -26,6 +26,7 @@ #define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) ) #define CACHE_EXT_TREE 0x54524545 /* "TREE" */ +#define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUC" */ struct index_state the_index; @@ -156,7 +157,7 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st) return 0; } -int is_empty_blob_sha1(const unsigned char *sha1) +static int is_empty_blob_sha1(const unsigned char *sha1) { static const unsigned char empty_blob_sha1[20] = { 0xe6,0x9d,0xe2,0x9b,0xb2,0xd1,0xd6,0x43,0x4b,0x8b, @@ -259,12 +260,17 @@ int ie_match_stat(const struct index_state *istate, { unsigned int changed; int ignore_valid = options & CE_MATCH_IGNORE_VALID; + int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE; int assume_racy_is_modified = options & CE_MATCH_RACY_IS_DIRTY; /* * If it's marked as always valid in the index, it's * valid whatever the checked-out copy says. + * + * skip-worktree has the same effect with higher precedence */ + if (!ignore_skip_worktree && ce_skip_worktree(ce)) + return 0; if (!ignore_valid && (ce->ce_flags & CE_VALID)) return 0; @@ -449,6 +455,7 @@ int remove_index_entry_at(struct index_state *istate, int pos) { struct cache_entry *ce = istate->cache[pos]; + record_resolve_undo(istate, ce); remove_name_hash(ce); istate->cache_changed = 1; istate->cache_nr--; @@ -564,7 +571,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, int size, namelen, was_same; mode_t st_mode = st->st_mode; struct cache_entry *ce, *alias; - unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_RACY_IS_DIRTY; + unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE|CE_MATCH_RACY_IS_DIRTY; int verbose = flags & (ADD_CACHE_VERBOSE | ADD_CACHE_PRETEND); int pretend = flags & ADD_CACHE_PRETEND; int intent_only = flags & ADD_CACHE_INTENT; @@ -605,7 +612,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) { /* Nothing changed, really */ free(ce); - ce_mark_uptodate(alias); + if (!S_ISGITLINK(alias->ce_mode)) + ce_mark_uptodate(alias); alias->ce_flags |= CE_ADDED; return 0; } @@ -1000,14 +1008,20 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, struct cache_entry *updated; int changed, size; int ignore_valid = options & CE_MATCH_IGNORE_VALID; + int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE; if (ce_uptodate(ce)) return ce; /* - * CE_VALID means the user promised us that the change to - * the work tree does not matter and told us not to worry. + * CE_VALID or CE_SKIP_WORKTREE means the user promised us + * that the change to the work tree does not matter and told + * us not to worry. */ + if (!ignore_skip_worktree && ce_skip_worktree(ce)) { + ce_mark_uptodate(ce); + return ce; + } if (!ignore_valid && (ce->ce_flags & CE_VALID)) { ce_mark_uptodate(ce); return ce; @@ -1037,7 +1051,8 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, * because CE_UPTODATE flag is in-core only; * we are not going to write this change out. */ - ce_mark_uptodate(ce); + if (!S_ISGITLINK(ce->ce_mode)) + ce_mark_uptodate(ce); return ce; } } @@ -1141,7 +1156,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p return has_errors; } -struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really) +static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really) { return refresh_cache_ent(&the_index, ce, really, NULL); } @@ -1170,6 +1185,9 @@ static int read_index_extension(struct index_state *istate, case CACHE_EXT_TREE: istate->cache_tree = cache_tree_read(data, sz); break; + case CACHE_EXT_RESOLVE_UNDO: + istate->resolve_undo = resolve_undo_read(data, sz); + break; default: if (*ext < 'A' || 'Z' < *ext) return error("index uses %.4s extension, which we do not understand", @@ -1322,7 +1340,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, @@ -1349,6 +1367,7 @@ int is_index_unborn(struct index_state *istate) int discard_index(struct index_state *istate) { + resolve_undo_clear_index(istate); istate->cache_nr = 0; istate->cache_changed = 0; istate->timestamp.sec = 0; @@ -1574,6 +1593,17 @@ int write_index(struct index_state *istate, int newfd) if (err) return -1; } + if (istate->resolve_undo) { + struct strbuf sb = STRBUF_INIT; + + resolve_undo_write(&sb, istate->resolve_undo); + err = write_index_ext_header(&c, newfd, CACHE_EXT_RESOLVE_UNDO, + sb.len) < 0 + || ce_write(&c, newfd, sb.buf, sb.len) < 0; + strbuf_release(&sb); + if (err) + return -1; + } if (ce_flush(&c, newfd) || fstat(newfd, &st)) return -1; @@ -1606,9 +1636,8 @@ int read_index_unmerged(struct index_state *istate) len = strlen(ce->name); size = cache_entry_size(len); new_ce = xcalloc(1, size); - hashcpy(new_ce->sha1, ce->sha1); memcpy(new_ce->name, ce->name, len); - new_ce->ce_flags = create_ce_flags(len, 0); + new_ce->ce_flags = create_ce_flags(len, 0) | CE_CONFLICTED; new_ce->ce_mode = ce->ce_mode; if (add_index_entry(istate, new_ce, 0)) return error("%s: cannot drop to stage #0", @@ -1618,81 +1647,6 @@ int read_index_unmerged(struct index_state *istate) return unmerged; } -struct update_callback_data -{ - int flags; - int add_errors; -}; - -static void update_callback(struct diff_queue_struct *q, - struct diff_options *opt, void *cbdata) -{ - int i; - struct update_callback_data *data = cbdata; - - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - const char *path = p->one->path; - switch (p->status) { - default: - die("unexpected diff status %c", p->status); - case DIFF_STATUS_UNMERGED: - /* - * ADD_CACHE_IGNORE_REMOVAL is unset if "git - * add -u" is calling us, In such a case, a - * missing work tree file needs to be removed - * if there is an unmerged entry at stage #2, - * but such a diff record is followed by - * another with DIFF_STATUS_DELETED (and if - * there is no stage #2, we won't see DELETED - * nor MODIFIED). We can simply continue - * either way. - */ - if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL)) - continue; - /* - * Otherwise, it is "git add path" is asking - * to explicitly add it; we fall through. A - * missing work tree file is an error and is - * caught by add_file_to_index() in such a - * case. - */ - case DIFF_STATUS_MODIFIED: - case DIFF_STATUS_TYPE_CHANGED: - if (add_file_to_index(&the_index, path, data->flags)) { - if (!(data->flags & ADD_CACHE_IGNORE_ERRORS)) - die("updating files failed"); - data->add_errors++; - } - break; - case DIFF_STATUS_DELETED: - if (data->flags & ADD_CACHE_IGNORE_REMOVAL) - break; - if (!(data->flags & ADD_CACHE_PRETEND)) - remove_file_from_index(&the_index, path); - if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE)) - printf("remove '%s'\n", path); - break; - } - } -} - -int add_files_to_cache(const char *prefix, const char **pathspec, int flags) -{ - struct update_callback_data data; - struct rev_info rev; - init_revisions(&rev, prefix); - setup_revisions(0, NULL, &rev, NULL); - rev.prune_data = pathspec; - rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; - rev.diffopt.format_callback = update_callback; - data.flags = flags; - data.add_errors = 0; - rev.diffopt.format_callback_data = &data; - run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); - return !!data.add_errors; -} - /* * Returns 1 if the path is an "other" path with respect to * the index; that is, the path is not mentioned in the index at all, 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); } @@ -518,6 +519,13 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int * return ref; } +/* The argument to filter_refs */ +struct ref_filter { + const char *pattern; + each_ref_fn *fn; + void *cb_data; +}; + int read_ref(const char *ref, unsigned char *sha1) { if (resolve_ref(ref, sha1, 1, NULL)) @@ -544,6 +552,15 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim, return fn(entry->name + trim, entry->sha1, entry->flag, cb_data); } +static int filter_refs(const char *ref, const unsigned char *sha, int flags, + void *data) +{ + struct ref_filter *filter = (struct ref_filter *)data; + if (fnmatch(filter->pattern, ref, 0)) + return 0; + return filter->fn(ref, sha, flags, filter->cb_data); +} + int peel_ref(const char *ref, unsigned char *sha1) { int flag; @@ -673,6 +690,43 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data) return do_for_each_ref("refs/replace/", fn, 13, 0, cb_data); } +int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, + const char *prefix, void *cb_data) +{ + struct strbuf real_pattern = STRBUF_INIT; + struct ref_filter filter; + const char *has_glob_specials; + int ret; + + if (!prefix && prefixcmp(pattern, "refs/")) + strbuf_addstr(&real_pattern, "refs/"); + else if (prefix) + strbuf_addstr(&real_pattern, prefix); + strbuf_addstr(&real_pattern, pattern); + + has_glob_specials = strpbrk(pattern, "?*["); + if (!has_glob_specials) { + /* Append implied '/' '*' if not present. */ + if (real_pattern.buf[real_pattern.len - 1] != '/') + strbuf_addch(&real_pattern, '/'); + /* No need to check for '*', there is none. */ + strbuf_addch(&real_pattern, '*'); + } + + filter.pattern = real_pattern.buf; + filter.fn = fn; + filter.cb_data = cb_data; + ret = for_each_ref(filter_refs, &filter); + + strbuf_release(&real_pattern); + return ret; +} + +int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data) +{ + return for_each_glob_ref_in(fn, pattern, NULL, cb_data); +} + int for_each_rawref(each_ref_fn fn, void *cb_data) { return do_for_each_ref("refs/", fn, 0, @@ -25,11 +25,13 @@ extern int for_each_tag_ref(each_ref_fn, void *); extern int for_each_branch_ref(each_ref_fn, void *); extern int for_each_remote_ref(each_ref_fn, void *); extern int for_each_replace_ref(each_ref_fn, void *); +extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *); +extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, 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..d388120851 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -4,24 +4,136 @@ #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, proto_git_candidate = 1; - 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); + + /* try again with "plain" url (no ? or & appended) */ + if (http_ret != HTTP_OK) { + free(refs_url); + strbuf_reset(&buffer); + + proto_git_candidate = 0; + strbuf_addf(&buffer, "%s/info/refs", url); + refs_url = strbuf_detach(&buffer, NULL); + + http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); + } + switch (http_ret) { case HTTP_OK: break; @@ -33,10 +145,88 @@ 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 && proto_git_candidate + && 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 in, int out, void *data) +{ + struct discovery *heads = data; + int err = 0; + if (write_in_full(out, heads->buf, heads->len) != heads->len) + err = 1; + close(out); + 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; + async.out = -1; + + 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 +250,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 +260,536 @@ 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; + unsigned initial_buffer : 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) { + rpc->initial_buffer = 0; + 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; +} + +#ifndef NO_CURL_IOCTL +static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp) +{ + struct rpc_state *rpc = clientp; + + switch (cmd) { + case CURLIOCMD_NOP: + return CURLIOE_OK; + + case CURLIOCMD_RESTARTREAD: + if (rpc->initial_buffer) { + rpc->pos = 0; + return CURLIOE_OK; + } + fprintf(stderr, "Unable to rewind rpc post data - try increasing http.postBuffer\n"); + return CURLIOE_FAILRESTART; + + default: + return CURLIOE_UNKNOWNCMD; + } +} +#endif + +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"); + rpc->initial_buffer = 1; + curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out); + curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc); +#ifndef NO_CURL_IOCTL + curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, rpc_ioctl); + curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, rpc); +#endif + 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-result", 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 +799,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 +815,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; @@ -1207,6 +1247,56 @@ int match_refs(struct ref *src, struct ref **dst, return 0; } +void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, + int force_update) +{ + struct ref *ref; + + for (ref = remote_refs; ref; ref = ref->next) { + if (ref->peer_ref) + hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); + else if (!send_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; + } + + /* This part determines what can overwrite what. + * The rules are: + * + * (0) you can always use --force or +A:B notation to + * selectively force individual ref pairs. + * + * (1) if the old thing does not exist, it is OK. + * + * (2) if you do not have the old thing, you are not allowed + * to overwrite it; you would not know what you are losing + * otherwise. + * + * (3) if both new and old are commit-ish, and new is a + * descendant of old, it is OK. + * + * (4) regardless of all of the above, removing :B is + * always allowed. + */ + + ref->nonfastforward = + !ref->deletion && + !is_null_sha1(ref->old_sha1) && + (!has_sha1_file(ref->old_sha1) + || !ref_newer(ref->new_sha1, ref->old_sha1)); + + if (ref->nonfastforward && !ref->force && !force_update) { + ref->status = REF_STATUS_REJECT_NONFASTFORWARD; + continue; + } + } +} + struct branch *branch_get(const char *name) { struct branch *ret; @@ -1586,3 +1676,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,8 +91,15 @@ 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); +void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, + int force_update); /* * Given a list of the remote refs and the specification of things to @@ -154,4 +163,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 @@ -1,8 +1,11 @@ #include "cache.h" #include "string-list.h" #include "rerere.h" -#include "xdiff/xdiff.h" #include "xdiff-interface.h" +#include "dir.h" +#include "resolve-undo.h" +#include "ll-merge.h" +#include "attr.h" /* if rerere_enabled == -1, fall back to detection of .git/rr-cache */ static int rerere_enabled = -1; @@ -83,61 +86,106 @@ static inline void ferr_puts(const char *s, FILE *fp, int *err) ferr_write(s, strlen(s), fp, err); } -static int handle_file(const char *path, - unsigned char *sha1, const char *output) +struct rerere_io { + int (*getline)(struct strbuf *, struct rerere_io *); + FILE *output; + int wrerror; + /* some more stuff */ +}; + +static void rerere_io_putstr(const char *str, struct rerere_io *io) +{ + if (io->output) + ferr_puts(str, io->output, &io->wrerror); +} + +static void rerere_io_putconflict(int ch, int size, struct rerere_io *io) +{ + char buf[64]; + + while (size) { + if (size < sizeof(buf) - 2) { + memset(buf, ch, size); + buf[size] = '\n'; + buf[size + 1] = '\0'; + size = 0; + } else { + int sz = sizeof(buf) - 1; + if (size <= sz) + sz -= (sz - size) + 1; + memset(buf, ch, sz); + buf[sz] = '\0'; + size -= sz; + } + rerere_io_putstr(buf, io); + } +} + +static void rerere_io_putmem(const char *mem, size_t sz, struct rerere_io *io) +{ + if (io->output) + ferr_write(mem, sz, io->output, &io->wrerror); +} + +struct rerere_io_file { + struct rerere_io io; + FILE *input; +}; + +static int rerere_file_getline(struct strbuf *sb, struct rerere_io *io_) +{ + struct rerere_io_file *io = (struct rerere_io_file *)io_; + return strbuf_getwholeline(sb, io->input, '\n'); +} + +static int is_cmarker(char *buf, int marker_char, int marker_size, int want_sp) +{ + while (marker_size--) + if (*buf++ != marker_char) + return 0; + if (want_sp && *buf != ' ') + return 0; + return isspace(*buf); +} + +static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_size) { git_SHA_CTX ctx; - char buf[1024]; int hunk_no = 0; enum { RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL, } hunk = RR_CONTEXT; struct strbuf one = STRBUF_INIT, two = STRBUF_INIT; - FILE *f = fopen(path, "r"); - FILE *out = NULL; - int wrerror = 0; - - if (!f) - return error("Could not open %s", path); - - if (output) { - out = fopen(output, "w"); - if (!out) { - fclose(f); - return error("Could not write %s", output); - } - } + struct strbuf buf = STRBUF_INIT; if (sha1) git_SHA1_Init(&ctx); - while (fgets(buf, sizeof(buf), f)) { - if (!prefixcmp(buf, "<<<<<<< ")) { + while (!io->getline(&buf, io)) { + if (is_cmarker(buf.buf, '<', marker_size, 1)) { if (hunk != RR_CONTEXT) goto bad; hunk = RR_SIDE_1; - } else if (!prefixcmp(buf, "|||||||") && isspace(buf[7])) { + } else if (is_cmarker(buf.buf, '|', marker_size, 0)) { if (hunk != RR_SIDE_1) goto bad; hunk = RR_ORIGINAL; - } else if (!prefixcmp(buf, "=======") && isspace(buf[7])) { + } else if (is_cmarker(buf.buf, '=', marker_size, 0)) { if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL) goto bad; hunk = RR_SIDE_2; - } else if (!prefixcmp(buf, ">>>>>>> ")) { + } else if (is_cmarker(buf.buf, '>', marker_size, 1)) { if (hunk != RR_SIDE_2) goto bad; if (strbuf_cmp(&one, &two) > 0) strbuf_swap(&one, &two); hunk_no++; hunk = RR_CONTEXT; - if (out) { - ferr_puts("<<<<<<<\n", out, &wrerror); - ferr_write(one.buf, one.len, out, &wrerror); - ferr_puts("=======\n", out, &wrerror); - ferr_write(two.buf, two.len, out, &wrerror); - ferr_puts(">>>>>>>\n", out, &wrerror); - } + rerere_io_putconflict('<', marker_size, io); + rerere_io_putmem(one.buf, one.len, io); + rerere_io_putconflict('=', marker_size, io); + rerere_io_putmem(two.buf, two.len, io); + rerere_io_putconflict('>', marker_size, io); if (sha1) { git_SHA1_Update(&ctx, one.buf ? one.buf : "", one.len + 1); @@ -147,13 +195,13 @@ static int handle_file(const char *path, strbuf_reset(&one); strbuf_reset(&two); } else if (hunk == RR_SIDE_1) - strbuf_addstr(&one, buf); + strbuf_addstr(&one, buf.buf); else if (hunk == RR_ORIGINAL) ; /* discard */ else if (hunk == RR_SIDE_2) - strbuf_addstr(&two, buf); - else if (out) - ferr_puts(buf, out, &wrerror); + strbuf_addstr(&two, buf.buf); + else + rerere_io_putstr(buf.buf, io); continue; bad: hunk = 99; /* force error exit */ @@ -161,26 +209,138 @@ static int handle_file(const char *path, } strbuf_release(&one); strbuf_release(&two); + strbuf_release(&buf); - fclose(f); - if (wrerror) - error("There were errors while writing %s (%s)", - path, strerror(wrerror)); - if (out && fclose(out)) - wrerror = error("Failed to flush %s: %s", - path, strerror(errno)); if (sha1) git_SHA1_Final(sha1, &ctx); - if (hunk != RR_CONTEXT) { + if (hunk != RR_CONTEXT) + return -1; + return hunk_no; +} + +static int handle_file(const char *path, unsigned char *sha1, const char *output) +{ + int hunk_no = 0; + struct rerere_io_file io; + int marker_size = ll_merge_marker_size(path); + + memset(&io, 0, sizeof(io)); + io.io.getline = rerere_file_getline; + io.input = fopen(path, "r"); + io.io.wrerror = 0; + if (!io.input) + return error("Could not open %s", path); + + if (output) { + io.io.output = fopen(output, "w"); + if (!io.io.output) { + fclose(io.input); + return error("Could not write %s", output); + } + } + + hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size); + + fclose(io.input); + if (io.io.wrerror) + error("There were errors while writing %s (%s)", + path, strerror(io.io.wrerror)); + if (io.io.output && fclose(io.io.output)) + io.io.wrerror = error("Failed to flush %s: %s", + path, strerror(errno)); + + if (hunk_no < 0) { if (output) unlink_or_warn(output); return error("Could not parse conflict hunks in %s", path); } - if (wrerror) + if (io.io.wrerror) return -1; return hunk_no; } +struct rerere_io_mem { + struct rerere_io io; + struct strbuf input; +}; + +static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_) +{ + struct rerere_io_mem *io = (struct rerere_io_mem *)io_; + char *ep; + size_t len; + + strbuf_release(sb); + if (!io->input.len) + return -1; + ep = strchrnul(io->input.buf, '\n'); + if (*ep == '\n') + ep++; + len = ep - io->input.buf; + strbuf_add(sb, io->input.buf, len); + strbuf_remove(&io->input, 0, len); + return 0; +} + +static int handle_cache(const char *path, unsigned char *sha1, const char *output) +{ + mmfile_t mmfile[3]; + mmbuffer_t result = {NULL, 0}; + struct cache_entry *ce; + int pos, len, i, hunk_no; + struct rerere_io_mem io; + int marker_size = ll_merge_marker_size(path); + + /* + * Reproduce the conflicted merge in-core + */ + len = strlen(path); + pos = cache_name_pos(path, len); + if (0 <= pos) + return -1; + pos = -pos - 1; + + for (i = 0; i < 3; i++) { + enum object_type type; + unsigned long size; + + mmfile[i].size = 0; + mmfile[i].ptr = NULL; + if (active_nr <= pos) + break; + ce = active_cache[pos++]; + if (ce_namelen(ce) != len || memcmp(ce->name, path, len) + || ce_stage(ce) != i + 1) + break; + mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size); + mmfile[i].size = size; + } + for (i = 0; i < 3; i++) { + if (!mmfile[i].ptr && !mmfile[i].size) + mmfile[i].ptr = xstrdup(""); + } + ll_merge(&result, path, &mmfile[0], + &mmfile[1], "ours", + &mmfile[2], "theirs", 0); + for (i = 0; i < 3; i++) + free(mmfile[i].ptr); + + memset(&io, 0, sizeof(io)); + io.io.getline = rerere_mem_getline; + if (output) + io.io.output = fopen(output, "w"); + else + io.io.output = NULL; + strbuf_init(&io.input, 0); + strbuf_attach(&io.input, result.ptr, result.size, result.size); + + hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size); + strbuf_release(&io.input); + if (io.io.output) + fclose(io.io.output); + return hunk_no; +} + static int find_conflict(struct string_list *conflict) { int i; @@ -204,19 +364,19 @@ static int find_conflict(struct string_list *conflict) static int merge(const char *name, const char *path) { int ret; - mmfile_t cur, base, other; + mmfile_t cur = {NULL, 0}, base = {NULL, 0}, other = {NULL, 0}; mmbuffer_t result = {NULL, 0}; - xpparam_t xpp = {XDF_NEED_MINIMAL}; if (handle_file(path, NULL, rerere_path(name, "thisimage")) < 0) return 1; if (read_mmfile(&cur, rerere_path(name, "thisimage")) || read_mmfile(&base, rerere_path(name, "preimage")) || - read_mmfile(&other, rerere_path(name, "postimage"))) - return 1; - ret = xdl_merge(&base, &cur, "", &other, "", - &xpp, XDL_MERGE_ZEALOUS, &result); + read_mmfile(&other, rerere_path(name, "postimage"))) { + ret = 1; + goto out; + } + ret = ll_merge(&result, path, &base, &cur, "", &other, "", 0); if (!ret) { FILE *f = fopen(path, "w"); if (!f) @@ -229,6 +389,7 @@ static int merge(const char *name, const char *path) strerror(errno)); } +out: free(cur.ptr); free(base.ptr); free(other.ptr); @@ -367,7 +528,7 @@ static int is_rerere_enabled(void) return 1; } -int setup_rerere(struct string_list *merge_rr) +int setup_rerere(struct string_list *merge_rr, int flags) { int fd; @@ -375,6 +536,8 @@ int setup_rerere(struct string_list *merge_rr) if (!is_rerere_enabled()) return -1; + if (flags & (RERERE_AUTOUPDATE|RERERE_NOAUTOUPDATE)) + rerere_autoupdate = !!(flags & RERERE_AUTOUPDATE); merge_rr_path = git_pathdup("MERGE_RR"); fd = hold_lock_file_for_update(&write_lock, merge_rr_path, LOCK_DIE_ON_ERROR); @@ -382,13 +545,62 @@ int setup_rerere(struct string_list *merge_rr) return fd; } -int rerere(void) +int rerere(int flags) { struct string_list merge_rr = { NULL, 0, 0, 1 }; int fd; - fd = setup_rerere(&merge_rr); + fd = setup_rerere(&merge_rr, flags); if (fd < 0) return 0; return do_plain_rerere(&merge_rr, fd); } + +static int rerere_forget_one_path(const char *path, struct string_list *rr) +{ + const char *filename; + char *hex; + unsigned char sha1[20]; + int ret; + + ret = handle_cache(path, sha1, NULL); + if (ret < 1) + return error("Could not parse conflict hunks in '%s'", path); + hex = xstrdup(sha1_to_hex(sha1)); + filename = rerere_path(hex, "postimage"); + if (unlink(filename)) + return (errno == ENOENT + ? error("no remembered resolution for %s", path) + : error("cannot unlink %s: %s", filename, strerror(errno))); + + handle_cache(path, sha1, rerere_path(hex, "preimage")); + fprintf(stderr, "Updated preimage for '%s'\n", path); + + + string_list_insert(path, rr)->util = hex; + fprintf(stderr, "Forgot resolution for %s\n", path); + return 0; +} + +int rerere_forget(const char **pathspec) +{ + int i, fd; + struct string_list conflict = { NULL, 0, 0, 1 }; + struct string_list merge_rr = { NULL, 0, 0, 1 }; + + if (read_cache() < 0) + return error("Could not read index"); + + fd = setup_rerere(&merge_rr, RERERE_NOAUTOUPDATE); + + unmerge_cache(pathspec); + find_conflict(&conflict); + for (i = 0; i < conflict.nr; i++) { + struct string_list_item *it = &conflict.items[i]; + if (!match_pathspec(pathspec, it->string, strlen(it->string), + 0, NULL)) + continue; + rerere_forget_one_path(it->string, &merge_rr); + } + return write_rr(&merge_rr, fd); +} @@ -3,9 +3,16 @@ #include "string-list.h" -extern int setup_rerere(struct string_list *); -extern int rerere(void); +#define RERERE_AUTOUPDATE 01 +#define RERERE_NOAUTOUPDATE 02 + +extern int setup_rerere(struct string_list *, int); +extern int rerere(int); extern const char *rerere_path(const char *hex, const char *file); extern int has_rerere_resolution(const char *hex); +extern int rerere_forget(const char **); + +#define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \ + "update the index with reused conflict resolution if possible") #endif diff --git a/resolve-undo.c b/resolve-undo.c new file mode 100644 index 0000000000..0f50ee0484 --- /dev/null +++ b/resolve-undo.c @@ -0,0 +1,176 @@ +#include "cache.h" +#include "dir.h" +#include "resolve-undo.h" +#include "string-list.h" + +/* The only error case is to run out of memory in string-list */ +void record_resolve_undo(struct index_state *istate, struct cache_entry *ce) +{ + struct string_list_item *lost; + struct resolve_undo_info *ui; + struct string_list *resolve_undo; + int stage = ce_stage(ce); + + if (!stage) + return; + + if (!istate->resolve_undo) { + resolve_undo = xcalloc(1, sizeof(*resolve_undo)); + resolve_undo->strdup_strings = 1; + istate->resolve_undo = resolve_undo; + } + resolve_undo = istate->resolve_undo; + lost = string_list_insert(ce->name, resolve_undo); + if (!lost->util) + lost->util = xcalloc(1, sizeof(*ui)); + ui = lost->util; + hashcpy(ui->sha1[stage - 1], ce->sha1); + ui->mode[stage - 1] = ce->ce_mode; +} + +static int write_one(struct string_list_item *item, void *cbdata) +{ + struct strbuf *sb = cbdata; + struct resolve_undo_info *ui = item->util; + int i; + + if (!ui) + return 0; + strbuf_addstr(sb, item->string); + strbuf_addch(sb, 0); + for (i = 0; i < 3; i++) + strbuf_addf(sb, "%o%c", ui->mode[i], 0); + for (i = 0; i < 3; i++) { + if (!ui->mode[i]) + continue; + strbuf_add(sb, ui->sha1[i], 20); + } + return 0; +} + +void resolve_undo_write(struct strbuf *sb, struct string_list *resolve_undo) +{ + for_each_string_list(write_one, resolve_undo, sb); +} + +struct string_list *resolve_undo_read(const char *data, unsigned long size) +{ + struct string_list *resolve_undo; + size_t len; + char *endptr; + int i; + + resolve_undo = xcalloc(1, sizeof(*resolve_undo)); + resolve_undo->strdup_strings = 1; + + while (size) { + struct string_list_item *lost; + struct resolve_undo_info *ui; + + len = strlen(data) + 1; + if (size <= len) + goto error; + lost = string_list_insert(data, resolve_undo); + if (!lost->util) + lost->util = xcalloc(1, sizeof(*ui)); + ui = lost->util; + size -= len; + data += len; + + for (i = 0; i < 3; i++) { + ui->mode[i] = strtoul(data, &endptr, 8); + if (!endptr || endptr == data || *endptr) + goto error; + len = (endptr + 1) - (char*)data; + if (size <= len) + goto error; + size -= len; + data += len; + } + + for (i = 0; i < 3; i++) { + if (!ui->mode[i]) + continue; + if (size < 20) + goto error; + hashcpy(ui->sha1[i], (const unsigned char *)data); + size -= 20; + data += 20; + } + } + return resolve_undo; + +error: + string_list_clear(resolve_undo, 1); + error("Index records invalid resolve-undo information"); + return NULL; +} + +void resolve_undo_clear_index(struct index_state *istate) +{ + struct string_list *resolve_undo = istate->resolve_undo; + if (!resolve_undo) + return; + string_list_clear(resolve_undo, 1); + free(resolve_undo); + istate->resolve_undo = NULL; + istate->cache_changed = 1; +} + +int unmerge_index_entry_at(struct index_state *istate, int pos) +{ + struct cache_entry *ce; + struct string_list_item *item; + struct resolve_undo_info *ru; + int i, err = 0; + + if (!istate->resolve_undo) + return pos; + + ce = istate->cache[pos]; + if (ce_stage(ce)) { + /* already unmerged */ + while ((pos < istate->cache_nr) && + ! strcmp(istate->cache[pos]->name, ce->name)) + pos++; + return pos - 1; /* return the last entry processed */ + } + item = string_list_lookup(ce->name, istate->resolve_undo); + if (!item) + return pos; + ru = item->util; + if (!ru) + return pos; + remove_index_entry_at(istate, pos); + for (i = 0; i < 3; i++) { + struct cache_entry *nce; + if (!ru->mode[i]) + continue; + nce = make_cache_entry(ru->mode[i], ru->sha1[i], + ce->name, i + 1, 0); + if (add_index_entry(istate, nce, ADD_CACHE_OK_TO_ADD)) { + err = 1; + error("cannot unmerge '%s'", ce->name); + } + } + if (err) + return pos; + free(ru); + item->util = NULL; + return unmerge_index_entry_at(istate, pos); +} + +void unmerge_index(struct index_state *istate, const char **pathspec) +{ + int i; + + if (!istate->resolve_undo) + return; + + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; + if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) + continue; + i = unmerge_index_entry_at(istate, i); + } +} diff --git a/resolve-undo.h b/resolve-undo.h new file mode 100644 index 0000000000..845876911d --- /dev/null +++ b/resolve-undo.h @@ -0,0 +1,16 @@ +#ifndef RESOLVE_UNDO_H +#define RESOLVE_UNDO_H + +struct resolve_undo_info { + unsigned int mode[3]; + unsigned char sha1[3][20]; +}; + +extern void record_resolve_undo(struct index_state *, struct cache_entry *); +extern void resolve_undo_write(struct strbuf *, struct string_list *); +extern struct string_list *resolve_undo_read(const char *, unsigned long); +extern void resolve_undo_clear_index(struct index_state *); +extern int unmerge_index_entry_at(struct index_state *, int); +extern void unmerge_index(struct index_state *, const char **); + +#endif diff --git a/revision.c b/revision.c index 9fc4e8d381..3ba6d991f6 100644 --- a/revision.c +++ b/revision.c @@ -134,10 +134,20 @@ static void add_pending_object_with_mode(struct rev_info *revs, struct object *o { if (revs->no_walk && (obj->flags & UNINTERESTING)) revs->no_walk = 0; - if (revs->reflog_info && obj->type == OBJ_COMMIT && - add_reflog_for_walk(revs->reflog_info, - (struct commit *)obj, name)) - return; + if (revs->reflog_info && obj->type == OBJ_COMMIT) { + struct strbuf buf = STRBUF_INIT; + int len = interpret_branch_name(name, &buf); + int st; + + if (0 < len && name[len] && buf.len) + strbuf_addstr(&buf, name + len); + st = add_reflog_for_walk(revs->reflog_info, + (struct commit *)obj, + buf.buf[0] ? buf.buf: name); + strbuf_release(&buf); + if (st) + return; + } add_object_array_with_mode(obj, name, &revs->pending, mode); } @@ -268,7 +278,7 @@ static int tree_difference = REV_TREE_SAME; static void file_add_remove(struct diff_options *options, int addremove, unsigned mode, const unsigned char *sha1, - const char *fullpath) + const char *fullpath, unsigned dirty_submodule) { int diff = addremove == '+' ? REV_TREE_NEW : REV_TREE_OLD; @@ -281,7 +291,8 @@ static void file_change(struct diff_options *options, unsigned old_mode, unsigned new_mode, const unsigned char *old_sha1, const unsigned char *new_sha1, - const char *fullpath) + const char *fullpath, + unsigned old_dirty_submodule, unsigned new_dirty_submodule) { tree_difference = REV_TREE_DIFFERENT; DIFF_OPT_SET(options, HAS_CHANGES); @@ -699,12 +710,18 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, return 0; } +static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs, + unsigned flags) +{ + cb->all_revs = revs; + cb->all_flags = flags; +} + static void handle_refs(struct rev_info *revs, unsigned flags, int (*for_each)(each_ref_fn, void *)) { struct all_refs_cb cb; - cb.all_revs = revs; - cb.all_flags = flags; + init_all_refs_cb(&cb, revs, flags); for_each(handle_one_ref, &cb); } @@ -791,7 +808,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 +970,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; + + /* 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; +} - while (fgets(line, sizeof(line), stdin) != NULL) { - int len = strlen(line); - if (len && line[len - 1] == '\n') - line[--len] = '\0'; +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 +1049,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; @@ -1122,13 +1178,22 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->verbose_header = 1; } else if (!strcmp(arg, "--pretty")) { revs->verbose_header = 1; + revs->pretty_given = 1; get_commit_format(arg+8, revs); } else if (!prefixcmp(arg, "--pretty=") || !prefixcmp(arg, "--format=")) { revs->verbose_header = 1; + revs->pretty_given = 1; get_commit_format(arg+9, revs); + } else if (!strcmp(arg, "--show-notes")) { + revs->show_notes = 1; + revs->show_notes_given = 1; + } else if (!strcmp(arg, "--no-notes")) { + revs->show_notes = 0; + revs->show_notes_given = 1; } else if (!strcmp(arg, "--oneline")) { revs->verbose_header = 1; get_commit_format("oneline", revs); + revs->pretty_given = 1; revs->abbrev_commit = 1; } else if (!strcmp(arg, "--graph")) { revs->topo_order = 1; @@ -1218,6 +1283,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 +1330,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 +1342,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 +1364,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; @@ -1267,6 +1378,30 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch handle_refs(revs, flags, for_each_remote_ref); continue; } + if (!prefixcmp(arg, "--glob=")) { + struct all_refs_cb cb; + init_all_refs_cb(&cb, revs, flags); + for_each_glob_ref(handle_one_ref, arg + 7, &cb); + continue; + } + if (!prefixcmp(arg, "--branches=")) { + struct all_refs_cb cb; + init_all_refs_cb(&cb, revs, flags); + for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb); + continue; + } + if (!prefixcmp(arg, "--tags=")) { + struct all_refs_cb cb; + init_all_refs_cb(&cb, revs, flags); + for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb); + continue; + } + if (!prefixcmp(arg, "--remotes=")) { + struct all_refs_cb cb; + init_all_refs_cb(&cb, revs, flags); + for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb); + continue; + } if (!strcmp(arg, "--reflog")) { handle_reflog(revs, flags); continue; @@ -1283,6 +1418,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 +1453,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..a14deefc25 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 */ @@ -79,10 +80,15 @@ struct rev_info { /* Format info */ unsigned int shown_one:1, show_merge:1, + show_notes:1, + show_notes_given:1, + pretty_given:1, abbrev_commit:1, 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 +134,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/run-command.c b/run-command.c index cf2d8f7fae..0cd7f02ffe 100644 --- a/run-command.c +++ b/run-command.c @@ -8,12 +8,130 @@ static inline void close_pair(int fd[2]) close(fd[1]); } +#ifndef WIN32 static inline void dup_devnull(int to) { int fd = open("/dev/null", O_RDWR); dup2(fd, to); close(fd); } +#endif + +static const char **prepare_shell_cmd(const char **argv) +{ + int argc, nargc = 0; + const char **nargv; + + for (argc = 0; argv[argc]; argc++) + ; /* just counting */ + /* +1 for NULL, +3 for "sh -c" plus extra $0 */ + nargv = xmalloc(sizeof(*nargv) * (argc + 1 + 3)); + + if (argc < 1) + die("BUG: shell command is empty"); + + if (strcspn(argv[0], "|&;<>()$`\\\"' \t\n*?[#~=%") != strlen(argv[0])) { + nargv[nargc++] = "sh"; + nargv[nargc++] = "-c"; + + if (argc < 2) + nargv[nargc++] = argv[0]; + else { + struct strbuf arg0 = STRBUF_INIT; + strbuf_addf(&arg0, "%s \"$@\"", argv[0]); + nargv[nargc++] = strbuf_detach(&arg0, NULL); + } + } + + for (argc = 0; argv[argc]; argc++) + nargv[nargc++] = argv[argc]; + nargv[nargc] = NULL; + + return nargv; +} + +#ifndef WIN32 +static int execv_shell_cmd(const char **argv) +{ + const char **nargv = prepare_shell_cmd(argv); + trace_argv_printf(nargv, "trace: exec:"); + execvp(nargv[0], (char **)nargv); + free(nargv); + return -1; +} +#endif + +#ifndef WIN32 +static int child_err = 2; +static int child_notifier = -1; + +static void notify_parent(void) +{ + write(child_notifier, "", 1); +} + +static NORETURN void die_child(const char *err, va_list params) +{ + char msg[4096]; + int len = vsnprintf(msg, sizeof(msg), err, params); + if (len > sizeof(msg)) + len = sizeof(msg); + + write(child_err, "fatal: ", 7); + write(child_err, msg, len); + write(child_err, "\n", 1); + exit(128); +} + +static inline void set_cloexec(int fd) +{ + int flags = fcntl(fd, F_GETFD); + if (flags >= 0) + fcntl(fd, F_SETFD, flags | FD_CLOEXEC); +} +#endif + +static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) +{ + int status, code = -1; + pid_t waiting; + int failed_errno = 0; + + while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) + ; /* nothing */ + + if (waiting < 0) { + failed_errno = errno; + error("waitpid for %s failed: %s", argv0, strerror(errno)); + } else if (waiting != pid) { + error("waitpid is confused (%s)", argv0); + } else if (WIFSIGNALED(status)) { + code = WTERMSIG(status); + error("%s died of signal %d", argv0, code); + /* + * This return value is chosen so that code & 0xff + * mimics the exit code that a POSIX shell would report for + * a program that died from this signal. + */ + code -= 128; + } else if (WIFEXITED(status)) { + code = WEXITSTATUS(status); + /* + * Convert special exit code when execvp failed. + */ + if (code == 127) { + code = -1; + failed_errno = ENOENT; + if (!silent_exec_failure) + error("cannot run %s: %s", argv0, + strerror(ENOENT)); + } + } else { + error("waitpid is confused (%s)", argv0); + } + errno = failed_errno; + return code; +} int start_command(struct child_process *cmd) { @@ -76,9 +194,30 @@ fail_pipe: trace_argv_printf(cmd->argv, "trace: run_command:"); #ifndef WIN32 +{ + int notify_pipe[2]; + if (pipe(notify_pipe)) + notify_pipe[0] = notify_pipe[1] = -1; + fflush(NULL); cmd->pid = fork(); if (!cmd->pid) { + /* + * Redirect the channel to write syscall error messages to + * before redirecting the process's stderr so that all die() + * in subsequent call paths use the parent's stderr. + */ + if (cmd->no_stderr || need_err) { + child_err = dup(2); + set_cloexec(child_err); + } + set_die_routine(die_child); + + close(notify_pipe[0]); + set_cloexec(notify_pipe[1]); + child_notifier = notify_pipe[1]; + atexit(notify_parent); + if (cmd->no_stdin) dup_devnull(0); else if (need_in) { @@ -94,6 +233,9 @@ fail_pipe: else if (need_err) { dup2(fderr[1], 2); close_pair(fderr); + } else if (cmd->err > 1) { + dup2(cmd->err, 2); + close(cmd->err); } if (cmd->no_stdout) @@ -119,58 +261,84 @@ fail_pipe: unsetenv(*cmd->env); } } - if (cmd->preexec_cb) + if (cmd->preexec_cb) { + /* + * We cannot predict what the pre-exec callback does. + * Forgo parent notification. + */ + close(child_notifier); + child_notifier = -1; + cmd->preexec_cb(); + } if (cmd->git_cmd) { execv_git_cmd(cmd->argv); + } else if (cmd->use_shell) { + execv_shell_cmd(cmd->argv); } else { execvp(cmd->argv[0], (char *const*) cmd->argv); } - trace_printf("trace: exec '%s' failed: %s\n", cmd->argv[0], - strerror(errno)); - exit(127); + /* + * Do not check for cmd->silent_exec_failure; the parent + * process will check it when it sees this exit code. + */ + if (errno == ENOENT) + exit(127); + else + die_errno("cannot exec '%s'", cmd->argv[0]); } if (cmd->pid < 0) error("cannot fork() for %s: %s", cmd->argv[0], strerror(failed_errno = errno)); + + /* + * Wait for child's execvp. If the execvp succeeds (or if fork() + * failed), EOF is seen immediately by the parent. Otherwise, the + * child process sends a single byte. + * Note that use of this infrastructure is completely advisory, + * therefore, we keep error checks minimal. + */ + close(notify_pipe[1]); + if (read(notify_pipe[0], ¬ify_pipe[1], 1) == 1) { + /* + * At this point we know that fork() succeeded, but execvp() + * failed. Errors have been reported to our stderr. + */ + wait_or_whine(cmd->pid, cmd->argv[0], + cmd->silent_exec_failure); + failed_errno = errno; + cmd->pid = -1; + } + close(notify_pipe[0]); +} #else { - int s0 = -1, s1 = -1, s2 = -1; /* backups of stdin, stdout, stderr */ + int fhin = 0, fhout = 1, fherr = 2; const char **sargv = cmd->argv; char **env = environ; - if (cmd->no_stdin) { - s0 = dup(0); - dup_devnull(0); - } else if (need_in) { - s0 = dup(0); - dup2(fdin[0], 0); - } else if (cmd->in) { - s0 = dup(0); - dup2(cmd->in, 0); - } - - if (cmd->no_stderr) { - s2 = dup(2); - dup_devnull(2); - } else if (need_err) { - s2 = dup(2); - dup2(fderr[1], 2); - } - - if (cmd->no_stdout) { - s1 = dup(1); - dup_devnull(1); - } else if (cmd->stdout_to_stderr) { - s1 = dup(1); - dup2(2, 1); - } else if (need_out) { - s1 = dup(1); - dup2(fdout[1], 1); - } else if (cmd->out > 1) { - s1 = dup(1); - dup2(cmd->out, 1); - } + if (cmd->no_stdin) + fhin = open("/dev/null", O_RDWR); + else if (need_in) + fhin = dup(fdin[0]); + else if (cmd->in) + fhin = dup(cmd->in); + + if (cmd->no_stderr) + fherr = open("/dev/null", O_RDWR); + else if (need_err) + fherr = dup(fderr[1]); + else if (cmd->err > 2) + fherr = dup(cmd->err); + + if (cmd->no_stdout) + fhout = open("/dev/null", O_RDWR); + else if (cmd->stdout_to_stderr) + fhout = dup(fherr); + else if (need_out) + fhout = dup(fdout[1]); + else if (cmd->out > 1) + fhout = dup(cmd->out); if (cmd->dir) die("chdir in start_command() not implemented"); @@ -179,9 +347,12 @@ fail_pipe: if (cmd->git_cmd) { cmd->argv = prepare_git_cmd(cmd->argv); + } else if (cmd->use_shell) { + cmd->argv = prepare_shell_cmd(cmd->argv); } - cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env); + cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env, + fhin, fhout, fherr); failed_errno = errno; if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT)) error("cannot spawn %s: %s", cmd->argv[0], strerror(errno)); @@ -192,12 +363,12 @@ fail_pipe: free(cmd->argv); cmd->argv = sargv; - if (s0 >= 0) - dup2(s0, 0), close(s0); - if (s1 >= 0) - dup2(s1, 1), close(s1); - if (s2 >= 0) - dup2(s2, 2), close(s2); + if (fhin != 0) + close(fhin); + if (fhout != 1) + close(fhout); + if (fherr != 2) + close(fherr); } #endif @@ -228,52 +399,12 @@ fail_pipe: if (need_err) close(fderr[1]); + else if (cmd->err) + close(cmd->err); return 0; } -static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) -{ - int status, code = -1; - pid_t waiting; - int failed_errno = 0; - - while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) - ; /* nothing */ - - if (waiting < 0) { - failed_errno = errno; - error("waitpid for %s failed: %s", argv0, strerror(errno)); - } else if (waiting != pid) { - error("waitpid is confused (%s)", argv0); - } else if (WIFSIGNALED(status)) { - code = WTERMSIG(status); - error("%s died of signal %d", argv0, code); - /* - * This return value is chosen so that code & 0xff - * mimics the exit code that a POSIX shell would report for - * a program that died from this signal. - */ - code -= 128; - } else if (WIFEXITED(status)) { - code = WEXITSTATUS(status); - /* - * Convert special exit code when execvp failed. - */ - if (code == 127) { - code = -1; - failed_errno = ENOENT; - if (!silent_exec_failure) - error("cannot run %s: %s", argv0, - strerror(ENOENT)); - } - } else { - error("waitpid is confused (%s)", argv0); - } - errno = failed_errno; - return code; -} - int finish_command(struct child_process *cmd) { return wait_or_whine(cmd->pid, cmd->argv[0], cmd->silent_exec_failure); @@ -297,6 +428,7 @@ static void prepare_run_command_v_opt(struct child_process *cmd, cmd->git_cmd = opt & RUN_GIT_CMD ? 1 : 0; cmd->stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0; cmd->silent_exec_failure = opt & RUN_SILENT_EXEC_FAILURE ? 1 : 0; + cmd->use_shell = opt & RUN_USING_SHELL ? 1 : 0; } int run_command_v_opt(const char **argv, int opt) @@ -319,17 +451,51 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const static unsigned __stdcall run_thread(void *data) { struct async *async = data; - return async->proc(async->fd_for_proc, async->data); + return async->proc(async->proc_in, async->proc_out, async->data); } #endif int start_async(struct async *async) { - int pipe_out[2]; + int need_in, need_out; + int fdin[2], fdout[2]; + int proc_in, proc_out; - if (pipe(pipe_out) < 0) - return error("cannot create pipe: %s", strerror(errno)); - async->out = pipe_out[0]; + need_in = async->in < 0; + if (need_in) { + if (pipe(fdin) < 0) { + if (async->out > 0) + close(async->out); + return error("cannot create pipe: %s", strerror(errno)); + } + async->in = fdin[1]; + } + + need_out = async->out < 0; + if (need_out) { + if (pipe(fdout) < 0) { + if (need_in) + close_pair(fdin); + else if (async->in) + close(async->in); + return error("cannot create pipe: %s", strerror(errno)); + } + async->out = fdout[0]; + } + + if (need_in) + proc_in = fdin[0]; + else if (async->in) + proc_in = async->in; + else + proc_in = -1; + + if (need_out) + proc_out = fdout[1]; + else if (async->out) + proc_out = async->out; + else + proc_out = -1; #ifndef WIN32 /* Flush stdio before fork() to avoid cloning buffers */ @@ -338,24 +504,47 @@ int start_async(struct async *async) async->pid = fork(); if (async->pid < 0) { error("fork (async) failed: %s", strerror(errno)); - close_pair(pipe_out); - return -1; + goto error; } if (!async->pid) { - close(pipe_out[0]); - exit(!!async->proc(pipe_out[1], async->data)); + if (need_in) + close(fdin[1]); + if (need_out) + close(fdout[0]); + exit(!!async->proc(proc_in, proc_out, async->data)); } - close(pipe_out[1]); + + if (need_in) + close(fdin[0]); + else if (async->in) + close(async->in); + + if (need_out) + close(fdout[1]); + else if (async->out) + close(async->out); #else - async->fd_for_proc = pipe_out[1]; + async->proc_in = proc_in; + async->proc_out = proc_out; async->tid = (HANDLE) _beginthreadex(NULL, 0, run_thread, async, 0, NULL); if (!async->tid) { error("cannot create thread: %s", strerror(errno)); - close_pair(pipe_out); - return -1; + goto error; } #endif return 0; + +error: + if (need_in) + close_pair(fdin); + else if (async->in) + close(async->in); + + if (need_out) + close_pair(fdout); + else if (async->out) + close(async->out); + return -1; } int finish_async(struct async *async) diff --git a/run-command.h b/run-command.h index fb342090e3..94619f52d9 100644 --- a/run-command.h +++ b/run-command.h @@ -18,7 +18,7 @@ struct child_process { * - Specify > 0 to set a channel to a particular FD as follows: * .in: a readable FD, becomes child's stdin * .out: a writable FD, becomes child's stdout/stderr - * .err > 0 not supported + * .err: a writable FD, becomes child's stderr * The specified FD is closed by start_command(), even in case * of errors! */ @@ -33,6 +33,7 @@ struct child_process { unsigned git_cmd:1; /* if this is to be git sub-command */ unsigned silent_exec_failure:1; unsigned stdout_to_stderr:1; + unsigned use_shell:1; void (*preexec_cb)(void); }; @@ -46,6 +47,7 @@ extern int run_hook(const char *index_file, const char *name, ...); #define RUN_GIT_CMD 2 /*If this is to be git sub-command */ #define RUN_COMMAND_STDOUT_TO_STDERR 4 #define RUN_SILENT_EXEC_FAILURE 8 +#define RUN_USING_SHELL 16 int run_command_v_opt(const char **argv, int opt); /* @@ -64,17 +66,20 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const */ struct async { /* - * proc writes to fd and closes it; + * proc reads from in; closes it before return + * proc writes to out; closes it before return * returns 0 on success, non-zero on failure */ - int (*proc)(int fd, void *data); + int (*proc)(int in, int out, void *data); void *data; + int in; /* caller writes here and closes it */ int out; /* caller reads from here and closes it */ #ifndef WIN32 pid_t pid; #else HANDLE tid; - int fd_for_proc; + int proc_in; + int proc_out; #endif }; 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, @@ -64,6 +64,31 @@ 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); +} + +static void NORETURN die_verify_filename(const char *prefix, const char *arg) +{ + unsigned char sha1[20]; + unsigned mode; + /* try a detailed diagnostic ... */ + get_sha1_with_mode_1(arg, sha1, &mode, 0, prefix); + /* ... or fall back the most general message. */ + die("ambiguous argument '%s': unknown revision or path not in the working tree.\n" + "Use '--' to separate paths from revisions", arg); + +} + /* * Verify a filename that we got as an argument for a pathspec * entry. Note that a filename that begins with "-" never verifies @@ -73,18 +98,11 @@ 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_verify_filename(prefix, arg); } /* @@ -94,19 +112,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) @@ -193,7 +206,7 @@ int is_inside_work_tree(void) } /* - * set_work_tree() is only ever called if you set GIT_DIR explicitely. + * set_work_tree() is only ever called if you set GIT_DIR explicitly. * The old behaviour (which we retain here) is to set the work tree root * to the cwd, unless overridden by the config, the command line, or * GIT_WORK_TREE. @@ -250,6 +263,8 @@ static int check_repository_format_gently(int *nongit_ok) const char *read_gitfile_gently(const char *path) { char *buf; + char *dir; + const char *slash; struct stat st; int fd; size_t len; @@ -274,9 +289,23 @@ const char *read_gitfile_gently(const char *path) if (len < 9) die("No path in gitfile: %s", path); buf[len] = '\0'; - if (!is_git_directory(buf + 8)) - die("Not a git repository: %s", buf + 8); - path = make_absolute_path(buf + 8); + dir = buf + 8; + + if (!is_absolute_path(dir) && (slash = strrchr(path, '/'))) { + size_t pathlen = slash+1 - path; + size_t dirlen = pathlen + len - 8; + dir = xmalloc(dirlen + 1); + strncpy(dir, path, pathlen); + strncpy(dir + pathlen, buf + 8, len - 8); + dir[dirlen] = '\0'; + free(buf); + buf = dir; + } + + if (!is_git_directory(dir)) + die("Not a git repository: %s", dir); + path = make_absolute_path(dir); + free(buf); return path; } @@ -375,9 +404,9 @@ const char *setup_git_directory_gently(int *nongit_ok) inside_work_tree = 0; if (offset != len) { cwd[offset] = '\0'; - setenv(GIT_DIR_ENVIRONMENT, cwd, 1); + set_git_dir(cwd); } else - setenv(GIT_DIR_ENVIRONMENT, ".", 1); + set_git_dir("."); check_repository_format_gently(nongit_ok); return NULL; } diff --git a/sha1_file.c b/sha1_file.c index 63981fb3fd..657825e14e 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -35,54 +35,6 @@ static size_t sz_fmt(size_t s) { return s; } const unsigned char null_sha1[20]; -const signed char hexval_table[256] = { - -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ - 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ - 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ - -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ - -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ - -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ - -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ - -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ - -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ - -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ -}; - -int get_sha1_hex(const char *hex, unsigned char *sha1) -{ - int i; - for (i = 0; i < 20; i++) { - unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]); - if (val & ~0xff) - return -1; - *sha1++ = val; - hex += 2; - } - return 0; -} - static inline int offset_1st_component(const char *path) { if (has_dos_drive_prefix(path)) @@ -133,24 +85,6 @@ int safe_create_leading_directories_const(const char *path) return result; } -char *sha1_to_hex(const unsigned char *sha1) -{ - static int bufno; - static char hexbuffer[4][50]; - static const char hex[] = "0123456789abcdef"; - char *buffer = hexbuffer[3 & ++bufno], *buf = buffer; - int i; - - for (i = 0; i < 20; i++) { - unsigned int val = *sha1++; - *buf++ = hex[val >> 4]; - *buf++ = hex[val & 0xf]; - } - *buf = '\0'; - - return buffer; -} - static void fill_sha1_path(char *pathbuf, const unsigned char *sha1) { int i; @@ -1232,7 +1166,7 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size, const unsigned char *sha1) { int bytes = strlen(buffer) + 1; - unsigned char *buf = xmalloc(1+size); + unsigned char *buf = xmallocz(size); unsigned long n; int status = Z_OK; @@ -1260,7 +1194,6 @@ static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size while (status == Z_OK) status = git_inflate(stream, Z_FINISH); } - buf[size] = 0; if (status == Z_STREAM_END && !stream->avail_in) { git_inflate_end(stream); return buf; @@ -1583,8 +1516,7 @@ static void *unpack_compressed_entry(struct packed_git *p, z_stream stream; unsigned char *buffer, *in; - buffer = xmalloc(size + 1); - buffer[size] = 0; + buffer = xmallocz(size); memset(&stream, 0, sizeof(stream)); stream.next_out = buffer; stream.avail_out = size + 1; @@ -2458,14 +2390,6 @@ int has_pack_index(const unsigned char *sha1) return 1; } -int has_pack_file(const unsigned char *sha1) -{ - struct stat st; - if (stat(sha1_pack_name(sha1), &st)) - return 0; - return 1; -} - int has_sha1_pack(const unsigned char *sha1) { struct pack_entry e; diff --git a/sha1_name.c b/sha1_name.c index 9677afdadb..bf92417838 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -5,6 +5,7 @@ #include "blob.h" #include "tree-walk.h" #include "refs.h" +#include "remote.h" static int find_short_object_filename(int len, const char *name, unsigned char *sha1) { @@ -240,7 +241,8 @@ static int ambiguous_path(const char *path, int len) /* * *string and *len will only be substituted, and *string returned (for - * later free()ing) if the string passed in is of the form @{-<n>}. + * later free()ing) if the string passed in is a magic short-hand form + * to name a branch. */ static char *substitute_branch_name(const char **string, int *len) { @@ -322,6 +324,20 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) return logs_found; } +static inline int upstream_mark(const char *string, int len) +{ + const char *suffix[] = { "@{upstream}", "@{u}" }; + int i; + + for (i = 0; i < ARRAY_SIZE(suffix); i++) { + int suffix_len = strlen(suffix[i]); + if (suffix_len <= len + && !memcmp(string, suffix[i], suffix_len)) + return suffix_len; + } + return 0; +} + static int get_sha1_1(const char *name, int len, unsigned char *sha1); static int get_sha1_basic(const char *str, int len, unsigned char *sha1) @@ -339,8 +355,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) if (len && str[len-1] == '}') { for (at = len-2; at >= 0; at--) { if (str[at] == '@' && str[at+1] == '{') { - reflog_len = (len-1) - (at+2); - len = at; + if (!upstream_mark(str + at, len - at)) { + reflog_len = (len-1) - (at+2); + len = at; + } break; } } @@ -380,6 +398,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) unsigned long co_time; int co_tz, co_cnt; + /* a @{-N} placed anywhere except the start is an error */ + if (str[at+2] == '-') + return -1; + /* Is it asking for N-th entry, or approxidate? */ for (i = nth = 0; 0 <= nth && i < reflog_len; i++) { char ch = str[at+2+i]; @@ -394,9 +416,12 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) } else if (0 <= nth) at_time = 0; else { + int errors = 0; char *tmp = xstrndup(str + at + 2, reflog_len); - at_time = approxidate(tmp); + at_time = approxidate_careful(tmp, &errors); free(tmp); + if (errors) + return -1; } if (read_ref_at(real_ref, at_time, nth, sha1, NULL, &co_time, &co_tz, &co_cnt)) { @@ -739,17 +764,10 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, } /* - * This reads "@{-N}" syntax, finds the name of the Nth previous - * branch we were on, and places the name of the branch in the given - * buf and returns the number of characters parsed if successful. - * - * If the input is not of the accepted format, it returns a negative - * number to signal an error. - * - * If the input was ok but there are not N branch switches in the - * reflog, it returns 0. + * Parse @{-N} syntax, return the number of characters parsed + * if successful; otherwise signal an error with negative value. */ -int interpret_branch_name(const char *name, struct strbuf *buf) +static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf) { long nth; int i, retval; @@ -793,6 +811,122 @@ release_return: return retval; } +int get_sha1_mb(const char *name, unsigned char *sha1) +{ + struct commit *one, *two; + struct commit_list *mbs; + unsigned char sha1_tmp[20]; + const char *dots; + int st; + + dots = strstr(name, "..."); + if (!dots) + return get_sha1(name, sha1); + if (dots == name) + st = get_sha1("HEAD", sha1_tmp); + else { + struct strbuf sb; + strbuf_init(&sb, dots - name); + strbuf_add(&sb, name, dots - name); + st = get_sha1(sb.buf, sha1_tmp); + strbuf_release(&sb); + } + if (st) + return st; + one = lookup_commit_reference_gently(sha1_tmp, 0); + if (!one) + return -1; + + if (get_sha1(dots[3] ? (dots + 3) : "HEAD", sha1_tmp)) + return -1; + two = lookup_commit_reference_gently(sha1_tmp, 0); + if (!two) + return -1; + mbs = get_merge_bases(one, two, 1); + if (!mbs || mbs->next) + st = -1; + else { + st = 0; + hashcpy(sha1, mbs->item->object.sha1); + } + free_commit_list(mbs); + return st; +} + +/* + * This reads short-hand syntax that not only evaluates to a commit + * object name, but also can act as if the end user spelled the name + * of the branch from the command line. + * + * - "@{-N}" finds the name of the Nth previous branch we were on, and + * places the name of the branch in the given buf and returns the + * number of characters parsed if successful. + * + * - "<branch>@{upstream}" finds the name of the other ref that + * <branch> is configured to merge with (missing <branch> defaults + * to the current branch), and places the name of the branch in the + * given buf and returns the number of characters parsed if + * successful. + * + * If the input is not of the accepted format, it returns a negative + * number to signal an error. + * + * If the input was ok but there are not N branch switches in the + * reflog, it returns 0. + */ +int interpret_branch_name(const char *name, struct strbuf *buf) +{ + char *cp; + struct branch *upstream; + int namelen = strlen(name); + int len = interpret_nth_prior_checkout(name, buf); + int tmp_len; + + if (!len) + return len; /* syntax Ok, not enough switches */ + if (0 < len && len == namelen) + return len; /* consumed all */ + else if (0 < len) { + /* we have extra data, which might need further processing */ + struct strbuf tmp = STRBUF_INIT; + int used = buf->len; + int ret; + + strbuf_add(buf, name + len, namelen - len); + ret = interpret_branch_name(buf->buf, &tmp); + /* that data was not interpreted, remove our cruft */ + if (ret < 0) { + strbuf_setlen(buf, used); + return len; + } + strbuf_reset(buf); + strbuf_addbuf(buf, &tmp); + strbuf_release(&tmp); + /* tweak for size of {-N} versus expanded ref name */ + return ret - used + len; + } + + cp = strchr(name, '@'); + if (!cp) + return -1; + tmp_len = upstream_mark(cp, namelen - (cp - name)); + if (!tmp_len) + return -1; + len = cp + tmp_len - name; + cp = xstrndup(name, cp - name); + upstream = branch_get(*cp ? cp : NULL); + if (!upstream + || !upstream->merge + || !upstream->merge[0]->dst) + return error("No upstream branch found for '%s'", cp); + free(cp); + cp = shorten_unambiguous_ref(upstream->merge[0]->dst, 0); + strbuf_reset(buf); + strbuf_addstr(buf, cp); + free(cp); + return len; +} + /* * This is like "get_sha1_basic()", except it allows "sha1 expressions", * notably "xyz^" for "parent of xyz" @@ -803,7 +937,100 @@ int get_sha1(const char *name, unsigned char *sha1) return get_sha1_with_mode(name, sha1, &unused); } -int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode) +/* Must be called only when object_name:filename doesn't exist. */ +static void diagnose_invalid_sha1_path(const char *prefix, + const char *filename, + const unsigned char *tree_sha1, + const char *object_name) +{ + struct stat st; + unsigned char sha1[20]; + unsigned mode; + + if (!prefix) + prefix = ""; + + if (!lstat(filename, &st)) + die("Path '%s' exists on disk, but not in '%s'.", + filename, object_name); + if (errno == ENOENT || errno == ENOTDIR) { + char *fullname = xmalloc(strlen(filename) + + strlen(prefix) + 1); + strcpy(fullname, prefix); + strcat(fullname, filename); + + if (!get_tree_entry(tree_sha1, fullname, + sha1, &mode)) { + die("Path '%s' exists, but not '%s'.\n" + "Did you mean '%s:%s'?", + fullname, + filename, + object_name, + fullname); + } + die("Path '%s' does not exist in '%s'", + filename, object_name); + } +} + +/* Must be called only when :stage:filename doesn't exist. */ +static void diagnose_invalid_index_path(int stage, + const char *prefix, + const char *filename) +{ + struct stat st; + struct cache_entry *ce; + int pos; + unsigned namelen = strlen(filename); + unsigned fullnamelen; + char *fullname; + + if (!prefix) + prefix = ""; + + /* Wrong stage number? */ + pos = cache_name_pos(filename, namelen); + if (pos < 0) + pos = -pos - 1; + if (pos < active_nr) { + ce = active_cache[pos]; + if (ce_namelen(ce) == namelen && + !memcmp(ce->name, filename, namelen)) + die("Path '%s' is in the index, but not at stage %d.\n" + "Did you mean ':%d:%s'?", + filename, stage, + ce_stage(ce), filename); + } + + /* Confusion between relative and absolute filenames? */ + fullnamelen = namelen + strlen(prefix); + fullname = xmalloc(fullnamelen + 1); + strcpy(fullname, prefix); + strcat(fullname, filename); + pos = cache_name_pos(fullname, fullnamelen); + if (pos < 0) + pos = -pos - 1; + if (pos < active_nr) { + ce = active_cache[pos]; + if (ce_namelen(ce) == fullnamelen && + !memcmp(ce->name, fullname, fullnamelen)) + die("Path '%s' is in the index, but not '%s'.\n" + "Did you mean ':%d:%s'?", + fullname, filename, + ce_stage(ce), fullname); + } + + if (!lstat(filename, &st)) + die("Path '%s' exists on disk, but not in the index.", filename); + if (errno == ENOENT || errno == ENOTDIR) + die("Path '%s' does not exist (neither on disk nor in the index).", + filename); + + free(fullname); +} + + +int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, int gently, const char *prefix) { int ret, bracket_depth; int namelen = strlen(name); @@ -849,6 +1076,8 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode) } pos++; } + if (!gently) + diagnose_invalid_index_path(stage, prefix, cp); return -1; } for (cp = name, bracket_depth = 0; *cp; cp++) { @@ -861,9 +1090,25 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode) } if (*cp == ':') { unsigned char tree_sha1[20]; - if (!get_sha1_1(name, cp-name, tree_sha1)) - return get_tree_entry(tree_sha1, cp+1, sha1, - mode); + char *object_name = NULL; + if (!gently) { + object_name = xmalloc(cp-name+1); + strncpy(object_name, name, cp-name); + object_name[cp-name] = '\0'; + } + if (!get_sha1_1(name, cp-name, tree_sha1)) { + const char *filename = cp+1; + ret = get_tree_entry(tree_sha1, filename, sha1, mode); + if (!gently) { + diagnose_invalid_sha1_path(prefix, filename, + tree_sha1, object_name); + free(object_name); + } + return ret; + } else { + if (!gently) + die("Invalid object name '%s'.", object_name); + } } return ret; } diff --git a/show-index.c b/show-index.c index 45bb535773..4c0ac138af 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)) { @@ -43,7 +48,7 @@ int main(int argc, char **argv) unsigned char sha1[20]; uint32_t crc; uint32_t off; - } *entries = xmalloc(nr * sizeof(entries[0])); + } *entries = malloc(nr * sizeof(entries[0])); for (i = 0; i < nr; i++) if (fread(entries[i].sha1, 20, 1, stdin) != 1) die("unable to read sha1 %u/%u", i, nr); 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; @@ -10,6 +10,15 @@ int prefixcmp(const char *str, const char *prefix) return (unsigned char)*prefix - (unsigned char)*str; } +int suffixcmp(const char *str, const char *suffix) +{ + int len = strlen(str), suflen = strlen(suffix); + if (len < suflen) + return -1; + else + return strcmp(str + len - suflen, suffix); +} + /* * Used as the default ->buf value, so that people can always assume * buf is non NULL and ->buf is NUL terminated even for a freshly @@ -91,13 +100,6 @@ void strbuf_ltrim(struct strbuf *sb) sb->buf[sb->len] = '\0'; } -void strbuf_tolower(struct strbuf *sb) -{ - int i; - for (i = 0; i < sb->len; i++) - sb->buf[i] = tolower(sb->buf[i]); -} - struct strbuf **strbuf_split(const struct strbuf *sb, int delim) { int alloc = 2, pos = 0; @@ -227,6 +229,12 @@ void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, break; format = percent + 1; + if (*format == '%') { + strbuf_addch(sb, '%'); + format++; + continue; + } + consumed = fn(sb, format, context); if (consumed) format += consumed; @@ -251,6 +259,17 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, return 0; } +void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src) +{ + int i, len = src->len; + + for (i = 0; i < len; i++) { + if (src->buf[i] == '%') + strbuf_addch(dst, '%'); + strbuf_addch(dst, src->buf[i]); + } +} + size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f) { size_t res; @@ -81,7 +81,6 @@ extern void strbuf_trim(struct strbuf *); extern void strbuf_rtrim(struct strbuf *); extern void strbuf_ltrim(struct strbuf *); extern int strbuf_cmp(const struct strbuf *, const struct strbuf *); -extern void strbuf_tolower(struct strbuf *); extern struct strbuf **strbuf_split(const struct strbuf *, int delim); extern void strbuf_list_free(struct strbuf **); @@ -105,6 +104,7 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s) { strbuf_add(sb, s, strlen(s)); } static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) { + strbuf_grow(sb, sb2->len); strbuf_add(sb, sb2->buf, sb2->len); } extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len); @@ -116,8 +116,9 @@ struct strbuf_expand_dict_entry { const char *value; }; extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context); +extern void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src); -__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/string-list.h b/string-list.h index 14bbc477de..6569cf607b 100644 --- a/string-list.h +++ b/string-list.h @@ -1,5 +1,5 @@ -#ifndef PATH_LIST_H -#define PATH_LIST_H +#ifndef STRING_LIST_H +#define STRING_LIST_H struct string_list_item { char *string; @@ -39,4 +39,4 @@ struct string_list_item *string_list_append(const char *string, struct string_li void sort_string_list(struct string_list *list); int unsorted_string_list_has_string(struct string_list *list, const char *string); -#endif /* PATH_LIST_H */ +#endif /* STRING_LIST_H */ diff --git a/submodule.c b/submodule.c new file mode 100644 index 0000000000..7d70c4f7bf --- /dev/null +++ b/submodule.c @@ -0,0 +1,173 @@ +#include "cache.h" +#include "submodule.h" +#include "dir.h" +#include "diff.h" +#include "commit.h" +#include "revision.h" +#include "run-command.h" + +static int add_submodule_odb(const char *path) +{ + struct strbuf objects_directory = STRBUF_INIT; + struct alternate_object_database *alt_odb; + int ret = 0; + + strbuf_addf(&objects_directory, "%s/.git/objects/", path); + if (!is_directory(objects_directory.buf)) { + ret = -1; + goto done; + } + /* 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)) + goto done; + + 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(); +done: + strbuf_release(&objects_directory); + return ret; +} + +void show_submodule_summary(FILE *f, const char *path, + unsigned char one[20], unsigned char two[20], + unsigned dirty_submodule, + 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 (dirty_submodule) + strbuf_add(&sb, "-dirty", 6); + 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); +} + +int is_submodule_modified(const char *path) +{ + int len; + struct child_process cp; + const char *argv[] = { + "status", + "--porcelain", + NULL, + }; + char *env[4]; + struct strbuf buf = STRBUF_INIT; + + strbuf_addf(&buf, "%s/.git/", path); + if (!is_directory(buf.buf)) { + strbuf_release(&buf); + /* The submodule is not checked out, so it is not modified */ + return 0; + + } + strbuf_reset(&buf); + + strbuf_addf(&buf, "GIT_WORK_TREE=%s", path); + env[0] = strbuf_detach(&buf, NULL); + strbuf_addf(&buf, "GIT_DIR=%s/.git", path); + env[1] = strbuf_detach(&buf, NULL); + strbuf_addf(&buf, "GIT_INDEX_FILE"); + env[2] = strbuf_detach(&buf, NULL); + env[3] = NULL; + + memset(&cp, 0, sizeof(cp)); + cp.argv = argv; + cp.env = (const char *const *)env; + cp.git_cmd = 1; + cp.no_stdin = 1; + cp.out = -1; + if (start_command(&cp)) + die("Could not run git status --porcelain"); + + len = strbuf_read(&buf, cp.out, 1024); + close(cp.out); + + if (finish_command(&cp)) + die("git status --porcelain failed"); + + free(env[0]); + free(env[1]); + free(env[2]); + strbuf_release(&buf); + return len != 0; +} diff --git a/submodule.h b/submodule.h new file mode 100644 index 0000000000..233696555e --- /dev/null +++ b/submodule.h @@ -0,0 +1,10 @@ +#ifndef SUBMODULE_H +#define SUBMODULE_H + +void show_submodule_summary(FILE *f, const char *path, + unsigned char one[20], unsigned char two[20], + unsigned dirty_submodule, + const char *del, const char *add, const char *reset); +int is_submodule_modified(const char *path); + +#endif diff --git a/symlinks.c b/symlinks.c index 7b0a86d357..8860120011 100644 --- a/symlinks.c +++ b/symlinks.c @@ -179,37 +179,6 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len, return ret_flags; } -/* - * Invalidate the given 'name' from the cache, if 'name' matches - * completely with the cache. - */ -void invalidate_lstat_cache(const char *name, int len) -{ - int match_len, previous_slash; - struct cache_def *cache = &default_cache; /* FIXME */ - - match_len = longest_path_match(name, len, cache->path, cache->len, - &previous_slash); - if (len == match_len) { - if ((cache->track_flags & FL_DIR) && previous_slash > 0) { - cache->path[previous_slash] = '\0'; - cache->len = previous_slash; - cache->flags = FL_DIR; - } else { - reset_lstat_cache(cache); - } - } -} - -/* - * Completely clear the contents of the cache - */ -void clear_lstat_cache(void) -{ - struct cache_def *cache = &default_cache; /* FIXME */ - reset_lstat_cache(cache); -} - #define USE_ONLY_LSTAT 0 /* @@ -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/diff-lib.sh b/t/diff-lib.sh index 4bddeb591e..75a35fcd06 100644 --- a/t/diff-lib.sh +++ b/t/diff-lib.sh @@ -1,7 +1,5 @@ : -_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" sanitize_diff_raw='/^:/s/ '"$_x40"' '"$_x40"' \([A-Z]\)[0-9]* / X X \1# /' compare_diff_raw () { # When heuristics are improved, the score numbers would change. diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh index 845253274b..5a734b1b7b 100644 --- a/t/gitweb-lib.sh +++ b/t/gitweb-lib.sh @@ -25,6 +25,7 @@ our \$favicon = 'file:///$TEST_DIRECTORY/../gitweb/git-favicon.png'; our \$projects_list = ''; our \$export_ok = ''; our \$strict_export = ''; +our \$maxload = undef; EOF @@ -52,10 +53,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.sh b/t/lib-httpd.sh index 6765b08065..28aff887b5 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -12,16 +12,29 @@ fi HTTPD_PARA="" +for DEFAULT_HTTPD_PATH in '/usr/sbin/httpd' '/usr/sbin/apache2' +do + if test -x "$DEFAULT_HTTPD_PATH" + then + break + fi +done + +for DEFAULT_HTTPD_MODULE_PATH in '/usr/libexec/apache2' \ + '/usr/lib/apache2/modules' \ + '/usr/lib64/httpd/modules' \ + '/usr/lib/httpd/modules' +do + if test -d "$DEFAULT_HTTPD_MODULE_PATH" + then + break + fi +done + case $(uname) in Darwin) - DEFAULT_HTTPD_PATH='/usr/sbin/httpd' - DEFAULT_HTTPD_MODULE_PATH='/usr/libexec/apache2' HTTPD_PARA="$HTTPD_PARA -DDarwin" ;; - *) - DEFAULT_HTTPD_PATH='/usr/sbin/apache2' - DEFAULT_HTTPD_MODULE_PATH='/usr/lib/apache2/modules' - ;; esac LIB_HTTPD_PATH=${LIB_HTTPD_PATH-"$DEFAULT_HTTPD_PATH"} @@ -49,6 +62,11 @@ then say "skipping test, at least Apache version 2 is required" test_done fi + if ! test -d "$DEFAULT_HTTPD_MODULE_PATH" + then + say "Apache module directory not found. Skipping tests." + test_done + fi LIB_HTTPD_MODULE_PATH="$DEFAULT_HTTPD_MODULE_PATH" fi diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 21aa42f1c6..4961505d1d 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -8,6 +8,33 @@ 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} + SetEnv GIT_HTTP_EXPORT_ALL +</Location> +<Location /smart_noexport/> + SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} +</Location> +ScriptAlias /smart/ ${GIT_EXEC_PATH}/git-http-backend/ +ScriptAlias /smart_noexport/ ${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 +53,7 @@ SSLEngine On LoadModule dav_fs_module modules/mod_dav_fs.so DAVLockDB DAVLock - <Location /> + <Location /dumb/> Dav on </Location> </IfDefine> diff --git a/t/lib-patch-mode.sh b/t/lib-patch-mode.sh index 75a3ee283d..ce36f34d03 100755..100644 --- a/t/lib-patch-mode.sh +++ b/t/lib-patch-mode.sh @@ -1,3 +1,5 @@ +: included from t2016 and others + . ./test-lib.sh if ! test_have_prereq PERL; then diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 260a231933..6aefe27593 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -2,21 +2,33 @@ # After setting the fake editor with this function, you can # -# - override the commit message with $FAKE_COMMIT_MESSAGE, +# - override the commit message with $FAKE_COMMIT_MESSAGE # - amend the commit message with $FAKE_COMMIT_AMEND # - check that non-commit messages have a certain line count with $EXPECT_COUNT -# - rewrite a rebase -i script with $FAKE_LINES in the form +# - check the commit count in the commit message header with $EXPECT_HEADER_COUNT +# - rewrite a rebase -i script as directed by $FAKE_LINES. +# $FAKE_LINES consists of a sequence of words separated by spaces. +# The following word combinations are possible: # -# "[<lineno1>] [<lineno2>]..." +# "<lineno>" -- add a "pick" line with the SHA1 taken from the +# specified line. # -# If a line number is prefixed with "squash" or "edit", the respective line's -# command will be replaced with the specified one. +# "<cmd> <lineno>" -- add a line with the specified command +# ("squash", "fixup", "edit", or "reword") and the SHA1 taken +# from the specified line. +# +# "#" -- Add a comment line. +# +# ">" -- Add a blank line. set_fake_editor () { echo "#!$SHELL_PATH" >fake-editor.sh cat >> fake-editor.sh <<\EOF case "$1" in */COMMIT_EDITMSG) + test -z "$EXPECT_HEADER_COUNT" || + test "$EXPECT_HEADER_COUNT" = "$(sed -n '1s/^# This is a combination of \(.*\) commits\./\1/p' < "$1")" || + exit test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1" test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1" exit @@ -28,19 +40,24 @@ test -z "$EXPECT_COUNT" || test -z "$FAKE_LINES" && exit grep -v '^#' < "$1" > "$1".tmp rm -f "$1" +echo 'rebase -i script before editing:' cat "$1".tmp action=pick for line in $FAKE_LINES; do case $line in - squash|edit) + squash|fixup|edit|reword) action="$line";; + "#") + echo '# comment' >> "$1";; + ">") + echo >> "$1";; *) - echo sed -n "${line}s/^pick/$action/p" - sed -n "${line}p" < "$1".tmp sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1" action=pick;; esac done +echo 'rebase -i script after editing:' +cat "$1" EOF test_set_editor "$(pwd)/fake-editor.sh" diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index 4e72b53140..c3e7e322a8 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -4,21 +4,8 @@ test_description='CRLF conversion' . ./test-lib.sh -q_to_nul () { - perl -pe 'y/Q/\000/' -} - -q_to_cr () { - tr Q '\015' -} - -append_cr () { - sed -e 's/$/Q/' | tr Q '\015' -} - -remove_cr () { - tr '\015' Q <"$1" | grep Q >/dev/null && - tr '\015' Q <"$1" | sed -ne 's/Q$//p' +has_cr() { + tr '\015' Q <"$1" | grep Q >/dev/null } test_expect_success setup ' @@ -156,7 +143,7 @@ test_expect_success 'checkout with autocrlf=true' ' for f in one dir/two do - remove_cr "$f" >tmp && mv -f tmp $f && + remove_cr <"$f" >tmp && mv -f tmp $f && git update-index -- $f || { echo "Eh? $f" false @@ -180,7 +167,7 @@ test_expect_success 'checkout with autocrlf=input' ' for f in one dir/two do - if remove_cr "$f" >/dev/null + if has_cr "$f" then echo "Eh? $f" false @@ -245,7 +232,7 @@ test_expect_success 'apply patch (autocrlf=true)' ' git read-tree --reset -u HEAD && git apply patch.file && - test "$patched" = "`remove_cr one | git hash-object --stdin`" || { + test "$patched" = "`remove_cr <one | git hash-object --stdin`" || { echo "Eh? apply without index" false } @@ -272,7 +259,7 @@ test_expect_success 'apply patch --index (autocrlf=true)' ' git apply --index patch.file && test "$patched" = `git rev-parse :one` && - test "$patched" = "`remove_cr one | git hash-object --stdin`" || { + test "$patched" = "`remove_cr <one | git hash-object --stdin`" || { echo "Eh? apply with --index" false } @@ -285,7 +272,7 @@ test_expect_success '.gitattributes says two is binary' ' git config core.autocrlf true && git read-tree --reset -u HEAD && - if remove_cr dir/two >/dev/null + if has_cr dir/two then echo "Huh?" false @@ -293,7 +280,7 @@ test_expect_success '.gitattributes says two is binary' ' : happy fi && - if remove_cr one >/dev/null + if has_cr one then : happy else @@ -301,7 +288,7 @@ test_expect_success '.gitattributes says two is binary' ' false fi && - if remove_cr three >/dev/null + if has_cr three then echo "Huh?" false @@ -316,7 +303,7 @@ test_expect_success '.gitattributes says two is input' ' echo "two crlf=input" >.gitattributes && git read-tree --reset -u HEAD && - if remove_cr dir/two >/dev/null + if has_cr dir/two then echo "Huh?" false @@ -331,7 +318,7 @@ test_expect_success '.gitattributes says two and three are text' ' echo "t* crlf" >.gitattributes && git read-tree --reset -u HEAD && - if remove_cr dir/two >/dev/null + if has_cr dir/two then : happy else @@ -339,7 +326,7 @@ test_expect_success '.gitattributes says two and three are text' ' false fi && - if remove_cr three >/dev/null + if has_cr three then : happy else @@ -357,14 +344,14 @@ test_expect_success 'in-tree .gitattributes (1)' ' rm -rf tmp one dir .gitattributes patch.file three && git read-tree --reset -u HEAD && - if remove_cr one >/dev/null + if has_cr one then echo "Eh? one should not have CRLF" false else : happy fi && - remove_cr three >/dev/null || { + has_cr three || { echo "Eh? three should still have CRLF" false } @@ -376,14 +363,14 @@ test_expect_success 'in-tree .gitattributes (2)' ' git read-tree --reset HEAD && git checkout-index -f -q -u -a && - if remove_cr one >/dev/null + if has_cr one then echo "Eh? one should not have CRLF" false else : happy fi && - remove_cr three >/dev/null || { + has_cr three || { echo "Eh? three should still have CRLF" false } @@ -396,14 +383,14 @@ test_expect_success 'in-tree .gitattributes (3)' ' git checkout-index -u .gitattributes && git checkout-index -u one dir/two three && - if remove_cr one >/dev/null + if has_cr one then echo "Eh? one should not have CRLF" false else : happy fi && - remove_cr three >/dev/null || { + has_cr three || { echo "Eh? three should still have CRLF" false } @@ -416,14 +403,14 @@ test_expect_success 'in-tree .gitattributes (4)' ' git checkout-index -u one dir/two three && git checkout-index -u .gitattributes && - if remove_cr one >/dev/null + if has_cr one then echo "Eh? one should not have CRLF" false else : happy fi && - remove_cr three >/dev/null || { + has_cr three || { echo "Eh? three should still have CRLF" false } @@ -456,7 +443,7 @@ test_expect_success 'checkout when deleting .gitattributes' ' git checkout master~1 && git checkout master && - remove_cr .file2 >/dev/null + has_cr .file2 ' diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index 8fc39d77ce..6cb8d60ea2 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -4,7 +4,8 @@ test_description='blob conversion via gitattributes' . ./test-lib.sh -cat <<\EOF >rot13.sh +cat <<EOF >rot13.sh +#!$SHELL_PATH tr \ 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \ 'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM' diff --git a/t/t0022-crlf-rename.sh b/t/t0022-crlf-rename.sh index f1e1d48869..7af3fbcc7b 100755 --- a/t/t0022-crlf-rename.sh +++ b/t/t0022-crlf-rename.sh @@ -12,7 +12,7 @@ test_expect_success setup ' test_tick && git commit -m Initial && - sed -e "s/\$/
/" "$TEST_DIRECTORY"/t0022-crlf-rename.sh >elpmas && + append_cr <"$TEST_DIRECTORY"/t0022-crlf-rename.sh >elpmas && git add elpmas && rm -f sample && diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh new file mode 100755 index 0000000000..10b26e4d8e --- /dev/null +++ b/t/t0061-run-command.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# +# Copyright (c) 2009 Ilari Liusvaara +# + +test_description='Test run command' + +. ./test-lib.sh + +test_expect_success 'start_command reports ENOENT' ' + test-run-command start-command-ENOENT ./does-not-exist +' + +test_done diff --git a/t/t0101-at-syntax.sh b/t/t0101-at-syntax.sh new file mode 100755 index 0000000000..a1998b558f --- /dev/null +++ b/t/t0101-at-syntax.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +test_description='various @{whatever} syntax tests' +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit one && + test_commit two +' + +check_at() { + echo "$2" >expect && + git log -1 --format=%s "$1" >actual && + test_cmp expect actual +} + +test_expect_success '@{0} shows current' ' + check_at @{0} two +' + +test_expect_success '@{1} shows old' ' + check_at @{1} one +' + +test_expect_success '@{now} shows current' ' + check_at @{now} two +' + +test_expect_success '@{2001-09-17} (before the first commit) shows old' ' + check_at @{2001-09-17} one +' + +test_expect_success 'silly approxidates work' ' + check_at @{3.hot.dogs.on.2001-09-17} one +' + +test_expect_success 'notice misspelled upstream' ' + test_must_fail git log -1 --format=%s @{usptream} +' + +test_expect_success 'complain about total nonsense' ' + test_must_fail git log -1 --format=%s @{utter.bogosity} +' + +test_done diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh index 22ba7a5442..4f171722d9 100755 --- a/t/t1000-read-tree-m-3way.sh +++ b/t/t1000-read-tree-m-3way.sh @@ -126,9 +126,6 @@ cat >expected <<\EOF 100644 X 0 Z/NN EOF -_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" - check_result () { git ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current && test_cmp expected current diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index 271bc4e17f..6327d205cb 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 @@ -26,8 +26,6 @@ read_tree_twoway () { git read-tree -m "$1" "$2" && git ls-files --stage } -_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" compare_change () { sed -n >current \ -e '/^--- /d; /^+++ /d; /^@@ /d;' \ @@ -51,7 +49,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 +298,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/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh index 5e40cec530..0241329a08 100755 --- a/t/t1002-read-tree-m-u-2way.sh +++ b/t/t1002-read-tree-m-u-2way.sh @@ -10,8 +10,6 @@ This is identical to t1001, but uses -u to update the work tree as well. ' . ./test-lib.sh -_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" compare_change () { sed >current \ -e '1{/^diff --git /d;}' \ diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh new file mode 100755 index 0000000000..62246dbf95 --- /dev/null +++ b/t/t1011-read-tree-sparse-checkout.sh @@ -0,0 +1,150 @@ +#!/bin/sh + +test_description='sparse checkout tests' + +. ./test-lib.sh + +cat >expected <<EOF +100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0 init.t +100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/added +EOF +test_expect_success 'setup' ' + test_commit init && + echo modified >> init.t && + mkdir sub && + touch sub/added && + git add init.t sub/added && + git commit -m "modified and added" && + git tag top && + git rm sub/added && + git commit -m removed && + git tag removed && + git checkout top && + git ls-files --stage > result && + test_cmp expected result +' + +cat >expected.swt <<EOF +H init.t +H sub/added +EOF +test_expect_success 'read-tree without .git/info/sparse-checkout' ' + git read-tree -m -u HEAD && + git ls-files --stage > result && + test_cmp expected result && + git ls-files -t > result && + test_cmp expected.swt result +' + +test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' ' + echo > .git/info/sparse-checkout + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test -f sub/added +' + +test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' ' + git config core.sparsecheckout true && + echo > .git/info/sparse-checkout && + git read-tree --no-sparse-checkout -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test -f sub/added +' + +test_expect_success 'read-tree with empty .git/info/sparse-checkout' ' + git config core.sparsecheckout true && + echo > .git/info/sparse-checkout && + test_must_fail git read-tree -m -u HEAD && + git ls-files --stage > result && + test_cmp expected result && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test -f sub/added +' + +cat >expected.swt <<EOF +S init.t +H sub/added +EOF +test_expect_success 'match directories with trailing slash' ' + echo sub/ > .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test ! -f init.t && + test -f sub/added +' + +cat >expected.swt <<EOF +H init.t +H sub/added +EOF +test_expect_failure 'match directories without trailing slash' ' + echo init.t > .git/info/sparse-checkout && + echo sub >> .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test ! -f init.t && + test -f sub/added +' + +cat >expected.swt <<EOF +H init.t +S sub/added +EOF +test_expect_success 'checkout area changes' ' + echo init.t > .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test ! -f sub/added +' + +test_expect_success 'read-tree updates worktree, absent case' ' + echo sub/added > .git/info/sparse-checkout && + git checkout -f top && + git read-tree -m -u HEAD^ && + test ! -f init.t +' + +test_expect_success 'read-tree updates worktree, dirty case' ' + echo sub/added > .git/info/sparse-checkout && + git checkout -f top && + echo dirty > init.t && + git read-tree -m -u HEAD^ && + grep -q dirty init.t && + rm init.t +' + +test_expect_success 'read-tree removes worktree, dirty case' ' + echo init.t > .git/info/sparse-checkout && + git checkout -f top && + echo dirty > added && + git read-tree -m -u HEAD^ && + grep -q dirty added +' + +test_expect_success 'read-tree adds to worktree, absent case' ' + echo init.t > .git/info/sparse-checkout && + git checkout -f removed && + git read-tree -u -m HEAD^ && + test ! -f sub/added +' + +test_expect_success 'read-tree adds to worktree, dirty case' ' + echo init.t > .git/info/sparse-checkout && + git checkout -f removed && + mkdir sub && + echo dirty > sub/added && + git read-tree -u -m HEAD^ && + grep -q dirty sub/added +' + +test_done diff --git a/t/t1012-read-tree-df.sh b/t/t1012-read-tree-df.sh new file mode 100755 index 0000000000..9811d467da --- /dev/null +++ b/t/t1012-read-tree-df.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +test_description='read-tree D/F conflict corner cases' + +. ./test-lib.sh + +maketree () { + ( + rm -f .git/index .git/index.lock && + git clean -d -f -f -q -x && + name="$1" && + shift && + for it + do + path=$(expr "$it" : '\([^:]*\)') && + mkdir -p $(dirname "$path") && + echo "$it" >"$path" && + git update-index --add "$path" || exit + done && + git tag "$name" $(git write-tree) + ) +} + +settree () { + rm -f .git/index .git/index.lock && + git clean -d -f -f -q -x && + git read-tree "$1" && + git checkout-index -f -q -u -a && + git update-index --refresh +} + +checkindex () { + git ls-files -s | + sed "s|^[0-7][0-7]* $_x40 \([0-3]\) |\1 |" >current && + cat >expect && + test_cmp expect current +} + +test_expect_success setup ' + maketree O-000 a/b-2/c/d a/b/c/d a/x && + maketree A-000 a/b-2/c/d a/b/c/d a/x && + maketree A-001 a/b-2/c/d a/b/c/d a/b/c/e a/x && + maketree B-000 a/b-2/c/d a/b a/x && + + maketree O-010 t-0 t/1 t/2 t=3 && + maketree A-010 t-0 t t=3 && + maketree B-010 t/1: t=3: && + + maketree O-020 ds/dma/ioat.c ds/dma/ioat_dca.c && + maketree A-020 ds/dma/ioat/Makefile ds/dma/ioat/registers.h && + : +' + +test_expect_success '3-way (1)' ' + settree A-000 && + git read-tree -m -u O-000 A-000 B-000 && + checkindex <<-EOF + 3 a/b + 0 a/b-2/c/d + 1 a/b/c/d + 2 a/b/c/d + 0 a/x + EOF +' + +test_expect_success '3-way (2)' ' + settree A-001 && + git read-tree -m -u O-000 A-001 B-000 && + checkindex <<-EOF + 3 a/b + 0 a/b-2/c/d + 1 a/b/c/d + 2 a/b/c/d + 2 a/b/c/e + 0 a/x + EOF +' + +test_expect_success '3-way (3)' ' + settree A-010 && + git read-tree -m -u O-010 A-010 B-010 && + checkindex <<-EOF + 2 t + 1 t-0 + 2 t-0 + 1 t/1 + 3 t/1 + 1 t/2 + 0 t=3 + EOF +' + +test_expect_success '2-way (1)' ' + settree O-020 && + git read-tree -m -u O-020 A-020 && + checkindex <<-EOF + 0 ds/dma/ioat/Makefile + 0 ds/dma/ioat/registers.h + EOF +' + +test_done 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/t1300-repo-config.sh b/t/t1300-repo-config.sh index 83b7294010..f11f98c3ce 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -398,6 +398,17 @@ test_expect_success 'alternative GIT_CONFIG' 'cmp output expect' test_expect_success 'alternative GIT_CONFIG (--file)' \ 'git config --file other-config -l > output && cmp output expect' +test_expect_success 'refer config from subdirectory' ' + mkdir x && + ( + cd x && + echo strasse >expect + git config --get --file ../other-config ein.bahn >actual && + test_cmp expect actual + ) + +' + GIT_CONFIG=other-config git config anwohner.park ausweis cat > expect << EOF @@ -683,6 +694,34 @@ test_expect_success 'set --bool-or-int' ' rm .git/config +cat >expect <<\EOF +[path] + home = ~/ + normal = /dev/null + trailingtilde = foo~ +EOF + +test_expect_success 'set --path' ' + git config --path path.home "~/" && + git config --path path.normal "/dev/null" && + git config --path path.trailingtilde "foo~" && + test_cmp expect .git/config' + +cat >expect <<EOF +$HOME/ +/dev/null +foo~ +EOF + +test_expect_success 'get --path' ' + git config --get --path path.home > result && + git config --get --path path.normal >> result && + git config --get --path path.trailingtilde >> result && + test_cmp expect result +' + +rm .git/config + git config quote.leading " test" git config quote.ending "test " git config quote.semicolon "test;test" 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/t1450-fsck.sh b/t/t1450-fsck.sh index a22632f483..49cae3ed52 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -66,12 +66,12 @@ tagger T A Gger <tagger@example.com> 1234567890 -0000 This is an invalid tag. EOF -test_expect_failure 'tag pointing to nonexistent' ' - tag=$(git hash-object -w --stdin < invalid-tag) && +test_expect_success 'tag pointing to nonexistent' ' + tag=$(git hash-object -t tag -w --stdin < invalid-tag) && echo $tag > .git/refs/tags/invalid && - git fsck --tags 2>out && + test_must_fail git fsck --tags >out && cat out && - grep "could not load tagged object" out && + grep "broken link" out && rm .git/refs/tags/invalid ' @@ -84,12 +84,12 @@ tagger T A Gger <tagger@example.com> 1234567890 -0000 This is an invalid tag. EOF -test_expect_failure 'tag pointing to something else than its type' ' - tag=$(git hash-object -w --stdin < wrong-tag) && +test_expect_success 'tag pointing to something else than its type' ' + tag=$(git hash-object -t tag -w --stdin < wrong-tag) && echo $tag > .git/refs/tags/wrong && - git fsck --tags 2>out && + test_must_fail git fsck --tags 2>out && cat out && - grep "some sane error message" out && + grep "error in tag.*broken links" out && rm .git/refs/tags/wrong ' diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh index 74e6443664..9df301211c 100755 --- a/t/t1501-worktree.sh +++ b/t/t1501-worktree.sh @@ -189,4 +189,10 @@ test_expect_success 'absolute pathspec should fail gracefully' ' ) ' +test_expect_success 'make_relative_path handles double slashes in GIT_DIR' ' + : > dummy_file + echo git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file && + git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file +' + test_done diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh new file mode 100755 index 0000000000..af721f9719 --- /dev/null +++ b/t/t1506-rev-parse-diagnosis.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='test git rev-parse diagnosis for invalid argument' + +exec </dev/null + +. ./test-lib.sh + +HASH_file= + +test_expect_success 'set up basic repo' ' + echo one > file.txt && + mkdir subdir && + echo two > subdir/file.txt && + echo three > subdir/file2.txt && + git add . && + git commit -m init && + echo four > index-only.txt && + git add index-only.txt && + echo five > disk-only.txt +' + +test_expect_success 'correct file objects' ' + HASH_file=$(git rev-parse HEAD:file.txt) && + git rev-parse HEAD:subdir/file.txt && + git rev-parse :index-only.txt && + (cd subdir && + git rev-parse HEAD:subdir/file2.txt && + test $HASH_file = $(git rev-parse HEAD:file.txt) && + test $HASH_file = $(git rev-parse :file.txt) && + test $HASH_file = $(git rev-parse :0:file.txt) ) +' + +test_expect_success 'incorrect revision id' ' + test_must_fail git rev-parse foobar:file.txt 2>error && + grep "Invalid object name '"'"'foobar'"'"'." error && + test_must_fail git rev-parse foobar 2> error && + grep "unknown revision or path not in the working tree." error +' + +test_expect_success 'incorrect file in sha1:path' ' + test_must_fail git rev-parse HEAD:nothing.txt 2> error && + grep "fatal: Path '"'"'nothing.txt'"'"' does not exist in '"'"'HEAD'"'"'" error && + test_must_fail git rev-parse HEAD:index-only.txt 2> error && + grep "fatal: Path '"'"'index-only.txt'"'"' exists on disk, but not in '"'"'HEAD'"'"'." error && + (cd subdir && + test_must_fail git rev-parse HEAD:file2.txt 2> error && + grep "Did you mean '"'"'HEAD:subdir/file2.txt'"'"'?" error ) +' + +test_expect_success 'incorrect file in :path and :N:path' ' + test_must_fail git rev-parse :nothing.txt 2> error && + grep "fatal: Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error && + test_must_fail git rev-parse :1:nothing.txt 2> error && + grep "Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error && + test_must_fail git rev-parse :1:file.txt 2> error && + grep "Did you mean '"'"':0:file.txt'"'"'?" error && + (cd subdir && + test_must_fail git rev-parse :1:file.txt 2> error && + grep "Did you mean '"'"':0:file.txt'"'"'?" error && + test_must_fail git rev-parse :file2.txt 2> error && + grep "Did you mean '"'"':0:subdir/file2.txt'"'"'?" error && + test_must_fail git rev-parse :2:file2.txt 2> error && + grep "Did you mean '"'"':0:subdir/file2.txt'"'"'?" error) && + test_must_fail git rev-parse :disk-only.txt 2> error && + grep "fatal: Path '"'"'disk-only.txt'"'"' exists on disk, but not in the index." error +' + +test_done diff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh new file mode 100755 index 0000000000..8c8dfdaf9f --- /dev/null +++ b/t/t1507-rev-parse-upstream.sh @@ -0,0 +1,139 @@ +#!/bin/sh + +test_description='test <branch>@{upstream} syntax' + +. ./test-lib.sh + + +test_expect_success 'setup' ' + + test_commit 1 && + git checkout -b side && + test_commit 2 && + git checkout master && + git clone . clone && + test_commit 3 && + (cd clone && + test_commit 4 && + git branch --track my-side origin/side) + +' + +full_name () { + (cd clone && + git rev-parse --symbolic-full-name "$@") +} + +commit_subject () { + (cd clone && + git show -s --pretty=format:%s "$@") +} + +test_expect_success '@{upstream} resolves to correct full name' ' + test refs/remotes/origin/master = "$(full_name @{upstream})" +' + +test_expect_success '@{u} resolves to correct full name' ' + test refs/remotes/origin/master = "$(full_name @{u})" +' + +test_expect_success 'my-side@{upstream} resolves to correct full name' ' + test refs/remotes/origin/side = "$(full_name my-side@{u})" +' + +test_expect_success 'my-side@{u} resolves to correct commit' ' + git checkout side && + test_commit 5 && + (cd clone && git fetch) && + test 2 = "$(commit_subject my-side)" && + test 5 = "$(commit_subject my-side@{u})" +' + +test_expect_success 'not-tracking@{u} fails' ' + test_must_fail full_name non-tracking@{u} && + (cd clone && git checkout --no-track -b non-tracking) && + test_must_fail full_name non-tracking@{u} +' + +test_expect_success '<branch>@{u}@{1} resolves correctly' ' + test_commit 6 && + (cd clone && git fetch) && + test 5 = $(commit_subject my-side@{u}@{1}) +' + +test_expect_success '@{u} without specifying branch fails on a detached HEAD' ' + git checkout HEAD^0 && + test_must_fail git rev-parse @{u} +' + +test_expect_success 'checkout -b new my-side@{u} forks from the same' ' +( + cd clone && + git checkout -b new my-side@{u} && + git rev-parse --symbolic-full-name my-side@{u} >expect && + git rev-parse --symbolic-full-name new@{u} >actual && + test_cmp expect actual +) +' + +test_expect_success 'merge my-side@{u} records the correct name' ' +( + sq="'\''" && + cd clone || exit + git checkout master || exit + git branch -D new ;# can fail but is ok + git branch -t new my-side@{u} && + git merge -s ours new@{u} && + git show -s --pretty=format:%s >actual && + echo "Merge remote branch ${sq}origin/side${sq}" >expect && + test_cmp expect actual +) +' + +test_expect_success 'branch -d other@{u}' ' + git checkout -t -b other master && + git branch -d @{u} && + git for-each-ref refs/heads/master >actual && + >expect && + test_cmp expect actual +' + +test_expect_success 'checkout other@{u}' ' + git branch -f master HEAD && + git checkout -t -b another master && + git checkout @{u} && + git symbolic-ref HEAD >actual && + echo refs/heads/master >expect && + test_cmp expect actual +' + +cat >expect <<EOF +commit 8f489d01d0cc65c3b0f09504ec50b5ed02a70bd5 +Reflog: master@{0} (C O Mitter <committer@example.com>) +Reflog message: branch: Created from HEAD +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:15:13 2005 -0700 + + 3 +EOF +test_expect_success 'log -g other@{u}' ' + git log -1 -g other@{u} >actual && + test_cmp expect actual +' + +cat >expect <<EOF +commit 8f489d01d0cc65c3b0f09504ec50b5ed02a70bd5 +Reflog: master@{Thu Apr 7 15:17:13 2005 -0700} (C O Mitter <committer@example.com>) +Reflog message: branch: Created from HEAD +Author: A U Thor <author@example.com> +Date: Thu Apr 7 15:15:13 2005 -0700 + + 3 +EOF + +test_expect_success 'log -g other@{u}@{now}' ' + git log -1 -g other@{u}@{now} >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t1508-at-combinations.sh b/t/t1508-at-combinations.sh new file mode 100755 index 0000000000..d5d6244178 --- /dev/null +++ b/t/t1508-at-combinations.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +test_description='test various @{X} syntax combinations together' +. ./test-lib.sh + +check() { +test_expect_${3:-success} "$1 = $2" " + echo '$2' >expect && + git log -1 --format=%s '$1' >actual && + test_cmp expect actual +" +} +nonsense() { +test_expect_${2:-success} "$1 is nonsensical" " + test_must_fail git log -1 '$1' +" +} +fail() { + "$@" failure +} + +test_expect_success 'setup' ' + test_commit master-one && + test_commit master-two && + git checkout -b upstream-branch && + test_commit upstream-one && + test_commit upstream-two && + git checkout -b old-branch && + test_commit old-one && + test_commit old-two && + git checkout -b new-branch && + test_commit new-one && + test_commit new-two && + git config branch.old-branch.remote . && + git config branch.old-branch.merge refs/heads/master && + git config branch.new-branch.remote . && + git config branch.new-branch.merge refs/heads/upstream-branch +' + +check HEAD new-two +check "@{1}" new-one +check "@{-1}" old-two +check "@{-1}@{1}" old-one +check "@{u}" upstream-two +check "@{u}@{1}" upstream-one +check "@{-1}@{u}" master-two +check "@{-1}@{u}@{1}" master-one +nonsense "@{u}@{-1}" +nonsense "@{1}@{u}" + +test_done diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh index 87b30a268c..b44de9dc62 100755 --- a/t/t2012-checkout-last.sh +++ b/t/t2012-checkout-last.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='checkout can switch to last branch' +test_description='checkout can switch to last branch and merge base' . ./test-lib.sh @@ -91,4 +91,29 @@ test_expect_success 'switch to twelfth from the last' ' test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13" ' +test_expect_success 'merge base test setup' ' + git checkout -b another other && + echo "hello again" >>world && + git add world && + git commit -m third +' + +test_expect_success 'another...master' ' + git checkout another && + git checkout another...master && + test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)" +' + +test_expect_success '...master' ' + git checkout another && + git checkout ...master && + test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)" +' + +test_expect_success 'master...' ' + git checkout another && + git checkout master... && + test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)" +' + test_done diff --git a/t/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh new file mode 100755 index 0000000000..cb7effe0a3 --- /dev/null +++ b/t/t2030-unresolve-info.sh @@ -0,0 +1,170 @@ +#!/bin/sh + +test_description='undoing resolution' + +. ./test-lib.sh + +check_resolve_undo () { + msg=$1 + shift + while case $# in + 0) break ;; + 1|2|3) die "Bug in check-resolve-undo test" ;; + esac + do + path=$1 + shift + for stage in 1 2 3 + do + sha1=$1 + shift + case "$sha1" in + '') continue ;; + esac + sha1=$(git rev-parse --verify "$sha1") + printf "100644 %s %s\t%s\n" $sha1 $stage $path + done + done >"$msg.expect" && + git ls-files --resolve-undo >"$msg.actual" && + test_cmp "$msg.expect" "$msg.actual" +} + +prime_resolve_undo () { + git reset --hard && + git checkout second^0 && + test_tick && + test_must_fail git merge third^0 && + echo merge does not leave anything && + check_resolve_undo empty && + echo different >fi/le && + git add fi/le && + echo resolving records && + check_resolve_undo recorded fi/le initial:fi/le second:fi/le third:fi/le +} + +test_expect_success setup ' + mkdir fi && + test_commit initial fi/le first && + git branch side && + git branch another && + test_commit second fi/le second && + git checkout side && + test_commit third fi/le third && + git checkout another && + test_commit fourth fi/le fourth && + git checkout master +' + +test_expect_success 'add records switch clears' ' + prime_resolve_undo && + test_tick && + git commit -m merged && + echo committing keeps && + check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le && + git checkout second^0 && + echo switching clears && + check_resolve_undo cleared +' + +test_expect_success 'rm records reset clears' ' + prime_resolve_undo && + test_tick && + git commit -m merged && + echo committing keeps && + check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le && + + echo merge clears upfront && + test_must_fail git merge fourth^0 && + check_resolve_undo nuked && + + git rm -f fi/le && + echo resolving records && + check_resolve_undo recorded fi/le initial:fi/le HEAD:fi/le fourth:fi/le && + + git reset --hard && + echo resetting discards && + check_resolve_undo discarded +' + +test_expect_success 'plumbing clears' ' + prime_resolve_undo && + test_tick && + git commit -m merged && + echo committing keeps && + check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le && + + echo plumbing clear && + git update-index --clear-resolve-undo && + check_resolve_undo cleared +' + +test_expect_success 'add records checkout -m undoes' ' + prime_resolve_undo && + git diff HEAD && + git checkout --conflict=merge fi/le && + echo checkout used the record and removed it && + check_resolve_undo removed && + echo the index and the work tree is unmerged again && + git diff >actual && + grep "^++<<<<<<<" actual +' + +test_expect_success 'unmerge with plumbing' ' + prime_resolve_undo && + git update-index --unresolve fi/le && + git ls-files -u >actual && + test $(wc -l <actual) = 3 +' + +test_expect_success 'rerere and rerere forget' ' + mkdir .git/rr-cache && + prime_resolve_undo && + echo record the resolution && + git rerere && + rerere_id=$(cd .git/rr-cache && echo */postimage) && + rerere_id=${rerere_id%/postimage} && + test -f .git/rr-cache/$rerere_id/postimage && + git checkout -m fi/le && + echo resurrect the conflict && + grep "^=======" fi/le && + echo reresolve the conflict && + git rerere && + test "z$(cat fi/le)" = zdifferent && + echo register the resolution again && + git add fi/le && + check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le && + test -z "$(git ls-files -u)" && + git rerere forget fi/le && + ! test -f .git/rr-cache/$rerere_id/postimage && + tr "\0" "\n" <.git/MERGE_RR >actual && + echo "$rerere_id fi/le" >expect && + test_cmp expect actual +' + +test_expect_success 'rerere and rerere forget (subdirectory)' ' + rm -fr .git/rr-cache && + mkdir .git/rr-cache && + prime_resolve_undo && + echo record the resolution && + (cd fi && git rerere) && + rerere_id=$(cd .git/rr-cache && echo */postimage) && + rerere_id=${rerere_id%/postimage} && + test -f .git/rr-cache/$rerere_id/postimage && + (cd fi && git checkout -m le) && + echo resurrect the conflict && + grep "^=======" fi/le && + echo reresolve the conflict && + (cd fi && git rerere) && + test "z$(cat fi/le)" = zdifferent && + echo register the resolution again && + (cd fi && git add le) && + check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le && + test -z "$(git ls-files -u)" && + (cd fi && git rerere forget le) && + ! test -f .git/rr-cache/$rerere_id/postimage && + tr "\0" "\n" <.git/MERGE_RR >actual && + echo "$rerere_id fi/le" >expect && + test_cmp expect actual +' + +test_done diff --git a/t/t2104-update-index-skip-worktree.sh b/t/t2104-update-index-skip-worktree.sh new file mode 100755 index 0000000000..1d0879be06 --- /dev/null +++ b/t/t2104-update-index-skip-worktree.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# +# Copyright (c) 2008 Nguyễn Thái Ngọc Duy +# + +test_description='skip-worktree bit test' + +. ./test-lib.sh + +cat >expect.full <<EOF +H 1 +H 2 +H sub/1 +H sub/2 +EOF + +cat >expect.skip <<EOF +S 1 +H 2 +S sub/1 +H sub/2 +EOF + +test_expect_success 'setup' ' + mkdir sub && + touch ./1 ./2 sub/1 sub/2 && + git add 1 2 sub/1 sub/2 && + git ls-files -t | test_cmp expect.full - +' + +test_expect_success 'index is at version 2' ' + test "$(test-index-version < .git/index)" = 2 +' + +test_expect_success 'update-index --skip-worktree' ' + git update-index --skip-worktree 1 sub/1 && + git ls-files -t | test_cmp expect.skip - +' + +test_expect_success 'index is at version 3 after having some skip-worktree entries' ' + test "$(test-index-version < .git/index)" = 3 +' + +test_expect_success 'ls-files -t' ' + git ls-files -t | test_cmp expect.skip - +' + +test_expect_success 'update-index --no-skip-worktree' ' + git update-index --no-skip-worktree 1 sub/1 && + git ls-files -t | test_cmp expect.full - +' + +test_expect_success 'index version is back to 2 when there is no skip-worktree entry' ' + test "$(test-index-version < .git/index)" = 2 +' + +test_done diff --git a/t/t2105-update-index-gitfile.sh b/t/t2105-update-index-gitfile.sh new file mode 100755 index 0000000000..641607d89a --- /dev/null +++ b/t/t2105-update-index-gitfile.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# Copyright (c) 2010 Brad King +# + +test_description='git update-index for gitlink to .git file. +' + +. ./test-lib.sh + +test_expect_success 'submodule with absolute .git file' ' + mkdir sub1 && + (cd sub1 && + git init && + REAL="$(pwd)/.real" && + mv .git "$REAL" + echo "gitdir: $REAL" >.git && + test_commit first) +' + +test_expect_success 'add gitlink to absolute .git file' ' + git update-index --add -- sub1 +' + +test_expect_success 'submodule with relative .git file' ' + mkdir sub2 && + (cd sub2 && + git init && + mv .git .real && + echo "gitdir: .real" >.git && + test_commit first) +' + +test_expect_success 'add gitlink to relative .git file' ' + git update-index --add -- sub2 +' + +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/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index c65bca8388..6d2f2b67ee 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -64,6 +64,8 @@ two/*.4 echo '!*.2 !*.8' >one/two/.gitignore +allignores='.gitignore one/.gitignore one/two/.gitignore' + test_expect_success \ 'git ls-files --others with various exclude options.' \ 'git ls-files --others \ @@ -85,6 +87,26 @@ test_expect_success \ >output && test_cmp expect output' +test_expect_success 'setup skip-worktree gitignore' ' + git add $allignores && + git update-index --skip-worktree $allignores && + rm $allignores +' + +test_expect_success \ + 'git ls-files --others with various exclude options.' \ + 'git ls-files --others \ + --exclude=\*.6 \ + --exclude-per-directory=.gitignore \ + --exclude-from=.git/ignore \ + >output && + test_cmp expect output' + +test_expect_success 'restore gitignore' ' + git checkout $allignores && + rm .git/index +' + cat > excludes-file <<\EOF *.[1-8] e* @@ -153,4 +175,43 @@ test_expect_success 'negated exclude matches can override previous ones' ' grep "^a.1" output ' +test_expect_success 'subdirectory ignore (setup)' ' + mkdir -p top/l1/l2 && + ( + cd top && + git init && + echo /.gitignore >.gitignore && + echo l1 >>.gitignore && + echo l2 >l1/.gitignore && + >l1/l2/l1 + ) +' + +test_expect_success 'subdirectory ignore (toplevel)' ' + ( + cd top && + git ls-files -o --exclude-standard + ) >actual && + >expect && + test_cmp expect actual +' + +test_expect_success 'subdirectory ignore (l1/l2)' ' + ( + cd top/l1/l2 && + git ls-files -o --exclude-standard + ) >actual && + >expect && + test_cmp expect actual +' + +test_expect_success 'subdirectory ignore (l1)' ' + ( + cd top/l1 && + git ls-files -o --exclude-standard + ) >actual && + >expect && + test_cmp expect actual +' + test_done diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index 9b3fa2bdcd..9929f82021 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -276,11 +276,13 @@ test_expect_success 'fail if the index has unresolved entries' ' test_must_fail git merge "$c5" && test_must_fail git merge "$c5" 2> out && + grep "not possible because you have unmerged files" out && + git add -u && + test_must_fail git merge "$c5" 2> out && grep "You have not concluded your merge" out && rm -f .git/MERGE_HEAD && test_must_fail git merge "$c5" 2> out && - grep "You are in the middle of a conflicted merge" out - + grep "Your local changes to .* would be overwritten by merge." out ' test_expect_success 'merge-recursive remove conflict' ' diff --git a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh index ee60d03fe8..eee0d344d2 100755 --- a/t/t3100-ls-tree-restrict.sh +++ b/t/t3100-ls-tree-restrict.sh @@ -43,8 +43,6 @@ 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" test_output () { sed -e "s/ $_x40 / X /" <current >check test_cmp expected check diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh index 51cb4a30f5..06654c6f82 100755 --- a/t/t3101-ls-tree-dirname.sh +++ b/t/t3101-ls-tree-dirname.sh @@ -39,8 +39,6 @@ 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" test_output () { sed -e "s/ $_x40 / X /" <current >check test_cmp expected check @@ -141,4 +139,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/t3200-branch.sh b/t/t3200-branch.sh index d59a9b4aef..e0b760513c 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -468,4 +468,30 @@ test_expect_success 'detect misconfigured autosetuprebase (no value)' ' git config --unset branch.autosetuprebase ' +test_expect_success 'attempt to delete a branch without base and unmerged to HEAD' ' + git checkout my9 && + git config --unset branch.my8.merge && + test_must_fail git branch -d my8 +' + +test_expect_success 'attempt to delete a branch merged to its base' ' + # we are on my9 which is the initial commit; traditionally + # we would not have allowed deleting my8 that is not merged + # to my9, but it is set to track master that already has my8 + git config branch.my8.merge refs/heads/master && + git branch -d my8 +' + +test_expect_success 'attempt to delete a branch merged to its base' ' + git checkout master && + echo Third >>A && + git commit -m "Third commit" A && + git branch -t my10 my9 && + git branch -f my10 HEAD^ && + # we are on master which is at the third commit, and my10 + # is behind us, so traditionally we would have allowed deleting + # it; but my10 is set to track my9 that is further behind. + test_must_fail git branch -d my10 +' + test_done diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh new file mode 100755 index 0000000000..714626d2d6 --- /dev/null +++ b/t/t3301-notes.sh @@ -0,0 +1,210 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes E. Schindelin +# + +test_description='Test commit notes' + +. ./test-lib.sh + +cat > fake_editor.sh << \EOF +#!/bin/sh +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 +' + +cat >expect << EOF +commit 15023535574ded8b1a89052b32673f84cf9582b8 +tree e070e3af51011e47b183c33adf9736736a525709 +parent 1584215f1d29c65e99c6c6848626553fdd07fd75 +author A U Thor <author@example.com> 1112912173 -0700 +committer C O Mitter <committer@example.com> 1112912173 -0700 + + 4th +EOF +test_expect_success 'git log --pretty=raw does not show notes' ' + git log -1 --pretty=raw >output && + test_cmp expect output +' + +cat >>expect <<EOF + +Notes: + spam +$whitespace + xyzzy +$whitespace + foo + bar + baz +EOF +test_expect_success 'git log --show-notes' ' + git log -1 --pretty=raw --show-notes >output && + test_cmp expect output +' + +test_expect_success 'git log --no-notes' ' + git log -1 --no-notes >output && + ! grep spam output +' + +test_expect_success 'git format-patch does not show notes' ' + git format-patch -1 --stdout >output && + ! grep spam output +' + +test_expect_success 'git format-patch --show-notes does show notes' ' + git format-patch --show-notes -1 --stdout >output && + grep spam output +' + +for pretty in \ + "" --pretty --pretty=raw --pretty=short --pretty=medium \ + --pretty=full --pretty=fuller --pretty=format:%s --oneline +do + case "$pretty" in + "") p= not= negate="" ;; + ?*) p="$pretty" not=" not" negate="!" ;; + esac + test_expect_success "git show $pretty does$not show notes" ' + git show $p >output && + eval "$negate grep spam output" + ' +done + +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/t3400-rebase.sh b/t/t3400-rebase.sh index 4e6a44b623..4314ad2d66 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -134,10 +134,6 @@ test_expect_success 'rebase -q is quiet' ' test ! -s output.out ' -q_to_cr () { - tr Q '\015' -} - test_expect_success 'Rebase a commit that sprinkles CRs in' ' ( echo "One" diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 4cae019521..4e3513709e 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -14,58 +14,47 @@ that the result still makes sense. set_fake_editor -# set up two branches like this: +# Set up the repository like this: # -# A - B - C - D - E +# one - two - three - four (conflict-branch) +# / +# A - B - C - D - E (master) +# | \ +# | F - G - H (branch1) +# | \ +# \ I (branch2) # \ -# F - G - H -# \ -# I +# J - K - L - M (no-conflict-branch) # -# where B, D and G touch the same file. +# where A, B, D and G all touch file1, and one, two, three, four all +# touch file "conflict". test_expect_success 'setup' ' - : > file1 && - git add file1 && - test_tick && - git commit -m A && - git tag A && - echo 1 > file1 && - test_tick && - git commit -m B file1 && - : > file2 && - git add file2 && - test_tick && - git commit -m C && - echo 2 > file1 && - test_tick && - git commit -m D file1 && - : > file3 && - git add file3 && - test_tick && - git commit -m E && + test_commit A file1 && + test_commit B file1 && + test_commit C file2 && + test_commit D file1 && + test_commit E file3 && git checkout -b branch1 A && - : > file4 && - git add file4 && - test_tick && - git commit -m F && - git tag F && - echo 3 > file1 && - test_tick && - git commit -m G file1 && - : > file5 && - git add file5 && - test_tick && - git commit -m H && + test_commit F file4 && + test_commit G file1 && + test_commit H file5 && git checkout -b branch2 F && - : > file6 && - git add file6 && - test_tick && - git commit -m I && - git tag I + test_commit I file6 + git checkout -b conflict-branch A && + for n in one two three four + do + test_commit $n conflict + done && + git checkout -b no-conflict-branch A && + for n in J K L M + do + test_commit $n file$n + done ' test_expect_success 'no changes are a nop' ' + git checkout branch2 && git rebase -i F && test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" && test $(git rev-parse I) = $(git rev-parse HEAD) @@ -111,19 +100,20 @@ test_expect_success 'exchange two commits' ' cat > expect << EOF diff --git a/file1 b/file1 -index e69de29..00750ed 100644 +index f70f10e..fd79235 100644 --- a/file1 +++ b/file1 -@@ -0,0 +1 @@ -+3 +@@ -1 +1 @@ +-A ++G EOF cat > expect2 << EOF <<<<<<< HEAD -2 +D ======= -3 ->>>>>>> b7ca976... G +G +>>>>>>> 51047de... G EOF test_expect_success 'stop on conflicting pick' ' @@ -161,7 +151,8 @@ test_expect_success 'squash' ' test_tick && GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 && echo "******************************" && - FAKE_LINES="1 squash 2" git rebase -i --onto master HEAD~2 && + FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=2 \ + git rebase -i --onto master HEAD~2 && test B = $(cat file7) && test $(git rev-parse HEAD^) = $(git rev-parse master) ' @@ -256,30 +247,113 @@ test_expect_success 'verbose flag is heeded, even after --continue' ' test_expect_success 'multi-squash only fires up editor once' ' base=$(git rev-parse HEAD~4) && FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 squash 2 squash 3 squash 4" \ + EXPECT_HEADER_COUNT=4 \ git rebase -i $base && test $base = $(git rev-parse HEAD^) && test 1 = $(git show | grep ONCE | wc -l) ' +test_expect_success 'multi-fixup does not fire up editor' ' + git checkout -b multi-fixup E && + base=$(git rev-parse HEAD~4) && + FAKE_COMMIT_AMEND="NEVER" FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \ + git rebase -i $base && + test $base = $(git rev-parse HEAD^) && + test 0 = $(git show | grep NEVER | wc -l) && + git checkout to-be-rebased && + git branch -D multi-fixup +' + +test_expect_success 'commit message used after conflict' ' + git checkout -b conflict-fixup conflict-branch && + base=$(git rev-parse HEAD~4) && + ( + FAKE_LINES="1 fixup 3 fixup 4" && + export FAKE_LINES && + test_must_fail git rebase -i $base + ) && + echo three > conflict && + git add conflict && + FAKE_COMMIT_AMEND="ONCE" EXPECT_HEADER_COUNT=2 \ + git rebase --continue && + test $base = $(git rev-parse HEAD^) && + test 1 = $(git show | grep ONCE | wc -l) && + git checkout to-be-rebased && + git branch -D conflict-fixup +' + +test_expect_success 'commit message retained after conflict' ' + git checkout -b conflict-squash conflict-branch && + base=$(git rev-parse HEAD~4) && + ( + FAKE_LINES="1 fixup 3 squash 4" && + export FAKE_LINES && + test_must_fail git rebase -i $base + ) && + echo three > conflict && + git add conflict && + FAKE_COMMIT_AMEND="TWICE" EXPECT_HEADER_COUNT=2 \ + git rebase --continue && + test $base = $(git rev-parse HEAD^) && + test 2 = $(git show | grep TWICE | wc -l) && + git checkout to-be-rebased && + git branch -D conflict-squash +' + +cat > expect-squash-fixup << EOF +B + +D + +ONCE +EOF + +test_expect_success 'squash and fixup generate correct log messages' ' + git checkout -b squash-fixup E && + base=$(git rev-parse HEAD~4) && + FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 squash 3 fixup 4" \ + EXPECT_HEADER_COUNT=4 \ + git rebase -i $base && + git cat-file commit HEAD | sed -e 1,/^\$/d > actual-squash-fixup && + test_cmp expect-squash-fixup actual-squash-fixup && + git checkout to-be-rebased && + git branch -D squash-fixup +' + +test_expect_success 'squash ignores comments' ' + git checkout -b skip-comments E && + base=$(git rev-parse HEAD~4) && + FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="# 1 # squash 2 # squash 3 # squash 4 #" \ + EXPECT_HEADER_COUNT=4 \ + git rebase -i $base && + test $base = $(git rev-parse HEAD^) && + test 1 = $(git show | grep ONCE | wc -l) && + git checkout to-be-rebased && + git branch -D skip-comments +' + +test_expect_success 'squash ignores blank lines' ' + git checkout -b skip-blank-lines E && + base=$(git rev-parse HEAD~4) && + FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="> 1 > squash 2 > squash 3 > squash 4 >" \ + EXPECT_HEADER_COUNT=4 \ + git rebase -i $base && + test $base = $(git rev-parse HEAD^) && + test 1 = $(git show | grep ONCE | wc -l) && + git checkout to-be-rebased && + git branch -D skip-blank-lines +' + test_expect_success 'squash works as expected' ' - for n in one two three four - do - echo $n >> file$n && - git add file$n && - git commit -m $n - done && + git checkout -b squash-works no-conflict-branch && one=$(git rev-parse HEAD~3) && - FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 && + FAKE_LINES="1 squash 3 2" EXPECT_HEADER_COUNT=2 \ + git rebase -i HEAD~3 && test $one = $(git rev-parse HEAD~2) ' test_expect_success 'interrupted squash works as expected' ' - for n in one two three four - do - echo $n >> conflict && - git add conflict && - git commit -m $n - done && + git checkout -b interrupted-squash conflict-branch && one=$(git rev-parse HEAD~3) && ( FAKE_LINES="1 squash 3 2" && @@ -296,12 +370,7 @@ test_expect_success 'interrupted squash works as expected' ' ' test_expect_success 'interrupted squash works as expected (case 2)' ' - for n in one two three four - do - echo $n >> conflict && - git add conflict && - git commit -m $n - done && + git checkout -b interrupted-squash2 conflict-branch && one=$(git rev-parse HEAD~3) && ( FAKE_LINES="3 squash 1 2" && @@ -470,4 +539,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/t3408-rebase-multi-line.sh b/t/t3408-rebase-multi-line.sh index e12cd578e8..2062b858bb 100755 --- a/t/t3408-rebase-multi-line.sh +++ b/t/t3408-rebase-multi-line.sh @@ -32,8 +32,8 @@ test_expect_success rebase ' git checkout side && git rebase master && - git cat-file commit HEAD | sed -e "1,/^$/d" >actual && - git cat-file commit side@{1} | sed -e "1,/^$/d" >expect && + git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + git cat-file commit side@{1} | sed -e "1,/^\$/d" >expect && test_cmp expect actual ' 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/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh new file mode 100755 index 0000000000..b63f4e2d67 --- /dev/null +++ b/t/t3415-rebase-autosquash.sh @@ -0,0 +1,73 @@ +#!/bin/sh + +test_description='auto squash' + +. ./test-lib.sh + +test_expect_success setup ' + echo 0 >file0 && + git add . && + test_tick && + git commit -m "initial commit" && + echo 0 >file1 && + echo 2 >file2 && + git add . && + test_tick && + git commit -m "first commit" && + echo 3 >file3 && + git add . && + test_tick && + git commit -m "second commit" && + git tag base +' + +test_expect_success 'auto fixup' ' + git reset --hard base && + echo 1 >file1 && + git add -u && + test_tick && + git commit -m "fixup! first" + + git tag final-fixup && + test_tick && + git rebase --autosquash -i HEAD^^^ && + git log --oneline >actual && + test 3 = $(wc -l <actual) && + git diff --exit-code final-fixup && + test 1 = "$(git cat-file blob HEAD^:file1)" && + test 1 = $(git cat-file commit HEAD^ | grep first | wc -l) +' + +test_expect_success 'auto squash' ' + git reset --hard base && + echo 1 >file1 && + git add -u && + test_tick && + git commit -m "squash! first" + + git tag final-squash && + test_tick && + git rebase --autosquash -i HEAD^^^ && + git log --oneline >actual && + test 3 = $(wc -l <actual) && + git diff --exit-code final-squash && + test 1 = "$(git cat-file blob HEAD^:file1)" && + test 2 = $(git cat-file commit HEAD^ | grep first | wc -l) +' + +test_expect_success 'misspelled auto squash' ' + git reset --hard base && + echo 1 >file1 && + git add -u && + test_tick && + git commit -m "squash! forst" + git tag final-missquash && + test_tick && + git rebase --autosquash -i HEAD^^^ && + git log --oneline >actual && + test 4 = $(wc -l <actual) && + git diff --exit-code final-missquash && + test 0 = $(git rev-list final-missquash...HEAD | wc -l) +' + +test_done diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh new file mode 100755 index 0000000000..ddf2f64853 --- /dev/null +++ b/t/t3416-rebase-onto-threedots.sh @@ -0,0 +1,105 @@ +#!/bin/sh + +test_description='git rebase --onto A...B' + +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-rebase.sh" + +# Rebase only the tip commit of "topic" on merge base between "master" +# and "topic". Cannot do this for "side" with "master" because there +# is no single merge base. +# +# +# F---G topic G' +# / / +# A---B---C---D---E master --> A---B---C---D---E +# \ \ / +# \ x +# \ / \ +# H---I---J---K side + +test_expect_success setup ' + test_commit A && + test_commit B && + git branch side && + test_commit C && + git branch topic && + git checkout side && + test_commit H && + git checkout master && + test_tick && + git merge H && + git tag D && + test_commit E && + git checkout topic && + test_commit F && + test_commit G && + git checkout side && + test_tick && + git merge C && + git tag I && + test_commit J && + test_commit K +' + +test_expect_success 'rebase --onto master...topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + git rebase --onto master...topic F && + git rev-parse HEAD^1 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase --onto master...' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + git rebase --onto master... F && + git rev-parse HEAD^1 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase --onto master...side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + test_must_fail git rebase --onto master...side J +' + +test_expect_success 'rebase -i --onto master...topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + set_fake_editor && + EXPECT_COUNT=1 git rebase -i --onto master...topic F && + git rev-parse HEAD^1 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase -i --onto master...' ' + git reset --hard && + git checkout topic && + git reset --hard G && + set_fake_editor && + EXPECT_COUNT=1 git rebase -i --onto master... F && + git rev-parse HEAD^1 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase -i --onto master...side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + test_must_fail git rebase -i --onto master...side J +' + +test_done diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index bb4cf00d78..7f858151d4 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -66,7 +66,7 @@ test_expect_success 'revert forbidden on dirty working tree' ' echo content >extra_file && git add extra_file && test_must_fail git revert HEAD 2>errors && - grep "Dirty index" errors + grep "Your local changes would be overwritten by " errors ' diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 76b1bb4545..0aaf0ad84b 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -271,4 +271,12 @@ test_expect_success 'choking "git rm" should not let it die with cruft' ' test "$status" != 0 ' +test_expect_success 'rm removes subdirectories recursively' ' + mkdir -p dir/subdir/subsubdir && + echo content >dir/subdir/subsubdir/file && + git add dir/subdir/subsubdir/file && + git rm -f dir/subdir/subsubdir/file && + ! test -d dir +' + test_done diff --git a/t/t3902-quoted.sh b/t/t3902-quoted.sh index 5868052425..29103f65dc 100755 --- a/t/t3902-quoted.sh +++ b/t/t3902-quoted.sh @@ -25,7 +25,7 @@ for_each_name () { for name in \ Name "Name and a${LF}LF" "Name and an${HT}HT" "Name${DQ}" \ "$FN$HT$GN" "$FN$LF$GN" "$FN $GN" "$FN$GN" "$FN$DQ$GN" \ - "With SP in it" + "With SP in it" "$FN/file" do eval "$1" done @@ -33,6 +33,7 @@ for_each_name () { test_expect_success setup ' + mkdir "$FN" && for_each_name "echo initial >\"\$name\"" git add . && git commit -q -m Initial && @@ -54,6 +55,7 @@ With SP in it "\346\277\261\351\207\216\n\347\264\224" "\346\277\261\351\207\216 \347\264\224" "\346\277\261\351\207\216\"\347\264\224" +"\346\277\261\351\207\216/file" "\346\277\261\351\207\216\347\264\224" EOF @@ -67,6 +69,7 @@ With SP in it "濱野\n純" 濱野 純 "濱野\"純" +濱野/file 濱野純 EOF @@ -97,6 +100,13 @@ test_expect_success 'check fully quoted output from diff-tree' ' ' +test_expect_success 'check fully quoted output from ls-tree' ' + + git ls-tree --name-only -r HEAD >current && + test_cmp expect.quoted current + +' + test_expect_success 'setting core.quotepath' ' git config --bool core.quotepath false @@ -130,4 +140,11 @@ test_expect_success 'check fully quoted output from diff-tree' ' ' +test_expect_success 'check fully quoted output from ls-tree' ' + + git ls-tree --name-only -r HEAD >current && + test_cmp expect.raw current + +' + test_done diff --git a/t/t4006-diff-mode.sh b/t/t4006-diff-mode.sh index 8c1b81e248..ff8c2f7532 100755 --- a/t/t4006-diff-mode.sh +++ b/t/t4006-diff-mode.sh @@ -20,8 +20,6 @@ test_expect_success \ 'test_chmod +x rezrov && git diff-index $tree >current' -_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" sed -e 's/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /' <current >check echo ":100644 100755 X X M rezrov" >expected diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh index f64aa48d24..bc46563afc 100755 --- a/t/t4012-diff-binary.sh +++ b/t/t4012-diff-binary.sh @@ -77,10 +77,6 @@ test_expect_success 'apply binary patch' \ tree1=`git write-tree` && test "$tree1" = "$tree0"' -q_to_nul() { - perl -pe 'y/Q/\000/' -} - nul_to_q() { perl -pe 'y/\000/Q/' } 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..f2a2aaa2b9 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -93,9 +93,9 @@ test_expect_success 'extra headers' ' git config --add format.headers "Cc: S. E. Cipient <scipient@example.com> " && git format-patch --stdout master..side > patch2 && - sed -e "/^$/q" patch2 > hdrs2 && - grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs2 && - grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs2 + sed -e "/^\$/q" patch2 > hdrs2 && + grep "^To: R. E. Cipient <rcipient@example.com>\$" hdrs2 && + grep "^Cc: S. E. Cipient <scipient@example.com>\$" hdrs2 ' @@ -104,9 +104,9 @@ test_expect_success 'extra headers without newlines' ' git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" && git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>" && git format-patch --stdout master..side >patch3 && - sed -e "/^$/q" patch3 > hdrs3 && - grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs3 && - grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs3 + sed -e "/^\$/q" patch3 > hdrs3 && + grep "^To: R. E. Cipient <rcipient@example.com>\$" hdrs3 && + grep "^Cc: S. E. Cipient <scipient@example.com>\$" hdrs3 ' @@ -115,32 +115,32 @@ test_expect_success 'extra headers with multiple To:s' ' git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" && git config --add format.headers "To: S. E. Cipient <scipient@example.com>" && git format-patch --stdout master..side > patch4 && - sed -e "/^$/q" patch4 > hdrs4 && - grep "^To: R. E. Cipient <rcipient@example.com>,$" hdrs4 && - grep "^ *S. E. Cipient <scipient@example.com>$" hdrs4 + sed -e "/^\$/q" patch4 > hdrs4 && + grep "^To: R. E. Cipient <rcipient@example.com>,\$" hdrs4 && + grep "^ *S. E. Cipient <scipient@example.com>\$" hdrs4 ' test_expect_success 'additional command line cc' ' git config --replace-all format.headers "Cc: R. E. Cipient <rcipient@example.com>" && - git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch5 && - grep "^Cc: R. E. Cipient <rcipient@example.com>,$" patch5 && - grep "^ *S. E. Cipient <scipient@example.com>$" patch5 + git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch5 && + grep "^Cc: R. E. Cipient <rcipient@example.com>,\$" patch5 && + grep "^ *S. E. Cipient <scipient@example.com>\$" patch5 ' test_expect_success 'command line headers' ' git config --unset-all format.headers && - git format-patch --add-header="Cc: R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch6 && - grep "^Cc: R. E. Cipient <rcipient@example.com>$" patch6 + git format-patch --add-header="Cc: R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch6 && + grep "^Cc: R. E. Cipient <rcipient@example.com>\$" patch6 ' test_expect_success 'configuration headers and command line headers' ' git config --replace-all format.headers "Cc: R. E. Cipient <rcipient@example.com>" && - git format-patch --add-header="Cc: S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch7 && - grep "^Cc: R. E. Cipient <rcipient@example.com>,$" patch7 && - grep "^ *S. E. Cipient <scipient@example.com>$" patch7 + git format-patch --add-header="Cc: S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch7 && + grep "^Cc: R. E. Cipient <rcipient@example.com>,\$" patch7 && + grep "^ *S. E. Cipient <scipient@example.com>\$" patch7 ' test_expect_success 'multiple files' ' @@ -406,9 +406,9 @@ test_expect_success 'cover-letter inherits diff options' ' git mv file foo && git commit -m foo && git format-patch --cover-letter -1 && - ! grep "file => foo .* 0 *$" 0000-cover-letter.patch && + ! grep "file => foo .* 0 *\$" 0000-cover-letter.patch && git format-patch --cover-letter -1 -M && - grep "file => foo .* 0 *$" 0000-cover-letter.patch + grep "file => foo .* 0 *\$" 0000-cover-letter.patch ' @@ -425,7 +425,7 @@ EOF test_expect_success 'shortlog of cover-letter wraps overly-long onelines' ' git format-patch --cover-letter -2 && - sed -e "1,/A U Thor/d" -e "/^$/q" < 0000-cover-letter.patch > output && + sed -e "1,/A U Thor/d" -e "/^\$/q" < 0000-cover-letter.patch > output && test_cmp expect output ' @@ -450,7 +450,7 @@ EOF test_expect_success 'format-patch respects -U' ' git format-patch -U4 -2 && - sed -e "1,/^$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output && + sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output && test_cmp expect output ' @@ -471,7 +471,7 @@ EOF test_expect_success 'format-patch -p suppresses stat' ' git format-patch -p -2 && - sed -e "1,/^$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output && + sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output && test_cmp expect output ' @@ -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/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh index 5cf8924b21..83c1914771 100755 --- a/t/t4027-diff-submodule.sh +++ b/t/t4027-diff-submodule.sh @@ -32,7 +32,8 @@ test_expect_success setup ' cd sub && git rev-list HEAD ) && - echo ":160000 160000 $3 $_z40 M sub" >expect + echo ":160000 160000 $3 $_z40 M sub" >expect && + subtip=$3 subprev=$2 ' test_expect_success 'git diff --raw HEAD' ' @@ -50,6 +51,87 @@ test_expect_success 'git diff-files --raw' ' test_cmp expect actual.files ' +expect_from_to () { + printf "%sSubproject commit %s\n+Subproject commit %s\n" \ + "-" "$1" "$2" +} + +test_expect_success 'git diff HEAD' ' + git diff HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev && + test_cmp expect.body actual.body +' + +test_expect_success 'git diff HEAD with dirty submodule (work tree)' ' + echo >>sub/world && + git diff HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev-dirty && + test_cmp expect.body actual.body +' + +test_expect_success 'git diff HEAD with dirty submodule (index)' ' + ( + cd sub && + git reset --hard && + echo >>world && + git add world + ) && + git diff HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev-dirty && + test_cmp expect.body actual.body +' + +test_expect_success 'git diff HEAD with dirty submodule (untracked)' ' + ( + cd sub && + git reset --hard && + git clean -qfdx && + >cruft + ) && + git diff HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev-dirty && + test_cmp expect.body actual.body +' + +test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)' ' + git commit -m "x" sub && + echo >>sub/world && + git diff HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual.body +' + +test_expect_success 'git diff HEAD with dirty submodule (index, refs match)' ' + ( + cd sub && + git reset --hard && + echo >>world && + git add world + ) && + git diff HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual.body +' + +test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)' ' + ( + cd sub && + git reset --hard && + git clean -qfdx && + >cruft + ) && + git diff HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual.body +' + test_expect_success 'git diff (empty submodule dir)' ' : >empty && rm -rf sub/* sub/.git && diff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh index a3f0897a52..88c5619ae7 100755 --- a/t/t4030-diff-textconv.sh +++ b/t/t4030-diff-textconv.sh @@ -48,7 +48,7 @@ test_expect_success 'file is considered binary by plumbing' ' test_expect_success 'setup textconv filters' ' echo file diff=foo >.gitattributes && - git config diff.foo.textconv "$PWD"/hexdump && + git config diff.foo.textconv "\"$(pwd)\""/hexdump && git config diff.fail.textconv false ' diff --git a/t/t4031-diff-rewrite-binary.sh b/t/t4031-diff-rewrite-binary.sh index a894c60622..7e7b307a24 100755 --- a/t/t4031-diff-rewrite-binary.sh +++ b/t/t4031-diff-rewrite-binary.sh @@ -54,7 +54,7 @@ chmod +x dump test_expect_success 'setup textconv' ' echo file diff=foo >.gitattributes && - git config diff.foo.textconv "$PWD"/dump + git config diff.foo.textconv "\"$(pwd)\""/dump ' test_expect_success 'rewrite diff respects textconv' ' 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/t4038-diff-combined.sh b/t/t4038-diff-combined.sh index 2cf7e01ac2..7584efa36b 100755 --- a/t/t4038-diff-combined.sh +++ b/t/t4038-diff-combined.sh @@ -76,7 +76,7 @@ test_expect_success 'check combined output (1)' ' verify_helper sidewithone ' -test_expect_failure 'check combined output (2)' ' +test_expect_success 'check combined output (2)' ' git show sidesansone -- >sidesansone && verify_helper sidesansone ' 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..464305405a --- /dev/null +++ b/t/t4041-diff-submodule.sh @@ -0,0 +1,327 @@ +#!/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 && +test_expect_success 'submodule is up to date' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +EOF +" + +test_expect_success 'submodule contains untracked content' " + echo new > sm1/new-file && + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head6-dirty: +EOF +" + +test_expect_success 'submodule contains untracked and modifed content' " + echo new > sm1/foo6 && + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head6-dirty: +EOF +" + +test_expect_success 'submodule contains modifed content' " + rm -f sm1/new-file && + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head6-dirty: +EOF +" + +(cd sm1; git commit -mchange foo6 >/dev/null) && +head8=$(cd sm1; git rev-parse --verify HEAD | cut -c1-7) && +test_expect_success 'submodule is modified' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head8: + > change +EOF +" + +test_expect_success 'modified submodule contains untracked content' " + echo new > sm1/new-file && + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head8-dirty: + > change +EOF +" + +test_expect_success 'modified submodule contains untracked and modifed content' " + echo modification >> sm1/foo6 && + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head8-dirty: + > change +EOF +" + +test_expect_success 'modified submodule contains modifed content' " + rm -f sm1/new-file && + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head8-dirty: + > change +EOF +" + +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/t4120-apply-popt.sh b/t/t4120-apply-popt.sh index 83d4ba6798..b463b4f05c 100755 --- a/t/t4120-apply-popt.sh +++ b/t/t4120-apply-popt.sh @@ -22,4 +22,9 @@ test_expect_success 'apply git diff with -p2' ' git apply -p2 patch.file ' +test_expect_success 'apply with too large -p' ' + test_must_fail git apply --stat -p3 patch.file 2>err && + grep "removing 3 leading" err +' + test_done diff --git a/t/t4125-apply-ws-fuzz.sh b/t/t4125-apply-ws-fuzz.sh index 3b471b641b..9671de7999 100755 --- a/t/t4125-apply-ws-fuzz.sh +++ b/t/t4125-apply-ws-fuzz.sh @@ -37,11 +37,11 @@ test_expect_success setup ' # patch-2 is the same as patch-1 but is based # on a version that already has whitespace fixed, # and does not introduce whitespace breakages. - sed -e "s/ $//" patch-1 >patch-2 && + sed -e "s/ \$//" patch-1 >patch-2 && # If all whitespace breakages are fixed the contents # should look like file-fixed - sed -e "s/ $//" file-1 >file-fixed + sed -e "s/ \$//" file-1 >file-fixed ' diff --git a/t/t4133-apply-filenames.sh b/t/t4133-apply-filenames.sh new file mode 100755 index 0000000000..34218071b6 --- /dev/null +++ b/t/t4133-apply-filenames.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# Copyright (c) 2010 Andreas Gruenbacher +# + +test_description='git apply filename consistency check' + +. ./test-lib.sh + +test_expect_success setup ' + cat > bad1.patch <<EOF +diff --git a/f b/f +new file mode 100644 +index 0000000..d00491f +--- /dev/null ++++ b/f-blah +@@ -0,0 +1 @@ ++1 +EOF + cat > bad2.patch <<EOF +diff --git a/f b/f +deleted file mode 100644 +index d00491f..0000000 +--- b/f-blah ++++ /dev/null +@@ -1 +0,0 @@ +-1 +EOF +' + +test_expect_success 'apply diff with inconsistent filenames in headers' ' + test_must_fail git apply bad1.patch 2>err + grep "inconsistent new filename" err + test_must_fail git apply bad2.patch 2>err + grep "inconsistent old filename" err +' + +test_done diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 8296605234..810b04b817 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -83,6 +83,12 @@ test_expect_success setup ' echo "X-Fake-Field: Line Three" && git format-patch --stdout first | sed -e "1d" } > patch1.eml && + { + echo "X-Fake-Field: Line One" && + echo "X-Fake-Field: Line Two" && + echo "X-Fake-Field: Line Three" && + git format-patch --stdout first | sed -e "1d" + } | append_cr >patch1-crlf.eml && sed -n -e "3,\$p" msg >file && git add file && test_tick && @@ -123,6 +129,15 @@ test_expect_success 'am applies patch e-mail not in a mbox' ' test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)" ' +test_expect_success 'am applies patch e-mail not in a mbox with CRLF' ' + git checkout first && + git am patch1-crlf.eml && + ! test -d .git/rebase-apply && + test -z "$(git diff second)" && + test "$(git rev-parse second)" = "$(git rev-parse HEAD)" && + test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)" +' + GIT_AUTHOR_NAME="Another Thor" GIT_AUTHOR_EMAIL="a.thor@example.com" GIT_COMMITTER_NAME="Co M Miter" @@ -287,7 +302,7 @@ test_expect_success 'am --committer-date-is-author-date' ' git checkout first && test_tick && git am --committer-date-is-author-date patch1 && - git cat-file commit HEAD | sed -e "/^$/q" >head1 && + git cat-file commit HEAD | sed -e "/^\$/q" >head1 && at=$(sed -ne "/^author /s/.*> //p" head1) && ct=$(sed -ne "/^committer /s/.*> //p" head1) && test "$at" = "$ct" @@ -297,7 +312,7 @@ test_expect_success 'am without --committer-date-is-author-date' ' git checkout first && test_tick && git am patch1 && - git cat-file commit HEAD | sed -e "/^$/q" >head1 && + git cat-file commit HEAD | sed -e "/^\$/q" >head1 && at=$(sed -ne "/^author /s/.*> //p" head1) && ct=$(sed -ne "/^committer /s/.*> //p" head1) && test "$at" != "$ct" @@ -311,7 +326,7 @@ test_expect_success 'am --ignore-date' ' git checkout first && test_tick && git am --ignore-date patch1 && - git cat-file commit HEAD | sed -e "/^$/q" >head1 && + git cat-file commit HEAD | sed -e "/^\$/q" >head1 && at=$(sed -ne "/^author /s/.*> //p" head1) && echo "$at" | grep "+0000" ' diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index a6bc028a57..bb402c3780 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -217,7 +217,22 @@ test_expect_success 'rerere.autoupdate' ' git checkout version2 && test_must_fail git merge fifth && test 0 = $(git ls-files -u | wc -l) +' +test_expect_success 'merge --rerere-autoupdate' ' + git config --unset rerere.autoupdate + git reset --hard && + git checkout version2 && + test_must_fail git merge --rerere-autoupdate fifth && + test 0 = $(git ls-files -u | wc -l) +' + +test_expect_success 'merge --no-rerere-autoupdate' ' + git config rerere.autoupdate true + git reset --hard && + git checkout version2 && + test_must_fail git merge --no-rerere-autoupdate fifth && + test 2 = $(git ls-files -u | wc -l) ' test_done diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 1e952ca55b..1dc224f6fb 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 @@ -234,7 +255,7 @@ EOF test_expect_success 'log --graph with merge' ' git log --graph --date-order --pretty=tformat:%s | - sed "s/ *$//" >actual && + sed "s/ *\$//" >actual && test_cmp expect actual ' @@ -294,7 +315,7 @@ EOF test_expect_success 'log --graph with full output' ' git log --graph --date-order --pretty=short | git name-rev --name-only --stdin | - sed "s/Merge:.*/Merge: A B/;s/ *$//" >actual && + sed "s/Merge:.*/Merge: A B/;s/ *\$//" >actual && test_cmp expect actual ' @@ -362,7 +383,7 @@ EOF test_expect_success 'log --graph with merge' ' git log --graph --date-order --pretty=tformat:%s | - sed "s/ *$//" >actual && + sed "s/ *\$//" >actual && test_cmp expect actual ' diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 0037f63d91..27bfba55bd 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -189,6 +189,16 @@ test_expect_success 'git archive --format=zip with --output' \ 'git archive --format=zip --output=d2.zip HEAD && test_cmp d.zip d2.zip' +test_expect_success 'git archive with --output, inferring format' ' + git archive --output=d3.zip HEAD && + test_cmp d.zip d3.zip +' + +test_expect_success 'git archive with --output, override inferred format' ' + git archive --format=tar --output=d4.zip HEAD && + test_cmp b.tar d4.zip +' + $UNZIP -v >/dev/null 2>&1 if [ $? -eq 127 ]; then say "Skipping ZIP tests, because unzip was not found" diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index e2aa254eae..7649b810b1 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -16,7 +16,9 @@ test_expect_success \ perl -e "print \"a\" x 4096;" > a && perl -e "print \"b\" x 4096;" > b && perl -e "print \"c\" x 4096;" > c && - git update-index --add a b c && + test-genrandom "seed a" 2097152 > a_big && + test-genrandom "seed b" 2097152 > b_big && + git update-index --add a a_big b b_big c && cat c >d && echo foo >>d && git update-index --add d && tree=`git write-tree` && commit=`git commit-tree $tree </dev/null` && { @@ -280,26 +282,8 @@ test_expect_success \ :' -test_expect_success \ - 'fake a SHA1 hash collision' \ - 'test -f .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67 && - cp -f .git/objects/9d/235ed07cd19811a6ceb342de82f190e49c9f68 \ - .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67' - -test_expect_success \ - 'make sure index-pack detects the SHA1 collision' \ - 'test_must_fail git index-pack -o bad.idx test-3.pack 2>msg && - grep "SHA1 COLLISION FOUND" msg' - -test_expect_success \ - 'honor pack.packSizeLimit' \ - 'git config pack.packSizeLimit 200 && - packname_4=$(git pack-objects test-4 <obj-list) && - test 3 = $(ls test-4-*.pack | wc -l)' - test_expect_success 'unpacking with --strict' ' - git config --unset pack.packsizelimit && for j in a b c d e f g do for i in 0 1 2 3 4 5 6 7 8 9 @@ -392,10 +376,42 @@ test_expect_success 'index-pack with --strict' ' ) ' -test_expect_success 'tolerate absurdly small packsizelimit' ' - git config pack.packSizeLimit 2 && - packname_9=$(git pack-objects test-9 <obj-list) && - test $(wc -l <obj-list) = $(ls test-9-*.pack | wc -l) +test_expect_success 'honor pack.packSizeLimit' ' + git config pack.packSizeLimit 3m && + packname_10=$(git pack-objects test-10 <obj-list) && + test 2 = $(ls test-10-*.pack | wc -l) +' + +test_expect_success 'verify resulting packs' ' + git verify-pack test-10-*.pack +' + +test_expect_success 'tolerate packsizelimit smaller than biggest object' ' + git config pack.packSizeLimit 1 && + packname_11=$(git pack-objects test-11 <obj-list) && + test 5 = $(ls test-11-*.pack | wc -l) ' +test_expect_success 'verify resulting packs' ' + git verify-pack test-11-*.pack +' + +# +# WARNING! +# +# The following test is destructive. Please keep the next +# two tests at the end of this file. +# + +test_expect_success \ + 'fake a SHA1 hash collision' \ + 'test -f .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67 && + cp -f .git/objects/9d/235ed07cd19811a6ceb342de82f190e49c9f68 \ + .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67' + +test_expect_success \ + 'make sure index-pack detects the SHA1 collision' \ + 'test_must_fail git index-pack -o bad.idx test-3.pack 2>msg && + grep "SHA1 COLLISION FOUND" msg' + test_done 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..17bcb0b040 100755 --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh @@ -17,22 +17,22 @@ test_expect_success setup ' commit1=$(echo modify | git commit-tree $tree1 -p $commit0) && git update-ref refs/heads/master $commit0 && git update-ref refs/heads/tofail $commit1 && - git clone ./. victim && - GIT_DIR=victim/.git git update-ref refs/heads/tofail $commit1 && + git clone --bare ./. victim.git && + 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 ' -cat >victim/.git/hooks/pre-receive <<'EOF' +cat >victim.git/hooks/pre-receive <<'EOF' #!/bin/sh printf %s "$@" >>$GIT_DIR/pre-receive.args cat - >$GIT_DIR/pre-receive.stdin echo STDOUT pre-receive echo STDERR pre-receive >&2 EOF -chmod u+x victim/.git/hooks/pre-receive +chmod u+x victim.git/hooks/pre-receive -cat >victim/.git/hooks/update <<'EOF' +cat >victim.git/hooks/update <<'EOF' #!/bin/sh echo "$@" >>$GIT_DIR/update.args read x; printf %s "$x" >$GIT_DIR/update.stdin @@ -40,77 +40,77 @@ echo STDOUT update $1 echo STDERR update $1 >&2 test "$1" = refs/heads/master || exit EOF -chmod u+x victim/.git/hooks/update +chmod u+x victim.git/hooks/update -cat >victim/.git/hooks/post-receive <<'EOF' +cat >victim.git/hooks/post-receive <<'EOF' #!/bin/sh printf %s "$@" >>$GIT_DIR/post-receive.args cat - >$GIT_DIR/post-receive.stdin echo STDOUT post-receive echo STDERR post-receive >&2 EOF -chmod u+x victim/.git/hooks/post-receive +chmod u+x victim.git/hooks/post-receive -cat >victim/.git/hooks/post-update <<'EOF' +cat >victim.git/hooks/post-update <<'EOF' #!/bin/sh echo "$@" >>$GIT_DIR/post-update.args read x; printf %s "$x" >$GIT_DIR/post-update.stdin echo STDOUT post-update echo STDERR post-update >&2 EOF -chmod u+x victim/.git/hooks/post-update +chmod u+x victim.git/hooks/post-update test_expect_success push ' - test_must_fail git send-pack --force ./victim/.git \ + test_must_fail git send-pack --force ./victim.git \ master tofail >send.out 2>send.err ' test_expect_success 'updated as expected' ' - test $(GIT_DIR=victim/.git git rev-parse master) = $commit1 && - test $(GIT_DIR=victim/.git git rev-parse tofail) = $commit1 + test $(GIT_DIR=victim.git git rev-parse master) = $commit1 && + test $(GIT_DIR=victim.git git rev-parse tofail) = $commit1 ' test_expect_success 'hooks ran' ' - test -f victim/.git/pre-receive.args && - test -f victim/.git/pre-receive.stdin && - test -f victim/.git/update.args && - test -f victim/.git/update.stdin && - test -f victim/.git/post-receive.args && - test -f victim/.git/post-receive.stdin && - test -f victim/.git/post-update.args && - test -f victim/.git/post-update.stdin + test -f victim.git/pre-receive.args && + test -f victim.git/pre-receive.stdin && + test -f victim.git/update.args && + test -f victim.git/update.stdin && + test -f victim.git/post-receive.args && + test -f victim.git/post-receive.stdin && + test -f victim.git/post-update.args && + test -f victim.git/post-update.stdin ' test_expect_success 'pre-receive hook input' ' (echo $commit0 $commit1 refs/heads/master; echo $commit1 $commit0 refs/heads/tofail - ) | test_cmp - victim/.git/pre-receive.stdin + ) | test_cmp - victim.git/pre-receive.stdin ' test_expect_success 'update hook arguments' ' (echo refs/heads/master $commit0 $commit1; echo refs/heads/tofail $commit1 $commit0 - ) | test_cmp - victim/.git/update.args + ) | test_cmp - victim.git/update.args ' test_expect_success 'post-receive hook input' ' echo $commit0 $commit1 refs/heads/master | - test_cmp - victim/.git/post-receive.stdin + test_cmp - victim.git/post-receive.stdin ' test_expect_success 'post-update hook arguments' ' echo refs/heads/master | - test_cmp - victim/.git/post-update.args + test_cmp - victim.git/post-update.args ' test_expect_success 'all hook stdin is /dev/null' ' - ! test -s victim/.git/update.stdin && - ! test -s victim/.git/post-update.stdin + ! test -s victim.git/update.stdin && + ! test -s victim.git/post-update.stdin ' test_expect_success 'all *-receive hook args are empty' ' - ! test -s victim/.git/pre-receive.args && - ! test -s victim/.git/post-receive.args + ! test -s victim.git/pre-receive.args && + ! test -s victim.git/post-receive.args ' test_expect_success 'send-pack produced no output' ' @@ -118,20 +118,21 @@ test_expect_success 'send-pack produced no output' ' ' cat <<EOF >expect -STDOUT pre-receive -STDERR pre-receive -STDOUT update refs/heads/master -STDERR update refs/heads/master -STDOUT update refs/heads/tofail -STDERR update refs/heads/tofail -STDOUT post-receive -STDERR post-receive -STDOUT post-update -STDERR post-update +remote: STDOUT pre-receive +remote: STDERR pre-receive +remote: STDOUT update refs/heads/master +remote: STDERR update refs/heads/master +remote: STDOUT update refs/heads/tofail +remote: STDERR update refs/heads/tofail +remote: error: hook declined to update refs/heads/tofail +remote: STDOUT post-receive +remote: STDERR post-receive +remote: STDOUT post-update +remote: STDERR post-update EOF test_expect_success 'send-pack stderr contains hook messages' ' - grep ^STD send.err >actual && - test_cmp - actual <expect + grep ^remote: send.err | sed "s/ *\$//" >actual && + test_cmp expect actual ' test_done 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/t5505-remote.sh b/t/t5505-remote.sh index 852ccb5d7d..a82c5ffa1c 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 @@ -408,6 +419,20 @@ test_expect_success 'update default (overridden, with funny whitespace)' ' ' +test_expect_success 'update (with remotes.default defined)' ' + + (cd one && + for b in $(git branch -r) + do + git branch -r -d $b || break + done && + git config remotes.default "drosophila" && + git remote update && + git branch -r > output && + test_cmp expect output) + +' + test_expect_success '"remote show" does not show symbolic refs' ' git clone one three && @@ -508,5 +533,219 @@ test_expect_success 'show empty remote' ' ) ' -test_done +test_expect_success 'new remote' ' +( + git remote add someremote foo && + echo foo >expect && + git config --get-all remote.someremote.url >actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url bar' ' +( + git remote set-url someremote bar && + echo bar >expect && + git config --get-all remote.someremote.url >actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url baz bar' ' +( + git remote set-url someremote baz bar && + echo baz >expect && + git config --get-all remote.someremote.url >actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url zot bar' ' +( + test_must_fail git remote set-url someremote zot bar && + echo baz >expect && + git config --get-all remote.someremote.url >actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url --push zot baz' ' +( + test_must_fail git remote set-url --push someremote zot baz && + echo "YYY" >expect && + echo baz >>expect && + test_must_fail git config --get-all remote.someremote.pushurl >actual && + echo "YYY" >>actual && + git config --get-all remote.someremote.url >>actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url --push zot' ' +( + git remote set-url --push someremote zot && + echo zot >expect && + echo "YYY" >>expect && + echo baz >>expect && + git config --get-all remote.someremote.pushurl >actual && + echo "YYY" >>actual && + git config --get-all remote.someremote.url >>actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url --push qux zot' ' +( + git remote set-url --push someremote qux zot && + echo qux >expect && + echo "YYY" >>expect && + echo baz >>expect && + git config --get-all remote.someremote.pushurl >actual && + echo "YYY" >>actual && + git config --get-all remote.someremote.url >>actual && + cmp expect actual +) +' +test_expect_success 'remote set-url --push foo qu+x' ' +( + git remote set-url --push someremote foo qu+x && + echo foo >expect && + echo "YYY" >>expect && + echo baz >>expect && + git config --get-all remote.someremote.pushurl >actual && + echo "YYY" >>actual && + git config --get-all remote.someremote.url >>actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url --push --add aaa' ' +( + git remote set-url --push --add someremote aaa && + echo foo >expect && + echo aaa >>expect && + echo "YYY" >>expect && + echo baz >>expect && + git config --get-all remote.someremote.pushurl >actual && + echo "YYY" >>actual && + git config --get-all remote.someremote.url >>actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url --push bar aaa' ' +( + git remote set-url --push someremote bar aaa && + echo foo >expect && + echo bar >>expect && + echo "YYY" >>expect && + echo baz >>expect && + git config --get-all remote.someremote.pushurl >actual && + echo "YYY" >>actual && + git config --get-all remote.someremote.url >>actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url --push --delete bar' ' +( + git remote set-url --push --delete someremote bar && + echo foo >expect && + echo "YYY" >>expect && + echo baz >>expect && + git config --get-all remote.someremote.pushurl >actual && + echo "YYY" >>actual && + git config --get-all remote.someremote.url >>actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url --push --delete foo' ' +( + git remote set-url --push --delete someremote foo && + echo "YYY" >expect && + echo baz >>expect && + test_must_fail git config --get-all remote.someremote.pushurl >actual && + echo "YYY" >>actual && + git config --get-all remote.someremote.url >>actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url --add bbb' ' +( + git remote set-url --add someremote bbb && + echo "YYY" >expect && + echo baz >>expect && + echo bbb >>expect && + test_must_fail git config --get-all remote.someremote.pushurl >actual && + echo "YYY" >>actual && + git config --get-all remote.someremote.url >>actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url --delete .*' ' +( + test_must_fail git remote set-url --delete someremote .* && + echo "YYY" >expect && + echo baz >>expect && + echo bbb >>expect && + test_must_fail git config --get-all remote.someremote.pushurl >actual && + echo "YYY" >>actual && + git config --get-all remote.someremote.url >>actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url --delete bbb' ' +( + git remote set-url --delete someremote bbb && + echo "YYY" >expect && + echo baz >>expect && + test_must_fail git config --get-all remote.someremote.pushurl >actual && + echo "YYY" >>actual && + git config --get-all remote.someremote.url >>actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url --delete baz' ' +( + test_must_fail git remote set-url --delete someremote baz && + echo "YYY" >expect && + echo baz >>expect && + test_must_fail git config --get-all remote.someremote.pushurl >actual && + echo "YYY" >>actual && + git config --get-all remote.someremote.url >>actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url --add ccc' ' +( + git remote set-url --add someremote ccc && + echo "YYY" >expect && + echo baz >>expect && + echo ccc >>expect && + test_must_fail git config --get-all remote.someremote.pushurl >actual && + echo "YYY" >>actual && + git config --get-all remote.someremote.url >>actual && + cmp expect actual +) +' + +test_expect_success 'remote set-url --delete baz' ' +( + git remote set-url --delete someremote baz && + echo "YYY" >expect && + echo ccc >>expect && + test_must_fail git config --get-all remote.someremote.pushurl >actual && + echo "YYY" >>actual && + git config --get-all remote.someremote.url >>actual && + cmp expect actual +) +' + +test_done 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..721821ec92 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -341,4 +341,22 @@ test_expect_success 'fetch into the current branch with --update-head-ok' ' ' +test_expect_success 'fetch --dry-run' ' + + rm -f .git/FETCH_HEAD && + git fetch --dry-run . && + ! test -f .git/FETCH_HEAD +' + +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..0f04b2e894 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 ) } @@ -546,6 +547,32 @@ test_expect_success 'allow deleting an invalid remote ref' ' ' +test_expect_success 'allow deleting a ref using --delete' ' + mk_test heads/master && + (cd testrepo && git config receive.denyDeleteCurrent warn) && + git push testrepo --delete master && + (cd testrepo && test_must_fail git rev-parse --verify refs/heads/master) +' + +test_expect_success 'allow deleting a tag using --delete' ' + mk_test heads/master && + git tag -a -m dummy_message deltag heads/master && + git push testrepo --tags && + (cd testrepo && git rev-parse --verify -q refs/tags/deltag) && + git push testrepo --delete tag deltag && + (cd testrepo && test_must_fail git rev-parse --verify refs/tags/deltag) +' + +test_expect_success 'push --delete without args aborts' ' + mk_test heads/master && + test_must_fail git push testrepo --delete +' + +test_expect_success 'push --delete refuses src:dest refspecs' ' + mk_test heads/master && + test_must_fail git push testrepo --delete master:foo +' + test_expect_success 'warn on push to HEAD of non-bare repository' ' mk_test heads/master (cd testrepo && 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/t5523-push-upstream.sh b/t/t5523-push-upstream.sh new file mode 100755 index 0000000000..00da70763b --- /dev/null +++ b/t/t5523-push-upstream.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='push with --set-upstream' +. ./test-lib.sh + +test_expect_success 'setup bare parent' ' + git init --bare parent && + git remote add upstream parent +' + +test_expect_success 'setup local commit' ' + echo content >file && + git add file && + git commit -m one +' + +check_config() { + (echo $2; echo $3) >expect.$1 + (git config branch.$1.remote + git config branch.$1.merge) >actual.$1 + test_cmp expect.$1 actual.$1 +} + +test_expect_success 'push -u master:master' ' + git push -u upstream master:master && + check_config master upstream refs/heads/master +' + +test_expect_success 'push -u master:other' ' + git push -u upstream master:other && + check_config master upstream refs/heads/other +' + +test_expect_success 'push -u --dry-run master:otherX' ' + git push -u --dry-run upstream master:otherX && + check_config master upstream refs/heads/other +' + +test_expect_success 'push -u master2:master2' ' + git branch master2 && + git push -u upstream master2:master2 && + check_config master2 upstream refs/heads/master2 +' + +test_expect_success 'push -u master2:other2' ' + git push -u upstream master2:other2 && + check_config master2 upstream refs/heads/other2 +' + +test_expect_success 'push -u :master2' ' + git push -u upstream :master2 && + check_config master2 upstream refs/heads/other2 +' + +test_expect_success 'push -u --all' ' + git branch all1 && + git branch all2 && + git push -u --all && + check_config all1 upstream refs/heads/all1 && + check_config all2 upstream refs/heads/all2 +' + +test_expect_success 'push -u HEAD' ' + git checkout -b headbranch && + git push -u upstream HEAD && + check_config headbranch upstream refs/heads/headbranch +' + +test_done diff --git a/t/t5524-pull-msg.sh b/t/t5524-pull-msg.sh new file mode 100755 index 0000000000..8cccecc2fc --- /dev/null +++ b/t/t5524-pull-msg.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +test_description='git pull message generation' + +. ./test-lib.sh + +dollar='$Dollar' + +test_expect_success setup ' + test_commit initial afile original && + git clone . cloned && + ( + cd cloned && + echo added >bfile && + git add bfile && + test_tick && + git commit -m "add bfile" + ) && + test_tick && test_tick && + echo "original $dollar" >afile && + git add afile && + git commit -m "do not clobber $dollar signs" +' + +test_expect_success pull ' +( + cd cloned && + git pull --log && + git log -2 && + git cat-file commit HEAD >result && + grep Dollar result +) +' + +test_done 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..53f54a2789 --- /dev/null +++ b/t/t5541-http-push.sh @@ -0,0 +1,132 @@ +#!/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 +' + +test_expect_success 'non-fast-forward push fails' ' + cd "$ROOT_PATH"/test_repo_clone && + git checkout master && + echo "changed" > path2 && + git commit -a -m path2 --amend && + + HEAD=$(git rev-parse --verify HEAD) && + !(git push -v origin >output 2>&1) && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && + test $HEAD != $(git rev-parse --verify HEAD)) +' + +test_expect_success 'non-fast-forward push show ref status' ' + grep "^ ! \[rejected\][ ]*master -> master (non-fast-forward)$" output +' + +test_expect_success 'non-fast-forward push shows help message' ' + grep "To prevent you from losing history, non-fast-forward updates were rejected" \ + output +' + +test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper' ' + # create a dissimilarly-named remote ref so that git is unable to match the + # two refs (viz. local, remote) unless an explicit refspec is provided. + git push origin master:retsam + + echo "change changed" > path2 && + git commit -a -m path2 --amend && + + # push master too; this ensures there is at least one '"'push'"' command to + # the remote helper and triggers interaction with the helper. + !(git push -v origin +master master:retsam >output 2>&1) && + + grep "^ + [a-f0-9]*\.\.\.[a-f0-9]* *master -> master (forced update)$" output && + grep "^ ! \[rejected\] *master -> retsam (non-fast-forward)$" output && + + grep "To prevent you from losing history, non-fast-forward updates were rejected" \ + output +' + +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..7faa31a299 --- /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-result +> 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-noserver.sh b/t/t5560-http-backend-noserver.sh new file mode 100755 index 0000000000..44885b850c --- /dev/null +++ b/t/t5560-http-backend-noserver.sh @@ -0,0 +1,73 @@ +#!/bin/sh + +test_description='test git-http-backend-noserver' +. ./test-lib.sh + +HTTPD_DOCUMENT_ROOT_PATH="$TRASH_DIRECTORY" + +run_backend() { + echo "$2" | + QUERY_STRING="${1#*\?}" \ + GIT_PROJECT_ROOT="$HTTPD_DOCUMENT_ROOT_PATH" \ + PATH_INFO="${1%%\?*}" \ + git http-backend >act.out 2>act.err +} + +GET() { + export REQUEST_METHOD="GET" && + run_backend "/repo.git/$1" && + unset REQUEST_METHOD && + if ! grep "Status" act.out >act + then + printf "Status: 200 OK\r\n" >act + fi + printf "Status: $2\r\n" >exp && + test_cmp exp act +} + +POST() { + export REQUEST_METHOD="POST" && + export CONTENT_TYPE="application/x-$1-request" && + run_backend "/repo.git/$1" "$2" && + unset REQUEST_METHOD && + unset CONTENT_TYPE && + if ! grep "Status" act.out >act + then + printf "Status: 200 OK\r\n" >act + fi + printf "Status: $3\r\n" >exp && + test_cmp exp act +} + +log_div() { + return 0 +} + +. "$TEST_DIRECTORY"/t556x_common + +expect_aliased() { + export REQUEST_METHOD="GET" && + if test $1 = 0; then + run_backend "$2" + else + run_backend "$2" && + echo "fatal: '$2': aliased" >exp.err && + test_cmp exp.err act.err + fi + unset REQUEST_METHOD +} + +test_expect_success 'http-backend blocks bad PATH_INFO' ' + config http.getanyfile true && + + expect_aliased 0 /repo.git/HEAD && + + expect_aliased 1 /repo.git/../HEAD && + expect_aliased 1 /../etc/passwd && + expect_aliased 1 ../etc/passwd && + expect_aliased 1 /etc//passwd && + expect_aliased 1 /etc/./passwd && + expect_aliased 1 //domain/data.txt +' + +test_done diff --git a/t/t5561-http-backend.sh b/t/t5561-http-backend.sh new file mode 100755 index 0000000000..8c6d0b2f20 --- /dev/null +++ b/t/t5561-http-backend.sh @@ -0,0 +1,149 @@ +#!/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-'5561'} +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +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_DIRECTORY"/t556x_common + +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 + +### no git-daemon-export-ok +### +GET /smart_noexport/repo.git/HEAD HTTP/1.1 404 - +GET /smart_noexport/repo.git/info/refs HTTP/1.1 404 - +GET /smart_noexport/repo.git/objects/info/packs HTTP/1.1 404 - +GET /smart_noexport/repo.git/objects/info/alternates HTTP/1.1 404 - +GET /smart_noexport/repo.git/objects/info/http-alternates HTTP/1.1 404 - +GET /smart_noexport/repo.git/$LOOSE_URL HTTP/1.1 404 - +GET /smart_noexport/repo.git/$PACK_URL HTTP/1.1 404 - +GET /smart_noexport/repo.git/$IDX_URL HTTP/1.1 404 - + +### git-daemon-export-ok +### +GET /smart_noexport/repo.git/HEAD HTTP/1.1 200 +GET /smart_noexport/repo.git/info/refs HTTP/1.1 200 +GET /smart_noexport/repo.git/objects/info/packs HTTP/1.1 200 +GET /smart_noexport/repo.git/objects/info/alternates HTTP/1.1 200 - +GET /smart_noexport/repo.git/objects/info/http-alternates HTTP/1.1 200 - +GET /smart_noexport/repo.git/$LOOSE_URL HTTP/1.1 200 +GET /smart_noexport/repo.git/$PACK_URL HTTP/1.1 200 +GET /smart_noexport/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/t556x_common b/t/t556x_common new file mode 100755 index 0000000000..be024e551c --- /dev/null +++ b/t/t556x_common @@ -0,0 +1,122 @@ +#!/bin/sh + +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 +} + +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" +} + +SMART=smart +export GIT_HTTP_EXPORT_ALL=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" +' +SMART=smart_noexport +unset GIT_HTTP_EXPORT_ALL +test_expect_success 'no export by default' ' + log_div "no git-daemon-export-ok" + get_static_files "404 Not Found" +' +test_expect_success 'export if git-daemon-export-ok' ' + log_div "git-daemon-export-ok" + (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + touch git-daemon-export-ok + ) && + get_static_files "200 OK" +' +SMART=smart +export GIT_HTTP_EXPORT_ALL=1 +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" +' 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/t5702-clone-options.sh b/t/t5702-clone-options.sh index 27825f5f31..02cb024723 100755 --- a/t/t5702-clone-options.sh +++ b/t/t5702-clone-options.sh @@ -27,7 +27,8 @@ test_expect_success 'redirected clone' ' ' test_expect_success 'redirected clone -v' ' - git clone -v "file://$(pwd)/parent" clone-redirected-v >out 2>err && + git clone --progress "file://$(pwd)/parent" clone-redirected-progress \ + >out 2>err && test -s err ' diff --git a/t/t5705-clone-2gb.sh b/t/t5705-clone-2gb.sh index 9f52154cac..adfaae8c5b 100755 --- a/t/t5705-clone-2gb.sh +++ b/t/t5705-clone-2gb.sh @@ -31,7 +31,7 @@ test_expect_success 'setup' ' echo "data 5" && echo ">2gb" && cat commit) | - git fast-import && + git fast-import --big-file-threshold=2 && test ! -f exit-status ' diff --git a/t/t6000lib.sh b/t/t6000lib.sh index f55627b641..985d517a1c 100755..100644 --- a/t/t6000lib.sh +++ b/t/t6000lib.sh @@ -1,3 +1,5 @@ +: included from 6002 and others + [ -d .git/refs/tags ] || mkdir -p .git/refs/tags :> sed.script diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 59d1f6283b..b0047d3c6b 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -19,6 +19,13 @@ test_cmp expect.$1 output.$1 " } +test_format percent %%h <<'EOF' +commit 131a310eb913d107dd3c09a65d1651175898735d +%h +commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 +%h +EOF + test_format hash %H%n%h <<'EOF' commit 131a310eb913d107dd3c09a65d1651175898735d 131a310eb913d107dd3c09a65d1651175898735d @@ -162,4 +169,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/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index 510bb9679f..af34a1e817 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -8,9 +8,6 @@ note () { git tag "$1" } -_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" - unnote () { git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\)) |\1 |g" } 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/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh new file mode 100755 index 0000000000..8d3fa7d014 --- /dev/null +++ b/t/t6018-rev-list-glob.sh @@ -0,0 +1,195 @@ +#!/bin/sh + +test_description='rev-list/rev-parse --glob' + +. ./test-lib.sh + +commit () { + test_tick && + echo $1 > foo && + git add foo && + git commit -m "$1" +} + +compare () { + # Split arguments on whitespace. + git $1 $2 >expected && + git $1 $3 >actual && + test_cmp expected actual +} + +test_expect_success 'setup' ' + + commit master && + git checkout -b subspace/one master && + commit one && + git checkout -b subspace/two master && + commit two && + git checkout -b subspace-x master && + commit subspace-x && + git checkout -b other/three master && + commit three && + git checkout -b someref master && + commit some && + git checkout master && + commit master2 && + git tag foo/bar master && + git update-ref refs/remotes/foo/baz master +' + +test_expect_success 'rev-parse --glob=refs/heads/subspace/*' ' + + compare rev-parse "subspace/one subspace/two" "--glob=refs/heads/subspace/*" + +' + +test_expect_success 'rev-parse --glob=heads/subspace/*' ' + + compare rev-parse "subspace/one subspace/two" "--glob=heads/subspace/*" + +' + +test_expect_success 'rev-parse --glob=refs/heads/subspace/' ' + + compare rev-parse "subspace/one subspace/two" "--glob=refs/heads/subspace/" + +' + +test_expect_success 'rev-parse --glob=heads/subspace/' ' + + compare rev-parse "subspace/one subspace/two" "--glob=heads/subspace/" + +' + +test_expect_success 'rev-parse --glob=heads/subspace' ' + + compare rev-parse "subspace/one subspace/two" "--glob=heads/subspace" + +' + +test_expect_success 'rev-parse --branches=subspace/*' ' + + compare rev-parse "subspace/one subspace/two" "--branches=subspace/*" + +' + +test_expect_success 'rev-parse --branches=subspace/' ' + + compare rev-parse "subspace/one subspace/two" "--branches=subspace/" + +' + +test_expect_success 'rev-parse --branches=subspace' ' + + compare rev-parse "subspace/one subspace/two" "--branches=subspace" + +' + +test_expect_success 'rev-parse --glob=heads/subspace/* --glob=heads/other/*' ' + + compare rev-parse "subspace/one subspace/two other/three" "--glob=heads/subspace/* --glob=heads/other/*" + +' + +test_expect_success 'rev-parse --glob=heads/someref/* master' ' + + compare rev-parse "master" "--glob=heads/someref/* master" + +' + +test_expect_success 'rev-parse --glob=heads/*' ' + + compare rev-parse "master other/three someref subspace-x subspace/one subspace/two" "--glob=heads/*" + +' + +test_expect_success 'rev-parse --tags=foo' ' + + compare rev-parse "foo/bar" "--tags=foo" + +' + +test_expect_success 'rev-parse --remotes=foo' ' + + compare rev-parse "foo/baz" "--remotes=foo" + +' + +test_expect_success 'rev-list --glob=refs/heads/subspace/*' ' + + compare rev-list "subspace/one subspace/two" "--glob=refs/heads/subspace/*" + +' + +test_expect_success 'rev-list --glob=heads/subspace/*' ' + + compare rev-list "subspace/one subspace/two" "--glob=heads/subspace/*" + +' + +test_expect_success 'rev-list --glob=refs/heads/subspace/' ' + + compare rev-list "subspace/one subspace/two" "--glob=refs/heads/subspace/" + +' + +test_expect_success 'rev-list --glob=heads/subspace/' ' + + compare rev-list "subspace/one subspace/two" "--glob=heads/subspace/" + +' + +test_expect_success 'rev-list --glob=heads/subspace' ' + + compare rev-list "subspace/one subspace/two" "--glob=heads/subspace" + +' + +test_expect_success 'rev-list --branches=subspace/*' ' + + compare rev-list "subspace/one subspace/two" "--branches=subspace/*" + +' + +test_expect_success 'rev-list --branches=subspace/' ' + + compare rev-list "subspace/one subspace/two" "--branches=subspace/" + +' + +test_expect_success 'rev-list --branches=subspace' ' + + compare rev-list "subspace/one subspace/two" "--branches=subspace" + +' +test_expect_success 'rev-list --glob=heads/someref/* master' ' + + compare rev-list "master" "--glob=heads/someref/* master" + +' + +test_expect_success 'rev-list --glob=heads/subspace/* --glob=heads/other/*' ' + + compare rev-list "subspace/one subspace/two other/three" "--glob=heads/subspace/* --glob=heads/other/*" + +' + +test_expect_success 'rev-list --glob=heads/*' ' + + compare rev-list "master other/three someref subspace-x subspace/one subspace/two" "--glob=heads/*" + +' + +test_expect_success 'rev-list --tags=foo' ' + + compare rev-list "foo/bar" "--tags=foo" + +' + +test_expect_success 'rev-list --remotes=foo' ' + + compare rev-list "foo/baz" "--remotes=foo" + +' + +test_done diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index 7dcf391914..d605024cf8 100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -64,6 +64,10 @@ cp new1.txt test.txt test_expect_success "merge without conflict" \ "git merge-file test.txt orig.txt new2.txt" +cp new1.txt test.txt +test_expect_success "merge without conflict (--quiet)" \ + "git merge-file --quiet test.txt orig.txt new2.txt" + cp new1.txt test2.txt test_expect_success "merge without conflict (missing LF at EOF)" \ "git merge-file test2.txt orig.txt new2.txt" @@ -146,8 +150,8 @@ test_expect_success 'binary files cannot be merged' ' grep "Cannot merge binary files" merge.err ' -sed -e "s/deerit.$/deerit;/" -e "s/me;$/me./" < new5.txt > new6.txt -sed -e "s/deerit.$/deerit,/" -e "s/me;$/me,/" < new5.txt > new7.txt +sed -e "s/deerit.\$/deerit;/" -e "s/me;\$/me./" < new5.txt > new6.txt +sed -e "s/deerit.\$/deerit,/" -e "s/me;\$/me,/" < new5.txt > new7.txt test_expect_success 'MERGE_ZEALOUS simplifies non-conflicts' ' diff --git a/t/t6026-merge-attr.sh b/t/t6026-merge-attr.sh index 1ba0a25223..5e439972be 100755 --- a/t/t6026-merge-attr.sh +++ b/t/t6026-merge-attr.sh @@ -70,6 +70,18 @@ test_expect_success 'check merge result in working tree' ' ' +test_expect_success 'retry the merge with longer context' ' + echo text conflict-marker-size=32 >>.gitattributes && + git checkout -m text && + sed -ne "/^\([<=>]\)\1\1\1*/{ + s/ .*$// + p + }" >actual text && + grep ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" actual && + grep "================================" actual && + grep "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" actual +' + cat >./custom-merge <<\EOF #!/bin/sh 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/t6029-merge-subtree.sh b/t/t6029-merge-subtree.sh index 5bbfa44e8d..3900d9f61f 100755 --- a/t/t6029-merge-subtree.sh +++ b/t/t6029-merge-subtree.sh @@ -52,6 +52,7 @@ test_expect_success 'initial merge' ' git merge -s ours --no-commit gui/master && git read-tree --prefix=git-gui/ -u gui/master && git commit -m "Merge git-gui as our subdirectory" && + git checkout -b work && git ls-files -s >actual && ( echo "100644 $o1 0 git-gui/git-gui.sh" @@ -65,9 +66,10 @@ test_expect_success 'merge update' ' echo git-gui2 > git-gui.sh && o3=$(git hash-object git-gui.sh) && git add git-gui.sh && + git checkout -b master2 && git commit -m "update git-gui" && cd ../git && - git pull -s subtree gui master && + git pull -s subtree gui master2 && git ls-files -s >actual && ( echo "100644 $o3 0 git-gui/git-gui.sh" @@ -76,4 +78,47 @@ test_expect_success 'merge update' ' test_cmp expected actual ' +test_expect_success 'initial ambiguous subtree' ' + cd ../git && + git reset --hard master && + git checkout -b master2 && + git merge -s ours --no-commit gui/master && + git read-tree --prefix=git-gui2/ -u gui/master && + git commit -m "Merge git-gui2 as our subdirectory" && + git checkout -b work2 && + git ls-files -s >actual && + ( + echo "100644 $o1 0 git-gui/git-gui.sh" + echo "100644 $o1 0 git-gui2/git-gui.sh" + echo "100644 $o2 0 git.c" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'merge using explicit' ' + cd ../git && + git reset --hard master2 && + git pull -Xsubtree=git-gui gui master2 && + git ls-files -s >actual && + ( + echo "100644 $o3 0 git-gui/git-gui.sh" + echo "100644 $o1 0 git-gui2/git-gui.sh" + echo "100644 $o2 0 git.c" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'merge2 using explicit' ' + cd ../git && + git reset --hard master2 && + git pull -Xsubtree=git-gui2 gui master2 && + git ls-files -s >actual && + ( + echo "100644 $o1 0 git-gui/git-gui.sh" + echo "100644 $o3 0 git-gui2/git-gui.sh" + echo "100644 $o2 0 git.c" + ) >expected && + test_cmp expected actual +' + test_done diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index dc9e8d030a..3b042aacd6 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/t6033-merge-crlf.sh b/t/t6033-merge-crlf.sh index 75d9602de4..e8d65eefb5 100755 --- a/t/t6033-merge-crlf.sh +++ b/t/t6033-merge-crlf.sh @@ -1,13 +1,5 @@ #!/bin/sh -append_cr () { - sed -e 's/$/Q/' | tr Q '\015' -} - -remove_cr () { - tr '\015' Q | sed -e 's/Q$//' -} - test_description='merge conflict in crlf repo b---M diff --git a/t/t6035-merge-dir-to-symlink.sh b/t/t6035-merge-dir-to-symlink.sh index 5b96fb0b37..3202e1de6d 100755 --- a/t/t6035-merge-dir-to-symlink.sh +++ b/t/t6035-merge-dir-to-symlink.sh @@ -74,7 +74,7 @@ test_expect_success 'setup a merge where dir a/b-2 changed to symlink' ' git tag test2 ' -test_expect_failure 'merge should not have conflicts (resolve)' ' +test_expect_success 'merge should not have conflicts (resolve)' ' git reset --hard && git checkout baseline^0 && git merge -s resolve test2 && diff --git a/t/t6037-merge-ours-theirs.sh b/t/t6037-merge-ours-theirs.sh new file mode 100755 index 0000000000..8ab3d61f44 --- /dev/null +++ b/t/t6037-merge-ours-theirs.sh @@ -0,0 +1,64 @@ +#!/bin/sh + +test_description='Merge-recursive ours and theirs variants' +. ./test-lib.sh + +test_expect_success setup ' + for i in 1 2 3 4 5 6 7 8 9 + do + echo "$i" + done >file && + git add file && + cp file elif && + git commit -m initial && + + sed -e "s/1/one/" -e "s/9/nine/" >file <elif && + git commit -a -m ours && + + git checkout -b side HEAD^ && + + sed -e "s/9/nueve/" >file <elif && + git commit -a -m theirs && + + git checkout master^0 +' + +test_expect_success 'plain recursive - should conflict' ' + git reset --hard master && + test_must_fail git merge -s recursive side && + grep nine file && + grep nueve file && + ! grep 9 file && + grep one file && + ! grep 1 file +' + +test_expect_success 'recursive favouring theirs' ' + git reset --hard master && + git merge -s recursive -Xtheirs side && + ! grep nine file && + grep nueve file && + ! grep 9 file && + grep one file && + ! grep 1 file +' + +test_expect_success 'recursive favouring ours' ' + git reset --hard master && + git merge -s recursive -X ours side && + grep nine file && + ! grep nueve file && + ! grep 9 file && + grep one file && + ! grep 1 file +' + +test_expect_success 'pull with -X' ' + git reset --hard master && git pull -s recursive -Xours . side && + git reset --hard master && git pull -s recursive -X ours . side && + git reset --hard master && git pull -s recursive -Xtheirs . side && + git reset --hard master && git pull -s recursive -X theirs . side && + git reset --hard master && ! git pull -s recursive -X bork . side +' + +test_done diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index 00e1de9627..1785e178a4 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 ' @@ -89,4 +89,25 @@ test_expect_success 'status when tracking annotated tags' ' grep "set up to track" actual && git checkout heavytrack ' + +test_expect_success 'setup tracking with branch --set-upstream on existing branch' ' + git branch from-master master && + test_must_fail git config branch.from-master.merge > actual && + git branch --set-upstream from-master master && + git config branch.from-master.merge > actual && + grep -q "^refs/heads/master$" actual +' + +test_expect_success '--set-upstream does not change branch' ' + git branch from-master2 master && + test_must_fail git config branch.from-master2.merge > actual && + git rev-list from-master2 && + git update-ref refs/heads/from-master2 from-master2^ && + git rev-parse from-master2 >expect2 && + git branch --set-upstream from-master2 master && + git config branch.from-master.merge > actual && + git rev-parse from-master2 >actual2 && + grep -q "^refs/heads/master$" actual && + cmp expect2 actual2 +' test_done 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/t7001-mv.sh b/t/t7001-mv.sh index 10b8f8c44b..65a35d94a0 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -189,6 +189,18 @@ test_expect_success 'absolute pathname outside should fail' '( )' +test_expect_success 'git mv to move multiple sources into a directory' ' + rm -fr .git && git init && + mkdir dir other && + >dir/a.txt && + >dir/b.txt && + git add dir/?.txt && + git mv dir/a.txt dir/b.txt other && + git ls-files >actual && + { echo other/a.txt; echo other/b.txt; } >expect && + test_cmp expect actual +' + test_expect_success 'git mv should not change sha1 of moved cache entry' ' rm -fr .git && diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index 3a103fec96..ebae1522c8 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 ' @@ -286,6 +291,14 @@ y:y yy z:zzz EOF +test_expect_success 'grep -q, silently report matches' ' + >empty && + git grep -q mmap >actual && + test_cmp empty actual && + test_must_fail git grep -q qfwfq >actual && + test_cmp empty actual +' + # Create 1024 file names that sort between "y" and "z" to make sure # the two files are handled by different calls to an external grep. # This depends on MAXARGS in builtin-grep.c being 1024 or less. @@ -297,8 +310,8 @@ test_expect_success 'grep -C1, hunk mark between files' ' test_cmp expected actual ' -test_expect_success 'grep -C1 --no-ext-grep, hunk mark between files' ' - git grep -C1 --no-ext-grep "^[yz]" >actual && +test_expect_success 'grep -C1 hunk mark between files' ' + git grep -C1 "^[yz]" >actual && test_cmp expected actual ' @@ -354,7 +367,7 @@ test_expect_success 'log grep (6)' ' test_expect_success 'grep with CE_VALID file' ' git update-index --assume-unchanged t/t && rm t/t && - test "$(git grep --no-ext-grep test)" = "t/t:test" && + test "$(git grep test)" = "t/t:test" && git update-index --no-assume-unchanged t/t && git checkout t/t ' @@ -421,4 +434,89 @@ test_expect_success 'grep -Fi' ' test_cmp expected actual ' +test_expect_success 'outside of git repository' ' + rm -fr non && + mkdir -p non/git/sub && + echo hello >non/git/file1 && + echo world >non/git/sub/file2 && + echo ".*o*" >non/git/.gitignore && + { + echo file1:hello && + echo sub/file2:world + } >non/expect.full && + echo file2:world >non/expect.sub + ( + GIT_CEILING_DIRECTORIES="$(pwd)/non/git" && + export GIT_CEILING_DIRECTORIES && + cd non/git && + test_must_fail git grep o && + git grep --no-index o >../actual.full && + test_cmp ../expect.full ../actual.full + cd sub && + test_must_fail git grep o && + git grep --no-index o >../../actual.sub && + test_cmp ../../expect.sub ../../actual.sub + ) +' + +test_expect_success 'inside git repository but with --no-index' ' + rm -fr is && + mkdir -p is/git/sub && + echo hello >is/git/file1 && + echo world >is/git/sub/file2 && + echo ".*o*" >is/git/.gitignore && + { + echo file1:hello && + echo sub/file2:world + } >is/expect.full && + : >is/expect.empty && + echo file2:world >is/expect.sub + ( + cd is/git && + git init && + test_must_fail git grep o >../actual.full && + test_cmp ../expect.empty ../actual.full && + git grep --no-index o >../actual.full && + test_cmp ../expect.full ../actual.full && + cd sub && + test_must_fail git grep o >../../actual.sub && + test_cmp ../../expect.empty ../../actual.sub && + git grep --no-index o >../../actual.sub && + test_cmp ../../expect.sub ../../actual.sub + ) +' + +test_expect_success 'setup double-dash tests' ' +cat >double-dash <<EOF && +-- +-> +other +EOF +git add double-dash +' + +cat >expected <<EOF +double-dash:-> +EOF +test_expect_success 'grep -- pattern' ' + git grep -- "->" >actual && + test_cmp expected actual +' +test_expect_success 'grep -- pattern -- pathspec' ' + git grep -- "->" -- double-dash >actual && + test_cmp expected actual +' +test_expect_success 'grep -e pattern -- path' ' + git grep -e "->" -- double-dash >actual && + test_cmp expected actual +' + +cat >expected <<EOF +double-dash:-- +EOF +test_expect_success 'grep -e -- -- path' ' + git grep -e -- -- double-dash >actual && + test_cmp expected actual +' + test_done diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index 329c851685..0da13a8d6b 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -288,4 +288,61 @@ 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_expect_success 'setup submodule' ' + rm -fr ?* .git && + git init && + test_commit file && + mkdir submod && + submodurl="$PWD/submod" && + ( cd submod && + git init && + test_commit file-in-submod ) && + git submodule add "$submodurl" && + git commit -m "added submodule" && + test_commit add-file && + ( cd submod && test_commit add-in-submodule ) && + git add submod && + git commit -m "changed submodule" && + git branch original HEAD +' + +orig_head=`git show-ref --hash --head HEAD` + +test_expect_success 'rewrite submodule with another content' ' + git filter-branch --tree-filter "test -d submod && { + rm -rf submod && + git rm -rf --quiet submod && + mkdir submod && + : > submod/file + } || :" HEAD && + test $orig_head != `git show-ref --hash --head HEAD` +' + +test_expect_success 'replace submodule revision' ' + git reset --hard original && + git filter-branch -f --tree-filter \ + "if git ls-files --error-unmatch -- submod > /dev/null 2>&1 + then git update-index --cacheinfo 160000 0123456789012345678901234567890123456789 submod + fi" HEAD && + test $orig_head != `git show-ref --hash --head HEAD` +' + 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/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh new file mode 100755 index 0000000000..bb4066f767 --- /dev/null +++ b/t/t7011-skip-worktree-reading.sh @@ -0,0 +1,163 @@ +#!/bin/sh +# +# Copyright (c) 2008 Nguyễn Thái Ngọc Duy +# + +test_description='skip-worktree bit test' + +. ./test-lib.sh + +cat >expect.full <<EOF +H 1 +H 2 +H init.t +H sub/1 +H sub/2 +EOF + +cat >expect.skip <<EOF +S 1 +H 2 +H init.t +S sub/1 +H sub/2 +EOF + +NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +ZERO_SHA0=0000000000000000000000000000000000000000 +setup_absent() { + test -f 1 && rm 1 + git update-index --remove 1 && + git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --skip-worktree 1 +} + +test_absent() { + echo "100644 $NULL_SHA1 0 1" > expected && + git ls-files --stage 1 > result && + test_cmp expected result && + test ! -f 1 +} + +setup_dirty() { + git update-index --force-remove 1 && + echo dirty > 1 && + git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --skip-worktree 1 +} + +test_dirty() { + echo "100644 $NULL_SHA1 0 1" > expected && + git ls-files --stage 1 > result && + test_cmp expected result && + echo dirty > expected + test_cmp expected 1 +} + +test_expect_success 'setup' ' + test_commit init && + mkdir sub && + touch ./1 ./2 sub/1 sub/2 && + git add 1 2 sub/1 sub/2 && + git update-index --skip-worktree 1 sub/1 && + git ls-files -t > result && + test_cmp expect.skip result +' + +test_expect_success 'update-index' ' + setup_absent && + git update-index 1 && + test_absent +' + +test_expect_success 'update-index' ' + setup_dirty && + git update-index 1 && + test_dirty +' + +test_expect_success 'update-index --remove' ' + setup_absent && + git update-index --remove 1 && + test -z "$(git ls-files 1)" && + test ! -f 1 +' + +test_expect_success 'update-index --remove' ' + setup_dirty && + git update-index --remove 1 && + test -z "$(git ls-files 1)" && + echo dirty > expected && + test_cmp expected 1 +' + +test_expect_success 'ls-files --delete' ' + setup_absent && + test -z "$(git ls-files -d)" +' + +test_expect_success 'ls-files --delete' ' + setup_dirty && + test -z "$(git ls-files -d)" +' + +test_expect_success 'ls-files --modified' ' + setup_absent && + test -z "$(git ls-files -m)" +' + +test_expect_success 'ls-files --modified' ' + setup_dirty && + test -z "$(git ls-files -m)" +' + +test_expect_success 'grep with skip-worktree file' ' + git update-index --no-skip-worktree 1 && + echo test > 1 && + git update-index 1 && + git update-index --skip-worktree 1 && + rm 1 && + test "$(git grep --no-ext-grep test)" = "1:test" +' + +echo ":000000 100644 $ZERO_SHA0 $NULL_SHA1 A 1" > expected +test_expect_success 'diff-index does not examine skip-worktree absent entries' ' + setup_absent && + git diff-index HEAD -- 1 > result && + test_cmp expected result +' + +test_expect_success 'diff-index does not examine skip-worktree dirty entries' ' + setup_dirty && + git diff-index HEAD -- 1 > result && + test_cmp expected result +' + +test_expect_success 'diff-files does not examine skip-worktree absent entries' ' + setup_absent && + test -z "$(git diff-files -- one)" +' + +test_expect_success 'diff-files does not examine skip-worktree dirty entries' ' + setup_dirty && + test -z "$(git diff-files -- one)" +' + +test_expect_success 'git-rm succeeds on skip-worktree absent entries' ' + setup_absent && + git rm 1 +' + +test_expect_success 'commit on skip-worktree absent entries' ' + git reset && + setup_absent && + test_must_fail git commit -m null 1 +' + +test_expect_success 'commit on skip-worktree dirty entries' ' + git reset && + setup_dirty && + test_must_fail git commit -m null 1 +' + +test_done diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh new file mode 100755 index 0000000000..8d8b1c0e25 --- /dev/null +++ b/t/t7012-skip-worktree-writing.sh @@ -0,0 +1,146 @@ +#!/bin/sh +# +# Copyright (c) 2008 Nguyễn Thái Ngọc Duy +# + +test_description='test worktree writing operations when skip-worktree is used' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit init && + echo modified >> init.t && + touch added && + git add init.t added && + git commit -m "modified and added" && + git tag top +' + +test_expect_success 'read-tree updates worktree, absent case' ' + git checkout -f top && + git update-index --skip-worktree init.t && + rm init.t && + git read-tree -m -u HEAD^ && + echo init > expected && + test_cmp expected init.t +' + +test_expect_success 'read-tree updates worktree, dirty case' ' + git checkout -f top && + git update-index --skip-worktree init.t && + echo dirty >> init.t && + test_must_fail git read-tree -m -u HEAD^ && + grep -q dirty init.t && + test "$(git ls-files -t init.t)" = "S init.t" && + git update-index --no-skip-worktree init.t +' + +test_expect_success 'read-tree removes worktree, absent case' ' + git checkout -f top && + git update-index --skip-worktree added && + rm added && + git read-tree -m -u HEAD^ && + test ! -f added +' + +test_expect_success 'read-tree removes worktree, dirty case' ' + git checkout -f top && + git update-index --skip-worktree added && + echo dirty >> added && + test_must_fail git read-tree -m -u HEAD^ && + grep -q dirty added && + test "$(git ls-files -t added)" = "S added" && + git update-index --no-skip-worktree added +' + +NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +ZERO_SHA0=0000000000000000000000000000000000000000 +setup_absent() { + test -f 1 && rm 1 + git update-index --remove 1 && + git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --skip-worktree 1 +} + +test_absent() { + echo "100644 $NULL_SHA1 0 1" > expected && + git ls-files --stage 1 > result && + test_cmp expected result && + test ! -f 1 +} + +setup_dirty() { + git update-index --force-remove 1 && + echo dirty > 1 && + git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --skip-worktree 1 +} + +test_dirty() { + echo "100644 $NULL_SHA1 0 1" > expected && + git ls-files --stage 1 > result && + test_cmp expected result && + echo dirty > expected + test_cmp expected 1 +} + +cat >expected <<EOF +S 1 +H 2 +H init.t +S sub/1 +H sub/2 +EOF + +test_expect_success 'index setup' ' + git checkout -f init && + mkdir sub && + touch ./1 ./2 sub/1 sub/2 && + git add 1 2 sub/1 sub/2 && + git update-index --skip-worktree 1 sub/1 && + git ls-files -t > result && + test_cmp expected result +' + +test_expect_success 'git-add ignores worktree content' ' + setup_absent && + git add 1 && + test_absent +' + +test_expect_success 'git-add ignores worktree content' ' + setup_dirty && + git add 1 && + test_dirty +' + +test_expect_success 'git-rm fails if worktree is dirty' ' + setup_dirty && + test_must_fail git rm 1 && + test_dirty +' + +cat >expected <<EOF +Would remove expected +Would remove result +EOF +test_expect_success 'git-clean, absent case' ' + setup_absent && + git clean -n > result && + test_cmp expected result +' + +test_expect_success 'git-clean, dirty case' ' + setup_dirty && + git clean -n > result && + test_cmp expected result +' + +test_expect_failure 'git-apply adds file' false +test_expect_failure 'git-apply updates file' false +test_expect_failure 'git-apply removes file' false +test_expect_failure 'git-mv to skip-worktree' false +test_expect_failure 'git-mv from skip-worktree' false +test_expect_failure 'git-checkout' false + +test_done 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/t7110-reset-merge.sh b/t/t7110-reset-merge.sh new file mode 100755 index 0000000000..8704d00196 --- /dev/null +++ b/t/t7110-reset-merge.sh @@ -0,0 +1,183 @@ +#!/bin/sh +# +# Copyright (c) 2009 Christian Couder +# + +test_description='Tests for "git reset --merge"' + +. ./test-lib.sh + +test_expect_success setup ' + for i in 1 2 3; do echo line $i; done >file1 && + cat file1 >file2 && + git add file1 file2 && + test_tick && + git commit -m "Initial commit" && + git tag initial && + echo line 4 >>file1 && + cat file1 >file2 && + test_tick && + git commit -m "add line 4 to file1" file1 && + git tag second +' + +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- +# file1: C C C D --merge D D D +# file2: C D D D --merge C D D +test_expect_success 'reset --merge is ok with changes in file it does not touch' ' + git reset --merge HEAD^ && + ! grep 4 file1 && + grep 4 file2 && + test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" && + test -z "$(git diff --cached)" +' + +test_expect_success 'reset --merge is ok when switching back' ' + git reset --merge second && + grep 4 file1 && + grep 4 file2 && + test "$(git rev-parse HEAD)" = "$(git rev-parse second)" && + test -z "$(git diff --cached)" +' + +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- +# file1: B B C D --merge D D D +# file2: C D D D --merge C D D +test_expect_success 'reset --merge discards changes added to index (1)' ' + git reset --hard second && + cat file1 >file2 && + echo "line 5" >> file1 && + git add file1 && + git reset --merge HEAD^ && + ! grep 4 file1 && + ! grep 5 file1 && + grep 4 file2 && + test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" && + test -z "$(git diff --cached)" +' + +test_expect_success 'reset --merge is ok again when switching back (1)' ' + git reset --hard initial && + echo "line 5" >> file2 && + git add file2 && + git reset --merge second && + ! grep 4 file2 && + ! grep 5 file1 && + grep 4 file1 && + test "$(git rev-parse HEAD)" = "$(git rev-parse second)" && + test -z "$(git diff --cached)" +' + +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- +# file1: C C C D --merge D D D +# file2: C C D D --merge D D D +test_expect_success 'reset --merge discards changes added to index (2)' ' + git reset --hard second && + echo "line 4" >> file2 && + git add file2 && + git reset --merge HEAD^ && + ! grep 4 file2 && + test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" && + test -z "$(git diff)" && + test -z "$(git diff --cached)" +' + +test_expect_success 'reset --merge is ok again when switching back (2)' ' + git reset --hard initial && + git reset --merge second && + ! grep 4 file2 && + grep 4 file1 && + test "$(git rev-parse HEAD)" = "$(git rev-parse second)" && + test -z "$(git diff --cached)" +' + +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- +# file1: A B B C --merge (disallowed) +test_expect_success 'reset --merge fails with changes in file it touches' ' + git reset --hard second && + echo "line 5" >> file1 && + test_tick && + git commit -m "add line 5" file1 && + sed -e "s/line 1/changed line 1/" <file1 >file3 && + mv file3 file1 && + test_must_fail git reset --merge HEAD^ 2>err.log && + grep file1 err.log | grep "not uptodate" +' + +test_expect_success 'setup 3 different branches' ' + git reset --hard second && + git branch branch1 && + git branch branch2 && + git branch branch3 && + git checkout branch1 && + echo "line 5 in branch1" >> file1 && + test_tick && + git commit -a -m "change in branch1" && + git checkout branch2 && + echo "line 5 in branch2" >> file1 && + test_tick && + git commit -a -m "change in branch2" && + git tag third && + git checkout branch3 && + echo a new file >file3 && + rm -f file1 && + git add file3 && + test_tick && + git commit -a -m "change in branch3" +' + +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- +# file1: X U B C --merge C C C +test_expect_success '"reset --merge HEAD^" is ok with pending merge' ' + git checkout third && + test_must_fail git merge branch1 && + git reset --merge HEAD^ && + test "$(git rev-parse HEAD)" = "$(git rev-parse second)" && + test -z "$(git diff --cached)" && + test -z "$(git diff)" +' + +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- +# file1: X U B B --merge B B B +test_expect_success '"reset --merge HEAD" is ok with pending merge' ' + git reset --hard third && + test_must_fail git merge branch1 && + git reset --merge HEAD && + test "$(git rev-parse HEAD)" = "$(git rev-parse third)" && + test -z "$(git diff --cached)" && + test -z "$(git diff)" +' + +test_expect_success '--merge with added/deleted' ' + git reset --hard third && + rm -f file2 && + test_must_fail git merge branch3 && + ! test -f file2 && + test -f file3 && + git diff --exit-code file3 && + git diff --exit-code branch3 file3 && + git reset --merge HEAD && + ! test -f file3 && + ! test -f file2 && + git diff --exit-code --cached +' + +test_done diff --git a/t/t7111-reset-table.sh b/t/t7111-reset-table.sh new file mode 100755 index 0000000000..de896c948d --- /dev/null +++ b/t/t7111-reset-table.sh @@ -0,0 +1,113 @@ +#!/bin/sh +# +# Copyright (c) 2010 Christian Couder +# + +test_description='Tests to check that "reset" options follow a known table' + +. ./test-lib.sh + + +test_expect_success 'creating initial commits' ' + test_commit E file1 && + test_commit D file1 && + test_commit C file1 +' + +while read W1 I1 H1 T opt W2 I2 H2 +do + test_expect_success "check: $W1 $I1 $H1 $T --$opt $W2 $I2 $H2" ' + git reset --hard C && + if test "$I1" != "$H1" + then + echo "$I1" >file1 && + git add file1 + fi && + if test "$W1" != "$I1" + then + echo "$W1" >file1 + fi && + if test "$W2" != "XXXXX" + then + git reset --$opt $T && + test "$(cat file1)" = "$W2" && + git checkout-index -f -- file1 && + test "$(cat file1)" = "$I2" && + git checkout -f HEAD -- file1 && + test "$(cat file1)" = "$H2" + else + test_must_fail git reset --$opt $T + fi + ' +done <<\EOF +A B C D soft A B D +A B C D mixed A D D +A B C D hard D D D +A B C D merge XXXXX +A B C C soft A B C +A B C C mixed A C C +A B C C hard C C C +A B C C merge XXXXX +B B C D soft B B D +B B C D mixed B D D +B B C D hard D D D +B B C D merge D D D +B B C C soft B B C +B B C C mixed B C C +B B C C hard C C C +B B C C merge C C C +B C C D soft B C D +B C C D mixed B D D +B C C D hard D D D +B C C D merge XXXXX +B C C C soft B C C +B C C C mixed B C C +B C C C hard C C C +B C C C merge B C C +EOF + +test_expect_success 'setting up branches to test with unmerged entries' ' + git reset --hard C && + git branch branch1 && + git branch branch2 && + git checkout branch1 && + test_commit B1 file1 && + git checkout branch2 && + test_commit B file1 +' + +while read W1 I1 H1 T opt W2 I2 H2 +do + test_expect_success "check: $W1 $I1 $H1 $T --$opt $W2 $I2 $H2" ' + git reset --hard B && + test_must_fail git merge branch1 && + cat file1 >X_file1 && + if test "$W2" != "XXXXX" + then + git reset --$opt $T && + if test "$W2" = "X" + then + test_cmp file1 X_file1 + else + test "$(cat file1)" = "$W2" + fi && + git checkout-index -f -- file1 && + test "$(cat file1)" = "$I2" && + git checkout -f HEAD -- file1 && + test "$(cat file1)" = "$H2" + else + test_must_fail git reset --$opt $T + fi + ' +done <<\EOF +X U B C soft XXXXX +X U B C mixed X C C +X U B C hard C C C +X U B C merge C C C +X U B B soft XXXXX +X U B B mixed X B B +X U B B hard B B B +X U B B merge B B B +EOF + +test_done diff --git a/t/t7201-co.sh b/t/t7201-co.sh index ebfd34df36..d20ed61b48 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -166,19 +166,31 @@ test_expect_success 'checkout -m with merge conflict' ' ! test -s current ' -test_expect_success 'checkout to detach HEAD' ' +test_expect_success 'checkout to detach HEAD (with advice declined)' ' + git config advice.detachedHead false && git checkout -f renamer && git clean -f && git checkout renamer^ 2>messages && - (cat >messages.expect <<EOF -Note: moving to '\''renamer^'\'' which isn'\''t a local branch -If you want to create a new branch from this checkout, you may do so -(now or later) by using -b with the checkout command again. Example: - git checkout -b <new_branch_name> -HEAD is now at 7329388... Initial A one, A two -EOF -) && - test_cmp messages.expect messages && + grep "HEAD is now at 7329388" messages && + test 1 -eq $(wc -l <messages) && + H=$(git rev-parse --verify HEAD) && + M=$(git show-ref -s --verify refs/heads/master) && + test "z$H" = "z$M" && + if git symbolic-ref HEAD >/dev/null 2>&1 + then + echo "OOPS, HEAD is still symbolic???" + false + else + : happy + fi +' + +test_expect_success 'checkout to detach HEAD' ' + git config advice.detachedHead true && + git checkout -f renamer && git clean -f && + git checkout renamer^ 2>messages && + grep "HEAD is now at 7329388" messages && + test 1 -lt $(wc -l <messages) && H=$(git rev-parse --verify HEAD) && M=$(git show-ref -s --verify refs/heads/master) && test "z$H" = "z$M" && @@ -542,4 +554,61 @@ test_expect_success 'switch out of non-branch' ' ! grep "^Previous HEAD" error.log ' +( + echo "#!$SHELL_PATH" + cat <<\EOF +O=$1 A=$2 B=$3 +cat "$A" >.tmp +exec >"$A" +echo '<<<<<<< filfre-theirs' +cat "$B" +echo '||||||| filfre-common' +cat "$O" +echo '=======' +cat ".tmp" +echo '>>>>>>> filfre-ours' +rm -f .tmp +exit 1 +EOF +) >filfre.sh +chmod +x filfre.sh + +test_expect_success 'custom merge driver with checkout -m' ' + git reset --hard && + + git config merge.filfre.driver "./filfre.sh %O %A %B" && + git config merge.filfre.name "Feel-free merge driver" && + git config merge.filfre.recursive binary && + echo "arm merge=filfre" >.gitattributes && + + git checkout -b left && + echo neutral >arm && + git add arm .gitattributes && + test_tick && + git commit -m neutral && + git branch right && + + echo left >arm && + test_tick && + git commit -a -m left && + git checkout right && + + echo right >arm && + test_tick && + git commit -a -m right && + + test_must_fail git merge left && + ( + for t in filfre-common left right + do + grep $t arm || exit 1 + done + exit 0 + ) && + + mv arm expect && + git checkout -m arm && + test_cmp expect arm +' + test_done diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 118c6ebb18..7d8ed68bef 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -22,6 +22,25 @@ test_expect_success 'setup' ' ' +test_expect_success 'git clean with skip-worktree .gitignore' ' + git update-index --skip-worktree .gitignore && + rm .gitignore && + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + git clean && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f src/part3.c && + test -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so && + git update-index --no-skip-worktree .gitignore && + git checkout .gitignore +' + test_expect_success 'git clean' ' mkdir -p build docs && diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 0f2ccc6cf0..1a4dc5f893 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -299,6 +299,15 @@ test_expect_success 'ls-files gracefully handles trailing slash' ' ' +test_expect_success 'moving to a commit without submodule does not leave empty dir' ' + rm -rf init && + mkdir init && + git reset --hard && + git checkout initial && + test ! -d init && + git checkout second +' + test_expect_success 'submodule <invalid-path> warns' ' git submodule no-such-submodule 2> output.err && @@ -306,4 +315,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/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh index 6cc16c39fe..d3c039f724 100755 --- a/t/t7401-submodule-summary.sh +++ b/t/t7401-submodule-summary.sh @@ -213,7 +213,7 @@ EOF test_expect_success '--for-status' " git submodule summary --for-status HEAD^ >actual && test_cmp actual - <<EOF -# Modified submodules: +# Submodule changes to be committed: # # * sm1 $head6...0000000: # 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/t7500-commit.sh b/t/t7500-commit.sh index 8eec0fa9bc..9f5c3edb03 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -150,7 +150,7 @@ EOF test_expect_success '--signoff' ' echo "yet another content *narf*" >> foo && echo "zort" | git commit -s -F - foo && - git cat-file commit HEAD | sed "1,/^$/d" > output && + git cat-file commit HEAD | sed "1,/^\$/d" > output && test_cmp expect output ' diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index e2ef53228e..7940901d47 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" \ @@ -117,7 +117,11 @@ test_expect_success \ test_expect_success \ "overriding author from command line" \ "echo 'gak' >file && \ - git commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a" + git commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a >output 2>&1" + +test_expect_success \ + "commit --author output mentions author" \ + "grep Rubber.Duck output" test_expect_success PERL \ "interactive add" \ @@ -141,10 +145,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 +215,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 +266,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..844fb43c6d 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -258,4 +258,122 @@ 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 + +' + +cat >.git/FAKE_EDITOR <<EOF +#!$SHELL_PATH +mv "\$1" "\$1.orig" +( + echo message + cat "\$1.orig" +) >"\$1" +EOF + +echo '## Custom template' >template + +clear_config () { + ( + git config --unset-all "$1" + case $? in + 0|5) exit 0 ;; + *) exit 1 ;; + esac + ) +} + +try_commit () { + git reset --hard && + echo >>negative && + GIT_EDITOR=.git/FAKE_EDITOR git commit -a $* $use_template && + case "$use_template" in + '') + ! grep "^## Custom template" .git/COMMIT_EDITMSG ;; + *) + grep "^## Custom template" .git/COMMIT_EDITMSG ;; + esac +} + +try_commit_status_combo () { + + test_expect_success 'commit' ' + clear_config commit.status && + try_commit "" && + grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + ' + + test_expect_success 'commit' ' + clear_config commit.status && + try_commit "" && + grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + ' + + test_expect_success 'commit --status' ' + clear_config commit.status && + try_commit --status && + grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + ' + + test_expect_success 'commit --no-status' ' + clear_config commit.status && + try_commit --no-status + ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + ' + + test_expect_success 'commit with commit.status = yes' ' + clear_config commit.status && + git config commit.status yes && + try_commit "" && + grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + ' + + test_expect_success 'commit with commit.status = no' ' + clear_config commit.status && + git config commit.status no && + try_commit "" && + ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + ' + + test_expect_success 'commit --status with commit.status = yes' ' + clear_config commit.status && + git config commit.status yes && + try_commit --status && + grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + ' + + test_expect_success 'commit --no-status with commit.status = yes' ' + clear_config commit.status && + git config commit.status yes && + try_commit --no-status && + ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + ' + + test_expect_success 'commit --status with commit.status = no' ' + clear_config commit.status && + git config commit.status no && + try_commit --status && + grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + ' + + test_expect_success 'commit --no-status with commit.status = no' ' + clear_config commit.status && + git config commit.status no && + try_commit --no-status && + ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + ' + +} + +try_commit_status_combo + +use_template="-t template" + +try_commit_status_combo + test_done diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index d9a08aac56..253c334319 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -5,34 +5,87 @@ test_description='git status for submodule' . ./test-lib.sh test_expect_success 'setup' ' - test_create_repo sub - cd sub && - : >bar && - git add bar && - git commit -m " Add bar" && - cd .. && - git add sub && + test_create_repo sub && + ( + cd sub && + : >bar && + git add bar && + git commit -m " Add bar" && + : >foo && + git add foo && + git commit -m " Add foo" + ) && + echo output > .gitignore && + git add sub .gitignore && git commit -m "Add submodule sub" ' test_expect_success 'status clean' ' - git status | - grep "nothing to commit" + git status >output && + grep "nothing to commit" output ' -test_expect_success 'status -a clean' ' - git status -a | - grep "nothing to commit" + +test_expect_success 'commit --dry-run -a clean' ' + test_must_fail git commit --dry-run -a >output && + grep "nothing to commit" output +' + +test_expect_success 'status with modified file in submodule' ' + (cd sub && git reset --hard) && + echo "changed" >sub/foo && + git status >output && + grep "modified: sub" output +' + +test_expect_success 'status with modified file in submodule (porcelain)' ' + (cd sub && git reset --hard) && + echo "changed" >sub/foo && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with added file in submodule' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + git status >output && + grep "modified: sub" output +' + +test_expect_success 'status with added file in submodule (porcelain)' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with untracked file in submodule' ' + (cd sub && git reset --hard) && + echo "content" >sub/new-file && + git status >output && + grep "modified: sub" output +' + +test_expect_success 'status with untracked file in submodule (porcelain)' ' + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF ' + test_expect_success 'rm submodule contents' ' rm -rf sub/* sub/.git ' + test_expect_success 'status clean (empty submodule dir)' ' - git status | - grep "nothing to commit" + git status >output && + grep "nothing to commit" output ' + test_expect_success 'status -a clean (empty submodule dir)' ' - git status -a | - grep "nothing to commit" + test_must_fail git commit --dry-run -a >output && + grep "nothing to commit" output ' test_done diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 93f875f500..556d0faa77 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 @@ -314,7 +579,7 @@ cat >expect <<EOF # # modified: dir1/modified # -# Modified submodules: +# Submodule changes to be committed: # # * sm 0000000...$head (1): # > Add foo @@ -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 ' @@ -376,7 +672,7 @@ cat >expect <<EOF # # modified: dir1/modified # -# Modified submodules: +# Submodule changes to be committed: # # * sm 0000000...$head (1): # > Add foo @@ -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/t7800-difftool.sh b/t/t7800-difftool.sh index fff6a6d0ea..19c72f55bf 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (c) 2009 David Aguilar +# Copyright (c) 2009, 2010 David Aguilar # test_description='git-difftool @@ -15,14 +15,19 @@ if ! test_have_prereq PERL; then test_done fi +LF=' +' + remove_config_vars() { # Unset all config variables used by git-difftool git config --unset diff.tool + git config --unset diff.guitool git config --unset difftool.test-tool.cmd git config --unset difftool.prompt git config --unset merge.tool git config --unset mergetool.test-tool.cmd + git config --unset mergetool.prompt return 0 } @@ -31,11 +36,11 @@ restore_test_defaults() # Restores the test defaults used by several tests remove_config_vars unset GIT_DIFF_TOOL - unset GIT_MERGE_TOOL unset GIT_DIFFTOOL_PROMPT unset GIT_DIFFTOOL_NO_PROMPT git config diff.tool test-tool && git config difftool.test-tool.cmd 'cat $LOCAL' + git config difftool.bogus-tool.cmd false } prompt_given() @@ -71,11 +76,22 @@ test_expect_success 'custom commands' ' # Ensures that git-difftool ignores bogus --tool values test_expect_success 'difftool ignores bad --tool values' ' - diff=$(git difftool --no-prompt --tool=bogus-tool branch) + diff=$(git difftool --no-prompt --tool=bad-tool branch) test "$?" = 1 && test "$diff" = "" ' +test_expect_success 'difftool honors --gui' ' + git config merge.tool bogus-tool && + git config diff.tool bogus-tool && + git config diff.guitool test-tool && + + diff=$(git difftool --no-prompt --gui branch) && + test "$diff" = "branch" && + + restore_test_defaults +' + # Specify the diff tool using $GIT_DIFF_TOOL test_expect_success 'GIT_DIFF_TOOL variable' ' git config --unset diff.tool @@ -94,15 +110,7 @@ test_expect_success 'GIT_DIFF_TOOL overrides' ' git config diff.tool bogus-tool && git config merge.tool bogus-tool && - GIT_MERGE_TOOL=test-tool && - export GIT_MERGE_TOOL && - diff=$(git difftool --no-prompt branch) && - test "$diff" = "branch" && - unset GIT_MERGE_TOOL && - - GIT_MERGE_TOOL=bogus-tool && GIT_DIFF_TOOL=test-tool && - export GIT_MERGE_TOOL && export GIT_DIFF_TOOL && diff=$(git difftool --no-prompt branch) && @@ -152,6 +160,17 @@ test_expect_success 'difftool.prompt config variable is false' ' restore_test_defaults ' +# Test that we don't have to pass --no-prompt when mergetool.prompt is false +test_expect_success 'difftool merge.prompt = false' ' + git config --unset difftool.prompt + git config mergetool.prompt false && + + diff=$(git difftool branch) && + test "$diff" = "branch" && + + restore_test_defaults +' + # Test that the -y flag can override difftool.prompt = true test_expect_success 'difftool.prompt can overridden with -y' ' git config difftool.prompt true && @@ -210,7 +229,39 @@ test_expect_success 'difftool.<tool>.path' ' diff=$(git difftool --tool=tkdiff --no-prompt branch) && git config --unset difftool.tkdiff.path && lines=$(echo "$diff" | grep file | wc -l) && - test "$lines" -eq 1 + test "$lines" -eq 1 && + + restore_test_defaults +' + +test_expect_success 'difftool --extcmd=cat' ' + diff=$(git difftool --no-prompt --extcmd=cat branch) && + test "$diff" = branch"$LF"master +' + +test_expect_success 'difftool --extcmd cat' ' + diff=$(git difftool --no-prompt --extcmd cat branch) && + test "$diff" = branch"$LF"master +' + +test_expect_success 'difftool -x cat' ' + diff=$(git difftool --no-prompt -x cat branch) && + test "$diff" = branch"$LF"master +' + +test_expect_success 'difftool --extcmd echo arg1' ' + diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"echo\ \$1\" branch) + test "$diff" = file +' + +test_expect_success 'difftool --extcmd cat arg1' ' + diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$1\" branch) + test "$diff" = master +' + +test_expect_success 'difftool --extcmd cat arg2' ' + diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$2\" branch) + test "$diff" = branch ' test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 84a7f03d46..c09f375288 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>' @@ -152,8 +186,8 @@ test_expect_success 'Prompting works' ' --smtp-server="$(pwd)/fake.sendmail" \ $patches \ 2>errors && - grep "^From: Example <from@example.com>$" msgtxt1 && - grep "^To: to@example.com$" msgtxt1 + grep "^From: Example <from@example.com>\$" msgtxt1 && + grep "^To: to@example.com\$" msgtxt1 ' test_expect_success 'cccmd works' ' @@ -202,7 +236,7 @@ test_expect_success 'Author From: in message body' ' --to=nobody@example.com \ --smtp-server="$(pwd)/fake.sendmail" \ $patches && - sed "1,/^$/d" < msgtxt1 > msgbody1 + sed "1,/^\$/d" < msgtxt1 > msgbody1 grep "From: A <author@example.com>" msgbody1 ' @@ -213,7 +247,7 @@ test_expect_success 'Author From: not in message body' ' --to=nobody@example.com \ --smtp-server="$(pwd)/fake.sendmail" \ $patches && - sed "1,/^$/d" < msgtxt1 > msgbody1 + sed "1,/^\$/d" < msgtxt1 > msgbody1 ! grep "From: A <author@example.com>" msgbody1 ' @@ -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..16408244d2 --- /dev/null +++ b/t/t9151-svn-mergeinfo.sh @@ -0,0 +1,56 @@ +#!/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 'commit made to merged branch is reachable from the merge' ' + before_commit=$(git rev-list --all --grep="trunk commit before merging trunk to b2") + merge_commit=$(git rev-list --all --grep="Merge trunk to b2") + not_reachable=$(git rev-list -1 $before_commit --not $merge_commit) + [ -z "$not_reachable" ] + ' + +test_expect_success 'merging two branches in one commit is detected correctly' ' + f1_commit=$(git rev-list --all --grep="make f1 branch from trunk") + f2_commit=$(git rev-list --all --grep="make f2 branch from trunk") + merge_commit=$(git rev-list --all --grep="Merge f1 and f2 to trunk") + not_reachable=$(git rev-list -1 $f1_commit $f2_commit --not $merge_commit) + [ -z "$not_reachable" ] + ' + +test_expect_failure '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..e1e138cb1a --- /dev/null +++ b/t/t9151/make-svnmerge-dump @@ -0,0 +1,305 @@ +#!/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 +mkdir tags +svn add trunk branches tags +i=$(commit $i "Setup trunk, branches, and tags") + +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 .. + +say "Branching b1 from trunk" +svn update +svn cp trunk branches/b1 +i=$(commit $i "make b1 branch from trunk") + +say "Branching b2 from trunk" +svn update +svn cp trunk branches/b2 +i=$(commit $i "make b2 branch from trunk") + +say "Make a commit to b2" +svn update +cd branches/b2 +echo "b2" > b2file +svn add b2file +i=$(commit $i "b2 update 1") +cd ../.. + +say "Make a commit to b1" +svn update +cd branches/b1 +echo "b1" > b1file +svn add b1file +i=$(commit $i "b1 update 1") +cd ../.. + +say "Merge b1 to trunk" +svn update +cd trunk +svn merge ../branches/b1/ --accept postpone +i=$(commit $i "Merge b1 to trunk") +cd .. + +say "Make a commit to trunk before merging trunk to b2" +svn update +cd trunk +echo "trunk" > trunkfile +svn add trunkfile +i=$(commit $i "trunk commit before merging trunk to b2") +cd .. + +say "Merge trunk to b2" +svn update +cd branches/b2 +svn merge ../../trunk/ --accept postpone +i=$(commit $i "Merge trunk to b2") +cd ../.. + +say "Merge b2 to trunk" +svn update +cd trunk +svn merge ../branches/b2/ --accept postpone +svn resolved b1file +svn resolved trunkfile +i=$(commit $i "Merge b2 to trunk") +cd .. + +say "Creating f1 from trunk with a new file" +svn update +svn cp trunk branches/f1 +cd branches/f1 +echo "f1" > f1file +svn add f1file +cd ../.. +i=$(commit $i "make f1 branch from trunk with a new file") + +say "Creating f2 from trunk with a new file" +svn update +svn cp trunk branches/f2 +cd branches/f2 +echo "f2" > f2file +svn add f2file +cd ../.. +i=$(commit $i "make f2 branch from trunk with a new file") + +say "Merge f1 and f2 to trunk in one go" +svn update +cd trunk +svn merge ../branches/f1/ --accept postpone +svn merge ../branches/f2/ --accept postpone +i=$(commit $i "Merge f1 and f2 to trunk") +cd .. + +say "Adding subdirectory to LEFT" +svn update +cd branches/left +mkdir subdir +echo "Yeehaw" > subdir/cowboy +svn add subdir +i=$(commit $i "add subdirectory to left branch") +cd ../../ + +say "Merging LEFT to TRUNK" +svn update +cd trunk +svn merge ../branches/left --accept postpone +i=$(commit $i "merge left to trunk") +cd .. + +say "Make PARTIAL branch" +svn update +svn cp trunk/subdir branches/partial +i=$(commit $i "make partial branch") + +say "Make a commit to PARTIAL" +svn update +cd branches/partial +echo "racecar" > palindromes +svn add palindromes +i=$(commit $i "partial update") +cd ../../ + +say "Merge PARTIAL to TRUNK" +svn update +cd trunk/subdir +svn merge ../../branches/partial --accept postpone +i=$(commit $i "merge partial to trunk") +cd ../../ + +say "Tagging trunk" +svn update +svn cp trunk tags/v1.0 +i=$(commit $i "tagging v1.0") + +say "Branching BUGFIX from v1.0" +svn update +svn cp tags/v1.0 branches/bugfix +i=$(commit $i "make bugfix branch from tag") + +say "Make a commit to BUGFIX" +svn update +cd branches/bugfix/ +echo "kayak" >> subdir/palindromes +i=$(commit $i "commit to bugfix") +cd ../../ + +say "Merge BUGFIX to TRUNK" +svn update +cd trunk +svn merge ../branches/bugfix/ --accept postpone +i=$(commit $i "Merge BUGFIX to TRUNK") +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..47cafcf528 --- /dev/null +++ b/t/t9151/svn-mergeinfo.dump @@ -0,0 +1,2388 @@ +SVN-fs-dump-format-version: 2 + +UUID: d6191530-2693-4a8e-98e7-b194d4c3edd8 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2010-01-19T04:14:02.832406Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 134 +Content-length: 134 + +K 7 +svn:log +V 36 +(r1) Setup trunk, branches, and tags +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:03.055172Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: tags +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: 111 +Content-length: 111 + +K 7 +svn:log +V 13 +(r2) ancestor +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:04.064506Z +PROPS-END + +Node-path: trunk/Makefile +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2401 +Text-content-md5: bfd8ff778d1492dc6758567373176a89 +Text-content-sha1: 103205ce331f7d64086dba497574734f78439590 +Content-length: 2411 + +PROPS-END +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 3 +Prop-content-length: 119 +Content-length: 119 + +K 7 +svn:log +V 21 +(r3) make left branch +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:06.040389Z +PROPS-END + +Node-path: branches/left +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk + + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/Makefile +Text-copy-source-md5: bfd8ff778d1492dc6758567373176a89 +Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590 + + +Revision-number: 4 +Prop-content-length: 120 +Content-length: 120 + +K 7 +svn:log +V 22 +(r4) make right branch +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:08.040905Z +PROPS-END + +Node-path: branches/right +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk + + +Node-path: branches/right/Makefile +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/Makefile +Text-copy-source-md5: bfd8ff778d1492dc6758567373176a89 +Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590 + + +Revision-number: 5 +Prop-content-length: 116 +Content-length: 116 + +K 7 +svn:log +V 18 +(r5) left update 1 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:09.049169Z +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: 117 +Content-length: 117 + +K 7 +svn:log +V 19 +(r6) right update 1 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:10.049350Z +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: 116 +Content-length: 116 + +K 7 +svn:log +V 18 +(r7) left update 2 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:11.049209Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2529 +Text-content-md5: f6b197cc3f2e89a83e545d4bb003de73 +Text-content-sha1: 2f656677cfec0bceec85e53036ffb63e25126f8e +Content-length: 2529 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 8 +Prop-content-length: 116 +Content-length: 116 + +K 7 +svn:log +V 18 +(r8) left update 3 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:12.049234Z +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: 123 +Content-length: 123 + +K 7 +svn:log +V 25 +(r9) make left sub-branch +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:14.040894Z +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: 128 +Content-length: 128 + +K 7 +svn:log +V 30 +(r10) left sub-branch update 1 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:15.049935Z +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: 125 +Content-length: 125 + +K 7 +svn:log +V 27 +(r11) Merge left to trunk 1 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:18.056594Z +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: 117 +Content-length: 117 + +K 7 +svn:log +V 19 +(r12) left update 4 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:19.049620Z +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: 118 +Content-length: 118 + +K 7 +svn:log +V 20 +(r13) right update 2 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:20.049659Z +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: 140 +Content-length: 140 + +K 7 +svn:log +V 42 +(r14) Cherry-pick right 2 commits to trunk +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:23.041991Z +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: 126 +Content-length: 126 + +K 7 +svn:log +V 28 +(r15) Merge right to trunk 1 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:26.054456Z +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: 118 +Content-length: 118 + +K 7 +svn:log +V 20 +(r16) right update 3 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:27.049955Z +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: 118 +Content-length: 118 + +K 7 +svn:log +V 20 +(r17) trunk update 1 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:28.049615Z +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: 134 +Content-length: 134 + +K 7 +svn:log +V 36 +(r18) Merge right to left sub-branch +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:31.061460Z +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: 128 +Content-length: 128 + +K 7 +svn:log +V 30 +(r19) left sub-branch update 2 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:32.049244Z +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: 117 +Content-length: 117 + +K 7 +svn:log +V 19 +(r20) left update 5 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:33.049332Z +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: 146 +Content-length: 146 + +K 7 +svn:log +V 48 +(r21) Cherry-pick left sub-branch commit to left +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:36.041839Z +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: 133 +Content-length: 133 + +K 7 +svn:log +V 35 +(r22) Merge left sub-branch to left +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:39.045014Z +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: 125 +Content-length: 125 + +K 7 +svn:log +V 27 +(r23) Merge left to trunk 2 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:42.052798Z +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: 130 +Content-length: 130 + +K 7 +svn:log +V 32 +(r24) non-merge right to trunk 2 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-01-19T04:14:44.038434Z +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 + + +Revision-number: 25 +Prop-content-length: 129 +Content-length: 129 + +K 7 +svn:log +V 31 +(r25) make b1 branch from trunk +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:18:56.084589Z +PROPS-END + +Node-path: branches/b1 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 24 +Node-copyfrom-path: trunk + + +Revision-number: 26 +Prop-content-length: 129 +Content-length: 129 + +K 7 +svn:log +V 31 +(r26) make b2 branch from trunk +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:18:59.076940Z +PROPS-END + +Node-path: branches/b2 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 25 +Node-copyfrom-path: trunk + + +Revision-number: 27 +Prop-content-length: 115 +Content-length: 115 + +K 7 +svn:log +V 17 +(r27) b2 update 1 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:01.095762Z +PROPS-END + +Node-path: branches/b2/b2file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 3 +Text-content-md5: 5edbdd57cba621eb3c6e601bf563b4dc +Text-content-sha1: 9d4b38049776bd0a2074d67cad23f8eaed35a3b3 +Content-length: 13 + +PROPS-END +b2 + + +Revision-number: 28 +Prop-content-length: 115 +Content-length: 115 + +K 7 +svn:log +V 17 +(r28) b1 update 1 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:03.097465Z +PROPS-END + +Node-path: branches/b1/b1file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 3 +Text-content-md5: 08778dfd9ac4f603231896aba7aad523 +Text-content-sha1: b551771aa4ad5b14123fc3bd98d89db2bc0edd4f +Content-length: 13 + +PROPS-END +b1 + + +Revision-number: 29 +Prop-content-length: 121 +Content-length: 121 + +K 7 +svn:log +V 23 +(r29) Merge b1 to trunk +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:06.073175Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 118 +Content-length: 118 + +K 13 +svn:mergeinfo +V 83 +/branches/b1:25-28 +/branches/left:2-22 +/branches/left-sub:4-19 +/branches/right:2-22 +PROPS-END + + +Node-path: trunk/b1file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 28 +Node-copyfrom-path: branches/b1/b1file +Text-copy-source-md5: 08778dfd9ac4f603231896aba7aad523 +Text-copy-source-sha1: b551771aa4ad5b14123fc3bd98d89db2bc0edd4f + + +Revision-number: 30 +Prop-content-length: 143 +Content-length: 143 + +K 7 +svn:log +V 45 +(r30) trunk commit before merging trunk to b2 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:08.096353Z +PROPS-END + +Node-path: trunk/trunkfile +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 6 +Text-content-md5: edf45fe5c98c5367733b39bbb2bb20d9 +Text-content-sha1: 7361d1685e5c86dfc523620cfaf598f196f86239 +Content-length: 16 + +PROPS-END +trunk + + +Revision-number: 31 +Prop-content-length: 121 +Content-length: 121 + +K 7 +svn:log +V 23 +(r31) Merge trunk to b2 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:11.081541Z +PROPS-END + +Node-path: branches/b2 +Node-kind: dir +Node-action: change +Prop-content-length: 131 +Content-length: 131 + +K 13 +svn:mergeinfo +V 96 +/branches/b1:25-28 +/branches/left:2-22 +/branches/left-sub:4-19 +/branches/right:2-22 +/trunk:26-30 +PROPS-END + + +Node-path: branches/b2/b1file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 30 +Node-copyfrom-path: trunk/b1file +Text-copy-source-md5: 08778dfd9ac4f603231896aba7aad523 +Text-copy-source-sha1: b551771aa4ad5b14123fc3bd98d89db2bc0edd4f + + +Node-path: branches/b2/trunkfile +Node-kind: file +Node-action: add +Node-copyfrom-rev: 30 +Node-copyfrom-path: trunk/trunkfile +Text-copy-source-md5: edf45fe5c98c5367733b39bbb2bb20d9 +Text-copy-source-sha1: 7361d1685e5c86dfc523620cfaf598f196f86239 + + +Revision-number: 32 +Prop-content-length: 121 +Content-length: 121 + +K 7 +svn:log +V 23 +(r32) Merge b2 to trunk +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:14.117939Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 138 +Content-length: 138 + +K 13 +svn:mergeinfo +V 102 +/branches/b1:25-28 +/branches/b2:26-31 +/branches/left:2-22 +/branches/left-sub:4-19 +/branches/right:2-22 +PROPS-END + + +Node-path: trunk/b2file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 31 +Node-copyfrom-path: branches/b2/b2file +Text-copy-source-md5: 5edbdd57cba621eb3c6e601bf563b4dc +Text-copy-source-sha1: 9d4b38049776bd0a2074d67cad23f8eaed35a3b3 + + +Revision-number: 33 +Prop-content-length: 145 +Content-length: 145 + +K 7 +svn:log +V 47 +(r33) make f1 branch from trunk with a new file +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:17.105832Z +PROPS-END + +Node-path: branches/f1 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 32 +Node-copyfrom-path: trunk + + +Node-path: branches/f1/f1file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 3 +Text-content-md5: 2b1abc6b6c5c0018851f9f8e6475563b +Text-content-sha1: aece6dfba588900e00d95601d22b4408d49580af +Content-length: 13 + +PROPS-END +f1 + + +Revision-number: 34 +Prop-content-length: 145 +Content-length: 145 + +K 7 +svn:log +V 47 +(r34) make f2 branch from trunk with a new file +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:20.110057Z +PROPS-END + +Node-path: branches/f2 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 33 +Node-copyfrom-path: trunk + + +Node-path: branches/f2/f2file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 3 +Text-content-md5: 575c5638d60271457e54ab7d07309502 +Text-content-sha1: 1c49a440c352f3473efa9512255033b94dc7def0 +Content-length: 13 + +PROPS-END +f2 + + +Revision-number: 35 +Prop-content-length: 128 +Content-length: 128 + +K 7 +svn:log +V 30 +(r35) Merge f1 and f2 to trunk +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:24.081490Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 173 +Content-length: 173 + +K 13 +svn:mergeinfo +V 137 +/branches/b1:25-28 +/branches/b2:26-31 +/branches/f1:33-34 +/branches/f2:34 +/branches/left:2-22 +/branches/left-sub:4-19 +/branches/right:2-22 +PROPS-END + + +Node-path: trunk/f1file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 34 +Node-copyfrom-path: branches/f1/f1file +Text-copy-source-md5: 2b1abc6b6c5c0018851f9f8e6475563b +Text-copy-source-sha1: aece6dfba588900e00d95601d22b4408d49580af + + +Node-path: trunk/f2file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 34 +Node-copyfrom-path: branches/f2/f2file +Text-copy-source-md5: 575c5638d60271457e54ab7d07309502 +Text-copy-source-sha1: 1c49a440c352f3473efa9512255033b94dc7def0 + + +Revision-number: 36 +Prop-content-length: 135 +Content-length: 135 + +K 7 +svn:log +V 37 +(r36) add subdirectory to left branch +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:26.113516Z +PROPS-END + +Node-path: branches/left/subdir +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: branches/left/subdir/cowboy +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 7 +Text-content-md5: f1d6530278ad409e68cc675476ad995f +Text-content-sha1: 732d9e3e5c391ffd767a98b45ddcc848de778cea +Content-length: 17 + +PROPS-END +Yeehaw + + +Revision-number: 37 +Prop-content-length: 123 +Content-length: 123 + +K 7 +svn:log +V 25 +(r37) merge left to trunk +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:29.073699Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 173 +Content-length: 173 + +K 13 +svn:mergeinfo +V 137 +/branches/b1:25-28 +/branches/b2:26-31 +/branches/f1:33-34 +/branches/f2:34 +/branches/left:2-36 +/branches/left-sub:4-19 +/branches/right:2-22 +PROPS-END + + +Node-path: trunk/subdir +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 36 +Node-copyfrom-path: branches/left/subdir + + +Revision-number: 38 +Prop-content-length: 123 +Content-length: 123 + +K 7 +svn:log +V 25 +(r38) make partial branch +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:32.072243Z +PROPS-END + +Node-path: branches/partial +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 37 +Node-copyfrom-path: trunk/subdir + + +Revision-number: 39 +Prop-content-length: 118 +Content-length: 118 + +K 7 +svn:log +V 20 +(r39) partial update +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:34.097961Z +PROPS-END + +Node-path: branches/partial/palindromes +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 8 +Text-content-md5: 5d1c2024fb5efc4eef812856df1b080c +Text-content-sha1: 5f8509ddd14c91a52864dd1447344e706f9bbc69 +Content-length: 18 + +PROPS-END +racecar + + +Revision-number: 40 +Prop-content-length: 126 +Content-length: 126 + +K 7 +svn:log +V 28 +(r40) merge partial to trunk +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:37.080211Z +PROPS-END + +Node-path: trunk/subdir +Node-kind: dir +Node-action: change +Prop-content-length: 246 +Content-length: 246 + +K 13 +svn:mergeinfo +V 210 +/branches/b1/subdir:25-28 +/branches/b2/subdir:26-31 +/branches/f1/subdir:33-34 +/branches/f2/subdir:34 +/branches/left/subdir:2-36 +/branches/left-sub/subdir:4-19 +/branches/partial:38-39 +/branches/right/subdir:2-22 +PROPS-END + + +Node-path: trunk/subdir/palindromes +Node-kind: file +Node-action: add +Node-copyfrom-rev: 39 +Node-copyfrom-path: branches/partial/palindromes +Text-copy-source-md5: 5d1c2024fb5efc4eef812856df1b080c +Text-copy-source-sha1: 5f8509ddd14c91a52864dd1447344e706f9bbc69 + + +Revision-number: 41 +Prop-content-length: 116 +Content-length: 116 + +K 7 +svn:log +V 18 +(r41) tagging v1.0 +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:40.083460Z +PROPS-END + +Node-path: tags/v1.0 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 40 +Node-copyfrom-path: trunk + + +Revision-number: 42 +Prop-content-length: 131 +Content-length: 131 + +K 7 +svn:log +V 33 +(r42) make bugfix branch from tag +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:43.118075Z +PROPS-END + +Node-path: branches/bugfix +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 41 +Node-copyfrom-path: tags/v1.0 + + +Revision-number: 43 +Prop-content-length: 120 +Content-length: 120 + +K 7 +svn:log +V 22 +(r43) commit to bugfix +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:45.079536Z +PROPS-END + +Node-path: branches/bugfix/subdir/palindromes +Node-kind: file +Node-action: change +Text-content-length: 14 +Text-content-md5: 3b12d98578a3f4320ba97e66da54fe5f +Text-content-sha1: 672931c9e8ac2c408209efab2f015638b6d64042 +Content-length: 14 + +racecar +kayak + + +Revision-number: 44 +Prop-content-length: 125 +Content-length: 125 + +K 7 +svn:log +V 27 +(r44) Merge BUGFIX to TRUNK +K 10 +svn:author +V 3 +adm +K 8 +svn:date +V 27 +2010-02-22T06:19:48.078914Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 210 +Content-length: 210 + +K 13 +svn:mergeinfo +V 174 +/branches/b1:25-28 +/branches/b2:26-31 +/branches/bugfix:42-43 +/branches/f1:33-34 +/branches/f2:34 +/branches/left:2-36 +/branches/left-sub:4-19 +/branches/right:2-22 +/tags/v1.0:41 +PROPS-END + + +Node-path: trunk/subdir +Node-kind: dir +Node-action: change +Prop-content-length: 297 +Content-length: 297 + +K 13 +svn:mergeinfo +V 261 +/branches/b1/subdir:25-28 +/branches/b2/subdir:26-31 +/branches/bugfix/subdir:42-43 +/branches/f1/subdir:33-34 +/branches/f2/subdir:34 +/branches/left/subdir:2-36 +/branches/left-sub/subdir:4-19 +/branches/partial:38-39 +/branches/right/subdir:2-22 +/tags/v1.0/subdir:41 +PROPS-END + + +Node-path: trunk/subdir/palindromes +Node-kind: file +Node-action: change +Text-content-length: 14 +Text-content-md5: 3b12d98578a3f4320ba97e66da54fe5f +Text-content-sha1: 672931c9e8ac2c408209efab2f015638b6d64042 +Content-length: 14 + +racecar +kayak + + 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/t9153-git-svn-rewrite-uuid.sh b/t/t9153-git-svn-rewrite-uuid.sh new file mode 100755 index 0000000000..88a2cfa233 --- /dev/null +++ b/t/t9153-git-svn-rewrite-uuid.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# +# Copyright (c) 2010 Jay Soffian +# + +test_description='git svn --rewrite-uuid test' + +. ./lib-git-svn.sh + +uuid=6cc8ada4-5932-4b4a-8242-3534ed8a3232 + +test_expect_success 'load svn repo' " + svnadmin load -q '$rawsvnrepo' < '$TEST_DIRECTORY/t9153/svn.dump' && + git svn init --minimize-url --rewrite-uuid='$uuid' '$svnrepo' && + git svn fetch + " + +test_expect_success 'verify uuid' " + git cat-file commit refs/remotes/git-svn~0 | \ + grep '^${git_svn_id}: .*@2 $uuid$' && + git cat-file commit refs/remotes/git-svn~1 | \ + grep '^${git_svn_id}: .*@1 $uuid$' + " + +test_done diff --git a/t/t9153/svn.dump b/t/t9153/svn.dump new file mode 100644 index 0000000000..0ddfe7025d --- /dev/null +++ b/t/t9153/svn.dump @@ -0,0 +1,75 @@ +SVN-fs-dump-format-version: 2 + +UUID: b4885626-c94f-4a6c-b179-00c030fc68e8 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2010-01-23T06:41:03.908576Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 109 +Content-length: 109 + +K 7 +svn:log +V 11 +initial foo +K 10 +svn:author +V 3 +jay +K 8 +svn:date +V 27 +2010-01-23T06:41:48.353776Z +PROPS-END + +Node-path: foo +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: d3b07384d113edec49eaa6238ad5ff00 +Text-content-sha1: f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 +Content-length: 14 + +PROPS-END +foo + + +Revision-number: 2 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 12 +now with bar +K 10 +svn:author +V 3 +jay +K 8 +svn:date +V 27 +2010-01-23T06:42:14.214640Z +PROPS-END + +Node-path: foo +Node-kind: file +Node-action: change +Text-content-length: 8 +Text-content-md5: f47c75614087a8dd938ba4acff252494 +Text-content-sha1: 4e48e2c9a3d2ca8a708cb0cc545700544efb5021 +Content-length: 8 + +foo +bar + + diff --git a/t/t9154-git-svn-fancy-glob.sh b/t/t9154-git-svn-fancy-glob.sh new file mode 100755 index 0000000000..a6a56a6cb9 --- /dev/null +++ b/t/t9154-git-svn-fancy-glob.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# +# Copyright (c) 2010 Jay Soffian +# + +test_description='git svn fancy glob test' + +. ./lib-git-svn.sh + +test_expect_success 'load svn repo' " + svnadmin load -q '$rawsvnrepo' < '$TEST_DIRECTORY/t9154/svn.dump' && + git svn init --minimize-url -T trunk '$svnrepo' && + git svn fetch + " + +test_expect_success 'add red branch' " + git config svn-remote.svn.branches 'branches/{red}:refs/remotes/*' && + git svn fetch && + git rev-parse refs/remotes/red && + test_must_fail git rev-parse refs/remotes/green && + test_must_fail git rev-parse refs/remotes/blue + " + +test_expect_success 'add green branch' " + GIT_CONFIG=.git/svn/.metadata git config --unset svn-remote.svn.branches-maxRev && + git config svn-remote.svn.branches 'branches/{red,green}:refs/remotes/*' && + git svn fetch && + git rev-parse refs/remotes/red && + git rev-parse refs/remotes/green && + test_must_fail git rev-parse refs/remotes/blue + " + +test_expect_success 'add all branches' " + GIT_CONFIG=.git/svn/.metadata git config --unset svn-remote.svn.branches-maxRev && + git config svn-remote.svn.branches 'branches/*:refs/remotes/*' && + git svn fetch && + git rev-parse refs/remotes/red && + git rev-parse refs/remotes/green && + git rev-parse refs/remotes/blue + " + +test_done diff --git a/t/t9154/svn.dump b/t/t9154/svn.dump new file mode 100644 index 0000000000..3dfabb67db --- /dev/null +++ b/t/t9154/svn.dump @@ -0,0 +1,222 @@ +SVN-fs-dump-format-version: 2 + +UUID: a18093a0-5f0b-44e0-8d88-8911ac7078db + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2010-01-23T07:40:25.660053Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 104 +Content-length: 104 + +K 7 +svn:log +V 7 +initial +K 10 +svn:author +V 3 +jay +K 8 +svn:date +V 27 +2010-01-23T07:41:33.636365Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk/foo +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: d3b07384d113edec49eaa6238ad5ff00 +Text-content-sha1: f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 +Content-length: 14 + +PROPS-END +foo + + +Revision-number: 2 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 12 +add branches +K 10 +svn:author +V 3 +jay +K 8 +svn:date +V 27 +2010-01-23T07:42:37.290694Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: branches/blue +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk + + +Node-path: branches/green +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk + + +Node-path: branches/red +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk + + +Revision-number: 3 +Prop-content-length: 108 +Content-length: 108 + +K 7 +svn:log +V 10 +red change +K 10 +svn:author +V 3 +jay +K 8 +svn:date +V 27 +2010-01-23T07:43:02.208918Z +PROPS-END + +Node-path: branches/red/foo +Node-kind: file +Node-action: change +Text-content-length: 8 +Text-content-md5: 64c3c8cf7d0233ab7627623a68888bd1 +Text-content-sha1: 95a0492027876adfd3891ec71ee37b79ee44d640 +Content-length: 8 + +foo +red + + +Revision-number: 4 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 12 +green change +K 10 +svn:author +V 3 +jay +K 8 +svn:date +V 27 +2010-01-23T07:43:15.746586Z +PROPS-END + +Node-path: branches/green/foo +Node-kind: file +Node-action: change +Text-content-length: 10 +Text-content-md5: 0209b6450891abc033d5eaaa9d3a8023 +Text-content-sha1: 87fc3bef9faeec48c0cd61dfc9851db377fdccf7 +Content-length: 10 + +foo +green + + +Revision-number: 5 +Prop-content-length: 109 +Content-length: 109 + +K 7 +svn:log +V 11 +blue change +K 10 +svn:author +V 3 +jay +K 8 +svn:date +V 27 +2010-01-23T07:43:29.364811Z +PROPS-END + +Node-path: branches/blue/foo +Node-kind: file +Node-action: change +Text-content-length: 9 +Text-content-md5: 9fbe4c13d0bae86386ae5209b2e6b275 +Text-content-sha1: cc4575083459a16f9aaef796c4a2456d64691ba0 +Content-length: 9 + +foo +blue + + +Revision-number: 6 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 12 +trunk change +K 10 +svn:author +V 3 +jay +K 8 +svn:date +V 27 +2010-01-23T07:44:01.313130Z +PROPS-END + +Node-path: trunk/foo +Node-kind: file +Node-action: change +Text-content-length: 10 +Text-content-md5: 1c4db977d7a57c3bae582aab87948516 +Text-content-sha1: 469c08df449e702cf2a1fe746244a9ef3f837fad +Content-length: 10 + +foo +trunk + + diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 821be7ce8d..131f032988 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -1088,4 +1088,498 @@ INPUT_END test_expect_success 'P: fail on blob mark in gitlink' ' test_must_fail git fast-import <input' +### +### series Q (notes) +### + +note1_data="The first note for the first commit" +note2_data="The first note for the second commit" +note3_data="The first note for the third commit" +note1b_data="The second note for the first commit" +note1c_data="The third note for the first commit" +note2b_data="The second note for the second 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 + +commit refs/notes/foobar +mark :10 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +notes (:10) +COMMIT + +N inline :3 +data <<EOF +$note1b_data +EOF + +commit refs/notes/foobar2 +mark :11 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +notes (:11) +COMMIT + +N inline :3 +data <<EOF +$note1c_data +EOF + +commit refs/notes/foobar +mark :12 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +notes (:12) +COMMIT + +deleteall +N inline :5 +data <<EOF +$note2b_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 first notes commit' \ + 'git cat-file commit refs/notes/foobar~2 | 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 first notes tree' \ + 'git cat-file -p refs/notes/foobar~2^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual' + +echo "$note1_data" >expect +test_expect_success \ + 'Q: verify first note for first commit' \ + 'git cat-file blob refs/notes/foobar~2:$commit1 >actual && test_cmp expect actual' + +echo "$note2_data" >expect +test_expect_success \ + 'Q: verify first note for second commit' \ + 'git cat-file blob refs/notes/foobar~2:$commit2 >actual && test_cmp expect actual' + +echo "$note3_data" >expect +test_expect_success \ + 'Q: verify first note for third commit' \ + 'git cat-file blob refs/notes/foobar~2:$commit3 >actual && test_cmp expect actual' + +cat >expect <<EOF +parent `git rev-parse --verify refs/notes/foobar~2` +author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +notes (:10) +EOF +test_expect_success \ + 'Q: verify second 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 second notes tree' \ + 'git cat-file -p refs/notes/foobar^^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual' + +echo "$note1b_data" >expect +test_expect_success \ + 'Q: verify second 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 first 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 first note for third commit' \ + 'git cat-file blob refs/notes/foobar^:$commit3 >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 (:11) +EOF +test_expect_success \ + 'Q: verify third notes commit' \ + 'git cat-file commit refs/notes/foobar2 | sed 1d >actual && + test_cmp expect actual' + +cat >expect.unsorted <<EOF +100644 blob $commit1 +EOF +cat expect.unsorted | sort >expect +test_expect_success \ + 'Q: verify third notes tree' \ + 'git cat-file -p refs/notes/foobar2^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual' + +echo "$note1c_data" >expect +test_expect_success \ + 'Q: verify third note for first commit' \ + 'git cat-file blob refs/notes/foobar2:$commit1 >actual && test_cmp expect actual' + +cat >expect <<EOF +parent `git rev-parse --verify refs/notes/foobar^` +author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +notes (:12) +EOF +test_expect_success \ + 'Q: verify fourth notes commit' \ + 'git cat-file commit refs/notes/foobar | sed 1d >actual && + test_cmp expect actual' + +cat >expect.unsorted <<EOF +100644 blob $commit2 +EOF +cat expect.unsorted | sort >expect +test_expect_success \ + 'Q: verify fourth notes tree' \ + 'git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual' + +echo "$note2b_data" >expect +test_expect_success \ + 'Q: verify second note for second commit' \ + 'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual' + +### +### series R (feature and option) +### + +cat >input <<EOF +feature no-such-feature-exists +EOF + +test_expect_success 'R: abort on unsupported feature' ' + test_must_fail git fast-import <input +' + +cat >input <<EOF +feature date-format=now +EOF + +test_expect_success 'R: supported feature is accepted' ' + git fast-import <input +' + +cat >input << EOF +blob +data 3 +hi +feature date-format=now +EOF + +test_expect_success 'R: abort on receiving feature after data command' ' + test_must_fail git fast-import <input +' + +cat >input << EOF +feature import-marks=git.marks +feature import-marks=git2.marks +EOF + +test_expect_success 'R: only one import-marks feature allowed per stream' ' + test_must_fail git fast-import <input +' + +cat >input << EOF +feature export-marks=git.marks +blob +mark :1 +data 3 +hi + +EOF + +test_expect_success \ + 'R: export-marks feature results in a marks file being created' \ + 'cat input | git fast-import && + grep :1 git.marks' + +test_expect_success \ + 'R: export-marks options can be overriden by commandline options' \ + 'cat input | git fast-import --export-marks=other.marks && + grep :1 other.marks' + +cat >input << EOF +feature import-marks=marks.out +feature export-marks=marks.new +EOF + +test_expect_success \ + 'R: import to output marks works without any content' \ + 'cat input | git fast-import && + test_cmp marks.out marks.new' + +cat >input <<EOF +feature import-marks=nonexistant.marks +feature export-marks=marks.new +EOF + +test_expect_success \ + 'R: import marks prefers commandline marks file over the stream' \ + 'cat input | git fast-import --import-marks=marks.out && + test_cmp marks.out marks.new' + + +cat >input <<EOF +feature import-marks=nonexistant.marks +feature export-marks=combined.marks +EOF + +test_expect_success 'R: multiple --import-marks= should be honoured' ' + head -n2 marks.out > one.marks && + tail -n +3 marks.out > two.marks && + git fast-import --import-marks=one.marks --import-marks=two.marks <input && + test_cmp marks.out combined.marks +' + +cat >input <<EOF +feature relative-marks +feature import-marks=relative.in +feature export-marks=relative.out +EOF + +test_expect_success 'R: feature relative-marks should be honoured' ' + mkdir -p .git/info/fast-import/ && + cp marks.new .git/info/fast-import/relative.in && + git fast-import <input && + test_cmp marks.new .git/info/fast-import/relative.out +' + +cat >input <<EOF +feature relative-marks +feature import-marks=relative.in +feature no-relative-marks +feature export-marks=non-relative.out +EOF + +test_expect_success 'R: feature no-relative-marks should be honoured' ' + git fast-import <input && + test_cmp marks.new non-relative.out +' + +cat >input << EOF +option git quiet +blob +data 3 +hi + +EOF + +touch empty + +test_expect_success 'R: quiet option results in no stats being output' ' + cat input | git fast-import 2> output && + test_cmp empty output +' + +cat >input <<EOF +option git non-existing-option +EOF + +test_expect_success 'R: die on unknown option' ' + test_must_fail git fast-import <input +' + +test_expect_success 'R: unknown commandline options are rejected' '\ + test_must_fail git fast-import --non-existing-option < /dev/null +' + +cat >input <<EOF +option non-existing-vcs non-existing-option +EOF + +test_expect_success 'R: ignore non-git options' ' + git fast-import <input +' + +## +## R: very large blobs +## +blobsize=$((2*1024*1024 + 53)) +test-genrandom bar $blobsize >expect +cat >input <<INPUT_END +commit refs/heads/big-file +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +R - big file +COMMIT + +M 644 inline big1 +data $blobsize +INPUT_END +cat expect >>input +cat >>input <<INPUT_END +M 644 inline big2 +data $blobsize +INPUT_END +cat expect >>input +echo >>input + +test_expect_success \ + 'R: blob bigger than threshold' \ + 'test_create_repo R && + git --git-dir=R/.git fast-import --big-file-threshold=1 <input' +test_expect_success \ + 'R: verify created pack' \ + ': >verify && + for p in R/.git/objects/pack/*.pack; + do + git verify-pack -v $p >>verify || exit; + done' +test_expect_success \ + 'R: verify written objects' \ + 'git --git-dir=R/.git cat-file blob big-file:big1 >actual && + test_cmp expect actual && + a=$(git --git-dir=R/.git rev-parse big-file:big1) && + b=$(git --git-dir=R/.git rev-parse big-file:big2) && + test $a = $b' +test_expect_success \ + 'R: blob appears only once' \ + 'n=$(grep $a verify | wc -l) && + test 1 = $n' + test_done diff --git a/t/t9301-fast-import-notes.sh b/t/t9301-fast-import-notes.sh new file mode 100755 index 0000000000..a5c99d8507 --- /dev/null +++ b/t/t9301-fast-import-notes.sh @@ -0,0 +1,623 @@ +#!/bin/sh +# +# Copyright (c) 2009 Johan Herland +# + +test_description='test git fast-import of notes objects' +. ./test-lib.sh + + +test_tick +cat >input <<INPUT_END +commit refs/heads/master +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +first commit +COMMIT + +M 644 inline foo +data <<EOF +file foo in first commit +EOF + +M 755 inline bar +data <<EOF +file bar in first commit +EOF + +M 644 inline baz/xyzzy +data <<EOF +file baz/xyzzy in first commit +EOF + +commit refs/heads/master +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +second commit +COMMIT + +M 644 inline foo +data <<EOF +file foo in second commit +EOF + +M 755 inline baz/xyzzy +data <<EOF +file baz/xyzzy in second commit +EOF + +commit refs/heads/master +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +third commit +COMMIT + +M 644 inline foo +data <<EOF +file foo in third commit +EOF + +commit refs/heads/master +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +fourth commit +COMMIT + +M 755 inline bar +data <<EOF +file bar in fourth commit +EOF + +INPUT_END + +test_expect_success 'set up master branch' ' + + git fast-import <input && + git whatchanged master +' + +commit4=$(git rev-parse refs/heads/master) +commit3=$(git rev-parse "$commit4^") +commit2=$(git rev-parse "$commit4~2") +commit1=$(git rev-parse "$commit4~3") + +test_tick +cat >input <<INPUT_END +commit refs/notes/test +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +first notes commit +COMMIT + +M 644 inline $commit1 +data <<EOF +first note for first commit +EOF + +M 755 inline $commit2 +data <<EOF +first note for second commit +EOF + +INPUT_END + +cat >expect <<EXPECT_END + fourth commit + third commit + second commit + first note for second commit + first commit + first note for first commit +EXPECT_END + +test_expect_success 'add notes with simple M command' ' + + git fast-import <input && + GIT_NOTES_REF=refs/notes/test git log | grep "^ " > actual && + test_cmp expect actual + +' + +test_tick +cat >input <<INPUT_END +commit refs/notes/test +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +second notes commit +COMMIT + +from refs/notes/test^0 +N inline $commit3 +data <<EOF +first note for third commit +EOF + +N inline $commit4 +data <<EOF +first note for fourth commit +EOF + +INPUT_END + +cat >expect <<EXPECT_END + fourth commit + first note for fourth commit + third commit + first note for third commit + second commit + first note for second commit + first commit + first note for first commit +EXPECT_END + +test_expect_success 'add notes with simple N command' ' + + git fast-import <input && + GIT_NOTES_REF=refs/notes/test git log | grep "^ " > actual && + test_cmp expect actual + +' + +test_tick +cat >input <<INPUT_END +commit refs/notes/test +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +third notes commit +COMMIT + +from refs/notes/test^0 +N inline $commit1 +data <<EOF +second note for first commit +EOF + +N inline $commit2 +data <<EOF +second note for second commit +EOF + +N inline $commit3 +data <<EOF +second note for third commit +EOF + +N inline $commit4 +data <<EOF +second note for fourth commit +EOF + +INPUT_END + +cat >expect <<EXPECT_END + fourth commit + second note for fourth commit + third commit + second note for third commit + second commit + second note for second commit + first commit + second note for first commit +EXPECT_END + +test_expect_success 'update existing notes with N command' ' + + git fast-import <input && + GIT_NOTES_REF=refs/notes/test git log | grep "^ " > actual && + test_cmp expect actual + +' + +test_tick +cat >input <<INPUT_END +commit refs/notes/test +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +fourth notes commit +COMMIT + +from refs/notes/test^0 +M 644 inline $(echo "$commit3" | sed "s|^..|&/|") +data <<EOF +prefix of note for third commit +EOF + +M 644 inline $(echo "$commit4" | sed "s|^..|&/|") +data <<EOF +prefix of note for fourth commit +EOF + +M 644 inline $(echo "$commit4" | sed "s|^\(..\)\(..\)|\1/\2/|") +data <<EOF +pre-prefix of note for fourth commit +EOF + +N inline $commit1 +data <<EOF +third note for first commit +EOF + +N inline $commit2 +data <<EOF +third note for second commit +EOF + +N inline $commit3 +data <<EOF +third note for third commit +EOF + +N inline $commit4 +data <<EOF +third note for fourth commit +EOF + + +INPUT_END + +cat >expect <<EXPECT_END + fourth commit + pre-prefix of note for fourth commit + prefix of note for fourth commit + third note for fourth commit + third commit + prefix of note for third commit + third note for third commit + second commit + third note for second commit + first commit + third note for first commit +EXPECT_END + +test_expect_success 'add concatentation notes with M command' ' + + git fast-import <input && + GIT_NOTES_REF=refs/notes/test git log | grep "^ " > actual && + test_cmp expect actual + +' + +test_tick +cat >input <<INPUT_END +commit refs/notes/test +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +fifth notes commit +COMMIT + +from refs/notes/test^0 +deleteall + +INPUT_END + +cat >expect <<EXPECT_END + fourth commit + third commit + second commit + first commit +EXPECT_END + +test_expect_success 'verify that deleteall also removes notes' ' + + git fast-import <input && + GIT_NOTES_REF=refs/notes/test git log | grep "^ " > actual && + test_cmp expect actual + +' + +test_tick +cat >input <<INPUT_END +commit refs/notes/test +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +sixth notes commit +COMMIT + +from refs/notes/test^0 +M 644 inline $commit1 +data <<EOF +third note for first commit +EOF + +M 644 inline $commit3 +data <<EOF +third note for third commit +EOF + +N inline $commit1 +data <<EOF +fourth note for first commit +EOF + +N inline $commit3 +data <<EOF +fourth note for third commit +EOF + +INPUT_END + +cat >expect <<EXPECT_END + fourth commit + third commit + fourth note for third commit + second commit + first commit + fourth note for first commit +EXPECT_END + +test_expect_success 'verify that later N commands override earlier M commands' ' + + git fast-import <input && + GIT_NOTES_REF=refs/notes/test git log | grep "^ " > actual && + test_cmp expect actual + +' + +# Write fast-import commands to create the given number of commits +fast_import_commits () { + my_ref=$1 + my_num_commits=$2 + my_append_to_file=$3 + my_i=0 + while test $my_i -lt $my_num_commits + do + my_i=$(($my_i + 1)) + test_tick + cat >>"$my_append_to_file" <<INPUT_END +commit $my_ref +mark :$my_i +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +commit #$my_i +COMMIT + +M 644 inline file +data <<EOF +file contents in commit #$my_i +EOF + +INPUT_END + done +} + +# Write fast-import commands to create the given number of notes annotating +# the commits created by fast_import_commits() +fast_import_notes () { + my_notes_ref=$1 + my_num_commits=$2 + my_append_to_file=$3 + my_note_append=$4 + test_tick + cat >>"$my_append_to_file" <<INPUT_END +commit $my_notes_ref +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +committing $my_num_commits notes +COMMIT + +INPUT_END + + my_i=0 + while test $my_i -lt $my_num_commits + do + my_i=$(($my_i + 1)) + cat >>"$my_append_to_file" <<INPUT_END +N inline :$my_i +data <<EOF +note for commit #$my_i$my_note_append +EOF + +INPUT_END + done +} + + +rm input expect +num_commits=400 +# Create lots of commits +fast_import_commits "refs/heads/many_commits" $num_commits input +# Create one note per above commit +fast_import_notes "refs/notes/many_notes" $num_commits input +# Add a couple of non-notes as well +test_tick +cat >>input <<INPUT_END +commit refs/notes/many_notes +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +committing some non-notes to the notes tree +COMMIT + +M 755 inline foobar/non-note.txt +data <<EOF +This is not a note, but rather a regular file residing in a notes tree +EOF + +M 644 inline deadbeef +data <<EOF +Non-note file +EOF + +M 644 inline de/adbeef +data <<EOF +Another non-note file +EOF + +INPUT_END +# Finally create the expected output from all these notes and commits +i=$num_commits +while test $i -gt 0 +do + cat >>expect <<EXPECT_END + commit #$i + note for commit #$i +EXPECT_END + i=$(($i - 1)) +done + +test_expect_success 'add lots of commits and notes' ' + + git fast-import <input && + GIT_NOTES_REF=refs/notes/many_notes git log refs/heads/many_commits | + grep "^ " > actual && + test_cmp expect actual + +' + +test_expect_success 'verify that lots of notes trigger a fanout scheme' ' + + # None of the entries in the top-level notes tree should be a full SHA1 + git ls-tree --name-only refs/notes/many_notes | + while read path + do + if test $(expr length "$path") -ge 40 + then + return 1 + fi + done + +' + +cat >>expect_non-note1 << EOF +This is not a note, but rather a regular file residing in a notes tree +EOF + +cat >>expect_non-note2 << EOF +Non-note file +EOF + +cat >>expect_non-note3 << EOF +Another non-note file +EOF + +test_expect_success 'verify that non-notes are untouched by a fanout change' ' + + git cat-file -p refs/notes/many_notes:foobar/non-note.txt > actual && + test_cmp expect_non-note1 actual && + git cat-file -p refs/notes/many_notes:deadbeef > actual && + test_cmp expect_non-note2 actual && + git cat-file -p refs/notes/many_notes:de/adbeef > actual && + test_cmp expect_non-note3 actual + +' +remaining_notes=10 +test_tick +cat >>input <<INPUT_END +commit refs/notes/many_notes +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +removing all notes but $remaining_notes +COMMIT +from refs/notes/many_notes^0 +INPUT_END + +i=$remaining_notes +while test $i -lt $num_commits +do + i=$(($i + 1)) + cat >>input <<INPUT_END +N 0000000000000000000000000000000000000000 :$i +INPUT_END +done + +i=$num_commits +rm expect +while test $i -gt 0 +do + cat >>expect <<EXPECT_END + commit #$i +EXPECT_END + if test $i -le $remaining_notes + then + cat >>expect <<EXPECT_END + note for commit #$i +EXPECT_END + fi + i=$(($i - 1)) +done + +test_expect_success 'remove lots of notes' ' + + git fast-import <input && + GIT_NOTES_REF=refs/notes/many_notes git log refs/heads/many_commits | + grep "^ " > actual && + test_cmp expect actual + +' + +test_expect_success 'verify that removing notes trigger fanout consolidation' ' + + # All entries in the top-level notes tree should be a full SHA1 + git ls-tree --name-only -r refs/notes/many_notes | + while read path + do + # Explicitly ignore the non-note paths + test "$path" = "foobar/non-note.txt" && continue + test "$path" = "deadbeef" && continue + test "$path" = "de/adbeef" && continue + + if test $(expr length "$path") -ne 40 + then + return 1 + fi + done + +' + +test_expect_success 'verify that non-notes are untouched by a fanout change' ' + + git cat-file -p refs/notes/many_notes:foobar/non-note.txt > actual && + test_cmp expect_non-note1 actual && + git cat-file -p refs/notes/many_notes:deadbeef > actual && + test_cmp expect_non-note2 actual && + git cat-file -p refs/notes/many_notes:de/adbeef > actual && + test_cmp expect_non-note3 actual + +' + + +rm input expect +num_notes_refs=10 +num_commits=16 +some_commits=8 +# Create commits +fast_import_commits "refs/heads/more_commits" $num_commits input +# Create one note per above commit per notes ref +i=0 +while test $i -lt $num_notes_refs +do + i=$(($i + 1)) + fast_import_notes "refs/notes/more_notes_$i" $num_commits input +done +# Trigger branch reloading in git-fast-import by repeating the note creation +i=0 +while test $i -lt $num_notes_refs +do + i=$(($i + 1)) + fast_import_notes "refs/notes/more_notes_$i" $some_commits input " (2)" +done +# Finally create the expected output from the notes in refs/notes/more_notes_1 +i=$num_commits +while test $i -gt 0 +do + note_data="note for commit #$i" + if test $i -le $some_commits + then + note_data="$note_data (2)" + fi + cat >>expect <<EXPECT_END + commit #$i + $note_data +EXPECT_END + i=$(($i - 1)) +done + +test_expect_success "add notes to $num_commits commits in each of $num_notes_refs refs" ' + + git fast-import --active-branches=5 <input && + GIT_NOTES_REF=refs/notes/more_notes_1 git log refs/heads/more_commits | + grep "^ " > actual && + test_cmp expect actual + +' + +test_done diff --git a/t/t9301-fast-export.sh b/t/t9350-fast-export.sh index 356964e53a..356964e53a 100755 --- a/t/t9301-fast-export.sh +++ b/t/t9350-fast-export.sh diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index c2ec3cb4bd..4327eb8baa 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -96,7 +96,7 @@ EOF test_expect_success 'pserver authentication' \ 'cat request-anonymous | git-cvsserver pserver >log 2>&1 && - sed -ne \$p log | grep "^I LOVE YOU$"' + sed -ne \$p log | grep "^I LOVE YOU\$"' test_expect_success 'pserver authentication failure (non-anonymous user)' \ 'if cat request-git | git-cvsserver pserver >log 2>&1 @@ -105,11 +105,11 @@ test_expect_success 'pserver authentication failure (non-anonymous user)' \ else true fi && - sed -ne \$p log | grep "^I HATE YOU$"' + sed -ne \$p log | grep "^I HATE YOU\$"' test_expect_success 'pserver authentication (login)' \ 'cat login-anonymous | git-cvsserver pserver >log 2>&1 && - sed -ne \$p log | grep "^I LOVE YOU$"' + sed -ne \$p log | grep "^I LOVE YOU\$"' test_expect_success 'pserver authentication failure (login/non-anonymous user)' \ 'if cat login-git | git-cvsserver pserver >log 2>&1 @@ -118,7 +118,7 @@ test_expect_success 'pserver authentication failure (login/non-anonymous user)' else true fi && - sed -ne \$p log | grep "^I HATE YOU$"' + sed -ne \$p log | grep "^I HATE YOU\$"' # misuse pserver authentication for testing of req_Root @@ -156,7 +156,7 @@ test_expect_success 'req_Root failure (conflicting roots)' \ test_expect_success 'req_Root (strict paths)' \ 'cat request-anonymous | git-cvsserver --strict-paths pserver "$SERVERDIR" >log 2>&1 && - sed -ne \$p log | grep "^I LOVE YOU$"' + sed -ne \$p log | grep "^I LOVE YOU\$"' test_expect_success 'req_Root failure (strict-paths)' ' ! cat request-anonymous | @@ -165,7 +165,7 @@ test_expect_success 'req_Root failure (strict-paths)' ' test_expect_success 'req_Root (w/o strict-paths)' \ 'cat request-anonymous | git-cvsserver pserver "$WORKDIR/" >log 2>&1 && - sed -ne \$p log | grep "^I LOVE YOU$"' + sed -ne \$p log | grep "^I LOVE YOU\$"' test_expect_success 'req_Root failure (w/o strict-paths)' ' ! cat request-anonymous | @@ -183,7 +183,7 @@ EOF test_expect_success 'req_Root (base-path)' \ 'cat request-base | git-cvsserver --strict-paths --base-path "$WORKDIR/" pserver "$SERVERDIR" >log 2>&1 && - sed -ne \$p log | grep "^I LOVE YOU$"' + sed -ne \$p log | grep "^I LOVE YOU\$"' test_expect_success 'req_Root failure (base-path)' ' ! cat request-anonymous | @@ -194,14 +194,14 @@ GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled false || exit 1 test_expect_success 'req_Root (export-all)' \ 'cat request-anonymous | git-cvsserver --export-all pserver "$WORKDIR" >log 2>&1 && - sed -ne \$p log | grep "^I LOVE YOU$"' + sed -ne \$p log | grep "^I LOVE YOU\$"' test_expect_success 'req_Root failure (export-all w/o whitelist)' \ '! (cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 || false)' test_expect_success 'req_Root (everything together)' \ 'cat request-base | git-cvsserver --export-all --strict-paths --base-path "$WORKDIR/" pserver "$SERVERDIR" >log 2>&1 && - sed -ne \$p log | grep "^I LOVE YOU$"' + sed -ne \$p log | grep "^I LOVE YOU\$"' GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true || exit 1 diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh index 40637d6782..ed7b513f3e 100755 --- a/t/t9401-git-cvsserver-crlf.sh +++ b/t/t9401-git-cvsserver-crlf.sh @@ -11,14 +11,6 @@ repository using cvs CLI client via git-cvsserver server' . ./test-lib.sh -q_to_nul () { - perl -pe 'y/Q/\000/' -} - -q_to_cr () { - tr Q '\015' -} - marked_as () { foundEntry="$(grep "^/$2/" "$1/CVS/Entries")" if [ x"$foundEntry" = x"" ] ; then diff --git a/t/t9501-gitweb-standalone-http-status.sh b/t/t9501-gitweb-standalone-http-status.sh index d0ff21d426..d196cc5ca9 100755 --- a/t/t9501-gitweb-standalone-http-status.sh +++ b/t/t9501-gitweb-standalone-http-status.sh @@ -33,7 +33,6 @@ test_expect_success \ grep "403 - Snapshot format not allowed" gitweb.output && gitweb_run "p=.git;a=snapshot;h=HEAD;sf=zip" && grep "403 - Unsupported snapshot format" gitweb.output' -test_debug 'cat gitweb.output' cat >>gitweb_config.perl <<\EOF @@ -50,7 +49,6 @@ test_expect_success \ grep "403 - Snapshot format not allowed" gitweb.output && gitweb_run "p=.git;a=snapshot;h=HEAD;sf=zip" && grep "Status: 200 OK" gitweb.output' -test_debug 'cat gitweb.output' cat >>gitweb_config.perl <<\EOF @@ -72,7 +70,68 @@ test_expect_success \ 'snapshots: tgz explicitly enabled' \ 'gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tgz" && grep "Status: 200 OK" gitweb.output' +test_debug 'cat gitweb.headers' + + +# ---------------------------------------------------------------------- +# 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.headers' + +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.headers' + +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' + + +# ---------------------------------------------------------------------- +# load checking + +# always hit the load limit +cat >>gitweb_config.perl <<\EOF +our $maxload = -1; +EOF + +test_expect_success 'load checking: load too high (default action)' ' + gitweb_run "p=.git" && + grep "Status: 503 Service Unavailable" gitweb.headers && + grep "503 - The load average on the server is too high" gitweb.body +' +test_debug 'cat gitweb.log' # just in case +test_debug 'cat gitweb.headers' + +# turn off load checking +cat >>gitweb_config.perl <<\EOF +our $maxload = undef; +EOF + 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..afd3053f96 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 @@ -74,6 +74,12 @@ case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in ;; esac +# Convenience +# +# A regexp to match 5 and 40 hexdigits +_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" + # Each test should start with something like this, after copyright notices: # # test_description='Description of this test... @@ -105,6 +111,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 +215,35 @@ 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' +} + +q_to_nul () { + perl -pe 'y/Q/\000/' +} + +q_to_cr () { + tr Q '\015' +} + +append_cr () { + sed -e 's/$/Q/' | tr Q '\015' +} + +remove_cr () { + tr '\015' Q | sed -e 's/Q$//' } test_tick () { @@ -551,19 +586,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 +649,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 +674,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 +780,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/test-chmtime.c b/test-chmtime.c index fe476cb618..92713d16da 100644 --- a/test-chmtime.c +++ b/test-chmtime.c @@ -1,7 +1,7 @@ /* * This program can either change modification time of the given * file(s) or just print it. The program does not change atime nor - * ctime (their values are explicitely preserved). + * ctime (their values are explicitly preserved). * * The mtime can be changed to an absolute value: * diff --git a/test-index-version.c b/test-index-version.c new file mode 100644 index 0000000000..bfaad9e09e --- /dev/null +++ b/test-index-version.c @@ -0,0 +1,14 @@ +#include "cache.h" + +int main(int argc, const char **argv) +{ + struct cache_header hdr; + int version; + + memset(&hdr,0,sizeof(hdr)); + if (read(0, &hdr, sizeof(hdr)) != sizeof(hdr)) + return 0; + version = ntohl(hdr.hdr_version); + printf("%d\n", version); + return 0; +} diff --git a/test-run-command.c b/test-run-command.c new file mode 100644 index 0000000000..0612bfa7cd --- /dev/null +++ b/test-run-command.c @@ -0,0 +1,35 @@ +/* + * test-run-command.c: test run command API. + * + * (C) 2009 Ilari Liusvaara <ilari.liusvaara@elisanet.fi> + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "git-compat-util.h" +#include "run-command.h" +#include <string.h> +#include <errno.h> + +int main(int argc, char **argv) +{ + struct child_process proc; + + memset(&proc, 0, sizeof(proc)); + + if (argc < 3) + return 1; + proc.argv = (const char **)argv+2; + + if (!strcmp(argv[1], "start-command-ENOENT")) { + if (start_command(&proc) < 0 && errno == ENOENT) + return 0; + fprintf(stderr, "FAIL %s\n", argv[1]); + return 1; + } + + fprintf(stderr, "check usage\n"); + return 1; +} diff --git a/transport-helper.c b/transport-helper.c index f57e84c676..f822972020 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -1,24 +1,108 @@ #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" + +static int debug; struct helper_data { const char *name; struct child_process *helper; - unsigned fetch : 1; + FILE *out; + unsigned fetch : 1, + import : 1, + option : 1, + push : 1, + connect : 1, + no_disconnect_req : 1; + /* These go from remote name (as in "list") to private name */ + struct refspec *refspecs; + int refspec_nr; + /* Transport options for fetch-pack/send-pack (should one of + * those be invoked). + */ + struct git_transport_options transport_options; }; +static void sendline(struct helper_data *helper, struct strbuf *buffer) +{ + if (debug) + fprintf(stderr, "Debug: Remote helper: -> %s", buffer->buf); + if (write_in_full(helper->helper->in, buffer->buf, buffer->len) + != buffer->len) + die_errno("Full write to remote helper failed"); +} + +static int recvline_fh(FILE *helper, struct strbuf *buffer) +{ + strbuf_reset(buffer); + if (debug) + fprintf(stderr, "Debug: Remote helper: Waiting...\n"); + if (strbuf_getline(buffer, helper, '\n') == EOF) { + if (debug) + fprintf(stderr, "Debug: Remote helper quit.\n"); + exit(128); + } + + if (debug) + fprintf(stderr, "Debug: Remote helper: <- %s\n", buffer->buf); + return 0; +} + +static int recvline(struct helper_data *helper, struct strbuf *buffer) +{ + return recvline_fh(helper->out, buffer); +} + +static void xchgline(struct helper_data *helper, struct strbuf *buffer) +{ + sendline(helper, buffer); + recvline(helper, buffer); +} + +static void write_constant(int fd, const char *str) +{ + if (debug) + fprintf(stderr, "Debug: Remote helper: -> %s", str); + if (write_in_full(fd, str, strlen(str)) != strlen(str)) + die_errno("Full write to remote helper failed"); +} + +const char *remove_ext_force(const char *url) +{ + if (url) { + const char *colon = strchr(url, ':'); + if (colon && colon[1] == ':') + return colon + 2; + } + return url; +} + +static void do_take_over(struct transport *transport) +{ + struct helper_data *data; + data = (struct helper_data *)transport->data; + transport_take_over(transport, data->helper); + fclose(data->out); + free(data); +} + 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; + int duped; + int code; if (data->helper) return data->helper; @@ -28,36 +112,100 @@ static struct child_process *get_helper(struct transport *transport) helper->out = -1; helper->err = 0; helper->argv = xcalloc(4, sizeof(*helper->argv)); - strbuf_addf(&buf, "remote-%s", data->name); + strbuf_addf(&buf, "git-remote-%s", data->name); helper->argv[0] = strbuf_detach(&buf, NULL); helper->argv[1] = transport->remote->name; - helper->argv[2] = transport->url; - helper->git_cmd = 1; - if (start_command(helper)) - die("Unable to run helper: git %s", helper->argv[0]); + helper->argv[2] = remove_ext_force(transport->url); + helper->git_cmd = 0; + helper->silent_exec_failure = 1; + code = start_command(helper); + if (code < 0 && errno == ENOENT) + die("Unable to find remote helper for '%s'", data->name); + else if (code != 0) + exit(code); + data->helper = helper; + data->no_disconnect_req = 0; + + /* + * Open the output as FILE* so strbuf_getline() can be used. + * Do this with duped fd because fclose() will close the fd, + * and stuff like taking over will require the fd to remain. + */ + duped = dup(helper->out); + if (duped < 0) + die_errno("Can't dup helper output fd"); + data->out = xfdopen(duped, "r"); - write_str_in_full(helper->in, "capabilities\n"); + write_constant(helper->in, "capabilities\n"); - file = xfdopen(helper->out, "r"); while (1) { - if (strbuf_getline(&buf, file, '\n') == EOF) - exit(128); /* child died, message supplied already */ + const char *capname; + int mandatory = 0; + recvline(data, &buf); if (!*buf.buf) break; - if (!strcmp(buf.buf, "fetch")) + + if (*buf.buf == '*') { + capname = buf.buf + 1; + mandatory = 1; + } else + capname = buf.buf; + + if (debug) + fprintf(stderr, "Debug: Got cap %s\n", capname); + if (!strcmp(capname, "fetch")) data->fetch = 1; + else if (!strcmp(capname, "option")) + data->option = 1; + else if (!strcmp(capname, "push")) + data->push = 1; + else if (!strcmp(capname, "import")) + data->import = 1; + else if (!data->refspecs && !prefixcmp(capname, "refspec ")) { + ALLOC_GROW(refspecs, + refspec_nr + 1, + refspec_alloc); + refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec ")); + } else if (!strcmp(capname, "connect")) { + data->connect = 1; + } else if (mandatory) { + die("Unknown mandatory capability %s. This remote " + "helper probably needs newer version of Git.\n", + capname); + } + } + 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); + if (debug) + fprintf(stderr, "Debug: Capabilities complete.\n"); return data->helper; } static int disconnect_helper(struct transport *transport) { struct helper_data *data = transport->data; + struct strbuf buf = STRBUF_INIT; + if (data->helper) { - write_str_in_full(data->helper->in, "\n"); + if (debug) + fprintf(stderr, "Debug: Disconnecting.\n"); + if (!data->no_disconnect_req) { + strbuf_addf(&buf, "\n"); + sendline(data, &buf); + } close(data->helper->in); + close(data->helper->out); + fclose(data->out); finish_command(data->helper); free((char *)data->helper->argv[0]); free(data->helper->argv); @@ -67,14 +215,99 @@ static int disconnect_helper(struct transport *transport) return 0; } +static const char *unsupported_options[] = { + TRANS_OPT_UPLOADPACK, + TRANS_OPT_RECEIVEPACK, + TRANS_OPT_THIN, + TRANS_OPT_KEEP + }; +static const char *boolean_options[] = { + TRANS_OPT_THIN, + TRANS_OPT_KEEP, + TRANS_OPT_FOLLOWTAGS + }; + +static int set_helper_option(struct transport *transport, + const char *name, const char *value) +{ + struct helper_data *data = transport->data; + struct strbuf buf = STRBUF_INIT; + int i, ret, is_bool = 0; + + get_helper(transport); + + 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'); + + xchgline(data, &buf); + + 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(2)); + + 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, const struct ref **to_fetch) + int nr_heads, struct ref **to_fetch) { - struct child_process *helper = get_helper(transport); - FILE *file = xfdopen(helper->out, "r"); + 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,21 +315,193 @@ static int fetch_with_fetch(struct transport *transport, strbuf_addf(&buf, "fetch %s %s\n", sha1_to_hex(posn->old_sha1), posn->name); - write_in_full(helper->in, buf.buf, buf.len); + } + + strbuf_addch(&buf, '\n'); + sendline(data, &buf); + + while (1) { + recvline(data, &buf); + + 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 helper_data *data = transport->data; + int i; + struct ref *posn; + struct strbuf buf = STRBUF_INIT; + + get_helper(transport); + + 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); + sendline(data, &buf); 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 process_connect_service(struct transport *transport, + const char *name, const char *exec) +{ + struct helper_data *data = transport->data; + struct strbuf cmdbuf = STRBUF_INIT; + struct child_process *helper; + int r, duped, ret = 0; + FILE *input; + + helper = get_helper(transport); + + /* + * Yes, dup the pipe another time, as we need unbuffered version + * of input pipe as FILE*. fclose() closes the underlying fd and + * stream buffering only can be changed before first I/O operation + * on it. + */ + duped = dup(helper->out); + if (duped < 0) + die_errno("Can't dup helper output fd"); + input = xfdopen(duped, "r"); + setvbuf(input, NULL, _IONBF, 0); + + /* + * Handle --upload-pack and friends. This is fire and forget... + * just warn if it fails. + */ + if (strcmp(name, exec)) { + r = set_helper_option(transport, "servpath", exec); + if (r > 0) + warning("Setting remote service path not supported by protocol."); + else if (r < 0) + warning("Invalid remote service path."); + } + + if (data->connect) + strbuf_addf(&cmdbuf, "connect %s\n", name); + else + goto exit; + + sendline(data, &cmdbuf); + recvline_fh(input, &cmdbuf); + if (!strcmp(cmdbuf.buf, "")) { + data->no_disconnect_req = 1; + if (debug) + fprintf(stderr, "Debug: Smart transport connection " + "ready.\n"); + ret = 1; + } else if (!strcmp(cmdbuf.buf, "fallback")) { + if (debug) + fprintf(stderr, "Debug: Falling back to dumb " + "transport.\n"); + } else + die("Unknown response to connect: %s", + cmdbuf.buf); + +exit: + fclose(input); + return ret; +} + +static int process_connect(struct transport *transport, + int for_push) +{ + struct helper_data *data = transport->data; + const char *name; + const char *exec; + + name = for_push ? "git-receive-pack" : "git-upload-pack"; + if (for_push) + exec = data->transport_options.receivepack; + else + exec = data->transport_options.uploadpack; + + return process_connect_service(transport, name, exec); +} + +static int connect_helper(struct transport *transport, const char *name, + const char *exec, int fd[2]) +{ + struct helper_data *data = transport->data; + + /* Get_helper so connect is inited. */ + get_helper(transport); + if (!data->connect) + die("Operation not supported by protocol."); + + if (!process_connect_service(transport, name, exec)) + die("Can't connect to subservice %s.", name); + + fd[0] = data->helper->out; + fd[1] = data->helper->in; 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; + if (process_connect(transport, 0)) { + do_take_over(transport); + return transport->fetch(transport, nr_heads, to_fetch); + } + count = 0; for (i = 0; i < nr_heads; i++) if (!(to_fetch[i]->status & REF_STATUS_UPTODATE)) @@ -108,27 +513,192 @@ 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 (process_connect(transport, 1)) { + do_take_over(transport); + return transport->push_refs(transport, remote_refs, flags); + } + + if (!remote_refs) { + fprintf(stderr, "No refs in common and none specified; doing nothing.\n" + "Perhaps you should specify a branch such as 'master'.\n"); + return 0; + } + + helper = get_helper(transport); + if (!data->push) + return 1; + + for (ref = remote_refs; ref; ref = ref->next) { + if (!ref->peer_ref && !mirror) + continue; + + /* Check for statuses set by set_ref_status_for_push() */ + switch (ref->status) { + case REF_STATUS_REJECT_NONFASTFORWARD: + case REF_STATUS_UPTODATE: + continue; + default: + ; /* do nothing */ + } + + 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'); + sendline(data, &buf); + + ref = remote_refs; + while (1) { + char *refname, *msg; + int status; + + recvline(data, &buf); + 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; + } + + if (ref->status != REF_STATUS_NONE) { + /* + * Earlier, the ref was marked not to be pushed, so ignore the ref + * status reported by the remote helper if the latter is 'no match'. + */ + if (status == REF_STATUS_NONE) + 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 (process_connect(transport, for_push)) { + do_take_over(transport); + return transport->get_refs_list(transport, for_push); + } + + 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) - exit(128); /* child died, message supplied already */ + recvline(data, &buf); if (!*buf.buf) break; @@ -145,8 +715,16 @@ 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); } + if (debug) + fprintf(stderr, "Debug: Read ref listing.\n"); strbuf_release(&buf); for (posn = ret; posn; posn = posn->next) @@ -160,9 +738,16 @@ int transport_helper_init(struct transport *transport, const char *name) struct helper_data *data = xcalloc(sizeof(*data), 1); data->name = name; + if (getenv("GIT_TRANSPORT_HELPER_DEBUG")) + debug = 1; + 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; + transport->connect = connect_helper; + transport->smart_options = &(data->transport_options); return 0; } diff --git a/transport.c b/transport.c index 298dc46ec5..08e4fa0354 100644 --- a/transport.c +++ b/transport.c @@ -8,6 +8,7 @@ #include "bundle.h" #include "dir.h" #include "refs.h" +#include "branch.h" /* rsync support */ @@ -135,6 +136,53 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list) } } +static void set_upstreams(struct transport *transport, struct ref *refs, + int pretend) +{ + struct ref *ref; + for (ref = refs; ref; ref = ref->next) { + const char *localname; + const char *tmp; + const char *remotename; + unsigned char sha[20]; + int flag = 0; + /* + * Check suitability for tracking. Must be successful / + * already up-to-date ref create/modify (not delete). + */ + if (ref->status != REF_STATUS_OK && + ref->status != REF_STATUS_UPTODATE) + continue; + if (!ref->peer_ref) + continue; + if (!ref->new_sha1 || is_null_sha1(ref->new_sha1)) + continue; + + /* Follow symbolic refs (mainly for HEAD). */ + localname = ref->peer_ref->name; + remotename = ref->name; + tmp = resolve_ref(localname, sha, 1, &flag); + if (tmp && flag & REF_ISSYMREF && + !prefixcmp(tmp, "refs/heads/")) + localname = tmp; + + /* Both source and destination must be local branches. */ + if (!localname || prefixcmp(localname, "refs/heads/")) + continue; + if (!remotename || prefixcmp(remotename, "refs/heads/")) + continue; + + if (!pretend) + install_branch_config(BRANCH_CONFIG_VERBOSE, + localname + 11, transport->remote->name, + remotename); + else + printf("Would set upstream of '%s' to '%s' of '%s'\n", + localname + 11, remotename + 11, + transport->remote->name); + } +} + static const char *rsync_url(const char *url) { return prefixcmp(url, "rsync://") ? skip_prefix(url, "rsync:") : url; @@ -143,7 +191,7 @@ static const char *rsync_url(const char *url) static struct ref *get_refs_via_rsync(struct transport *transport, int for_push) { struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT; - struct ref dummy, *tail = &dummy; + struct ref dummy = {0}, *tail = &dummy; struct child_process rsync; const char *args[5]; int temp_dir_len; @@ -204,7 +252,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 +397,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 +427,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); @@ -424,41 +443,36 @@ static int close_bundle(struct transport *transport) } struct git_transport_data { - unsigned thin : 1; - unsigned keep : 1; - unsigned followtags : 1; - int depth; + struct git_transport_options options; struct child_process *conn; int fd[2]; - const char *uploadpack; - const char *receivepack; + unsigned got_remote_heads : 1; struct extra_have_objects extra_have; }; -static int set_git_option(struct transport *connection, +static int set_git_option(struct git_transport_options *opts, const char *name, const char *value) { - struct git_transport_data *data = connection->data; if (!strcmp(name, TRANS_OPT_UPLOADPACK)) { - data->uploadpack = value; + opts->uploadpack = value; return 0; } else if (!strcmp(name, TRANS_OPT_RECEIVEPACK)) { - data->receivepack = value; + opts->receivepack = value; return 0; } else if (!strcmp(name, TRANS_OPT_THIN)) { - data->thin = !!value; + opts->thin = !!value; return 0; } else if (!strcmp(name, TRANS_OPT_FOLLOWTAGS)) { - data->followtags = !!value; + opts->followtags = !!value; return 0; } else if (!strcmp(name, TRANS_OPT_KEEP)) { - data->keep = !!value; + opts->keep = !!value; return 0; } else if (!strcmp(name, TRANS_OPT_DEPTH)) { if (!value) - data->depth = 0; + opts->depth = 0; else - data->depth = atoi(value); + opts->depth = atoi(value); return 0; } return 1; @@ -467,9 +481,15 @@ static int set_git_option(struct transport *connection, static int connect_setup(struct transport *transport, int for_push, int verbose) { struct git_transport_data *data = transport->data; + + if (data->conn) + return 0; + data->conn = git_connect(data->fd, transport->url, - for_push ? data->receivepack : data->uploadpack, + for_push ? data->options.receivepack : + data->options.uploadpack, verbose ? CONNECT_VERBOSE : 0); + return 0; } @@ -481,12 +501,13 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus connect_setup(transport, for_push, 0); get_remote_heads(data->fd[0], &refs, 0, NULL, for_push ? REF_NORMAL : 0, &data->extra_have); + data->got_remote_heads = 1; return refs; } 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)); @@ -498,22 +519,23 @@ static int fetch_refs_via_pack(struct transport *transport, struct ref *refs_tmp = NULL; memset(&args, 0, sizeof(args)); - args.uploadpack = data->uploadpack; - args.keep_pack = data->keep; + args.uploadpack = data->options.uploadpack; + args.keep_pack = data->options.keep; args.lock_pack = 1; - args.use_thin_pack = data->thin; - args.include_tag = data->followtags; + args.use_thin_pack = data->options.thin; + args.include_tag = data->options.followtags; args.verbose = (transport->verbose > 0); args.quiet = (transport->verbose < 0); - args.no_progress = args.quiet || (!transport->progress && !isatty(1)); - args.depth = data->depth; + args.no_progress = args.quiet || (!transport->progress && !isatty(2)); + args.depth = data->options.depth; for (i = 0; i < nr_heads; i++) origh[i] = heads[i] = xstrdup(to_fetch[i]->name); - if (!data->conn) { + if (!data->got_remote_heads) { connect_setup(transport, 0, 0); get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL); + data->got_remote_heads = 1; } refs = fetch_pack(&args, data->fd, data->conn, @@ -524,6 +546,7 @@ static int fetch_refs_via_pack(struct transport *transport, if (finish_connect(data->conn)) refs = NULL; data->conn = NULL; + data->got_remote_heads = 0; free_refs(refs_tmp); @@ -668,7 +691,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, @@ -752,17 +775,19 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re struct send_pack_args args; int ret; - if (!data->conn) { + if (!data->got_remote_heads) { struct ref *tmp_refs; connect_setup(transport, 1, 0); get_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL, NULL); + data->got_remote_heads = 1; } + 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; + args.use_thin_pack = data->options.thin; args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE); args.quiet = !!(flags & TRANSPORT_PUSH_QUIET); args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN); @@ -774,15 +799,28 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re close(data->fd[0]); ret |= finish_connect(data->conn); data->conn = NULL; + data->got_remote_heads = 0; return ret; } +static int connect_git(struct transport *transport, const char *name, + const char *executable, int fd[2]) +{ + struct git_transport_data *data = transport->data; + data->conn = git_connect(data->fd, transport->url, + executable, 0); + fd[0] = data->fd[0]; + fd[1] = data->fd[1]; + return 0; +} + static int disconnect_git(struct transport *transport) { struct git_transport_data *data = transport->data; if (data->conn) { - packet_flush(data->fd[1]); + if (data->got_remote_heads) + packet_flush(data->fd[1]); close(data->fd[0]); close(data->fd[1]); finish_connect(data->conn); @@ -792,6 +830,32 @@ static int disconnect_git(struct transport *transport) return 0; } +void transport_take_over(struct transport *transport, + struct child_process *child) +{ + struct git_transport_data *data; + + if (!transport->smart_options) + die("Bug detected: Taking over transport requires non-NULL " + "smart_options field."); + + data = xcalloc(1, sizeof(*data)); + data->options = *transport->smart_options; + data->conn = child; + data->fd[0] = data->conn->out; + data->fd[1] = data->conn->in; + data->got_remote_heads = 0; + transport->data = data; + + transport->set_option = NULL; + transport->get_refs_list = get_refs_via_connect; + transport->fetch = fetch_refs_via_pack; + transport->push = NULL; + transport->push_refs = git_transport_push; + transport->disconnect = disconnect_git; + transport->smart_options = &(data->options); +} + static int is_local(const char *url) { const char *colon = strchr(url, ':'); @@ -808,55 +872,120 @@ static int is_file(const char *url) return S_ISREG(buf.st_mode); } +static int is_url(const char *url) +{ + const char *url2, *first_slash; + + if (!url) + return 0; + url2 = url; + first_slash = strchr(url, '/'); + + /* Input with no slash at all or slash first can't be URL. */ + if (!first_slash || first_slash == url) + return 0; + /* Character before must be : and next must be /. */ + if (first_slash[-1] != ':' || first_slash[1] != '/') + return 0; + /* There must be something before the :// */ + if (first_slash == url + 1) + return 0; + /* + * Check all characters up to first slash - 1. Only alphanum + * is allowed. + */ + url2 = url; + while (url2 < first_slash - 1) { + if (!isalnum((unsigned char)*url2)) + return 0; + url2++; + } + + /* Valid enough. */ + return 1; +} + +static int external_specification_len(const char *url) +{ + return strchr(url, ':') - url; +} + struct transport *transport_get(struct remote *remote, const char *url) { + const char *helper; struct transport *ret = xcalloc(1, sizeof(*ret)); if (!remote) die("No remote provided to transport_get()"); + ret->got_remote_refs = 0; ret->remote = remote; + helper = remote->foreign_vcs; + + if (!url && remote->url) + url = remote->url[0]; ret->url = url; - if (!prefixcmp(url, "rsync:")) { + /* maybe it is a foreign URL? */ + if (url) { + const char *p = url; + + while (isalnum(*p)) + p++; + if (!prefixcmp(p, "::")) + helper = xstrndup(url, p - url); + } + + if (helper) { + transport_helper_init(ret, helper); + } else if (!prefixcmp(url, "rsync:")) { ret->get_refs_list = get_refs_via_rsync; ret->fetch = fetch_objs_via_rsync; ret->push = rsync_transport_push; - - } else if (!prefixcmp(url, "http://") - || !prefixcmp(url, "https://") - || !prefixcmp(url, "ftp://")) { - transport_helper_init(ret, "curl"); -#ifdef NO_CURL - error("git was compiled without libcurl support."); -#else - ret->push = curl_transport_push; -#endif - + ret->smart_options = NULL; } else if (is_local(url) && is_file(url)) { struct bundle_transport_data *data = xcalloc(1, sizeof(*data)); ret->data = data; ret->get_refs_list = get_refs_from_bundle; ret->fetch = fetch_refs_from_bundle; ret->disconnect = close_bundle; - - } else { + ret->smart_options = NULL; + } else if (!is_url(url) + || !prefixcmp(url, "file://") + || !prefixcmp(url, "git://") + || !prefixcmp(url, "ssh://") + || !prefixcmp(url, "git+ssh://") + || !prefixcmp(url, "ssh+git://")) { + /* These are builtin smart transports. */ struct git_transport_data *data = xcalloc(1, sizeof(*data)); ret->data = data; - ret->set_option = set_git_option; + ret->set_option = NULL; ret->get_refs_list = get_refs_via_connect; ret->fetch = fetch_refs_via_pack; ret->push_refs = git_transport_push; + ret->connect = connect_git; ret->disconnect = disconnect_git; + ret->smart_options = &(data->options); - data->thin = 1; data->conn = NULL; - data->uploadpack = "git-upload-pack"; + data->got_remote_heads = 0; + } else { + /* Unknown protocol in URL. Pass to external handler. */ + int len = external_specification_len(url); + char *handler = xmalloc(len + 1); + handler[len] = 0; + strncpy(handler, url, len); + transport_helper_init(ret, handler); + } + + if (ret->smart_options) { + ret->smart_options->thin = 1; + ret->smart_options->uploadpack = "git-upload-pack"; if (remote->uploadpack) - data->uploadpack = remote->uploadpack; - data->receivepack = "git-receive-pack"; + ret->smart_options->uploadpack = remote->uploadpack; + ret->smart_options->receivepack = "git-receive-pack"; if (remote->receivepack) - data->receivepack = remote->receivepack; + ret->smart_options->receivepack = remote->receivepack; } return ret; @@ -865,8 +994,23 @@ struct transport *transport_get(struct remote *remote, const char *url) int transport_set_option(struct transport *transport, const char *name, const char *value) { + int git_reports = 1, protocol_reports = 1; + + if (transport->smart_options) + git_reports = set_git_option(transport->smart_options, + name, value); + if (transport->set_option) - return transport->set_option(transport, name, value); + protocol_reports = transport->set_option(transport, name, + value); + + /* If either report is 0, report 0 (success). */ + if (!git_reports || !protocol_reports) + return 0; + /* If either reports -1 (invalid value), report -1. */ + if ((git_reports == -1) || (protocol_reports == -1)) + return -1; + /* Otherwise if both report unknown, report unknown. */ return 1; } @@ -877,9 +1021,13 @@ int transport_push(struct transport *transport, *nonfastforward = 0; verify_remote_names(refspec_nr, refspec); - if (transport->push) + if (transport->push) { + /* Maybe FIXME. But no important transport uses this case. */ + if (flags & TRANSPORT_PUSH_SET_UPSTREAM) + die("This transport does not support using --set-upstream"); + return transport->push(transport, refspec_nr, refspec, flags); - if (transport->push_refs) { + } else if (transport->push_refs) { struct ref *remote_refs = transport->get_refs_list(transport, 1); struct ref *local_refs = get_local_heads(); @@ -887,7 +1035,8 @@ int transport_push(struct transport *transport, int verbose = flags & TRANSPORT_PUSH_VERBOSE; int quiet = flags & TRANSPORT_PUSH_QUIET; int porcelain = flags & TRANSPORT_PUSH_PORCELAIN; - int ret; + int pretend = flags & TRANSPORT_PUSH_DRY_RUN; + int ret, err; if (flags & TRANSPORT_PUSH_ALL) match_flags |= MATCH_REFS_ALL; @@ -899,13 +1048,23 @@ int transport_push(struct transport *transport, return -1; } + set_ref_status_for_push(remote_refs, + flags & TRANSPORT_PUSH_MIRROR, + flags & TRANSPORT_PUSH_FORCE); + ret = transport->push_refs(transport, remote_refs, flags); + err = push_had_errors(remote_refs); + + ret |= err; - if (!quiet || push_had_errors(remote_refs)) + if (!quiet || err) print_push_status(transport->url, remote_refs, verbose | porcelain, porcelain, nonfastforward); + if (flags & TRANSPORT_PUSH_SET_UPSTREAM) + set_upstreams(transport, remote_refs, pretend); + if (!(flags & TRANSPORT_PUSH_DRY_RUN)) { struct ref *ref; for (ref = remote_refs; ref; ref = ref->next) @@ -921,21 +1080,25 @@ int transport_push(struct transport *transport, const struct ref *transport_get_remote_refs(struct transport *transport) { - if (!transport->remote_refs) + if (!transport->got_remote_refs) { transport->remote_refs = transport->get_refs_list(transport, 0); + transport->got_remote_refs = 1; + } + 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); @@ -956,6 +1119,7 @@ int transport_fetch_refs(struct transport *transport, const struct ref *refs) } rc = transport->fetch(transport, nr_heads, heads); + free(heads); return rc; } @@ -969,6 +1133,15 @@ void transport_unlock_pack(struct transport *transport) } } +int transport_connect(struct transport *transport, const char *name, + const char *exec, int fd[2]) +{ + if (transport->connect) + return transport->connect(transport, name, exec, fd); + else + die("Operation not supported by protocol"); +} + int transport_disconnect(struct transport *transport) { int ret = 0; diff --git a/transport.h b/transport.h index c14da6f1e5..6dd9ae182f 100644 --- a/transport.h +++ b/transport.h @@ -4,6 +4,15 @@ #include "cache.h" #include "remote.h" +struct git_transport_options { + unsigned thin : 1; + unsigned keep : 1; + unsigned followtags : 1; + int depth; + const char *uploadpack; + const char *receivepack; +}; + struct transport { struct remote *remote; const char *url; @@ -11,6 +20,12 @@ struct transport { const struct ref *remote_refs; /** + * Indicates whether we already called get_refs_list(); set by + * transport.c::transport_get_remote_refs(). + */ + unsigned got_remote_refs : 1; + + /** * Returns 0 if successful, positive if the option is not * recognized or is inapplicable, and negative if the option * is applicable but the value is invalid. @@ -18,16 +33,61 @@ 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); + int (*connect)(struct transport *connection, const char *name, + const char *executable, int fd[2]); + /** 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; - /* Force progress even if the output is not a tty */ + signed verbose : 3; + /* Force progress even if stderr is not a tty */ unsigned progress : 1; + /* + * If transport is at least potentially smart, this points to + * git_transport_options structure to use in case transport + * actually turns out to be smart. + */ + struct git_transport_options *smart_options; }; #define TRANSPORT_PUSH_ALL 1 @@ -37,6 +97,7 @@ struct transport { #define TRANSPORT_PUSH_VERBOSE 16 #define TRANSPORT_PUSH_PORCELAIN 32 #define TRANSPORT_PUSH_QUIET 64 +#define TRANSPORT_PUSH_SET_UPSTREAM 128 /* Returns a transport suitable for the url */ struct transport *transport_get(struct remote *, const char *); @@ -74,10 +135,15 @@ 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); +void transport_take_over(struct transport *transport, + struct child_process *child); + +int transport_connect(struct transport *transport, const char *name, + const char *exec, int fd[2]); /* Transport methods defined outside transport.c */ int transport_helper_init(struct transport *transport, const char *name); diff --git a/tree-diff.c b/tree-diff.c index 0459e54d3d..fe9f52c479 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -68,7 +68,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) { newbase[baselen + pathlen1] = 0; opt->change(opt, mode1, mode2, - sha1, sha2, newbase); + sha1, sha2, newbase, 0, 0); newbase[baselen + pathlen1] = '/'; } retval = diff_tree_sha1(sha1, sha2, newbase, opt); @@ -77,7 +77,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const } fullname = malloc_fullname(base, baselen, path1, pathlen1); - opt->change(opt, mode1, mode2, sha1, sha2, fullname); + opt->change(opt, mode1, mode2, sha1, sha2, fullname, 0, 0); free(fullname); return 0; } @@ -241,7 +241,7 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) { newbase[baselen + pathlen] = 0; - opt->add_remove(opt, *prefix, mode, sha1, newbase); + opt->add_remove(opt, *prefix, mode, sha1, newbase, 0); newbase[baselen + pathlen] = '/'; } @@ -252,7 +252,7 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree free(newbase); } else { char *fullname = malloc_fullname(base, baselen, path, pathlen); - opt->add_remove(opt, prefix[0], mode, sha1, fullname); + opt->add_remove(opt, prefix[0], mode, sha1, fullname, 0); free(fullname); } } @@ -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/tree-walk.c b/tree-walk.c index 02e2aed773..67a9a0c5a5 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -60,13 +60,6 @@ void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1) return buf; } -static int entry_compare(struct name_entry *a, struct name_entry *b) -{ - return df_name_compare( - a->path, tree_entry_len(a->path, a->sha1), a->mode, - b->path, tree_entry_len(b->path, b->sha1), b->mode); -} - static void entry_clear(struct name_entry *a) { memset(a, 0, sizeof(*a)); @@ -138,66 +131,264 @@ char *make_traverse_path(char *path, const struct traverse_info *info, const str return path; } +struct tree_desc_skip { + struct tree_desc_skip *prev; + const void *ptr; +}; + +struct tree_desc_x { + struct tree_desc d; + struct tree_desc_skip *skip; +}; + +static int name_compare(const char *a, int a_len, + const char *b, int b_len) +{ + int len = (a_len < b_len) ? a_len : b_len; + int cmp = memcmp(a, b, len); + if (cmp) + return cmp; + return (a_len - b_len); +} + +static int check_entry_match(const char *a, int a_len, const char *b, int b_len) +{ + /* + * The caller wants to pick *a* from a tree or nothing. + * We are looking at *b* in a tree. + * + * (0) If a and b are the same name, we are trivially happy. + * + * There are three possibilities where *a* could be hiding + * behind *b*. + * + * (1) *a* == "t", *b* == "ab" i.e. *b* sorts earlier than *a* no + * matter what. + * (2) *a* == "t", *b* == "t-2" and "t" is a subtree in the tree; + * (3) *a* == "t-2", *b* == "t" and "t-2" is a blob in the tree. + * + * Otherwise we know *a* won't appear in the tree without + * scanning further. + */ + + int cmp = name_compare(a, a_len, b, b_len); + + /* Most common case first -- reading sync'd trees */ + if (!cmp) + return cmp; + + if (0 < cmp) { + /* a comes after b; it does not matter if it is case (3) + if (b_len < a_len && !memcmp(a, b, b_len) && a[b_len] < '/') + return 1; + */ + return 1; /* keep looking */ + } + + /* b comes after a; are we looking at case (2)? */ + if (a_len < b_len && !memcmp(a, b, a_len) && b[a_len] < '/') + return 1; /* keep looking */ + + return -1; /* a cannot appear in the tree */ +} + +/* + * From the extended tree_desc, extract the first name entry, while + * paying attention to the candidate "first" name. Most importantly, + * when looking for an entry, if there are entries that sorts earlier + * in the tree object representation than that name, skip them and + * process the named entry first. We will remember that we haven't + * processed the first entry yet, and in the later call skip the + * entry we processed early when update_extended_entry() is called. + * + * E.g. if the underlying tree object has these entries: + * + * blob "t-1" + * blob "t-2" + * tree "t" + * blob "t=1" + * + * and the "first" asks for "t", remember that we still need to + * process "t-1" and "t-2" but extract "t". After processing the + * entry "t" from this call, the caller will let us know by calling + * update_extended_entry() that we can remember "t" has been processed + * already. + */ + +static void extended_entry_extract(struct tree_desc_x *t, + struct name_entry *a, + const char *first, + int first_len) +{ + const char *path; + int len; + struct tree_desc probe; + struct tree_desc_skip *skip; + + /* + * Extract the first entry from the tree_desc, but skip the + * ones that we already returned in earlier rounds. + */ + while (1) { + if (!t->d.size) { + entry_clear(a); + break; /* not found */ + } + entry_extract(&t->d, a); + for (skip = t->skip; skip; skip = skip->prev) + if (a->path == skip->ptr) + break; /* found */ + if (!skip) + break; + /* We have processed this entry already. */ + update_tree_entry(&t->d); + } + + if (!first || !a->path) + return; + + /* + * The caller wants "first" from this tree, or nothing. + */ + path = a->path; + len = tree_entry_len(a->path, a->sha1); + switch (check_entry_match(first, first_len, path, len)) { + case -1: + entry_clear(a); + case 0: + return; + default: + break; + } + + /* + * We need to look-ahead -- we suspect that a subtree whose + * name is "first" may be hiding behind the current entry "path". + */ + probe = t->d; + while (probe.size) { + entry_extract(&probe, a); + path = a->path; + len = tree_entry_len(a->path, a->sha1); + switch (check_entry_match(first, first_len, path, len)) { + case -1: + entry_clear(a); + case 0: + return; + default: + update_tree_entry(&probe); + break; + } + /* keep looking */ + } + entry_clear(a); +} + +static void update_extended_entry(struct tree_desc_x *t, struct name_entry *a) +{ + if (t->d.entry.path == a->path) { + update_tree_entry(&t->d); + } else { + /* we have returned this entry early */ + struct tree_desc_skip *skip = xmalloc(sizeof(*skip)); + skip->ptr = a->path; + skip->prev = t->skip; + t->skip = skip; + } +} + +static void free_extended_entry(struct tree_desc_x *t) +{ + struct tree_desc_skip *p, *s; + + for (s = t->skip; s; s = p) { + p = s->prev; + free(s); + } +} + int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) { int ret = 0; struct name_entry *entry = xmalloc(n*sizeof(*entry)); + int i; + struct tree_desc_x *tx = xcalloc(n, sizeof(*tx)); + + for (i = 0; i < n; i++) + tx[i].d = t[i]; for (;;) { - unsigned long mask = 0; - unsigned long dirmask = 0; - int i, last; + unsigned long mask, dirmask; + const char *first = NULL; + int first_len = 0; + struct name_entry *e; + int len; - last = -1; for (i = 0; i < n; i++) { - if (!t[i].size) + e = entry + i; + extended_entry_extract(tx + i, e, NULL, 0); + } + + /* + * A tree may have "t-2" at the current location even + * though it may have "t" that is a subtree behind it, + * and another tree may return "t". We want to grab + * all "t" from all trees to match in such a case. + */ + for (i = 0; i < n; i++) { + e = entry + i; + if (!e->path) continue; - entry_extract(t+i, entry+i); - if (last >= 0) { - int cmp = entry_compare(entry+i, entry+last); - - /* - * Is the new name bigger than the old one? - * Ignore it - */ - if (cmp > 0) + len = tree_entry_len(e->path, e->sha1); + if (!first) { + first = e->path; + first_len = len; + continue; + } + if (name_compare(e->path, len, first, first_len) < 0) { + first = e->path; + first_len = len; + } + } + + if (first) { + for (i = 0; i < n; i++) { + e = entry + i; + extended_entry_extract(tx + i, e, first, first_len); + /* Cull the ones that are not the earliest */ + if (!e->path) continue; - /* - * Is the new name smaller than the old one? - * Ignore all old ones - */ - if (cmp < 0) - mask = 0; + len = tree_entry_len(e->path, e->sha1); + if (name_compare(e->path, len, first, first_len)) + entry_clear(e); } + } + + /* Now we have in entry[i] the earliest name from the trees */ + mask = 0; + dirmask = 0; + for (i = 0; i < n; i++) { + if (!entry[i].path) + continue; mask |= 1ul << i; if (S_ISDIR(entry[i].mode)) dirmask |= 1ul << i; - last = i; } if (!mask) break; - dirmask &= mask; - - /* - * Clear all the unused name-entries. - */ - for (i = 0; i < n; i++) { - if (mask & (1ul << i)) - continue; - entry_clear(entry + i); - } ret = info->fn(n, mask, dirmask, entry, info); if (ret < 0) break; - if (ret) - mask &= ret; + mask &= ret; ret = 0; - for (i = 0; i < n; i++) { + for (i = 0; i < n; i++) if (mask & (1ul << i)) - update_tree_entry(t + i); - } + update_extended_entry(tx + i, entry + i); } free(entry); + for (i = 0; i < n; i++) + free_extended_entry(tx + i); + free(tx); return ret; } @@ -250,6 +441,7 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch if (name[0] == '\0') { hashcpy(sha1, root); + free(tree); return 0; } diff --git a/unpack-trees.c b/unpack-trees.c index 720f7a1616..75f54cac97 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -32,6 +32,12 @@ static struct unpack_trees_error_msgs unpack_plumbing_errors = { /* bind_overlap */ "Entry '%s' overlaps with '%s'. Cannot bind.", + + /* sparse_not_uptodate_file */ + "Entry '%s' not uptodate. Cannot update sparse checkout.", + + /* would_lose_orphaned */ + "Working tree file '%s' would be %s by sparse checkout update.", }; #define ERRORMSG(o,fld) \ @@ -61,8 +67,16 @@ static void unlink_entry(struct cache_entry *ce) { if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce))) return; - if (unlink_or_warn(ce->name)) - return; + if (S_ISGITLINK(ce->ce_mode)) { + if (rmdir(ce->name)) { + warning("unable to rmdir %s: %s", + ce->name, strerror(errno)); + return; + } + } + else + if (unlink_or_warn(ce->name)) + return; schedule_dir_for_removal(ce->name, ce_namelen(ce)); } @@ -78,7 +92,7 @@ static int check_updates(struct unpack_trees_options *o) if (o->update && o->verbose_update) { for (total = cnt = 0; cnt < index->cache_nr; cnt++) { struct cache_entry *ce = index->cache[cnt]; - if (ce->ce_flags & (CE_UPDATE | CE_REMOVE)) + if (ce->ce_flags & (CE_UPDATE | CE_REMOVE | CE_WT_REMOVE)) total++; } @@ -92,6 +106,13 @@ static int check_updates(struct unpack_trees_options *o) for (i = 0; i < index->cache_nr; i++) { struct cache_entry *ce = index->cache[i]; + if (ce->ce_flags & CE_WT_REMOVE) { + display_progress(progress, ++cnt); + if (o->update) + unlink_entry(ce); + continue; + } + if (ce->ce_flags & CE_REMOVE) { display_progress(progress, ++cnt); if (o->update) @@ -118,6 +139,57 @@ static int check_updates(struct unpack_trees_options *o) return errs != 0; } +static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o); +static int verify_absent_sparse(struct cache_entry *ce, const char *action, struct unpack_trees_options *o); + +static int will_have_skip_worktree(const struct cache_entry *ce, struct unpack_trees_options *o) +{ + const char *basename; + + if (ce_stage(ce)) + return 0; + + basename = strrchr(ce->name, '/'); + basename = basename ? basename+1 : ce->name; + return excluded_from_list(ce->name, ce_namelen(ce), basename, NULL, o->el) <= 0; +} + +static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o) +{ + int was_skip_worktree = ce_skip_worktree(ce); + + if (will_have_skip_worktree(ce, o)) + ce->ce_flags |= CE_SKIP_WORKTREE; + else + ce->ce_flags &= ~CE_SKIP_WORKTREE; + + /* + * We only care about files getting into the checkout area + * If merge strategies want to remove some, go ahead, this + * flag will be removed eventually in unpack_trees() if it's + * outside checkout area. + */ + if (ce->ce_flags & CE_REMOVE) + return 0; + + if (!was_skip_worktree && ce_skip_worktree(ce)) { + /* + * If CE_UPDATE is set, verify_uptodate() must be called already + * also stat info may have lost after merged_entry() so calling + * verify_uptodate() again may fail + */ + if (!(ce->ce_flags & CE_UPDATE) && verify_uptodate_sparse(ce, o)) + return -1; + ce->ce_flags |= CE_WT_REMOVE; + } + if (was_skip_worktree && !ce_skip_worktree(ce)) { + if (verify_absent_sparse(ce, "overwritten", o)) + return -1; + ce->ce_flags |= CE_UPDATE; + } + return 0; +} + static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o) { int ret = o->fn(src, o); @@ -126,23 +198,142 @@ static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_o return ret; } -static int unpack_index_entry(struct cache_entry *ce, struct unpack_trees_options *o) +static void mark_ce_used(struct cache_entry *ce, struct unpack_trees_options *o) +{ + ce->ce_flags |= CE_UNPACKED; + + if (o->cache_bottom < o->src_index->cache_nr && + o->src_index->cache[o->cache_bottom] == ce) { + int bottom = o->cache_bottom; + while (bottom < o->src_index->cache_nr && + o->src_index->cache[bottom]->ce_flags & CE_UNPACKED) + bottom++; + o->cache_bottom = bottom; + } +} + +static void mark_all_ce_unused(struct index_state *index) +{ + int i; + for (i = 0; i < index->cache_nr; i++) + index->cache[i]->ce_flags &= ~CE_UNPACKED; +} + +static int locate_in_src_index(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + struct index_state *index = o->src_index; + int len = ce_namelen(ce); + int pos = index_name_pos(index, ce->name, len); + if (pos < 0) + pos = -1 - pos; + return pos; +} + +/* + * We call unpack_index_entry() with an unmerged cache entry + * only in diff-index, and it wants a single callback. Skip + * the other unmerged entry with the same name. + */ +static void mark_ce_used_same_name(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + struct index_state *index = o->src_index; + int len = ce_namelen(ce); + int pos; + + for (pos = locate_in_src_index(ce, o); pos < index->cache_nr; pos++) { + struct cache_entry *next = index->cache[pos]; + if (len != ce_namelen(next) || + memcmp(ce->name, next->name, len)) + break; + mark_ce_used(next, o); + } +} + +static struct cache_entry *next_cache_entry(struct unpack_trees_options *o) +{ + const struct index_state *index = o->src_index; + int pos = o->cache_bottom; + + while (pos < index->cache_nr) { + struct cache_entry *ce = index->cache[pos]; + if (!(ce->ce_flags & CE_UNPACKED)) + return ce; + pos++; + } + return NULL; +} + +static void add_same_unmerged(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + struct index_state *index = o->src_index; + int len = ce_namelen(ce); + int pos = index_name_pos(index, ce->name, len); + + if (0 <= pos) + die("programming error in a caller of mark_ce_used_same_name"); + for (pos = -pos - 1; pos < index->cache_nr; pos++) { + struct cache_entry *next = index->cache[pos]; + if (len != ce_namelen(next) || + memcmp(ce->name, next->name, len)) + break; + add_entry(o, next, 0, 0); + mark_ce_used(next, o); + } +} + +static int unpack_index_entry(struct cache_entry *ce, + struct unpack_trees_options *o) { struct cache_entry *src[5] = { ce, NULL, }; + int ret; - o->pos++; + mark_ce_used(ce, o); if (ce_stage(ce)) { if (o->skip_unmerged) { add_entry(o, ce, 0, 0); return 0; } } - return call_unpack_fn(src, o); + ret = call_unpack_fn(src, o); + if (ce_stage(ce)) + mark_ce_used_same_name(ce, o); + return ret; +} + +static int find_cache_pos(struct traverse_info *, const struct name_entry *); + +static void restore_cache_bottom(struct traverse_info *info, int bottom) +{ + struct unpack_trees_options *o = info->data; + + if (o->diff_index_cached) + return; + o->cache_bottom = bottom; +} + +static int switch_cache_bottom(struct traverse_info *info) +{ + struct unpack_trees_options *o = info->data; + int ret, pos; + + if (o->diff_index_cached) + return 0; + ret = o->cache_bottom; + pos = find_cache_pos(info->prev, &info->name); + + if (pos < -1) + o->cache_bottom = -2 - pos; + else if (pos < 0) + o->cache_bottom = o->src_index->cache_nr; + return ret; } static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info) { - int i; + int i, ret, bottom; struct tree_desc t[MAX_UNPACK_TREES]; struct traverse_info newinfo; struct name_entry *p; @@ -163,7 +354,11 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long sha1 = names[i].sha1; fill_tree_descriptor(t+i, sha1); } - return traverse_trees(n, t, &newinfo); + + bottom = switch_cache_bottom(&newinfo); + ret = traverse_trees(n, t, &newinfo); + restore_cache_bottom(&newinfo, bottom); + return ret; } /* @@ -212,6 +407,20 @@ static int compare_entry(const struct cache_entry *ce, const struct traverse_inf return ce_namelen(ce) > traverse_path_len(info, n); } +static int ce_in_traverse_path(const struct cache_entry *ce, + const struct traverse_info *info) +{ + if (!info->prev) + return 1; + if (do_compare_entry(ce, info->prev, &info->name)) + return 0; + /* + * If ce (blob) is the same name as the path (which is a tree + * we will be descending into), it won't be inside it. + */ + return (info->pathlen < ce_namelen(ce)); +} + static struct cache_entry *create_ce_entry(const struct traverse_info *info, const struct name_entry *n, int stage) { int len = traverse_path_len(info, n); @@ -277,6 +486,125 @@ 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; +} + +/* NEEDSWORK: give this a better name and share with tree-walk.c */ +static int name_compare(const char *a, int a_len, + const char *b, int b_len) +{ + int len = (a_len < b_len) ? a_len : b_len; + int cmp = memcmp(a, b, len); + if (cmp) + return cmp; + return (a_len - b_len); +} + +/* + * The tree traversal is looking at name p. If we have a matching entry, + * return it. If name p is a directory in the index, do not return + * anything, as we will want to match it when the traversal descends into + * the directory. + */ +static int find_cache_pos(struct traverse_info *info, + const struct name_entry *p) +{ + int pos; + struct unpack_trees_options *o = info->data; + struct index_state *index = o->src_index; + int pfxlen = info->pathlen; + int p_len = tree_entry_len(p->path, p->sha1); + + for (pos = o->cache_bottom; pos < index->cache_nr; pos++) { + struct cache_entry *ce = index->cache[pos]; + const char *ce_name, *ce_slash; + int cmp, ce_len; + + if (!ce_in_traverse_path(ce, info)) + continue; + if (ce->ce_flags & CE_UNPACKED) + continue; + ce_name = ce->name + pfxlen; + ce_slash = strchr(ce_name, '/'); + if (ce_slash) + ce_len = ce_slash - ce_name; + else + ce_len = ce_namelen(ce) - pfxlen; + cmp = name_compare(p->path, p_len, ce_name, ce_len); + /* + * Exact match; if we have a directory we need to + * delay returning it. + */ + if (!cmp) + return ce_slash ? -2 - pos : pos; + if (0 < cmp) + continue; /* keep looking */ + /* + * ce_name sorts after p->path; could it be that we + * have files under p->path directory in the index? + * E.g. ce_name == "t-i", and p->path == "t"; we may + * have "t/a" in the index. + */ + if (p_len < ce_len && !memcmp(ce_name, p->path, p_len) && + ce_name[p_len] < '/') + continue; /* keep looking */ + break; + } + return -1; +} + +static struct cache_entry *find_cache_entry(struct traverse_info *info, + const struct name_entry *p) +{ + int pos = find_cache_pos(info, p); + struct unpack_trees_options *o = info->data; + + if (0 <= pos) + return o->src_index->cache[pos]; + else + return NULL; +} + +static void debug_path(struct traverse_info *info) +{ + if (info->prev) { + debug_path(info->prev); + if (*info->prev->name.path) + putchar('/'); + } + printf("%s", info->name.path); +} + +static void debug_name_entry(int i, struct name_entry *n) +{ + printf("ent#%d %06o %s\n", i, + n->path ? n->mode : 0, + n->path ? n->path : "(missing)"); +} + +static void debug_unpack_callback(int n, + unsigned long mask, + unsigned long dirmask, + struct name_entry *names, + struct traverse_info *info) +{ + int i; + printf("* unpack mask %lu, dirmask %lu, cnt %d ", + mask, dirmask, n); + debug_path(info); + putchar('\n'); + for (i = 0; i < n; i++) + debug_name_entry(i, names + i); +} + 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, }; @@ -287,25 +615,38 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str while (!p->mode) p++; + if (o->debug_unpack) + debug_unpack_callback(n, mask, dirmask, names, info); + /* Are we supposed to look at the index too? */ if (o->merge) { - while (o->pos < o->src_index->cache_nr) { - struct cache_entry *ce = o->src_index->cache[o->pos]; - int cmp = compare_entry(ce, info, p); + while (1) { + int cmp; + struct cache_entry *ce; + + if (o->diff_index_cached) + ce = next_cache_entry(o); + else + ce = find_cache_entry(info, p); + + if (!ce) + break; + 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) { - o->pos++; if (ce_stage(ce)) { /* - * If we skip unmerged index entries, we'll skip this - * entry *and* the tree entries associated with it! + * If we skip unmerged index + * entries, we'll skip this + * entry *and* the tree + * entries associated with it! */ if (o->skip_unmerged) { - add_entry(o, ce, 0, 0); + add_same_unmerged(ce, o); return mask; } } @@ -318,6 +659,13 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0) return -1; + if (src[0]) { + if (ce_stage(src[0])) + mark_ce_used_same_name(src[0], o); + else + mark_ce_used(src[0], o); + } + /* Now handle any directories.. */ if (dirmask) { unsigned long conflicts = mask & ~dirmask; @@ -334,11 +682,13 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str matches = cache_tree_matches_traversal(o->src_index->cache_tree, names, info); /* - * Everything under the name matches. Adjust o->pos to - * skip the entire hierarchy. + * Everything under the name matches; skip the + * entire hierarchy. diff_index_cached codepath + * special cases D/F conflicts in such a way that + * it does not do any look-ahead, so this is safe. */ if (matches) { - o->pos += matches; + o->cache_bottom += matches; return mask; } } @@ -352,25 +702,15 @@ 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. */ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o) { - int ret; + int i, ret; static struct cache_entry *dfc; + struct exclude_list el; if (len > MAX_UNPACK_TREES) die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES); @@ -380,13 +720,22 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options state.quiet = 1; state.refresh_cache = 1; + memset(&el, 0, sizeof(el)); + if (!core_apply_sparse_checkout || !o->update) + o->skip_sparse_checkout = 1; + if (!o->skip_sparse_checkout) { + if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, NULL, &el, 0) < 0) + o->skip_sparse_checkout = 1; + else + o->el = ⪙ + } + memset(&o->result, 0, sizeof(o->result)); o->result.initialized = 1; - if (o->src_index) { - o->result.timestamp.sec = o->src_index->timestamp.sec; - o->result.timestamp.nsec = o->src_index->timestamp.nsec; - } + o->result.timestamp.sec = o->src_index->timestamp.sec; + o->result.timestamp.nsec = o->src_index->timestamp.nsec; o->merge_size = len; + mark_all_ce_unused(o->src_index); if (!dfc) dfc = xcalloc(1, cache_entry_size(0)); @@ -400,27 +749,87 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options info.fn = unpack_callback; info.data = o; + if (o->prefix) { + /* + * Unpack existing index entries that sort before the + * prefix the tree is spliced into. Note that o->merge + * is always true in this case. + */ + while (1) { + struct cache_entry *ce = next_cache_entry(o); + if (!ce) + break; + if (ce_in_traverse_path(ce, &info)) + break; + if (unpack_index_entry(ce, o) < 0) + goto return_failed; + } + } + if (traverse_trees(len, t, &info) < 0) - return unpack_failed(o, NULL); + goto return_failed; } /* Any left-over entries in the index? */ if (o->merge) { - while (o->pos < o->src_index->cache_nr) { - struct cache_entry *ce = o->src_index->cache[o->pos]; + while (1) { + struct cache_entry *ce = next_cache_entry(o); + if (!ce) + break; if (unpack_index_entry(ce, o) < 0) - return unpack_failed(o, NULL); + goto return_failed; } } + mark_all_ce_unused(o->src_index); - if (o->trivial_merges_only && o->nontrivial_merge) - return unpack_failed(o, "Merge requires file-level merging"); + if (o->trivial_merges_only && o->nontrivial_merge) { + ret = unpack_failed(o, "Merge requires file-level merging"); + goto done; + } + + if (!o->skip_sparse_checkout) { + int empty_worktree = 1; + for (i = 0;i < o->result.cache_nr;i++) { + struct cache_entry *ce = o->result.cache[i]; + + if (apply_sparse_checkout(ce, o)) { + ret = -1; + goto done; + } + /* + * Merge strategies may set CE_UPDATE|CE_REMOVE outside checkout + * area as a result of ce_skip_worktree() shortcuts in + * verify_absent() and verify_uptodate(). Clear them. + */ + if (ce_skip_worktree(ce)) + ce->ce_flags &= ~(CE_UPDATE | CE_REMOVE); + else + empty_worktree = 0; + + } + if (o->result.cache_nr && empty_worktree) { + ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory"); + goto done; + } + } o->src_index = NULL; ret = check_updates(o) ? (-2) : 0; if (o->dst_index) *o->dst_index = o->result; + +done: + for (i = 0;i < el.nr;i++) + free(el.excludes[i]); + if (el.excludes) + free(el.excludes); + return ret; + +return_failed: + mark_all_ce_unused(o->src_index); + ret = unpack_failed(o, NULL); + goto done; } /* Here come the merge functions */ @@ -436,6 +845,8 @@ static int same(struct cache_entry *a, struct cache_entry *b) return 0; if (!a && !b) return 1; + if ((a->ce_flags | b->ce_flags) & CE_CONFLICTED) + return 0; return a->ce_mode == b->ce_mode && !hashcmp(a->sha1, b->sha1); } @@ -445,16 +856,17 @@ static int same(struct cache_entry *a, struct cache_entry *b) * When a CE gets turned into an unmerged entry, we * want it to be up-to-date */ -static int verify_uptodate(struct cache_entry *ce, - struct unpack_trees_options *o) +static int verify_uptodate_1(struct cache_entry *ce, + struct unpack_trees_options *o, + const char *error_msg) { struct stat st; - if (o->index_only || o->reset || ce_uptodate(ce)) + if (o->index_only || (!ce_skip_worktree(ce) && (o->reset || ce_uptodate(ce)))) return 0; if (!lstat(ce->name, &st)) { - unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID); + unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); if (!changed) return 0; /* @@ -471,7 +883,21 @@ static int verify_uptodate(struct cache_entry *ce, if (errno == ENOENT) return 0; return o->gently ? -1 : - error(ERRORMSG(o, not_uptodate_file), ce->name); + error(error_msg, ce->name); +} + +static int verify_uptodate(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o)) + return 0; + return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file)); +} + +static int verify_uptodate_sparse(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + return verify_uptodate_1(ce, o, ERRORMSG(o, sparse_not_uptodate_file)); } static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o) @@ -522,7 +948,9 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, * in that directory. */ namelen = strlen(ce->name); - for (i = o->pos; i < o->src_index->cache_nr; i++) { + for (i = locate_in_src_index(ce, o); + i < o->src_index->cache_nr; + i++) { struct cache_entry *ce2 = o->src_index->cache[i]; int len = ce_namelen(ce2); if (len < namelen || @@ -530,12 +958,14 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, ce2->name[namelen] != '/') break; /* - * ce2->name is an entry in the subdirectory. + * ce2->name is an entry in the subdirectory to be + * removed. */ if (!ce_stage(ce2)) { if (verify_uptodate(ce2, o)) return -1; add_entry(o, ce2, CE_REMOVE, 0); + mark_ce_used(ce2, o); } cnt++; } @@ -572,15 +1002,16 @@ static int icase_exists(struct unpack_trees_options *o, struct cache_entry *dst, struct cache_entry *src; src = index_name_exists(o->src_index, dst->name, ce_namelen(dst), 1); - return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID); + return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); } /* * We do not want to remove or overwrite a working tree file that * is not tracked, unless it is ignored. */ -static int verify_absent(struct cache_entry *ce, const char *action, - struct unpack_trees_options *o) +static int verify_absent_1(struct cache_entry *ce, const char *action, + struct unpack_trees_options *o, + const char *error_msg) { struct stat st; @@ -591,7 +1022,6 @@ static int verify_absent(struct cache_entry *ce, const char *action, return 0; if (!lstat(ce->name, &st)) { - int ret; int dtype = ce_to_dtype(ce); struct cache_entry *result; @@ -617,30 +1047,10 @@ 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. - */ - ret = verify_clean_subdirectory(ce, action, o); - if (ret < 0) - return ret; - - /* - * If this removed entries from the index, - * what that means is: - * - * (1) the caller unpack_callback() saw path/foo - * in the index, and it has not removed it because - * it thinks it is handling 'path' as blob with - * D/F conflict; - * (2) we will return "ok, we placed a merged entry - * in the index" which would cause o->pos to be - * incremented by one; - * (3) however, original o->pos now has 'path/foo' - * marked with "to be removed". - * - * We need to increment it by the number of - * deleted entries here. + * them. */ - o->pos += ret; + if (verify_clean_subdirectory(ce, action, o) < 0) + return -1; return 0; } @@ -660,13 +1070,30 @@ static int verify_absent(struct cache_entry *ce, const char *action, } return 0; } +static int verify_absent(struct cache_entry *ce, const char *action, + struct unpack_trees_options *o) +{ + if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o)) + return 0; + return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked)); +} + +static int verify_absent_sparse(struct cache_entry *ce, const char *action, + struct unpack_trees_options *o) +{ + return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_orphaned)); +} static int merged_entry(struct cache_entry *merge, struct cache_entry *old, struct unpack_trees_options *o) { int update = CE_UPDATE; - if (old) { + if (!old) { + if (verify_absent(merge, "overwritten", o)) + return -1; + invalidate_ce_path(merge, o); + } else if (!(old->ce_flags & CE_CONFLICTED)) { /* * See if we can re-use the old CE directly? * That way we get the uptodate stat info. @@ -680,13 +1107,16 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old, } else { if (verify_uptodate(old, o)) return -1; + if (ce_skip_worktree(old)) + update |= CE_SKIP_WORKTREE; invalidate_ce_path(old, o); } - } - else { - if (verify_absent(merge, "overwritten", o)) - return -1; - invalidate_ce_path(merge, o); + } else { + /* + * Previously unmerged entry left as an existence + * marker by read_index_unmerged(); + */ + invalidate_ce_path(old, o); } add_entry(o, merge, update, CE_STAGEMASK); @@ -702,7 +1132,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old, return -1; return 0; } - if (verify_uptodate(old, o)) + if (!(old->ce_flags & CE_CONFLICTED) && verify_uptodate(old, o)) return -1; add_entry(o, ce, CE_REMOVE, 0); invalidate_ce_path(ce, o); @@ -767,7 +1197,8 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o) remote = NULL; } - /* First, if there's a #16 situation, note that to prevent #13 + /* + * First, if there's a #16 situation, note that to prevent #13 * and #14. */ if (!same(remote, head)) { @@ -781,7 +1212,8 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o) } } - /* We start with cases where the index is allowed to match + /* + * We start with cases where the index is allowed to match * something other than the head: #14(ALT) and #2ALT, where it * is permitted to match the result instead. */ @@ -811,12 +1243,13 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o) if (!head && !remote && any_anc_missing) return 0; - /* Under the new "aggressive" rule, we resolve mostly trivial + /* + * Under the "aggressive" rule, we resolve mostly trivial * cases that we historically had git-merge-one-file resolve. */ if (o->aggressive) { - int head_deleted = !head && !df_conflict_head; - int remote_deleted = !remote && !df_conflict_remote; + int head_deleted = !head; + int remote_deleted = !remote; struct cache_entry *ce = NULL; if (index) @@ -895,7 +1328,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>. * @@ -1004,10 +1437,10 @@ int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o) if (old && same(old, a)) { int update = 0; - if (o->reset && !ce_uptodate(old)) { + if (o->reset && !ce_uptodate(old) && !ce_skip_worktree(old)) { struct stat st; if (lstat(old->name, &st) || - ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID)) + ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE)) update |= CE_UPDATE; } add_entry(o, old, update, 0); diff --git a/unpack-trees.h b/unpack-trees.h index d19df44f40..ef70eab390 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -4,6 +4,7 @@ #define MAX_UNPACK_TREES 8 struct unpack_trees_options; +struct exclude_list; typedef int (*merge_fn_t)(struct cache_entry **src, struct unpack_trees_options *options); @@ -14,6 +15,8 @@ struct unpack_trees_error_msgs { const char *not_uptodate_dir; const char *would_lose_untracked; const char *bind_overlap; + const char *sparse_not_uptodate_file; + const char *would_lose_orphaned; }; struct unpack_trees_options { @@ -28,9 +31,11 @@ struct unpack_trees_options { skip_unmerged, initial_checkout, diff_index_cached, + debug_unpack, + skip_sparse_checkout, gently; const char *prefix; - int pos; + int cache_bottom; struct dir_struct *dir; merge_fn_t fn; struct unpack_trees_error_msgs msgs; @@ -44,6 +49,8 @@ struct unpack_trees_options { struct index_state *dst_index; struct index_state *src_index; struct index_state result; + + struct exclude_list *el; /* for internal use */ }; extern int unpack_trees(unsigned n, struct tree_desc *t, diff --git a/upload-pack.c b/upload-pack.c index 0ea8516ebf..dc464d78b3 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) { @@ -103,12 +105,12 @@ static void show_edge(struct commit *commit) fprintf(pack_pipe, "-%s\n", sha1_to_hex(commit->object.sha1)); } -static int do_rev_list(int fd, void *create_full_pack) +static int do_rev_list(int in, int out, void *create_full_pack) { int i; struct rev_info revs; - pack_pipe = xfdopen(fd, "w"); + pack_pipe = xfdopen(out, "w"); init_revisions(&revs, NULL); revs.tag_objects = 1; revs.tree_objects = 1; @@ -160,8 +162,9 @@ static void create_pack_file(void) int arg = 0; if (shallow_nr) { + memset(&rev_list, 0, sizeof(rev_list)); rev_list.proc = do_rev_list; - rev_list.data = 0; + rev_list.out = -1; if (start_async(&rev_list)) die("git upload-pack: unable to fork git-rev-list"); argv[arg++] = "pack-objects"; @@ -430,7 +433,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; @@ -441,25 +444,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; @@ -519,7 +527,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; @@ -613,7 +623,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) @@ -637,12 +647,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(); @@ -664,6 +694,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, ...) diff --git a/userdiff.c b/userdiff.c index 57529ae63d..df992490d5 100644 --- a/userdiff.c +++ b/userdiff.c @@ -198,7 +198,7 @@ struct userdiff_driver *userdiff_find_by_path(const char *path) struct git_attr_check check; if (!attr) - attr = git_attr("diff", 4); + attr = git_attr("diff"); check.attr = attr; if (!path) @@ -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/ */ @@ -162,7 +163,7 @@ static int git_wcwidth(ucs_char_t ch) * If the string was not a valid UTF-8, *start pointer is set to NULL * and the return value is undefined. */ -ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p) +static ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p) { unsigned char *s = (unsigned char *)*start; ucs_char_t ch; @@ -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) @@ -3,13 +3,14 @@ typedef unsigned int ucs_char_t; /* assuming 32bit int */ -ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p); int utf8_width(const char **start, size_t *remainder_p); int utf8_strwidth(const char *string); 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); 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@@" "$@" @@ -34,6 +34,16 @@ void *xmalloc(size_t size) return ret; } +void *xmallocz(size_t size) +{ + void *ret; + if (size + 1 < size) + die("Data too large to fit into virtual memory space."); + ret = xmalloc(size + 1); + ((char*)ret)[size] = 0; + return ret; +} + /* * xmemdupz() allocates (len + 1) bytes of memory, duplicates "len" bytes of * "data" to the allocated memory, zero terminates the allocated memory, @@ -42,10 +52,7 @@ void *xmalloc(size_t size) */ void *xmemdupz(const void *data, size_t len) { - char *p = xmalloc(len + 1); - memcpy(p, data, len); - p[len] = '\0'; - return p; + return memcpy(xmallocz(len), data, len); } char *xstrndup(const char *str, size_t len) @@ -64,7 +64,7 @@ static void setup_whitespace_attr_check(struct git_attr_check *check) static struct git_attr *attr_whitespace; if (!attr_whitespace) - attr_whitespace = git_attr("whitespace", 10); + attr_whitespace = git_attr("whitespace"); check[0].attr = attr_whitespace; } diff --git a/wt-status.c b/wt-status.c index 38eb24536b..5807fc3211 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) { @@ -333,11 +343,13 @@ static void wt_status_collect_untracked(struct wt_status *s) DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES; setup_standard_excludes(&dir); - fill_directory(&dir, NULL); + fill_directory(&dir, s->pathspec); for (i = 0; i < dir.nr; i++) { 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); } @@ -447,7 +459,7 @@ static void wt_status_print_changed(struct wt_status *s) wt_status_print_trailer(s); } -static void wt_status_print_submodule_summary(struct wt_status *s) +static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted) { struct child_process sm_summary; char summary_limit[64]; @@ -456,11 +468,11 @@ static void wt_status_print_submodule_summary(struct wt_status *s) const char *argv[] = { "submodule", "summary", - "--cached", + uncommitted ? "--files" : "--cached", "--for-status", "--summary-limit", summary_limit, - s->amend ? "HEAD^" : "HEAD", + uncommitted ? NULL : (s->amend ? "HEAD^" : "HEAD"), NULL }; @@ -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"); @@ -572,8 +580,10 @@ void wt_status_print(struct wt_status *s) wt_status_print_updated(s); wt_status_print_unmerged(s); wt_status_print_changed(s); - if (s->submodule_summary) - wt_status_print_submodule_summary(s); + if (s->submodule_summary) { + wt_status_print_submodule_summary(s, 0); /* staged */ + wt_status_print_submodule_summary(s, 1); /* unstaged */ + } if (s->show_untracked_files) wt_status_print_untracked(s); else if (s->commitable) @@ -598,3 +608,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 */ diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 4da052a3ff..3f6229edbe 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -58,6 +58,12 @@ extern "C" { #define XDL_MERGE_ZEALOUS_ALNUM 3 #define XDL_MERGE_LEVEL_MASK 0x0f +/* merge favor modes */ +#define XDL_MERGE_FAVOR_OURS 1 +#define XDL_MERGE_FAVOR_THEIRS 2 +#define XDL_MERGE_FAVOR(flags) (((flags)>>4) & 3) +#define XDL_MERGE_FLAGS(level, style, favor) ((level)|(style)|((favor)<<4)) + /* merge output styles */ #define XDL_MERGE_DIFF3 0x8000 #define XDL_MERGE_STYLE_MASK 0x8000 @@ -108,9 +114,16 @@ long xdl_mmfile_size(mmfile_t *mmf); int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb); +typedef struct s_xmparam { + xpparam_t xpp; + int marker_size; +} xmparam_t; + +#define DEFAULT_CONFLICT_MARKER_SIZE 7 + int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, mmfile_t *mf2, const char *name2, - xpparam_t const *xpp, int level, mmbuffer_t *result); + xmparam_t const *xmp, int flags, mmbuffer_t *result); #ifdef __cplusplus } diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 1cb65a9516..8cbe45e675 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -145,13 +145,15 @@ static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, xdfenv_t *xe2, const char *name2, int size, int i, int style, - xdmerge_t *m, char *dest) + xdmerge_t *m, char *dest, int marker_size) { - const int marker_size = 7; int marker1_size = (name1 ? strlen(name1) + 1 : 0); int marker2_size = (name2 ? strlen(name2) + 1 : 0); int j; + if (marker_size <= 0) + marker_size = DEFAULT_CONFLICT_MARKER_SIZE; + /* Before conflicting part */ size += xdl_recs_copy(xe1, i, m->i1 - i, 0, dest ? dest + size : NULL); @@ -214,14 +216,20 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, xdfenv_t *xe2, const char *name2, - xdmerge_t *m, char *dest, int style) + int favor, + xdmerge_t *m, char *dest, int style, + int marker_size) { int size, i; for (size = i = 0; m; m = m->next) { + if (favor && !m->mode) + m->mode = favor; + if (m->mode == 0) size = fill_conflict_hunk(xe1, name1, xe2, name2, - size, i, style, m, dest); + size, i, style, m, dest, + marker_size); else if (m->mode == 1) size += xdl_recs_copy(xe1, i, m->i1 + m->chg1 - i, 0, dest ? dest + size : NULL); @@ -386,11 +394,13 @@ static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m, */ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, xdfenv_t *xe2, xdchange_t *xscr2, const char *name2, - int flags, xpparam_t const *xpp, mmbuffer_t *result) { + int flags, xmparam_t const *xmp, mmbuffer_t *result) { xdmerge_t *changes, *c; + xpparam_t const *xpp = &xmp->xpp; int i0, i1, i2, chg0, chg1, chg2; int level = flags & XDL_MERGE_LEVEL_MASK; int style = flags & XDL_MERGE_STYLE_MASK; + int favor = XDL_MERGE_FAVOR(flags); if (style == XDL_MERGE_DIFF3) { /* @@ -522,26 +532,29 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, } /* output */ if (result) { + int marker_size = xmp->marker_size; int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, - changes, NULL, style); + favor, changes, NULL, style, + marker_size); result->ptr = xdl_malloc(size); if (!result->ptr) { xdl_cleanup_merge(changes); return -1; } result->size = size; - xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes, - result->ptr, style); + xdl_fill_merge_buffer(xe1, name1, xe2, name2, favor, changes, + result->ptr, style, marker_size); } return xdl_cleanup_merge(changes); } int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, mmfile_t *mf2, const char *name2, - xpparam_t const *xpp, int flags, mmbuffer_t *result) { + xmparam_t const *xmp, int flags, mmbuffer_t *result) { xdchange_t *xscr1, *xscr2; xdfenv_t xe1, xe2; int status; + xpparam_t const *xpp = &xmp->xpp; result->ptr = NULL; result->size = 0; @@ -574,7 +587,7 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, } else { status = xdl_do_merge(&xe1, xscr1, name1, &xe2, xscr2, name2, - flags, xpp, result); + flags, xmp, result); } xdl_free_script(xscr1); xdl_free_script(xscr2); |