diff --git a/.gitignore b/.gitignore index c96e8095fa..2cb4002ba8 100644 --- a/.gitignore +++ b/.gitignore @@ -148,6 +148,7 @@ git-write-tree git-core-*/?* gitk-wish gitweb/gitweb.cgi +test-absolute-path test-chmtime test-date test-delta diff --git a/Documentation/Makefile b/Documentation/Makefile index 443114b046..fbefe9a45b 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -44,6 +44,11 @@ INSTALL?=install RM ?= rm -f DOC_REF = origin/man +infodir?=$(prefix)/share/info +MAKEINFO=makeinfo +INSTALL_INFO=install-info +DOCBOOK2X_TEXI=docbook2x-texi + -include ../config.mak.autogen -include ../config.mak @@ -67,6 +72,8 @@ man1: $(DOC_MAN1) man5: $(DOC_MAN5) man7: $(DOC_MAN7) +info: git.info + install: man $(INSTALL) -d -m755 $(DESTDIR)$(man1dir) $(INSTALL) -d -m755 $(DESTDIR)$(man5dir) @@ -75,6 +82,14 @@ install: man $(INSTALL) -m644 $(DOC_MAN5) $(DESTDIR)$(man5dir) $(INSTALL) -m644 $(DOC_MAN7) $(DESTDIR)$(man7dir) +install-info: info + $(INSTALL) -d -m755 $(DESTDIR)$(infodir) + $(INSTALL) -m644 git.info $(DESTDIR)$(infodir) + if test -r $(DESTDIR)$(infodir)/dir; then \ + $(INSTALL_INFO) --info-dir=$(DESTDIR)$(infodir) git.info ;\ + else \ + echo "No directory found in $(DESTDIR)$(infodir)" >&2 ; \ + fi ../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE $(MAKE) -C ../ GIT-VERSION-FILE @@ -104,13 +119,14 @@ cmds_txt = cmds-ancillaryinterrogators.txt \ $(cmds_txt): cmd-list.made cmd-list.made: cmd-list.perl $(MAN1_TXT) + $(RM) $@ perl ./cmd-list.perl date >$@ git.7 git.html: git.txt core-intro.txt clean: - $(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7 howto-index.txt howto/*.html doc.dep + $(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7 *.texi *.texi+ howto-index.txt howto/*.html doc.dep $(RM) $(cmds_txt) *.made %.html : %.txt @@ -138,6 +154,13 @@ XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css user-manual.html: user-manual.xml xsltproc $(XSLTOPTS) -o $@ $(XSLT) $< +git.info: user-manual.xml + $(RM) $@ $*.texi $*.texi+ + $(DOCBOOK2X_TEXI) user-manual.xml --to-stdout >$*.texi+ + perl fix-texi.perl <$*.texi+ >$*.texi + $(MAKEINFO) --no-split $*.texi + $(RM) $*.texi $*.texi+ + howto-index.txt: howto-index.sh $(wildcard howto/*.txt) $(RM) $@+ $@ sh ./howto-index.sh $(wildcard howto/*.txt) >$@+ diff --git a/Documentation/RelNotes-1.5.2.5.txt b/Documentation/RelNotes-1.5.2.5.txt new file mode 100644 index 0000000000..e8281c72a0 --- /dev/null +++ b/Documentation/RelNotes-1.5.2.5.txt @@ -0,0 +1,30 @@ +GIT v1.5.2.5 Release Notes +========================== + +Fixes since v1.5.2.4 +-------------------- + + * Bugfixes + + - "git add -u" had a serious data corruption problem in one + special case (when the changes to a subdirectory's files + consist only deletion of files). + + - "git add -u " did not work from a subdirectory. + + - "git apply" left an empty directory after all its files are + renamed away. + + - "git $anycmd foo/bar", when there is a file 'foo' in the + working tree, complained that "git $anycmd foo/bar --" form + should be used to disambiguate between revs and files, + which was completely bogus. + + - "git checkout-index" and other commands that checks out + files to the work tree tried unlink(2) on directories, + which is a sane thing to do on sane systems, but not on + Solaris when you are root. + + * Documentation Fixes and Updates + + - A handful documentation fixes. diff --git a/Documentation/RelNotes-1.5.3.txt b/Documentation/RelNotes-1.5.3.txt index 21bb1fc6f2..9c36e8baeb 100644 --- a/Documentation/RelNotes-1.5.3.txt +++ b/Documentation/RelNotes-1.5.3.txt @@ -9,6 +9,19 @@ Updates since v1.5.2 * The submodule support has Porcelain layer. + Note that the current submodule support is minimal and this is + deliberately so. A design decision we made is that operations + at the supermodule level do not recurse into submodules by + default. The expectation is that later we would add a + mechanism to tell git which submodules the user is interested + in, and this information might be used to determine the + recursive behaviour of certain commands (e.g. "git checkout" + and "git diff"), but currently we haven't agreed on what that + mechanism should look like. Therefore, if you use submodules, + you would probably need "git submodule update" on the + submodules you care about after running a "git checkout" at + the supermodule level. + * There are a handful pack-objects changes to help you cope better with repositories with pathologically large blobs in them. @@ -46,21 +59,21 @@ Updates since v1.5.2 - "git log" learned a new option "--follow", to follow renaming history of a single file. - - "git-filter-branch" lets you rewrite the revision history of + - "git filter-branch" lets you rewrite the revision history of specified branches. You can specify a number of filters to modify the commits, files and trees. - - "git-cvsserver" learned new options (--base-path, --export-all, - --strict-paths) inspired by git-daemon. + - "git cvsserver" learned new options (--base-path, --export-all, + --strict-paths) inspired by "git daemon". - "git daemon --base-path-relaxed" can help migrating a repository URL that did not use to use --base-path to use --base-path. - - "git-commit" can use "-t templatefile" option and commit.template + - "git commit" can use "-t templatefile" option and commit.template configuration variable to prime the commit message given to you in the editor. - - "git-submodule" command helps you manage the projects from + - "git submodule" command helps you manage the projects from the superproject that contain them. - In addition to core.compression configuration option, @@ -68,15 +81,15 @@ Updates since v1.5.2 independently tweak zlib compression levels used for loose and packed objects. - - "git-ls-tree -l" shows size of blobs pointed at by the + - "git ls-tree -l" shows size of blobs pointed at by the tree entries, similar to "/bin/ls -l". - - "git-rev-list" learned --regexp-ignore-case and + - "git rev-list" learned --regexp-ignore-case and --extended-regexp options to tweak its matching logic used for --grep fitering. - - "git-describe --contains" is a handier way to call more - obscure command "git-name-rev --tags". + - "git describe --contains" is a handier way to call more + obscure command "git name-rev --tags". - "git gc --aggressive" tells the command to spend more cycles to optimize the repository harder. @@ -112,6 +125,9 @@ Updates since v1.5.2 - "git config" learned NUL terminated output format via -z to help scripts. + - "git add" learned "--refresh ..." option to selectively refresh + the cached stat information. + - "git init -q" makes the command quieter. * Updated behavior of existing commands. @@ -126,9 +142,19 @@ Updates since v1.5.2 of the format ('tgz', 'tbz2' or 'zip'). Please update the your configuration file accordingly. + - "git clone" uses -l (hardlink files under .git) by default when + cloning locally. + + - "git bundle create" can now create a bundle without negative refs, + i.e. "everything since the beginning up to certain points". + - "git diff" (but not the plumbing level "git diff-tree") now recursively descends into trees by default. + - "git diff" does not show differences that come only from + stat-dirtiness in the form of "diff --git" header anymore. When + generating a textual diff, it shows a warning message at the end. + - The editor to use with many interactive commands can be overridden with GIT_EDITOR environment variable, or if it does not exist, with core.editor configuration variable. As @@ -143,8 +169,16 @@ Updates since v1.5.2 given strings now have shorter abbreviations. -i is for ignore case, and -E is for extended regexp. + - "git log" learned --log-size to show the number of bytes in + the log message part of the output to help qgit. + - "git svn dcommit" retains local merge information. + - "git svnimport" allows an empty string to be specified as the + trunk/ directory. This is necessary to suck data from a SVN + repository that doe not have trunk/ branches/ and tags/ organization + at all. + - "git config" to set values also honors type flags like --bool and --int. @@ -167,7 +201,7 @@ Updates since v1.5.2 and the handcrafted ones the old code created was not properly formed anyway. - - "git-push" pretends that you immediately fetched back from + - "git push" pretends that you immediately fetched back from the remote by updating corresponding remote tracking branches if you have any. @@ -177,10 +211,10 @@ Updates since v1.5.2 - "git commit --amend" is now compatible with various message source options such as -m/-C/-c/-F. - - "git-apply --whitespace=strip" removes blank lines added at + - "git apply --whitespace=strip" removes blank lines added at the end of the file. - - "git-fetch" over git native protocols with "-v" option shows + - "git fetch" over git native protocols with "-v" option shows connection status, and the IP address of the other end, to help diagnosing problems. @@ -195,10 +229,10 @@ Updates since v1.5.2 - "--find-copies-harder" option to diff family can now be spelled as "-C -C" for brevity. - - "git-mailsplit" (hence "git-am") can read from Maildir + - "git mailsplit" (hence "git am") can read from Maildir formatted mailboxes. - - "git-cvsserver" does not barf upon seeing "cvs login" + - "git cvsserver" does not barf upon seeing "cvs login" request. - "pack-objects" honors "delta" attribute set in @@ -208,7 +242,7 @@ Updates since v1.5.2 - "new-workdir" script (in contrib) can now be used with a bare repository. - - "git-mergetool" learned to use gvimdiff. + - "git mergetool" learned to use gvimdiff. - "gitview" (in contrib) has a better blame interface. @@ -223,8 +257,8 @@ Updates since v1.5.2 "oneline". - "git p4import" has been demoted to contrib status. For - a superior option, checkout the git-p4 front end to - git-fast-import (also in contrib). The man page and p4 + a superior option, checkout the "git p4" front end to + "git fast-import" (also in contrib). The man page and p4 rpm have been removed as well. - "git mailinfo" (hence "am") now tries to see if the message @@ -237,13 +271,15 @@ Updates since v1.5.2 without parameter defined with "func()", not "func(void)") have been eradicated. + - "git tag" and "git verify-tag" have been rewritten in C. + * Performance Tweaks - - git-pack-objects avoids re-deltification cost by caching + - "git pack-objects" avoids re-deltification cost by caching small enough delta results it creates while looking for the best delta candidates. - - git-pack-objects learned a new heuristcs to prefer delta + - "git pack-objects" learned a new heuristcs to prefer delta that is shallower in depth over the smallest delta possible. This improves both overall packfile access performance and packfile density. @@ -260,6 +296,13 @@ Updates since v1.5.2 - verifying pack contents done by "git fsck --full" got boost by carefully choosing the order to verify objects in them. + - "git read-tree -m" to read into an already populated index + has been optimized vastly. The effect of this can be seen + when switching branches that have differences in only a + handful paths. + + - "git commit paths..." has also been optimized. + Fixes since v1.5.2 ------------------ diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt index a46bf6ce70..17379f0576 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -64,11 +64,11 @@ of lines before or after the line given by . assigns blame to the lines that were moved down (i.e. A) to the child commit. With this option, both groups of lines are blamed on the parent. - - is optional but it is the lower bound on the number of - alphanumeric characters that git must detect as moving - within a file for it to associate those lines with the parent - commit. ++ + is optional but it is the lower bound on the number of +alphanumeric characters that git must detect as moving +within a file for it to associate those lines with the parent +commit. -C||:: In addition to `-M`, detect lines copied from other @@ -77,11 +77,11 @@ of lines before or after the line given by . around across files. When this option is given twice, the command looks for copies from all other files in the parent for the commit that creates the file in addition. - - is optional but it is the lower bound on the number of - alphanumeric characters that git must detect as moving - between files for it to associate those lines with the parent - commit. ++ + is optional but it is the lower bound on the number of +alphanumeric characters that git must detect as moving +between files for it to associate those lines with the parent +commit. -h, --help:: Show help message. diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl index 2143995ece..4ee76eaf99 100755 --- a/Documentation/cmd-list.perl +++ b/Documentation/cmd-list.perl @@ -68,6 +68,8 @@ for my $cat (qw(ancillaryinterrogators } } +# The following list is sorted with "sort -d" to make it easier +# to find entry in the resulting git.html manual page. __DATA__ git-add mainporcelain git-am mainporcelain @@ -80,9 +82,9 @@ git-blame ancillaryinterrogators git-branch mainporcelain git-bundle mainporcelain git-cat-file plumbinginterrogators -git-checkout-index plumbingmanipulators -git-checkout mainporcelain git-check-attr purehelpers +git-checkout mainporcelain +git-checkout-index plumbingmanipulators git-check-ref-format purehelpers git-cherry ancillaryinterrogators git-cherry-pick mainporcelain @@ -91,6 +93,7 @@ git-clean mainporcelain git-clone mainporcelain git-commit mainporcelain git-commit-tree plumbingmanipulators +git-config ancillarymanipulators git-convert-objects ancillarymanipulators git-count-objects ancillaryinterrogators git-cvsexportcommit foreignscminterface @@ -98,9 +101,9 @@ git-cvsimport foreignscminterface git-cvsserver foreignscminterface git-daemon synchingrepositories git-describe mainporcelain +git-diff mainporcelain git-diff-files plumbinginterrogators git-diff-index plumbinginterrogators -git-diff mainporcelain git-diff-tree plumbinginterrogators git-fast-import ancillarymanipulators git-fetch mainporcelain @@ -130,13 +133,13 @@ git-ls-remote plumbinginterrogators git-ls-tree plumbinginterrogators git-mailinfo purehelpers git-mailsplit purehelpers +git-merge mainporcelain git-merge-base plumbinginterrogators git-merge-file plumbingmanipulators git-merge-index plumbingmanipulators -git-merge mainporcelain git-merge-one-file purehelpers -git-merge-tree ancillaryinterrogators git-mergetool ancillarymanipulators +git-merge-tree ancillaryinterrogators git-mktag plumbingmanipulators git-mktree plumbingmanipulators git-mv mainporcelain @@ -157,9 +160,8 @@ git-rebase mainporcelain git-receive-pack synchelpers git-reflog ancillarymanipulators git-relink ancillarymanipulators -git-repack ancillarymanipulators -git-config ancillarymanipulators git-remote ancillarymanipulators +git-repack ancillarymanipulators git-request-pull foreignscminterface git-rerere ancillaryinterrogators git-reset mainporcelain diff --git a/Documentation/fix-texi.perl b/Documentation/fix-texi.perl new file mode 100755 index 0000000000..ff7d78f620 --- /dev/null +++ b/Documentation/fix-texi.perl @@ -0,0 +1,15 @@ +#!/usr/bin/perl -w + +while (<>) { + if (/^\@setfilename/) { + $_ = "\@setfilename git.info\n"; + } elsif (/^\@direntry/) { + print '@dircategory Development +@direntry +* Git: (git). A fast distributed revision control system +@end direntry +'; } + unless (/^\@direntry/../^\@end direntry/) { + print; + } +} diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 76d2b05854..3383aca9af 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -3,32 +3,37 @@ git-add(1) NAME ---- -git-add - Add file contents to the changeset to be committed next +git-add - Add file contents to the index SYNOPSIS -------- -'git-add' [-n] [-v] [-f] [--interactive | -i] [-u] [--] ... +'git-add' [-n] [-v] [-f] [--interactive | -i] [-u] [--refresh] [--] ... DESCRIPTION ----------- -All the changed file contents to be committed together in a single set -of changes must be "added" with the 'add' command before using the -'commit' command. This is not only for adding new files. Even modified -files must be added to the set of changes about to be committed. +This command adds the current content of new or modified files to the +index, thus staging that content for inclusion in the next commit. -This command can be performed multiple times before a commit. The added -content corresponds to the state of specified file(s) at the time the -'add' command is used. This means the 'commit' command will not consider -subsequent changes to already added content if it is not added again before -the commit. +The "index" holds a snapshot of the content of the working tree, and it +is this snapshot that is taken as the contents of the next commit. Thus +after making any changes to the working directory, and before running +the commit command, you must use the 'add' command to add any new or +modified files to the index. -The 'git status' command can be used to obtain a summary of what is included -for the next commit. +This command can be performed multiple times before a commit. It only +adds the content of the specified file(s) at the time the add command is +run; if you want subsequent changes included in the next commit, then +you must run 'git add' again to add the new content to the index. -This command can be used to add ignored files with `-f` (force) -option, but they have to be -explicitly and exactly specified from the command line. File globbing -and recursive behaviour do not add ignored files. +The 'git status' command can be used to obtain a summary of which +files have changes that are staged for the next commit. + +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 will be silently ignored. +The 'add' command can be used to add ignored files with the `-f` +(force) option. Please see gitlink:git-commit[1] for alternative ways to add content to a commit. @@ -63,6 +68,10 @@ OPTIONS command line. If no paths are specified, all tracked files are updated. +\--refresh:: + Don't add the file(s), but only refresh their stat() + information in the index. + \--:: This option can be used to separate command-line options from the list of files, (useful when filenames might be mistaken diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index f03f661652..4c7e3a2f7f 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -171,6 +171,20 @@ apply.whitespace:: When no `--whitespace` flag is given from the command line, this configuration item is used as the default. +Submodules +---------- +If the patch contains any changes to submodules then gitlink:git-apply[1] +treats these changes as follows. + +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 +are ignored and only the absence of presence of the corresponding +subdirectory is checked and (if possible) updated. Author ------ diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index a0a10e3e26..227f092e26 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -9,7 +9,8 @@ git-clone - Clone a repository into a new directory SYNOPSIS -------- [verse] -'git-clone' [--template=] [-l [-s]] [-q] [-n] [--bare] +'git-clone' [--template=] + [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [-o ] [-u ] [--reference ] [--depth ] [] @@ -40,8 +41,19 @@ OPTIONS this flag bypasses normal "git aware" transport mechanism and clones the repository by making a copy of HEAD and everything under objects and refs directories. - The files under .git/objects/ directory are hardlinked - to save space when possible. + The files under `.git/objects/` directory are hardlinked + to save space when possible. This is now the default when + the source repository is specified with `/path/to/repo` + syntax, so it essentially is a no-op option. To force + copying instead of hardlinking (which may be desirable + if you are trying to make a back-up of your repository), + but still avoid the usual "git aware" transport + mechanism, `--no-hardlinks` can be used. + +--no-hardlinks:: + Optimize the cloning process from a repository on a + local filesystem by copying files under `.git/objects` + directory. --shared:: -s:: diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 627994eb97..e54fb12103 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -15,26 +15,27 @@ SYNOPSIS DESCRIPTION ----------- -Use 'git commit' when you want to record your changes into the repository -along with a log message describing what the commit is about. All changes -to be committed must be explicitly identified using one of the following -methods: +Use 'git commit' to store the current contents of the index in a new +commit along with a log message describing the changes you have made. + +The content to be added can be specified in several ways: 1. by using gitlink:git-add[1] to incrementally "add" changes to the - next commit before using the 'commit' command (Note: even modified + index before using the 'commit' command (Note: even modified files must be "added"); -2. by using gitlink:git-rm[1] to identify content removal for the next - commit, again before using the 'commit' command; +2. by using gitlink:git-rm[1] to remove files from the working tree + and the index, again before using the 'commit' command; -3. by directly listing files containing changes to be committed as arguments - to the 'commit' command, in which cases only those files alone will be - considered for the commit; +3. by listing files as arguments to the 'commit' command, in which + case the commit will ignore changes staged in the index, and instead + record the current content of the listed files; -4. by using the -a switch with the 'commit' command to automatically "add" - changes from all known files i.e. files that have already been committed - before, and to automatically "rm" files that have been - removed from the working tree, and perform the actual commit. +4. by using the -a switch with the 'commit' command to automatically + "add" changes from all known files (i.e. all files that are already + listed in the index) and to automatically "rm" files in the index + that have been removed from the working tree, and then perform the + actual commit; 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 diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 63c1dbe812..5a90f65b5e 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -64,6 +64,13 @@ include::pretty-options.txt[] --follow:: Continue listing the history of a file beyond renames. +--log-size:: + Before the log message print out its size in bytes. Intended + mainly for porcelain tools consumption. If git is unable to + produce a valid value size is set to zero. + Note that only message is considered, if also a diff is shown + its size is not included. + ...:: Show only commits that affect the specified paths. diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 74a0da1ed4..0dd9caf867 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -79,7 +79,7 @@ the remote repository. -f, \--force:: Usually, the command refuses to update a remote ref that is - not a descendant of the local ref used to overwrite it. + not an ancestor of the local ref used to overwrite it. This flag disables the check. This can cause the remote repository to lose commits; use it with care. diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 96907d4863..a1b6dce73d 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -130,7 +130,7 @@ the following situation: then the command - git-rebase --onto topicA~5 topicA~2 topicA + git-rebase --onto topicA~5 topicA~3 topicA would result in the removal of commits F and G: diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index eea9c9cfe9..4b4d229e60 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -224,7 +224,7 @@ left-to-right. G H I J \ / \ / D E F - \ | / \ + \ | / \ \ | / | \|/ | B C diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 17121ade56..05f40cff6c 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -29,8 +29,8 @@ you create one. The latest stash you created is stored in `$GIT_DIR/refs/stash`; older stashes are found in the reflog of this reference and can be named using -the usual reflog syntax (e.g. `stash@\{1}` is the most recently -created stash, `stash@\{2}` is the one before it, `stash@\{2.hours.ago}` +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}` is also possible). OPTIONS @@ -45,7 +45,7 @@ save:: list:: List the stashes that you currently have. Each 'stash' is listed - with its name (e.g. `stash@\{0}` is the latest stash, `stash@\{1} is + with its name (e.g. `stash@\{0}` is the latest stash, `stash@\{1}` is the one before, etc.), the name of the branch that was current when the stash was made, and a short description of the commit the stash was based on. @@ -61,7 +61,7 @@ show []:: stashed state and its original parent. When no `` 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 - -p stash@\{2}` to view the second most recent stash in patch form). + -p stash@\{1}` to view the second most recent stash in patch form). apply []:: diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 6f16eb0328..8fd0fc6236 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -27,6 +27,13 @@ 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 any paths have been touched in the working tree (that is, +their modification times have changed) but their contents and +permissions are identical to those in the index file, the command +updates the index file. Running `git-status` can thus speed up +subsequent operations such as `git-diff` if the working tree +contains many paths that have been touched but not modified. + OUTPUT ------ diff --git a/Documentation/git-stripspace.txt b/Documentation/git-stripspace.txt index 1306d7bab7..5212358306 100644 --- a/Documentation/git-stripspace.txt +++ b/Documentation/git-stripspace.txt @@ -8,7 +8,7 @@ git-stripspace - Filter out empty lines SYNOPSIS -------- -'git-stripspace' < +'git-stripspace' [-s | --strip-comments] < DESCRIPTION ----------- @@ -16,6 +16,9 @@ Remove multiple empty lines, and empty lines at beginning and end. OPTIONS ------- +-s\|--strip-comments:: + In addition to empty lines, also strip lines starting with '#'. + :: Byte stream to act on. diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 0a210e4bea..816340b944 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -435,6 +435,26 @@ Tracking and contributing to an entire Subversion-managed project # of dcommit/rebase/show-ignore should be the same as above. ------------------------------------------------------------------------ +The initial 'git-svn clone' can be quite time-consuming +(especially for large Subversion repositories). If multiple +people (or one person with multiple machines) want to use +git-svn to interact with the same Subversion repository, you can +do the initial 'git-svn clone' to a repository on a server and +have each person clone that repository with 'git clone': + +------------------------------------------------------------------------ +# Do the initial import on a server + ssh server "cd /pub && git-svn clone http://svn.foo.org/project +# Clone locally + git clone server:/pub/project +# Tell git-svn which branch contains the Subversion commits + git update-ref refs/remotes/git-svn origin/master +# Initialize git-svn locally (be sure to use the same URL and -T/-b/-t options as were used on server) + git-svn init http://svn.foo.org/project +# Pull the latest changes from Subversion + git-svn rebase +------------------------------------------------------------------------ + REBASE VS. PULL/MERGE --------------------- diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index aee2c1bdc7..119117f0bd 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git-tag' [-a | -s | -u ] [-f] [-m | -F ] [] 'git-tag' -d ... 'git-tag' [-n []] -l [] -'git-tag' -v +'git-tag' -v ... DESCRIPTION ----------- @@ -23,7 +23,7 @@ Unless `-f` is given, the tag must not yet exist in If one of `-a`, `-s`, or `-u ` is passed, the command creates a 'tag' object, and requires the tag message. Unless -`-m ` is given, an editor is started for the user to type +`-m ` or `-F ` is given, an editor is started for the user to type in the tag message. Otherwise just the SHA1 object name of the commit object is @@ -59,15 +59,17 @@ OPTIONS Delete existing tags with the given names. -v:: - Verify the gpg signature of given the tag + Verify the gpg signature of the given tag names. -n :: specifies how many lines from the annotation, if any, are printed when using -l. The default is not to print any annotation lines. + If no number is given to `-n`, only the first line is printed. -l :: List tags with names that match the given pattern (or all if no pattern is given). + Typing "git tag" without arguments, also lists all tags. -m :: Use the given tag message (instead of prompting) diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt index 48d17fd9c4..ac7fb19154 100644 --- a/Documentation/git-verify-tag.txt +++ b/Documentation/git-verify-tag.txt @@ -3,11 +3,11 @@ git-verify-tag(1) NAME ---- -git-verify-tag - Check the GPG signature of tag +git-verify-tag - Check the GPG signature of tags SYNOPSIS -------- -'git-verify-tag' +'git-verify-tag' ... DESCRIPTION ----------- diff --git a/Documentation/git.txt b/Documentation/git.txt index 4c4d1746e0..8017997fb9 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -42,9 +42,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.5.2.4/git.html[documentation for release 1.5.2.4] +* link:v1.5.2.5/git.html[documentation for release 1.5.2.5] * release notes for + link:RelNotes-1.5.2.5.txt[1.5.2.5], link:RelNotes-1.5.2.4.txt[1.5.2.4], link:RelNotes-1.5.2.3.txt[1.5.2.3], link:RelNotes-1.5.2.2.txt[1.5.2.2], @@ -421,6 +422,22 @@ other to an empty string or to the value "cat", git will not launch a pager. +'GIT_SSH':: + If this environment variable is set then gitlink:git-fetch[1] + and gitlink:git-push[1] 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 + shell command to execute on that remote system. ++ +To pass options to the program that you want to list in GIT_SSH +you will need to wrap the program and options into a shell script, +then set GIT_SSH to refer to the shell script. ++ +Usually it is easier to configure any desired options through your +personal `.ssh/config` file. Please consult your ssh documentation +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, diff --git a/Documentation/install-doc-quick.sh b/Documentation/install-doc-quick.sh index e6601bdd82..5433cf8ced 100755 --- a/Documentation/install-doc-quick.sh +++ b/Documentation/install-doc-quick.sh @@ -7,7 +7,7 @@ mandir="$2" SUBDIRECTORY_OK=t USAGE=' ' . git-sh-setup -export GIT_DIR +cd_to_toplevel test -z "$mandir" && usage if ! git rev-parse --verify "$head^0" >/dev/null; then @@ -18,14 +18,14 @@ fi GIT_INDEX_FILE=`pwd`/.quick-doc.index export GIT_INDEX_FILE rm -f "$GIT_INDEX_FILE" +trap 'rm -f "$GIT_INDEX_FILE"' 0 + git read-tree $head git checkout-index -a -f --prefix="$mandir"/ if test -n "$GZ"; then - cd "$mandir" - for i in `git ls-tree -r --name-only $head` - do - gzip < $i > $i.gz && rm $i - done + git ls-tree -r --name-only $head | + xargs printf "$mandir/%s\n" | + xargs gzip -f fi rm -f "$GIT_INDEX_FILE" diff --git a/Documentation/urls.txt b/Documentation/urls.txt index 781df4174b..b38145faff 100644 --- a/Documentation/urls.txt +++ b/Documentation/urls.txt @@ -15,11 +15,11 @@ to name the remote repository: - ssh://{startsb}user@{endsb}host.xz/~/path/to/repo.git =============================================================== -SSH is the default transport protocol. You can optionally specify -which user to log-in as, and an alternate, scp-like syntax is also -supported. Both syntaxes support username expansion, -as does the native git protocol. The following three are -identical to the last three above, respectively: +SSH is the default transport protocol over the network. You can +optionally specify which user to log-in as, and an alternate, +scp-like syntax is also supported. Both syntaxes support +username expansion, as does the native git protocol. The following +three are identical to the last three above, respectively: =============================================================== - {startsb}user@{endsb}host.xz:/path/to/repo.git/ @@ -27,8 +27,12 @@ identical to the last three above, respectively: - {startsb}user@{endsb}host.xz:path/to/repo.git =============================================================== -To sync with a local directory, use: +To sync with a local directory, you can use: =============================================================== - /path/to/repo.git/ +- file:///path/to/repo.git/ =============================================================== + +They are mostly equivalent, except when cloning. See +gitlink:git-clone[1] for details. diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 0071cd070e..f89952ad84 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -1,4 +1,4 @@ -Git User's Manual (for version 1.5.1 or newer) +Git User's Manual (for version 1.5.3 or newer) ______________________________________________ @@ -1079,6 +1079,11 @@ $ git diff HEAD # difference between HEAD and working tree; what $ git status # a brief per-file summary of the above. ------------------------------------------------- +You can also use gitlink:git-gui[1] to create commits, view changes in +the index and the working tree files, and individually select diff hunks +for inclusion in the index (by right-clicking on the diff hunk and +choosing "Stage Hunk For Commit"). + [[creating-good-commit-messages]] Creating good commit messages ----------------------------- @@ -1484,6 +1489,38 @@ $ git show HEAD^:path/to/file which will display the given version of the file. +[[interrupted-work]] +Temporarily setting aside work in progress +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +While you are in the middle of working on something complicated, you +find an unrelated but obvious and trivial bug. You would like to fix it +before continuing. You can use gitlink:git-stash[1] to save the current +state of your work, and after fixing the bug (or, optionally after doing +so on a different branch and then coming back), unstash the +work-in-progress changes. + +------------------------------------------------ +$ git stash "work in progress for foo feature" +------------------------------------------------ + +This command will save your changes away to the `stash`, and +reset your working tree and the index to match the tip of your +current branch. Then you can make your fix as usual. + +------------------------------------------------ +... edit and test ... +$ git commit -a -m "blorpl: typofix" +------------------------------------------------ + +After that, you can go back to what you were working on with +`git stash apply`: + +------------------------------------------------ +$ git stash apply +------------------------------------------------ + + [[ensuring-good-performance]] Ensuring good performance ------------------------- @@ -1667,24 +1704,19 @@ one step: $ git pull origin master ------------------------------------------------- -In fact, "origin" is normally the default repository to pull from, -and the default branch is normally the HEAD of the remote repository, -so often you can accomplish the above with just +In fact, if you have "master" checked out, then by default "git pull" +merges from the HEAD branch of the origin repository. So often you can +accomplish the above with just a simple ------------------------------------------------- $ git pull ------------------------------------------------- -See the descriptions of the branch..remote and branch..merge -options in gitlink:git-config[1] to learn how to control these defaults -depending on the current branch. Also note that the --track option to -gitlink:git-branch[1] and gitlink:git-checkout[1] can be used to -automatically set the default remote branch to pull from at the time -that a branch is created: - -------------------------------------------------- -$ git checkout --track -b maint origin/maint -------------------------------------------------- +More generally, a branch that is created from a remote branch will pull +by default from that branch. See the descriptions of the +branch..remote and branch..merge options in +gitlink:git-config[1], and the discussion of the --track option in +gitlink:git-checkout[1], to learn how to control these defaults. In addition to saving you keystrokes, "git pull" also helps you by producing a default commit message documenting the branch and @@ -2479,8 +2511,10 @@ $ gitk origin..mywork & And browse through the list of patches in the mywork branch using gitk, applying them (possibly in a different order) to mywork-new using -cherry-pick, and possibly modifying them as you go using commit ---amend. +cherry-pick, and possibly modifying them as you go using commit --amend. +The git-gui[1] command may also help as it allows you to individually +select diff hunks for inclusion in the index (by right-clicking on the +diff hunk and choosing "Stage Hunk for Commit"). Another technique is to use git-format-patch to create a series of patches, then reset the state to before the patches: diff --git a/INSTALL b/INSTALL index 79e71b6922..289b046a44 100644 --- a/INSTALL +++ b/INSTALL @@ -5,8 +5,8 @@ Normally you can just do "make" followed by "make install", and that will install the git programs in your own ~/bin/ directory. If you want to do a global install, you can do - $ make prefix=/usr all doc ;# as yourself - # make prefix=/usr install install-doc ;# as root + $ make prefix=/usr all doc info ;# as yourself + # make prefix=/usr install install-doc install-info ;# as root (or prefix=/usr/local, of course). Just like any program suite that uses $prefix, the built results have some paths encoded, @@ -91,9 +91,13 @@ Issues of note: - To build and install documentation suite, you need to have the asciidoc/xmlto toolchain. Because not many people are inclined to install the tools, the default build target - ("make all") does _not_ build them. The documentation is - written for AsciiDoc 7, but "make ASCIIDOC8=YesPlease doc" - will let you format with AsciiDoc 8. + ("make all") does _not_ build them. + + Building and installing the info file additionally requires + makeinfo and docbook2X. Version 0.8.3 is known to work. + + The documentation is written for AsciiDoc 7, but "make + ASCIIDOC8=YesPlease doc" will let you format with AsciiDoc 8. Alternatively, pre-formatted documentation are available in "html" and "man" branches of the git repository itself. For @@ -116,3 +120,7 @@ Issues of note: would instead give you a copy of what you see at: http://www.kernel.org/pub/software/scm/git/docs/ + + It has been reported that docbook-xsl version 1.72 and 1.73 are + buggy; 1.72 misformats manual pages for callouts, and 1.73 needs + the patch in contrib/patches/docbook-xsl-manpages-charmap.patch diff --git a/Makefile b/Makefile index bd25c8abf6..f828f6cd12 100644 --- a/Makefile +++ b/Makefile @@ -208,7 +208,6 @@ SCRIPT_SH = \ git-pull.sh git-rebase.sh git-rebase--interactive.sh \ git-repack.sh git-request-pull.sh git-reset.sh \ git-sh-setup.sh \ - git-tag.sh git-verify-tag.sh \ git-am.sh \ git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \ git-merge-resolve.sh git-merge-ours.sh \ @@ -364,12 +363,14 @@ BUILTIN_OBJS = \ builtin-show-branch.o \ builtin-stripspace.o \ builtin-symbolic-ref.o \ + builtin-tag.o \ builtin-tar-tree.o \ builtin-unpack-objects.o \ builtin-update-index.o \ builtin-update-ref.o \ builtin-upload-archive.o \ builtin-verify-pack.o \ + builtin-verify-tag.o \ builtin-write-tree.o \ builtin-show-ref.o \ builtin-pack-refs.o @@ -941,6 +942,9 @@ perl/Makefile: perl/Git.pm perl/Makefile.PL GIT-CFLAGS doc: $(MAKE) -C Documentation all +info: + $(MAKE) -C Documentation info + TAGS: $(RM) TAGS $(FIND) . -name '*.[hcS]' -print | xargs etags -a @@ -1030,6 +1034,9 @@ endif install-doc: $(MAKE) -C Documentation install +install-info: + $(MAKE) -C Documentation install-info + quick-install-doc: $(MAKE) -C Documentation quick-install diff --git a/attr.c b/attr.c index a0712543b2..129399310a 100644 --- a/attr.c +++ b/attr.c @@ -257,6 +257,7 @@ static struct attr_stack { struct attr_stack *prev; char *origin; unsigned num_matches; + unsigned alloc; struct match_attr **attrs; } *attr_stack; @@ -287,6 +288,26 @@ static const char *builtin_attr[] = { NULL, }; +static void handle_attr_line(struct attr_stack *res, + const char *line, + const char *src, + int lineno, + int macro_ok) +{ + struct match_attr *a; + + a = parse_attr_line(line, src, lineno, macro_ok); + if (!a) + return; + if (res->alloc <= res->num_matches) { + res->alloc = alloc_nr(res->num_matches); + res->attrs = xrealloc(res->attrs, + sizeof(struct match_attr *) * + res->alloc); + } + res->attrs[res->num_matches++] = a; +} + static struct attr_stack *read_attr_from_array(const char **list) { struct attr_stack *res; @@ -294,42 +315,91 @@ static struct attr_stack *read_attr_from_array(const char **list) int lineno = 0; res = xcalloc(1, sizeof(*res)); - while ((line = *(list++)) != NULL) { - struct match_attr *a; - - a = parse_attr_line(line, "[builtin]", ++lineno, 1); - if (!a) - continue; - res->attrs = xrealloc(res->attrs, - sizeof(struct match_attr *) * (res->num_matches + 1)); - res->attrs[res->num_matches++] = a; - } + while ((line = *(list++)) != NULL) + handle_attr_line(res, line, "[builtin]", ++lineno, 1); return res; } static struct attr_stack *read_attr_from_file(const char *path, int macro_ok) { - FILE *fp; + FILE *fp = fopen(path, "r"); struct attr_stack *res; char buf[2048]; int lineno = 0; - res = xcalloc(1, sizeof(*res)); - fp = fopen(path, "r"); if (!fp) + return NULL; + res = xcalloc(1, sizeof(*res)); + while (fgets(buf, sizeof(buf), fp)) + handle_attr_line(res, buf, path, ++lineno, macro_ok); + fclose(fp); + return res; +} + +static void *read_index_data(const char *path) +{ + int pos, len; + unsigned long sz; + enum object_type type; + void *data; + + len = strlen(path); + pos = cache_name_pos(path, len); + if (pos < 0) { + /* + * We might be in the middle of a merge, in which + * case we would read stage #2 (ours). + */ + int i; + for (i = -pos - 1; + (pos < 0 && i < active_nr && + !strcmp(active_cache[i]->name, path)); + i++) + if (ce_stage(active_cache[i]) == 2) + pos = i; + } + if (pos < 0) + return NULL; + data = read_sha1_file(active_cache[pos]->sha1, &type, &sz); + if (!data || type != OBJ_BLOB) { + free(data); + return NULL; + } + return data; +} + +static struct attr_stack *read_attr(const char *path, int macro_ok) +{ + struct attr_stack *res; + char *buf, *sp; + int lineno = 0; + + res = read_attr_from_file(path, macro_ok); + if (res) return res; - while (fgets(buf, sizeof(buf), fp)) { - struct match_attr *a; + res = xcalloc(1, sizeof(*res)); - a = parse_attr_line(buf, path, ++lineno, macro_ok); - if (!a) - continue; - res->attrs = xrealloc(res->attrs, - sizeof(struct match_attr *) * (res->num_matches + 1)); - res->attrs[res->num_matches++] = a; + /* + * There is no checked out .gitattributes file there, but + * we might have it in the index. We allow operation in a + * sparsely checked out work tree, so read from it. + */ + buf = read_index_data(path); + if (!buf) + return res; + + for (sp = buf; *sp; ) { + char *ep; + int more; + for (ep = sp; *ep && *ep != '\n'; ep++) + ; + more = (*ep == '\n'); + *ep = '\0'; + handle_attr_line(res, sp, path, ++lineno, macro_ok); + sp = ep + more; } - fclose(fp); + free(buf); return res; } @@ -370,13 +440,15 @@ static void bootstrap_attr_stack(void) elem->prev = attr_stack; attr_stack = elem; - elem = read_attr_from_file(GITATTRIBUTES_FILE, 1); + elem = read_attr(GITATTRIBUTES_FILE, 1); elem->origin = strdup(""); elem->prev = attr_stack; attr_stack = elem; debug_push(elem); elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE), 1); + if (!elem) + elem = xcalloc(1, sizeof(*elem)); elem->origin = NULL; elem->prev = attr_stack; attr_stack = elem; @@ -441,7 +513,7 @@ static void prepare_attr_stack(const char *path, int dirlen) memcpy(pathbuf + dirlen, "/", 2); cp = strchr(pathbuf + len + 1, '/'); strcpy(cp + 1, GITATTRIBUTES_FILE); - elem = read_attr_from_file(pathbuf, 0); + elem = read_attr(pathbuf, 0); *cp = '\0'; elem->origin = strdup(pathbuf); elem->prev = attr_stack; diff --git a/builtin-add.c b/builtin-add.c index de5c108f8f..3dd4ded937 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -102,6 +102,7 @@ static void update_callback(struct diff_queue_struct *q, break; case DIFF_STATUS_DELETED: remove_file_from_cache(path); + cache_tree_invalidate_path(active_cache_tree, path); if (verbose) printf("remove '%s'\n", path); break; @@ -109,12 +110,12 @@ static void update_callback(struct diff_queue_struct *q, } } -static void update(int verbose, const char **files) +static void update(int verbose, const char *prefix, const char **files) { struct rev_info rev; - init_revisions(&rev, ""); + init_revisions(&rev, prefix); setup_revisions(0, NULL, &rev, NULL); - rev.prune_data = get_pathspec(rev.prefix, files); + rev.prune_data = get_pathspec(prefix, files); rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = update_callback; rev.diffopt.format_callback_data = &verbose; @@ -123,6 +124,23 @@ static void update(int verbose, const char **files) run_diff_files(&rev, 0); } +static void refresh(int verbose, const char **pathspec) +{ + char *seen; + int i, specs; + + for (specs = 0; pathspec[specs]; specs++) + /* nothing */; + seen = xcalloc(specs, 1); + if (read_cache() < 0) + die("index file corrupt"); + refresh_index(&the_index, verbose ? 0 : REFRESH_QUIET, pathspec, seen); + for (i = 0; i < specs; i++) { + if (!seen[i]) + die("pathspec '%s' did not match any files", pathspec[i]); + } +} + static int git_add_config(const char *var, const char *value) { if (!strcmp(var, "core.excludesfile")) { @@ -143,7 +161,7 @@ static const char ignore_warning[] = int cmd_add(int argc, const char **argv, const char *prefix) { int i, newfd; - int verbose = 0, show_only = 0, ignored_too = 0; + int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0; const char **pathspec; struct dir_struct dir; int add_interactive = 0; @@ -191,11 +209,15 @@ int cmd_add(int argc, const char **argv, const char *prefix) take_worktree_changes = 1; continue; } + if (!strcmp(arg, "--refresh")) { + refresh_only = 1; + continue; + } usage(builtin_add_usage); } if (take_worktree_changes) { - update(verbose, argv + i); + update(verbose, prefix, argv + i); goto finish; } @@ -206,6 +228,11 @@ int cmd_add(int argc, const char **argv, const char *prefix) } pathspec = get_pathspec(prefix, argv + i); + if (refresh_only) { + refresh(verbose, pathspec); + goto finish; + } + fill_directory(&dir, pathspec, ignored_too); if (show_only) { diff --git a/builtin-apply.c b/builtin-apply.c index 0a0b4a9e3f..25b1447901 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1984,6 +1984,25 @@ static int apply_fragments(struct buffer_desc *desc, struct patch *patch) return 0; } +static int read_file_or_gitlink(struct cache_entry *ce, char **buf_p, + unsigned long *size_p) +{ + if (!ce) + return 0; + + if (S_ISGITLINK(ntohl(ce->ce_mode))) { + *buf_p = xmalloc(100); + *size_p = snprintf(*buf_p, 100, + "Subproject commit %s\n", sha1_to_hex(ce->sha1)); + } else { + enum object_type type; + *buf_p = read_sha1_file(ce->sha1, &type, size_p); + if (!*buf_p) + return -1; + } + return 0; +} + static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce) { char *buf; @@ -1994,22 +2013,32 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * alloc = 0; buf = NULL; if (cached) { - if (ce) { - enum object_type type; - buf = read_sha1_file(ce->sha1, &type, &size); - if (!buf) + if (read_file_or_gitlink(ce, &buf, &size)) + return error("read of %s failed", patch->old_name); + alloc = size; + } else if (patch->old_name) { + if (S_ISGITLINK(patch->old_mode)) { + if (ce) + read_file_or_gitlink(ce, &buf, &size); + else { + /* + * There is no way to apply subproject + * patch without looking at the index. + */ + patch->fragments = NULL; + size = 0; + } + } + else { + size = xsize_t(st->st_size); + alloc = size + 8192; + buf = xmalloc(alloc); + if (read_old_data(st, patch->old_name, + &buf, &alloc, &size)) return error("read of %s failed", patch->old_name); - alloc = size; } } - else if (patch->old_name) { - size = xsize_t(st->st_size); - alloc = size + 8192; - buf = xmalloc(alloc); - if (read_old_data(st, patch->old_name, &buf, &alloc, &size)) - return error("read of %s failed", patch->old_name); - } desc.size = size; desc.alloc = alloc; @@ -2055,6 +2084,16 @@ static int check_to_create_blob(const char *new_name, int ok_if_exists) return 0; } +static int verify_index_match(struct cache_entry *ce, struct stat *st) +{ + if (S_ISGITLINK(ntohl(ce->ce_mode))) { + if (!S_ISDIR(st->st_mode)) + return -1; + return 0; + } + return ce_match_stat(ce, st, 1); +} + static int check_patch(struct patch *patch, struct patch *prev_patch) { struct stat st; @@ -2065,8 +2104,14 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) int ok_if_exists; patch->rejected = 1; /* we will drop this after we succeed */ + + /* + * Make sure that we do not have local modifications from the + * index when we are looking at the index. Also make sure + * we have the preimage file to be patched in the work tree, + * unless --cached, which tells git to apply only in the index. + */ if (old_name) { - int changed = 0; int stat_ret = 0; unsigned st_mode = 0; @@ -2096,15 +2141,12 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) lstat(old_name, &st)) return -1; } - if (!cached) - changed = ce_match_stat(ce, &st, 1); - if (changed) + if (!cached && verify_index_match(ce, &st)) return error("%s: does not match index", old_name); if (cached) st_mode = ntohl(ce->ce_mode); - } - else if (stat_ret < 0) + } else if (stat_ret < 0) return error("%s: %s", old_name, strerror(errno)); if (!cached) @@ -2354,7 +2396,11 @@ static void remove_file(struct patch *patch, int rmdir_empty) cache_tree_invalidate_path(active_cache_tree, patch->old_name); } if (!cached) { - if (!unlink(patch->old_name) && rmdir_empty) { + if (S_ISGITLINK(patch->old_mode)) { + if (rmdir(patch->old_name)) + warning("unable to remove submodule %s", + patch->old_name); + } else if (!unlink(patch->old_name) && rmdir_empty) { char *name = xstrdup(patch->old_name); char *end = strrchr(name, '/'); while (end) { @@ -2382,13 +2428,21 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned memcpy(ce->name, path, namelen); ce->ce_mode = create_ce_mode(mode); ce->ce_flags = htons(namelen); - if (!cached) { - if (lstat(path, &st) < 0) - die("unable to stat newly created file %s", path); - fill_stat_cache_info(ce, &st); + if (S_ISGITLINK(mode)) { + const char *s = buf; + + if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1)) + die("corrupt patch for subproject %s", path); + } else { + if (!cached) { + if (lstat(path, &st) < 0) + die("unable to stat newly created file %s", + path); + fill_stat_cache_info(ce, &st); + } + if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0) + die("unable to create backing store for newly created file %s", path); } - if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0) - die("unable to create backing store for newly created file %s", path); if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0) die("unable to add cache entry for %s", path); } @@ -2398,6 +2452,13 @@ static int try_create_file(const char *path, unsigned int mode, const char *buf, int fd; char *nbuf; + if (S_ISGITLINK(mode)) { + struct stat st; + if (!lstat(path, &st) && S_ISDIR(st.st_mode)) + return 0; + return mkdir(path, 0777); + } + if (has_symlinks && S_ISLNK(mode)) /* Although buf:size is counted string, it also is NUL * terminated. @@ -2508,7 +2569,7 @@ static void write_out_one_result(struct patch *patch, int phase) * thing: remove the old, write the new */ if (phase == 0) - remove_file(patch, 0); + remove_file(patch, patch->is_rename); if (phase == 1) create_file(patch); } diff --git a/builtin-bundle.c b/builtin-bundle.c index 6ae5ab04c9..1b650069c9 100644 --- a/builtin-bundle.c +++ b/builtin-bundle.c @@ -44,38 +44,21 @@ struct bundle_header { struct ref_list references; }; -/* this function returns the length of the string */ -static int read_string(int fd, char *buffer, int size) -{ - int i; - for (i = 0; i < size - 1; i++) { - ssize_t count = xread(fd, buffer + i, 1); - if (count < 0) - return error("Read error: %s", strerror(errno)); - if (count == 0) { - i--; - break; - } - if (buffer[i] == '\n') - break; - } - buffer[i + 1] = '\0'; - return i + 1; -} - /* returns an fd */ static int read_header(const char *path, struct bundle_header *header) { char buffer[1024]; - int fd = open(path, O_RDONLY); + int fd; + long fpos; + FILE *ffd = fopen(path, "rb"); - if (fd < 0) + if (!ffd) return error("could not open '%s'", path); - if (read_string(fd, buffer, sizeof(buffer)) < 0 || + if (!fgets(buffer, sizeof(buffer), ffd) || strcmp(buffer, bundle_signature)) { - close(fd); + fclose(ffd); return error("'%s' does not look like a v2 bundle file", path); } - while (read_string(fd, buffer, sizeof(buffer)) > 0 + while (fgets(buffer, sizeof(buffer), ffd) && buffer[0] != '\n') { int is_prereq = buffer[0] == '-'; int offset = is_prereq ? 1 : 0; @@ -97,6 +80,12 @@ static int read_header(const char *path, struct bundle_header *header) { add_to_ref_list(sha1, isspace(delim) ? buffer + 41 + offset : "", list); } + fpos = ftell(ffd); + fclose(ffd); + fd = open(path, O_RDONLY); + if (fd < 0) + return error("could not open '%s'", path); + lseek(fd, fpos, SEEK_SET); return fd; } @@ -200,18 +189,22 @@ static int list_heads(struct bundle_header *header, int argc, const char **argv) static int create_bundle(struct bundle_header *header, const char *path, int argc, const char **argv) { + static struct lock_file lock; int bundle_fd = -1; + int bundle_to_stdout; const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *)); const char **argv_pack = xmalloc(5 * sizeof(const char *)); int i, ref_count = 0; char buffer[1024]; struct rev_info revs; struct child_process rls; + FILE *rls_fout; - bundle_fd = (!strcmp(path, "-") ? 1 : - open(path, O_CREAT | O_EXCL | O_WRONLY, 0666)); - if (bundle_fd < 0) - return error("Could not create '%s': %s", path, strerror(errno)); + bundle_to_stdout = !strcmp(path, "-"); + if (bundle_to_stdout) + bundle_fd = 1; + else + bundle_fd = hold_lock_file_for_update(&lock, path, 1); /* write signature */ write_or_die(bundle_fd, bundle_signature, strlen(bundle_signature)); @@ -232,10 +225,11 @@ static int create_bundle(struct bundle_header *header, const char *path, rls.git_cmd = 1; if (start_command(&rls)) return -1; - while ((i = read_string(rls.out, buffer, sizeof(buffer))) > 0) { + rls_fout = fdopen(rls.out, "r"); + while (fgets(buffer, sizeof(buffer), rls_fout)) { unsigned char sha1[20]; if (buffer[0] == '-') { - write_or_die(bundle_fd, buffer, i); + write_or_die(bundle_fd, buffer, strlen(buffer)); if (!get_sha1_hex(buffer + 1, sha1)) { struct object *object = parse_object(sha1); object->flags |= UNINTERESTING; @@ -246,6 +240,7 @@ static int create_bundle(struct bundle_header *header, const char *path, object->flags |= SHOWN; } } + fclose(rls_fout); if (finish_command(&rls)) return error("rev-list died"); @@ -267,12 +262,49 @@ static int create_bundle(struct bundle_header *header, const char *path, * Make sure the refs we wrote out is correct; --max-count and * other limiting options could have prevented all the tips * from getting output. + * + * Non commit objects such as tags and blobs do not have + * this issue as they are not affected by those extra + * constraints. */ - if (!(e->item->flags & SHOWN)) { + if (!(e->item->flags & SHOWN) && e->item->type == OBJ_COMMIT) { warning("ref '%s' is excluded by the rev-list options", e->name); + free(ref); continue; } + /* + * If you run "git bundle create bndl v1.0..v2.0", the + * name of the positive ref is "v2.0" but that is the + * commit that is referenced by the tag, and not the tag + * itself. + */ + if (hashcmp(sha1, e->item->sha1)) { + /* + * Is this the positive end of a range expressed + * in terms of a tag (e.g. v2.0 from the range + * "v1.0..v2.0")? + */ + struct commit *one = lookup_commit_reference(sha1); + struct object *obj; + + if (e->item == &(one->object)) { + /* + * Need to include e->name as an + * independent ref to the pack-objects + * input, so that the tag is included + * in the output; otherwise we would + * end up triggering "empty bundle" + * error. + */ + obj = parse_object(sha1); + obj->flags |= SHOWN; + add_pending_object(&revs, obj, e->name); + } + free(ref); + continue; + } + ref_count++; write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40); write_or_die(bundle_fd, " ", 1); @@ -308,6 +340,9 @@ static int create_bundle(struct bundle_header *header, const char *path, } if (finish_command(&rls)) return error ("pack-objects died"); + close(bundle_fd); + if (!bundle_to_stdout) + commit_lock_file(&lock); return 0; } diff --git a/builtin-check-attr.c b/builtin-check-attr.c index 9d77f76ff1..d94973379c 100644 --- a/builtin-check-attr.c +++ b/builtin-check-attr.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "cache.h" #include "attr.h" #include "quote.h" @@ -10,6 +11,10 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix) struct git_attr_check *check; int cnt, i, doubledash; + if (read_cache() < 0) { + die("invalid cache"); + } + doubledash = -1; for (i = 1; doubledash < 0 && i < argc; i++) { if (!strcmp(argv[i], "--")) diff --git a/builtin-diff.c b/builtin-diff.c index b48121e6e2..6ed7b6842e 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -222,6 +222,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) prefix = setup_git_directory_gently(&nongit); git_config(git_diff_ui_config); init_revisions(&rev, prefix); + rev.diffopt.skip_stat_unmatch = 1; if (!setup_diff_no_index(&rev, argc, argv, nongit, prefix)) argc = 0; @@ -235,6 +236,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix) rev.diffopt.allow_external = 1; rev.diffopt.recursive = 1; + /* If the user asked for our exit code then don't start a + * pager or we would end up reporting its exit code instead. + */ + if (!rev.diffopt.exit_with_status) + setup_pager(); + /* Do we have --cached and not have a pending object, then * default to HEAD by hand. Eek. */ @@ -338,5 +345,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix) ent, ents); if (rev.diffopt.exit_with_status) result = rev.diffopt.has_changes; + + if ((rev.diffopt.output_format & DIFF_FORMAT_PATCH) + && (1 < rev.diffopt.skip_stat_unmatch)) + printf("Warning: %d path%s touched but unmodified. " + "Consider running git-status.\n", + rev.diffopt.skip_stat_unmatch - 1, + rev.diffopt.skip_stat_unmatch == 2 ? "" : "s"); return result; } diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 5e9d1fd86c..24926db27a 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -586,7 +586,7 @@ static off_t write_one(struct sha1file *f, static int open_object_dir_tmp(const char *path) { snprintf(tmpname, sizeof(tmpname), "%s/%s", get_object_directory(), path); - return mkstemp(tmpname); + return xmkstemp(tmpname); } /* forward declaration for write_pack_file */ @@ -612,8 +612,6 @@ static void write_pack_file(void) f = sha1fd(1, ""); } else { int fd = open_object_dir_tmp("tmp_pack_XXXXXX"); - if (fd < 0) - die("unable to create %s: %s\n", tmpname, strerror(errno)); pack_tmp_name = xstrdup(tmpname); f = sha1fd(fd, pack_tmp_name); } @@ -1275,9 +1273,8 @@ struct unpacked { unsigned depth; }; -static int delta_cacheable(struct unpacked *trg, struct unpacked *src, - unsigned long src_size, unsigned long trg_size, - unsigned long delta_size) +static int delta_cacheable(unsigned long src_size, unsigned long trg_size, + unsigned long delta_size) { if (max_delta_cache_size && delta_cache_size + delta_size > max_delta_cache_size) return 0; @@ -1399,7 +1396,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, trg_entry->delta_size = delta_size; trg->depth = src->depth + 1; - if (delta_cacheable(src, trg, src_size, trg_size, delta_size)) { + if (delta_cacheable(src_size, trg_size, delta_size)) { trg_entry->delta_data = xrealloc(delta_buf, delta_size); delta_cache_size += trg_entry->delta_size; } else diff --git a/builtin-read-tree.c b/builtin-read-tree.c index a3b17a3bd9..43cd56a3b5 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -13,14 +13,20 @@ #include "dir.h" #include "builtin.h" -static struct object_list *trees; +#define MAX_TREES 8 +static int nr_trees; +static struct tree *trees[MAX_TREES]; static int list_tree(unsigned char *sha1) { - struct tree *tree = parse_tree_indirect(sha1); + struct tree *tree; + + if (nr_trees >= MAX_TREES) + die("I cannot read more than %d trees", MAX_TREES); + tree = parse_tree_indirect(sha1); if (!tree) return -1; - object_list_append(&tree->object, &trees); + trees[nr_trees++] = tree; return 0; } @@ -76,11 +82,10 @@ static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree) static void prime_cache_tree(void) { - struct tree *tree = (struct tree *)trees->item; - if (!tree) + if (!nr_trees) return; active_cache_tree = cache_tree(); - prime_cache_tree_rec(active_cache_tree, tree); + prime_cache_tree_rec(active_cache_tree, trees[0]); } @@ -92,6 +97,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) { int i, newfd, stage = 0; unsigned char sha1[20]; + struct tree_desc t[MAX_TREES]; struct unpack_trees_options opts; memset(&opts, 0, sizeof(opts)); @@ -258,7 +264,12 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) opts.head_idx = 1; } - unpack_trees(trees, &opts); + for (i = 0; i < nr_trees; i++) { + struct tree *tree = trees[i]; + parse_tree(tree); + init_tree_desc(t+i, tree->buffer, tree->size); + } + unpack_trees(nr_trees, t, &opts); /* * When reading only one tree (either the most basic form, @@ -266,7 +277,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * valid cache-tree because the index must match exactly * what came from the tree. */ - if (trees && trees->item && !opts.prefix && (!opts.merge || (stage == 2))) { + if (nr_trees && !opts.prefix && (!opts.merge || (stage == 2))) { cache_tree_free(&active_cache_tree); prime_cache_tree(); } diff --git a/builtin-stripspace.c b/builtin-stripspace.c index 55716873dc..916355ca5d 100644 --- a/builtin-stripspace.c +++ b/builtin-stripspace.c @@ -76,6 +76,11 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) { char *buffer; unsigned long size; + int strip_comments = 0; + + if (argc > 1 && (!strcmp(argv[1], "-s") || + !strcmp(argv[1], "--strip-comments"))) + strip_comments = 1; size = 1024; buffer = xmalloc(size); @@ -84,7 +89,7 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) die("could not read the input"); } - size = stripspace(buffer, size, 0); + size = stripspace(buffer, size, strip_comments); write_or_die(1, buffer, size); if (size) putc('\n', stdout); diff --git a/builtin-tag.c b/builtin-tag.c new file mode 100644 index 0000000000..d6d38ad123 --- /dev/null +++ b/builtin-tag.c @@ -0,0 +1,460 @@ +/* + * Builtin "git tag" + * + * Copyright (c) 2007 Kristian Høgsberg , + * Carlos Rica + * Based on git-tag.sh and mktag.c by Linus Torvalds. + */ + +#include "cache.h" +#include "builtin.h" +#include "refs.h" +#include "tag.h" +#include "run-command.h" + +static const char builtin_tag_usage[] = + "git-tag [-n []] -l [] | [-a | -s | -u ] [-f | -d | -v] [-m | -F ] []"; + +static char signingkey[1000]; + +static void launch_editor(const char *path, char **buffer, unsigned long *len) +{ + const char *editor, *terminal; + struct child_process child; + const char *args[3]; + int fd; + + editor = getenv("GIT_EDITOR"); + if (!editor && editor_program) + editor = editor_program; + if (!editor) + editor = getenv("VISUAL"); + if (!editor) + editor = getenv("EDITOR"); + + terminal = getenv("TERM"); + if (!editor && (!terminal || !strcmp(terminal, "dumb"))) { + fprintf(stderr, + "Terminal is dumb but no VISUAL nor EDITOR defined.\n" + "Please supply the message using either -m or -F option.\n"); + exit(1); + } + + if (!editor) + editor = "vi"; + + memset(&child, 0, sizeof(child)); + child.argv = args; + args[0] = editor; + args[1] = path; + args[2] = NULL; + + if (run_command(&child)) + die("There was a problem with the editor %s.", editor); + + fd = open(path, O_RDONLY); + if (fd < 0) + die("could not open '%s': %s", path, strerror(errno)); + if (read_fd(fd, buffer, len)) { + free(*buffer); + die("could not read message file '%s': %s", + path, strerror(errno)); + } + close(fd); +} + +struct tag_filter { + const char *pattern; + int lines; +}; + +#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" + +static int show_reference(const char *refname, const unsigned char *sha1, + int flag, void *cb_data) +{ + struct tag_filter *filter = cb_data; + + if (!fnmatch(filter->pattern, refname, 0)) { + int i; + unsigned long size; + enum object_type type; + char *buf, *sp, *eol; + size_t len; + + if (!filter->lines) { + printf("%s\n", refname); + return 0; + } + printf("%-15s ", refname); + + sp = buf = read_sha1_file(sha1, &type, &size); + if (!buf) + return 0; + if (!size) { + free(buf); + return 0; + } + /* skip header */ + while (sp + 1 < buf + size && + !(sp[0] == '\n' && sp[1] == '\n')) + sp++; + /* only take up to "lines" lines, and strip the signature */ + for (i = 0, sp += 2; + i < filter->lines && sp < buf + size && + prefixcmp(sp, PGP_SIGNATURE "\n"); + i++) { + if (i) + printf("\n "); + eol = memchr(sp, '\n', size - (sp - buf)); + len = eol ? eol - sp : size - (sp - buf); + fwrite(sp, len, 1, stdout); + if (!eol) + break; + sp = eol + 1; + } + putchar('\n'); + free(buf); + } + + return 0; +} + +static int list_tags(const char *pattern, int lines) +{ + struct tag_filter filter; + char *newpattern; + + if (pattern == NULL) + pattern = ""; + + /* prepend/append * to the shell pattern: */ + newpattern = xmalloc(strlen(pattern) + 3); + sprintf(newpattern, "*%s*", pattern); + + filter.pattern = newpattern; + filter.lines = lines; + + for_each_tag_ref(show_reference, (void *) &filter); + + free(newpattern); + + return 0; +} + +typedef int (*each_tag_name_fn)(const char *name, const char *ref, + const unsigned char *sha1); + +static int for_each_tag_name(const char **argv, each_tag_name_fn fn) +{ + const char **p; + char ref[PATH_MAX]; + int had_error = 0; + unsigned char sha1[20]; + + for (p = argv; *p; p++) { + if (snprintf(ref, sizeof(ref), "refs/tags/%s", *p) + >= sizeof(ref)) { + error("tag name too long: %.*s...", 50, *p); + had_error = 1; + continue; + } + if (!resolve_ref(ref, sha1, 1, NULL)) { + error("tag '%s' not found.", *p); + had_error = 1; + continue; + } + if (fn(*p, ref, sha1)) + had_error = 1; + } + return had_error; +} + +static int delete_tag(const char *name, const char *ref, + const unsigned char *sha1) +{ + if (delete_ref(ref, sha1)) + return 1; + printf("Deleted tag '%s'\n", name); + return 0; +} + +static int verify_tag(const char *name, const char *ref, + const unsigned char *sha1) +{ + const char *argv_verify_tag[] = {"git-verify-tag", + "-v", "SHA1_HEX", NULL}; + argv_verify_tag[2] = sha1_to_hex(sha1); + + if (run_command_v_opt(argv_verify_tag, 0)) + return error("could not verify the tag '%s'", name); + return 0; +} + +static ssize_t do_sign(char *buffer, size_t size, size_t max) +{ + struct child_process gpg; + const char *args[4]; + char *bracket; + int len; + + if (!*signingkey) { + if (strlcpy(signingkey, git_committer_info(1), + sizeof(signingkey)) > sizeof(signingkey) - 1) + return error("committer info too long."); + bracket = strchr(signingkey, '>'); + if (bracket) + bracket[1] = '\0'; + } + + memset(&gpg, 0, sizeof(gpg)); + gpg.argv = args; + gpg.in = -1; + gpg.out = -1; + args[0] = "gpg"; + args[1] = "-bsau"; + args[2] = signingkey; + args[3] = NULL; + + if (start_command(&gpg)) + return error("could not run gpg."); + + write_or_die(gpg.in, buffer, size); + close(gpg.in); + gpg.close_in = 0; + len = read_in_full(gpg.out, buffer + size, max - size); + + finish_command(&gpg); + + if (len == max - size) + return error("could not read the entire signature from gpg."); + + return size + len; +} + +static const char tag_template[] = + "\n" + "#\n" + "# Write a tag message\n" + "#\n"; + +static int git_tag_config(const char *var, const char *value) +{ + if (!strcmp(var, "user.signingkey")) { + if (!value) + die("user.signingkey without value"); + if (strlcpy(signingkey, value, sizeof(signingkey)) + >= sizeof(signingkey)) + die("user.signingkey value too long"); + return 0; + } + + return git_default_config(var, value); +} + +#define MAX_SIGNATURE_LENGTH 1024 +/* message must be NULL or allocated, it will be reallocated and freed */ +static void create_tag(const unsigned char *object, const char *tag, + char *message, int sign, unsigned char *result) +{ + enum object_type type; + char header_buf[1024], *buffer = NULL; + int header_len, max_size; + unsigned long size = 0; + + type = sha1_object_info(object, NULL); + if (type <= OBJ_NONE) + die("bad object type."); + + header_len = snprintf(header_buf, sizeof(header_buf), + "object %s\n" + "type %s\n" + "tag %s\n" + "tagger %s\n\n", + sha1_to_hex(object), + typename(type), + tag, + git_committer_info(1)); + + if (header_len > sizeof(header_buf) - 1) + die("tag header too big."); + + if (!message) { + char *path; + int fd; + + /* write the template message before editing: */ + path = xstrdup(git_path("TAG_EDITMSG")); + fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); + if (fd < 0) + die("could not create file '%s': %s", + path, strerror(errno)); + write_or_die(fd, tag_template, strlen(tag_template)); + close(fd); + + launch_editor(path, &buffer, &size); + + unlink(path); + free(path); + } + else { + buffer = message; + size = strlen(message); + } + + size = stripspace(buffer, size, 1); + + if (!message && !size) + die("no tag message?"); + + /* insert the header and add the '\n' if needed: */ + max_size = header_len + size + (sign ? MAX_SIGNATURE_LENGTH : 0) + 1; + buffer = xrealloc(buffer, max_size); + if (size) + buffer[size++] = '\n'; + memmove(buffer + header_len, buffer, size); + memcpy(buffer, header_buf, header_len); + size += header_len; + + if (sign) { + size = do_sign(buffer, size, max_size); + if (size < 0) + die("unable to sign the tag"); + } + + if (write_sha1_file(buffer, size, tag_type, result) < 0) + die("unable to write tag file"); + free(buffer); +} + +int cmd_tag(int argc, const char **argv, const char *prefix) +{ + unsigned char object[20], prev[20]; + int annotate = 0, sign = 0, force = 0, lines = 0; + char *message = NULL; + char ref[PATH_MAX]; + const char *object_ref, *tag; + int i; + struct ref_lock *lock; + + git_config(git_tag_config); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (arg[0] != '-') + break; + if (!strcmp(arg, "-a")) { + annotate = 1; + continue; + } + if (!strcmp(arg, "-s")) { + annotate = 1; + sign = 1; + continue; + } + if (!strcmp(arg, "-f")) { + force = 1; + continue; + } + if (!strcmp(arg, "-n")) { + if (i + 1 == argc || *argv[i + 1] == '-') + /* no argument */ + lines = 1; + else + lines = isdigit(*argv[++i]) ? + atoi(argv[i]) : 1; + continue; + } + if (!strcmp(arg, "-m")) { + annotate = 1; + i++; + if (i == argc) + die("option -m needs an argument."); + if (message) + die("only one -F or -m option is allowed."); + message = xstrdup(argv[i]); + continue; + } + if (!strcmp(arg, "-F")) { + unsigned long len; + int fd; + + annotate = 1; + i++; + if (i == argc) + die("option -F needs an argument."); + if (message) + die("only one -F or -m option is allowed."); + + if (!strcmp(argv[i], "-")) + fd = 0; + else { + fd = open(argv[i], O_RDONLY); + if (fd < 0) + die("could not open '%s': %s", + argv[i], strerror(errno)); + } + len = 1024; + message = xmalloc(len); + if (read_fd(fd, &message, &len)) { + free(message); + die("cannot read %s", argv[i]); + } + continue; + } + if (!strcmp(arg, "-u")) { + annotate = 1; + sign = 1; + i++; + if (i == argc) + die("option -u needs an argument."); + if (strlcpy(signingkey, argv[i], sizeof(signingkey)) + >= sizeof(signingkey)) + die("argument to option -u too long"); + continue; + } + if (!strcmp(arg, "-l")) + return list_tags(argv[i + 1], lines); + if (!strcmp(arg, "-d")) + return for_each_tag_name(argv + i + 1, delete_tag); + if (!strcmp(arg, "-v")) + return for_each_tag_name(argv + i + 1, verify_tag); + usage(builtin_tag_usage); + } + + if (i == argc) { + if (annotate) + usage(builtin_tag_usage); + return list_tags(NULL, lines); + } + tag = argv[i++]; + + object_ref = i < argc ? argv[i] : "HEAD"; + if (i + 1 < argc) + die("too many params"); + + if (get_sha1(object_ref, object)) + die("Failed to resolve '%s' as a valid ref.", object_ref); + + if (snprintf(ref, sizeof(ref), "refs/tags/%s", tag) > sizeof(ref) - 1) + die("tag name too long: %.*s...", 50, tag); + if (check_ref_format(ref)) + die("'%s' is not a valid tag name.", tag); + + if (!resolve_ref(ref, prev, 1, NULL)) + hashclr(prev); + else if (!force) + die("tag '%s' already exists", tag); + + if (annotate) + create_tag(object, tag, message, sign, object); + + lock = lock_any_ref_for_update(ref, prev, 0); + if (!lock) + die("%s: cannot lock the ref", ref); + if (write_ref_sha1(lock, object, NULL) < 0) + die("%s: cannot update the ref", ref); + + return 0; +} diff --git a/builtin-update-index.c b/builtin-update-index.c index 509369e9e7..a7a4574f2b 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -86,9 +86,15 @@ static int process_lstat_error(const char *path, int err) static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st) { - int option, size = cache_entry_size(len); - struct cache_entry *ce = xcalloc(1, size); + int option, size; + struct cache_entry *ce; + /* Was the old index entry already up-to-date? */ + if (old && !ce_stage(old) && !ce_match_stat(old, st, 0)) + return 0; + + size = cache_entry_size(len); + ce = xcalloc(1, size); memcpy(ce->name, path, len); ce->ce_flags = htons(len); fill_stat_cache_info(ce, st); diff --git a/builtin-verify-tag.c b/builtin-verify-tag.c new file mode 100644 index 0000000000..dfcfcd0455 --- /dev/null +++ b/builtin-verify-tag.c @@ -0,0 +1,110 @@ +/* + * Builtin "git verify-tag" + * + * Copyright (c) 2007 Carlos Rica + * + * Based on git-verify-tag.sh + */ +#include "cache.h" +#include "builtin.h" +#include "tag.h" +#include "run-command.h" +#include + +static const char builtin_verify_tag_usage[] = + "git-verify-tag [-v|--verbose] ..."; + +#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" + +static int run_gpg_verify(const char *buf, unsigned long size, int verbose) +{ + struct child_process gpg; + const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL}; + char path[PATH_MAX], *eol; + size_t len; + int fd, ret; + + fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX"); + if (fd < 0) + return error("could not create temporary file '%s': %s", + path, strerror(errno)); + if (write_in_full(fd, buf, size) < 0) + return error("failed writing temporary file '%s': %s", + path, strerror(errno)); + close(fd); + + /* find the length without signature */ + len = 0; + while (len < size && prefixcmp(buf + len, PGP_SIGNATURE "\n")) { + eol = memchr(buf + len, '\n', size - len); + len += eol ? eol - (buf + len) + 1 : size - len; + } + if (verbose) + write_in_full(1, buf, len); + + memset(&gpg, 0, sizeof(gpg)); + gpg.argv = args_gpg; + gpg.in = -1; + gpg.out = 1; + args_gpg[2] = path; + if (start_command(&gpg)) + return error("could not run gpg."); + + write_in_full(gpg.in, buf, len); + close(gpg.in); + gpg.close_in = 0; + ret = finish_command(&gpg); + + unlink(path); + + return ret; +} + +static int verify_tag(const char *name, int verbose) +{ + enum object_type type; + unsigned char sha1[20]; + char *buf; + unsigned long size; + int ret; + + if (get_sha1(name, sha1)) + return error("tag '%s' not found.", name); + + type = sha1_object_info(sha1, NULL); + if (type != OBJ_TAG) + return error("%s: cannot verify a non-tag object of type %s.", + name, typename(type)); + + buf = read_sha1_file(sha1, &type, &size); + if (!buf) + return error("%s: unable to read file.", name); + + ret = run_gpg_verify(buf, size, verbose); + + free(buf); + return ret; +} + +int cmd_verify_tag(int argc, const char **argv, const char *prefix) +{ + int i = 1, verbose = 0, had_error = 0; + + git_config(git_default_config); + + if (argc == 1) + usage(builtin_verify_tag_usage); + + if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) { + verbose = 1; + i++; + } + + /* sometimes the program was terminated because this signal + * was received in the process of writing the gpg input: */ + signal(SIGPIPE, SIG_IGN); + while (i < argc) + if (verify_tag(argv[i++], verbose)) + had_error = 1; + return had_error; +} diff --git a/builtin.h b/builtin.h index 4cc228dace..bb720004af 100644 --- a/builtin.h +++ b/builtin.h @@ -70,12 +70,14 @@ extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); 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_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_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_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); extern int cmd_write_tree(int argc, const char **argv, const char *prefix); diff --git a/cache.h b/cache.h index a701139fd8..87daf8c845 100644 --- a/cache.h +++ b/cache.h @@ -173,7 +173,7 @@ extern struct index_state the_index; #define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos)) #define remove_file_from_cache(path) remove_file_from_index(&the_index, (path)) #define add_file_to_cache(path, verbose) add_file_to_index(&the_index, (path), (verbose)) -#define refresh_cache(flags) refresh_index(&the_index, flags) +#define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL) #define ce_match_stat(ce, st, really) ie_match_stat(&the_index, (ce), (st), (really)) #define ce_modified(ce, st, really) ie_modified(&the_index, (ce), (st), (really)) #endif @@ -258,6 +258,7 @@ extern int index_name_pos(struct index_state *, const char *name, int namelen); #define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */ #define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */ #define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */ +#define ADD_CACHE_JUST_APPEND 8 /* Append only; tree.c::read_tree() */ 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 int remove_index_entry_at(struct index_state *, int pos); @@ -277,7 +278,7 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); #define REFRESH_UNMERGED 0x0002 /* allow unmerged */ #define REFRESH_QUIET 0x0004 /* be quiet about it */ #define REFRESH_IGNORE_MISSING 0x0008 /* ignore non-existent */ -extern int refresh_index(struct index_state *, unsigned int flags); +extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen); struct lock_file { struct lock_file *next; @@ -574,6 +575,8 @@ extern char *pager_program; extern int pager_in_use; extern int pager_use_color; +extern char *editor_program; + /* base85 */ int decode_85(char *dst, const char *line, int linelen); void encode_85(char *buf, const unsigned char *data, int bytes); diff --git a/config.c b/config.c index 78bd267070..b0c6a24eb7 100644 --- a/config.c +++ b/config.c @@ -427,6 +427,11 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.editor")) { + editor_program = xstrdup(value); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } diff --git a/connect.c b/connect.c index f50db639a4..7393b173f5 100644 --- a/connect.c +++ b/connect.c @@ -146,6 +146,8 @@ static enum protocol get_protocol(const char *name) return PROTO_SSH; if (!strcmp(name, "ssh+git")) return PROTO_SSH; + if (!strcmp(name, "file")) + return PROTO_LOCAL; die("I don't handle protocol '%s'", name); } @@ -515,14 +517,14 @@ pid_t git_connect(int fd[2], char *url, const char *prog, int flags) end = host; path = strchr(end, c); - if (c == ':') { - /* host must have at least 2 chars to catch DOS C:/path */ - if (path && path - end > 1) { + /* host must have at least 2 chars to catch DOS C:/path */ + if (path && path - end > 1) { + if (c == ':') { protocol = PROTO_SSH; *path++ = '\0'; - } else - path = host; - } + } + } else + path = end; if (!path || !*path) die("No path specified. See 'man git-pull' for valid url syntax"); diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index f2b10fa5f6..82b9ed40d8 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -972,6 +972,11 @@ _git_show () __git_complete_file } +_git_stash () +{ + __gitcomp 'list show apply clear' +} + _git () { local i c=1 command __git_dir @@ -1028,6 +1033,7 @@ _git () shortlog) _git_shortlog ;; show) _git_show ;; show-branch) _git_log ;; + stash) _git_stash ;; whatchanged) _git_log ;; *) COMPREPLY=() ;; esac @@ -1073,6 +1079,7 @@ complete -o default -o nospace -F _git_remote git-remote complete -o default -o nospace -F _git_reset git-reset complete -o default -o nospace -F _git_shortlog git-shortlog complete -o default -o nospace -F _git_show git-show +complete -o default -o nospace -F _git_stash git-stash complete -o default -o nospace -F _git_log git-show-branch complete -o default -o nospace -F _git_log git-whatchanged diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index f6102fc344..be44e06c45 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -912,10 +912,12 @@ Return the list of files that haven't been handled." (defun git-setup-diff-buffer (buffer) "Setup a buffer for displaying a diff." - (with-current-buffer buffer - (diff-mode) - (goto-char (point-min)) - (setq buffer-read-only t)) + (let ((dir default-directory)) + (with-current-buffer buffer + (diff-mode) + (goto-char (point-min)) + (setq default-directory dir) + (setq buffer-read-only t))) (display-buffer buffer) (shrink-window-if-larger-than-buffer)) @@ -965,7 +967,13 @@ Return the list of files that haven't been handled." (defun git-diff-file-idiff () "Perform an interactive diff on the current file." (interactive) - (error "Interactive diffs not implemented yet.")) + (let ((files (git-marked-files-state 'added 'deleted 'modified))) + (unless (eq 1 (length files)) + (error "Cannot perform an interactive diff on multiple files.")) + (let* ((filename (car (git-get-filenames files))) + (buff1 (find-file-noselect filename)) + (buff2 (git-run-command-buffer (concat filename ".~HEAD~") "cat-file" "blob" (concat "HEAD:" filename)))) + (ediff-buffers buff1 buff2)))) (defun git-log-file () "Display a log of changes to the marked file(s)." diff --git a/git-tag.sh b/contrib/examples/git-tag.sh similarity index 100% rename from git-tag.sh rename to contrib/examples/git-tag.sh diff --git a/git-verify-tag.sh b/contrib/examples/git-verify-tag.sh similarity index 100% rename from git-verify-tag.sh rename to contrib/examples/git-verify-tag.sh diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index 41e86e76cb..6d0106237a 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -390,6 +390,30 @@ class P4Submit(Command): return result + def prepareSubmitTemplate(self): + # remove lines in the Files section that show changes to files outside the depot path we're committing into + template = "" + inFilesSection = False + for line in read_pipe_lines("p4 change -o"): + if inFilesSection: + if line.startswith("\t"): + # path starts and ends with a tab + path = line[1:] + lastTab = path.rfind("\t") + if lastTab != -1: + path = path[:lastTab] + if not path.startswith(self.depotPath): + continue + else: + inFilesSection = False + else: + if line.startswith("Files:"): + inFilesSection = True + + template += line + + return template + def applyCommit(self, id): if self.directSubmit: print "Applying local change in working directory/index" @@ -467,7 +491,7 @@ class P4Submit(Command): logMessage = logMessage.replace("\n", "\r\n") logMessage = logMessage.strip() - template = read_pipe("p4 change -o") + template = self.prepareSubmitTemplate() if self.interactive: submitTemplate = self.prepareLogMessage(template, logMessage) @@ -558,24 +582,24 @@ class P4Submit(Command): return False [upstream, settings] = findUpstreamBranchPoint() - depotPath = settings['depot-paths'][0] + self.depotPath = settings['depot-paths'][0] if len(self.origin) == 0: self.origin = upstream if self.verbose: print "Origin branch is " + self.origin - if len(depotPath) == 0: + if len(self.depotPath) == 0: print "Internal error: cannot locate perforce depot path from existing branches" sys.exit(128) - self.clientPath = p4Where(depotPath) + self.clientPath = p4Where(self.depotPath) if len(self.clientPath) == 0: - print "Error: Cannot locate perforce checkout of %s in client view" % depotPath + print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath sys.exit(128) - print "Perforce checkout for depot path %s located at %s" % (depotPath, self.clientPath) + print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath) self.oldWorkingDirectory = os.getcwd() if self.directSubmit: @@ -839,16 +863,20 @@ class P4Sync(Command): if file["action"] == "delete": self.gitStream.write("D %s\n" % relPath) else: - mode = 644 - if file["type"].startswith("x"): - mode = 755 - data = file['data'] + mode = "644" + if file["type"].startswith("x"): + mode = "755" + elif file["type"] == "symlink": + mode = "120000" + # p4 print on a symlink contains "target\n", so strip it off + data = data[:-1] + if self.isWindows and file["type"].endswith("text"): data = data.replace("\r\n", "\n") - self.gitStream.write("M %d inline %s\n" % (mode, relPath)) + self.gitStream.write("M %s inline %s\n" % (mode, relPath)) self.gitStream.write("data %s\n" % len(data)) self.gitStream.write(data) self.gitStream.write("\n") @@ -1294,7 +1322,7 @@ class P4Sync(Command): for line in output: changeNum = line.split(" ")[1] - changes.append(changeNum) + changes.append(int(changeNum)) changes.sort() diff --git a/contrib/hooks/update-paranoid b/contrib/hooks/update-paranoid index 5ee1835c80..068fa37083 100644 --- a/contrib/hooks/update-paranoid +++ b/contrib/hooks/update-paranoid @@ -102,6 +102,8 @@ my ($this_user) = getpwuid $<; # REAL_USER_ID my $repository_name; my %user_committer; my @allow_rules; +my @path_rules; +my %diff_cache; sub deny ($) { print STDERR "-Deny- $_[0]\n" if $debug; @@ -118,22 +120,36 @@ sub info ($) { print STDERR "-Info- $_[0]\n" if $debug; } -sub parse_config ($$) { - my ($data, $fn) = @_; - info "Loading $fn"; - open(I,'-|','git',"--git-dir=$acl_git",'cat-file','blob',$fn); +sub git_value (@) { + open(T,'-|','git',@_); local $_ = ; chop; close T; $_; +} + +sub match_string ($$) { + my ($acl_n, $ref) = @_; + ($acl_n eq $ref) + || ($acl_n =~ m,/$, && substr($ref,0,length $acl_n) eq $acl_n) + || ($acl_n =~ m,^\^, && $ref =~ m:$acl_n:); +} + +sub parse_config ($$$$) { + my $data = shift; + local $ENV{GIT_DIR} = shift; + my $br = shift; + my $fn = shift; + info "Loading $br:$fn"; + open(I,'-|','git','cat-file','blob',"$br:$fn"); my $section = ''; while () { chomp; if (/^\s*$/ || /^\s*#/) { } elsif (/^\[([a-z]+)\]$/i) { - $section = $1; + $section = lc $1; } elsif (/^\[([a-z]+)\s+"(.*)"\]$/i) { - $section = "$1.$2"; + $section = join('.',lc $1,$2); } elsif (/^\s*([a-z][a-z0-9]+)\s*=\s*(.*?)\s*$/i) { - push @{$data->{"$section.$1"}}, $2; + push @{$data->{join('.',$section,lc $1)}}, $2; } else { - deny "bad config file line $. in $fn"; + deny "bad config file line $. in $br:$fn"; } } close I; @@ -202,9 +218,40 @@ sub check_committers (@) { } } -sub git_value (@) { - open(T,'-|','git',@_); local $_ = ; chop; close T; - $_; +sub load_diff ($) { + my $base = shift; + my $d = $diff_cache{$base}; + unless ($d) { + local $/ = "\0"; + my %this_diff; + if ($base =~ /^0{40}$/) { + open(T,'-|','git','ls-tree', + '-r','--name-only','-z', + $new) or return undef; + while () { + chop; + $this_diff{$_} = 'A'; + } + close T or return undef; + } else { + open(T,'-|','git','diff-tree', + '-r','--name-status','-z', + $base,$new) or return undef; + while () { + my $op = $_; + chop $op; + + my $path = ; + chop $path; + + $this_diff{$path} = $op; + } + close T or return undef; + } + $d = \%this_diff; + $diff_cache{$base} = $d; + } + return $d; } deny "No GIT_DIR inherited from caller" unless $git_dir; @@ -231,14 +278,52 @@ $op = 'U' if ($op eq 'R' && $ref =~ m,^heads/, && $old eq git_value('merge-base',$old,$new)); -# Load the user's ACL file. +# Load the user's ACL file. Expand groups (user.memberof) one level. { my %data = ('user.committer' => []); - parse_config(\%data, "$acl_branch:users/$this_user.acl"); + parse_config(\%data,$acl_git,$acl_branch,"external/$repository_name.acl"); + + %data = ( + 'user.committer' => $data{'user.committer'}, + 'user.memberof' => [], + ); + parse_config(\%data,$acl_git,$acl_branch,"users/$this_user.acl"); + %user_committer = map {$_ => $_} @{$data{'user.committer'}}; - my $rules = $data{"repository.$repository_name.allow"} || []; + my $rule_key = "repository.$repository_name.allow"; + my $rules = $data{$rule_key} || []; + + foreach my $group (@{$data{'user.memberof'}}) { + my %g; + parse_config(\%g,$acl_git,$acl_branch,"groups/$group.acl"); + my $group_rules = $g{$rule_key}; + push @$rules, @$group_rules if $group_rules; + } + +RULE: foreach (@$rules) { - if (/^([CDRU ]+)\s+for\s+([^\s]+)$/) { + while (/\${user\.([a-z][a-zA-Z0-9]+)}/) { + my $k = lc $1; + my $v = $data{"user.$k"}; + next RULE unless defined $v; + next RULE if @$v != 1; + next RULE unless defined $v->[0]; + s/\${user\.$k}/$v->[0]/g; + } + + if (/^([AMD ]+)\s+of\s+([^\s]+)\s+for\s+([^\s]+)\s+diff\s+([^\s]+)$/) { + my ($ops, $pth, $ref, $bst) = ($1, $2, $3, $4); + $ops =~ s/ //g; + $pth =~ s/\\\\/\\/g; + $ref =~ s/\\\\/\\/g; + push @path_rules, [$ops, $pth, $ref, $bst]; + } elsif (/^([AMD ]+)\s+of\s+([^\s]+)\s+for\s+([^\s]+)$/) { + my ($ops, $pth, $ref) = ($1, $2, $3); + $ops =~ s/ //g; + $pth =~ s/\\\\/\\/g; + $ref =~ s/\\\\/\\/g; + push @path_rules, [$ops, $pth, $ref, $old]; + } elsif (/^([CDRU ]+)\s+for\s+([^\s]+)$/) { my $ops = $1; my $ref = $2; $ops =~ s/ //g; @@ -272,13 +357,65 @@ foreach my $acl_entry (@allow_rules) { next unless $acl_ops =~ /^[CDRU]+$/; # Uhh.... shouldn't happen. next unless $acl_n; next unless $op =~ /^[$acl_ops]$/; + next unless match_string $acl_n, $ref; - grant "Allowed by: $acl_ops for $acl_n" - if ( - ($acl_n eq $ref) - || ($acl_n =~ m,/$, && substr($ref,0,length $acl_n) eq $acl_n) - || ($acl_n =~ m,^\^, && $ref =~ m:$acl_n:) - ); + # Don't test path rules on branch deletes. + # + grant "Allowed by: $acl_ops for $acl_n" if $op eq 'D'; + + # Aggregate matching path rules; allow if there aren't + # any matching this ref. + # + my %pr; + foreach my $p_entry (@path_rules) { + my ($p_ops, $p_n, $p_ref, $p_bst) = @$p_entry; + next unless $p_ref; + push @{$pr{$p_bst}}, $p_entry if match_string $p_ref, $ref; + } + grant "Allowed by: $acl_ops for $acl_n" unless %pr; + + # Allow only if all changes against a single base are + # allowed by file path rules. + # + my @bad; + foreach my $p_bst (keys %pr) { + my $diff_ref = load_diff $p_bst; + deny "Cannot difference trees." unless ref $diff_ref; + + my %fd = %$diff_ref; + foreach my $p_entry (@{$pr{$p_bst}}) { + my ($p_ops, $p_n, $p_ref, $p_bst) = @$p_entry; + next unless $p_ops =~ /^[AMD]+$/; + next unless $p_n; + + foreach my $f_n (keys %fd) { + my $f_op = $fd{$f_n}; + next unless $f_op; + next unless $f_op =~ /^[$p_ops]$/; + delete $fd{$f_n} if match_string $p_n, $f_n; + } + last unless %fd; + } + + if (%fd) { + push @bad, [$p_bst, \%fd]; + } else { + # All changes relative to $p_bst were allowed. + # + grant "Allowed by: $acl_ops for $acl_n diff $p_bst"; + } + } + + foreach my $bad_ref (@bad) { + my ($p_bst, $fd) = @$bad_ref; + print STDERR "\n"; + print STDERR "Not allowed to make the following changes:\n"; + print STDERR "(base: $p_bst)\n"; + foreach my $f_n (sort keys %$fd) { + print STDERR " $fd->{$f_n} $f_n\n"; + } + } + deny "You are not permitted to $op $ref"; } close A; deny "You are not permitted to $op $ref"; diff --git a/contrib/patches/docbook-xsl-manpages-charmap.patch b/contrib/patches/docbook-xsl-manpages-charmap.patch new file mode 100644 index 0000000000..f2b08b4f4a --- /dev/null +++ b/contrib/patches/docbook-xsl-manpages-charmap.patch @@ -0,0 +1,21 @@ +From: Ismail Dönmez + +Trying to build the documentation with docbook-xsl 1.73 may result in +the following error. This patch fixes it. + +$ xmlto -m callouts.xsl man git-add.xml +runtime error: file +file:///usr/share/sgml/docbook/xsl-stylesheets-1.73.0/manpages/other.xsl line +129 element call-template +The called template 'read-character-map' was not found. + +--- docbook-xsl-1.73.0/manpages/docbook.xsl.manpages-charmap 2007-07-23 16:24:23.000000000 +0100 ++++ docbook-xsl-1.73.0/manpages/docbook.xsl 2007-07-23 16:25:16.000000000 +0100 +@@ -37,6 +37,7 @@ + + + ++ + + + diff --git a/diff-lib.c b/diff-lib.c index 92c0e39ad6..f5568c3b36 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -189,6 +189,7 @@ static int handle_diff_files_args(struct rev_info *revs, !strcmp(argv[1], "--no-index")) { revs->max_count = -2; revs->diffopt.exit_with_status = 1; + revs->diffopt.no_index = 1; } else if (!strcmp(argv[1], "-q")) *silent = 1; @@ -204,8 +205,10 @@ static int handle_diff_files_args(struct rev_info *revs, */ read_cache(); if (!is_in_index(revs->diffopt.paths[0]) || - !is_in_index(revs->diffopt.paths[1])) + !is_in_index(revs->diffopt.paths[1])) { revs->max_count = -2; + revs->diffopt.no_index = 1; + } } /* @@ -293,6 +296,7 @@ int setup_diff_no_index(struct rev_info *revs, else revs->diffopt.paths = argv + argc - 2; revs->diffopt.nr_paths = 2; + revs->diffopt.no_index = 1; revs->max_count = -2; return 0; } @@ -304,7 +308,7 @@ int run_diff_files_cmd(struct rev_info *revs, int argc, const char **argv) if (handle_diff_files_args(revs, argc, argv, &silent_on_removed)) return -1; - if (revs->max_count == -2) { + if (revs->diffopt.no_index) { if (revs->diffopt.nr_paths != 2) return error("need two files/directories with --no-index"); if (queue_diff(&revs->diffopt, revs->diffopt.paths[0], diff --git a/diff.c b/diff.c index e64625129d..6a99e0c84e 100644 --- a/diff.c +++ b/diff.c @@ -3138,11 +3138,64 @@ static void diffcore_apply_filter(const char *filter) *q = outq; } +static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) +{ + int i; + struct diff_queue_struct *q = &diff_queued_diff; + struct diff_queue_struct outq; + outq.queue = NULL; + outq.nr = outq.alloc = 0; + + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + + /* + * 1. Entries that come from stat info dirtyness + * 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 + * do not match these criteria. They have real + * differences. + * + * 2. At this point, the file is known to be modified, + * with the same mode and size, and the object + * name of one side is unknown. Need to inspect + * the identical contents. + */ + if (!DIFF_FILE_VALID(p->one) || /* (1) */ + !DIFF_FILE_VALID(p->two) || + (p->one->sha1_valid && p->two->sha1_valid) || + (p->one->mode != p->two->mode) || + diff_populate_filespec(p->one, 1) || + diff_populate_filespec(p->two, 1) || + (p->one->size != p->two->size) || + + diff_populate_filespec(p->one, 0) || /* (2) */ + diff_populate_filespec(p->two, 0) || + memcmp(p->one->data, p->two->data, p->one->size)) + diff_q(&outq, p); + else { + /* + * The caller can subtract 1 from skip_stat_unmatch + * to determine how many paths were dirty only + * due to stat info mismatch. + */ + if (!diffopt->no_index) + diffopt->skip_stat_unmatch++; + diff_free_filepair(p); + } + } + free(q->queue); + *q = outq; +} + void diffcore_std(struct diff_options *options) { if (options->quiet) return; + if (options->skip_stat_unmatch && !options->find_copies_harder) + diffcore_skip_stat_unmatch(options); if (options->break_opt != -1) diffcore_break(options->break_opt); if (options->detect_rename) diff --git a/diff.h b/diff.h index 9fd6d447d4..4546aad219 100644 --- a/diff.h +++ b/diff.h @@ -60,11 +60,13 @@ struct diff_options { color_diff_words:1, has_changes:1, quiet:1, + no_index:1, allow_external:1, exit_with_status:1; int context; int break_opt; int detect_rename; + int skip_stat_unmatch; int line_termination; int output_format; int pickaxe_opts; diff --git a/entry.c b/entry.c index 0625112339..fc3a506ece 100644 --- a/entry.c +++ b/entry.c @@ -112,6 +112,16 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout if (!new) return error("git-checkout-index: unable to read sha1 file of %s (%s)", path, sha1_to_hex(ce->sha1)); + + /* + * Convert from git internal format to working tree format + */ + buf = convert_to_working_tree(ce->name, new, &size); + if (buf) { + free(new); + new = buf; + } + if (to_tempfile) { strcpy(path, ".merge_file_XXXXXX"); fd = mkstemp(path); @@ -123,15 +133,6 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout path, strerror(errno)); } - /* - * Convert from git internal format to working tree format - */ - buf = convert_to_working_tree(ce->name, new, &size); - if (buf) { - free(new); - new = buf; - } - wrote = write_in_full(fd, new, size); close(fd); free(new); diff --git a/environment.c b/environment.c index 2af12fd689..b5a6c69f7c 100644 --- a/environment.c +++ b/environment.c @@ -33,6 +33,7 @@ size_t delta_base_cache_limit = 16 * 1024 * 1024; char *pager_program; int pager_in_use; int pager_use_color = 1; +char *editor_program; int auto_crlf = 0; /* 1: both ways, -1: only when adding git objects */ /* This is set by setup_git_dir_gently() and/or git_default_config() */ diff --git a/fast-import.c b/fast-import.c index 99a19d89e8..170cccdceb 100644 --- a/fast-import.c +++ b/fast-import.c @@ -663,9 +663,7 @@ static void start_packfile(void) snprintf(tmpfile, sizeof(tmpfile), "%s/tmp_pack_XXXXXX", get_object_directory()); - pack_fd = mkstemp(tmpfile); - if (pack_fd < 0) - die("Can't create %s: %s", tmpfile, strerror(errno)); + pack_fd = xmkstemp(tmpfile); p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2); strcpy(p->pack_name, tmpfile); p->pack_fd = pack_fd; @@ -727,9 +725,7 @@ static char *create_index(void) snprintf(tmpfile, sizeof(tmpfile), "%s/tmp_idx_XXXXXX", get_object_directory()); - idx_fd = mkstemp(tmpfile); - if (idx_fd < 0) - die("Can't create %s: %s", tmpfile, strerror(errno)); + idx_fd = xmkstemp(tmpfile); f = sha1fd(idx_fd, tmpfile); sha1write(f, array, 256 * sizeof(int)); SHA1_Init(&ctx); diff --git a/git-am.sh b/git-am.sh index 6cf0eeee71..b5ed8ca15c 100755 --- a/git-am.sh +++ b/git-am.sh @@ -103,7 +103,8 @@ It does not apply to blobs recorded in its index." } prec=4 -dotest=.dotest sign= utf8=t keep= skip= interactive= resolved= binary= resolvemsg= +dotest=.dotest sign= utf8=t keep= skip= interactive= resolved= binary= +resolvemsg= resume= git_apply_opt= while case "$#" in 0) break;; esac diff --git a/git-clone.sh b/git-clone.sh index ac525bda1b..910d220516 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -87,7 +87,7 @@ Perhaps git-update-server-info needs to be run there?" quiet= local=no -use_local=no +use_local_hardlink=yes local_shared=no unset template no_checkout= @@ -108,9 +108,13 @@ while no_checkout=yes ;; *,--na|*,--nak|*,--nake|*,--naked|\ *,-b|*,--b|*,--ba|*,--bar|*,--bare) bare=yes ;; - *,-l|*,--l|*,--lo|*,--loc|*,--loca|*,--local) use_local=yes ;; + *,-l|*,--l|*,--lo|*,--loc|*,--loca|*,--local) + use_local_hardlink=yes ;; + *,--no-h|*,--no-ha|*,--no-har|*,--no-hard|*,--no-hardl|\ + *,--no-hardli|*,--no-hardlin|*,--no-hardlink|*,--no-hardlinks) + use_local_hardlink=no ;; *,-s|*,--s|*,--sh|*,--sha|*,--shar|*,--share|*,--shared) - local_shared=yes; use_local=yes ;; + local_shared=yes; ;; 1,--template) usage ;; *,--template) shift; template="--template=$1" ;; @@ -211,7 +215,12 @@ else GIT_DIR="$D/.git" fi && export GIT_DIR && -git-init $quiet ${template+"$template"} || usage +GIT_CONFIG="$GIT_DIR/config" git-init $quiet ${template+"$template"} || usage + +if test -n "$bare" +then + GIT_CONFIG="$GIT_DIR/config" git config core.bare true +fi if test -n "$reference" then @@ -249,34 +258,36 @@ fi rm -f "$GIT_DIR/CLONE_HEAD" # We do local magic only when the user tells us to. -case "$local,$use_local" in -yes,yes) +case "$local" in +yes) ( cd "$repo/objects" ) || - die "-l flag seen but repository '$repo' is not local." + die "cannot chdir to local '$repo/objects'." - case "$local_shared" in - no) - # See if we can hardlink and drop "l" if not. - sample_file=$(cd "$repo" && \ - /usr/bin/find objects -type f -print | sed -e 1q) - - # objects directory should not be empty since we are cloning! - test -f "$repo/$sample_file" || exit - - l= - if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null - then - l=l - fi && - rm -f "$GIT_DIR/objects/sample" && - cd "$repo" && - /usr/bin/find objects -depth -print | cpio -pumd$l "$GIT_DIR/" || exit 1 - ;; - yes) - mkdir -p "$GIT_DIR/objects/info" - echo "$repo/objects" >> "$GIT_DIR/objects/info/alternates" - ;; - esac + if test "$local_shared" = yes + then + mkdir -p "$GIT_DIR/objects/info" + echo "$repo/objects" >>"$GIT_DIR/objects/info/alternates" + else + l= && + if test "$use_local_hardlink" = yes + then + # See if we can hardlink and drop "l" if not. + sample_file=$(cd "$repo" && \ + /usr/bin/find objects -type f -print | sed -e 1q) + # objects directory should not be empty because + # we are cloning! + test -f "$repo/$sample_file" || exit + if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null + then + rm -f "$GIT_DIR/objects/sample" + l=l + else + echo >&2 "Warning: -l asked but cannot hardlink to $repo" + fi + fi && + cd "$repo" && + /usr/bin/find objects -depth -print | cpio -pumd$l "$GIT_DIR/" || exit 1 + fi git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1 ;; *) diff --git a/git-compat-util.h b/git-compat-util.h index 98ad78d571..219ac871b4 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -314,6 +314,16 @@ static inline FILE *xfdopen(int fd, const char *mode) return stream; } +static inline int xmkstemp(char *template) +{ + int fd; + + fd = mkstemp(template); + if (fd < 0) + die("Unable to create temporary file: %s", strerror(errno)); + return fd; +} + static inline size_t xsize_t(off_t len) { return (size_t)len; diff --git a/git-cvsserver.perl b/git-cvsserver.perl index ae7d511589..13dbd27a80 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -1196,6 +1196,7 @@ sub req_ci $log->info("Lockless commit start, basing commit on '$tmpdir', index file is '$file_index'"); $ENV{GIT_DIR} = $state->{CVSROOT} . "/"; + $ENV{GIT_WORK_TREE} = "."; $ENV{GIT_INDEX_FILE} = $file_index; # Remember where the head was at the beginning. @@ -1721,6 +1722,7 @@ sub req_annotate $log->info("Temp checkoutdir creation successful, basing annotate session work on '$tmpdir', index file is '$file_index'"); $ENV{GIT_DIR} = $state->{CVSROOT} . "/"; + $ENV{GIT_WORK_TREE} = "."; $ENV{GIT_INDEX_FILE} = $file_index; chdir $tmpdir; diff --git a/git-filter-branch.sh b/git-filter-branch.sh index b5fa44920d..c42e4512cf 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -170,13 +170,6 @@ do esac done < "$tempdir"/backup-refs -case "$GIT_DIR" in -/*) - ;; -*) - GIT_DIR="$(pwd)/../../$GIT_DIR" - ;; -esac export GIT_DIR GIT_WORK_TREE=. # These refs should be updated if their heads were rewritten diff --git a/git-instaweb.sh b/git-instaweb.sh index cbc7418e35..b79c6b6a42 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -8,13 +8,7 @@ USAGE='[--start] [--stop] [--restart] . git-sh-setup -case "$GIT_DIR" in -/*) - fqgitdir="$GIT_DIR" ;; -*) - fqgitdir="$PWD/$GIT_DIR" ;; -esac - +fqgitdir="$GIT_DIR" local="`git config --bool --get instaweb.local`" httpd="`git config --get instaweb.httpd`" browser="`git config --get instaweb.browser`" diff --git a/git-send-email.perl b/git-send-email.perl index f43f92f957..69559b289a 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -137,7 +137,7 @@ my $compose_filename = ".msg.$$"; # Variables we fill in automatically, or via prompting: my (@to,@cc,@initial_cc,@bcclist,@xh, - $initial_reply_to,$initial_subject,@files,$from,$compose,$time); + $initial_reply_to,$initial_subject,@files,$author,$sender,$compose,$time); my $smtp_server; my $envelope_sender; @@ -179,7 +179,7 @@ if (!@bcclist or !$bcclist[0]) { # Begin by accumulating all the variables (defined above), that we will end up # needing, first, from the command line: -my $rc = GetOptions("from=s" => \$from, +my $rc = GetOptions("sender|from=s" => \$sender, "in-reply-to=s" => \$initial_reply_to, "subject=s" => \$initial_subject, "to=s" => \@to, @@ -216,8 +216,8 @@ foreach my $entry (@bcclist) { # Now, let's fill any that aren't set in with defaults: -my ($author) = $repo->ident_person('author'); -my ($committer) = $repo->ident_person('committer'); +my ($repoauthor) = $repo->ident_person('author'); +my ($repocommitter) = $repo->ident_person('committer'); my %aliases; my @alias_files = $repo->config('sendemail.aliasesfile'); @@ -254,17 +254,17 @@ if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) { } } -($from) = expand_aliases($from) if defined $from; +($sender) = expand_aliases($sender) if defined $sender; my $prompting = 0; -if (!defined $from) { - $from = $author || $committer; +if (!defined $sender) { + $sender = $repoauthor || $repocommitter; do { - $_ = $term->readline("Who should the emails appear to be from? [$from] "); + $_ = $term->readline("Who should the emails appear to be from? [$sender] "); } while (!defined $_); - $from = $_ if ($_); - print "Emails will be sent from: ", $from, "\n"; + $sender = $_ if ($_); + print "Emails will be sent from: ", $sender, "\n"; $prompting++; } @@ -289,7 +289,7 @@ sub expand_aliases { } @to = expand_aliases(@to); -@to = (map { sanitize_address_rfc822($_) } @to); +@to = (map { sanitize_address($_) } @to); @initial_cc = expand_aliases(@initial_cc); @bcclist = expand_aliases(@bcclist); @@ -330,7 +330,7 @@ if ($compose) { # effort to have it be unique open(C,">",$compose_filename) or die "Failed to open for writing $compose_filename: $!"; - print C "From $from # This line is ignored.\n"; + print C "From $sender # This line is ignored.\n"; printf C "Subject: %s\n\n", $initial_subject; printf C <\s*$/$1/; if ($have_email_valid) { - $address =~ s/^\s*<(.*)>\s*$/$1/; return scalar Email::Valid->address($address); } else { # less robust/correct than the monster regexp in Email::Valid, @@ -433,11 +433,11 @@ sub make_message_id my $date = time; my $pseudo_rand = int (rand(4200)); my $du_part; - for ($from, $committer, $author) { - $du_part = extract_valid_address($_); - last if ($du_part ne ''); + for ($sender, $repocommitter, $repoauthor) { + $du_part = extract_valid_address(sanitize_address($_)); + last if (defined $du_part and $du_part ne ''); } - if ($du_part eq '') { + if (not defined $du_part or $du_part eq '') { use Sys::Hostname qw(); $du_part = 'user@' . Sys::Hostname::hostname(); } @@ -459,22 +459,41 @@ sub unquote_rfc2047 { return "$_"; } -# If an address contains a . in the name portion, the name must be quoted. -sub sanitize_address_rfc822 +# use the simplest quoting being able to handle the recipient +sub sanitize_address { my ($recipient) = @_; - my ($recipient_name) = ($recipient =~ /^(.*?)\s+@,;:\\".\000-\037\177]/) { + $recipient_name =~ s/(["\\\r])/\\$1/; + $recipient_name = "\"$recipient_name\""; + } + + return "$recipient_name $recipient_addr"; + } sub send_message { my @recipients = unique_email_list(@to); - @cc = (map { sanitize_address_rfc822($_) } @cc); + @cc = (map { sanitize_address($_) } @cc); my $to = join (",\n\t", @recipients); @recipients = unique_email_list(@recipients,@cc,@bcclist); @recipients = (map { extract_valid_address($_) } @recipients); @@ -489,10 +508,10 @@ sub send_message if ($cc ne '') { $ccline = "\nCc: $cc"; } - $from = sanitize_address_rfc822($from); + my $sanitized_sender = sanitize_address($sender); make_message_id(); - my $header = "From: $from + my $header = "From: $sanitized_sender To: $to${ccline} Subject: $subject Date: $date @@ -509,7 +528,7 @@ X-Mailer: git-send-email $gitversion } my @sendmail_parameters = ('-i', @recipients); - my $raw_from = $from; + my $raw_from = $sanitized_sender; $raw_from = $envelope_sender if (defined $envelope_sender); $raw_from = extract_valid_address($raw_from); unshift (@sendmail_parameters, @@ -546,7 +565,7 @@ X-Mailer: git-send-email $gitversion } else { print "Sendmail: $smtp_server ".join(' ',@sendmail_parameters)."\n"; } - print "From: $from\nSubject: $subject\nCc: $cc\nTo: $to\n\n"; + print "From: $sanitized_sender\nSubject: $subject\nCc: $cc\nTo: $to\n\n"; if ($smtp) { print "Result: ", $smtp->code, ' ', ($smtp->message =~ /\n([^\n]+\n)$/s), "\n"; @@ -563,7 +582,7 @@ $subject = $initial_subject; foreach my $t (@files) { open(F,"<",$t) or die "can't open file $t"; - my $author_not_sender = undef; + my $author = undef; @cc = @initial_cc; @xh = (); my $input_format = undef; @@ -585,12 +604,11 @@ foreach my $t (@files) { $subject = $1; } elsif (/^(Cc|From):\s+(.*)$/) { - if (unquote_rfc2047($2) eq $from) { - $from = $2; + if (unquote_rfc2047($2) eq $sender) { next if ($suppress_from); } elsif ($1 eq 'From') { - $author_not_sender = $2; + $author = unquote_rfc2047($2); } printf("(mbox) Adding cc: %s from line '%s'\n", $2, $_) unless $quiet; @@ -634,9 +652,8 @@ foreach my $t (@files) { } } close F; - if (defined $author_not_sender) { - $author_not_sender = unquote_rfc2047($author_not_sender); - $message = "From: $author_not_sender\n\n$message"; + if (defined $author) { + $message = "From: $author\n\n$message"; } diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 8cbd153b62..185c5c6c95 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -116,6 +116,16 @@ then exit $exit } else - GIT_DIR=$(git rev-parse --git-dir) || exit + GIT_DIR=$(git rev-parse --git-dir) || { + exit=$? + echo >&2 "Failed to find a valid git directory." + exit $exit + } fi + +test -n "$GIT_DIR" && GIT_DIR=$(cd "$GIT_DIR" && pwd) || { + echo >&2 "Unable to determine absolute path of git directory" + exit 1 +} + : ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"} diff --git a/git-svn.perl b/git-svn.perl index ee7ef693fa..d162114e26 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -3501,11 +3501,17 @@ sub log_use_color { sub git_svn_log_cmd { my ($r_min, $r_max, @args) = @_; my $head = 'HEAD'; + my (@files, @log_opts); foreach my $x (@args) { - last if $x eq '--'; - next unless ::verify_ref("$x^0"); - $head = $x; - last; + if ($x eq '--' || @files) { + push @files, $x; + } else { + if (::verify_ref("$x^0")) { + $head = $x; + } else { + push @log_opts, $x; + } + } } my ($url, $rev, $uuid, $gs) = ::working_head_info($head); @@ -3515,13 +3521,13 @@ sub git_svn_log_cmd { push @cmd, '-r' unless $non_recursive; push @cmd, qw/--raw --name-status/ if $verbose; push @cmd, '--color' if log_use_color(); - return @cmd unless defined $r_max; - if ($r_max == $r_min) { + push @cmd, @log_opts; + if (defined $r_max && $r_max == $r_min) { push @cmd, '--max-count=1'; if (my $c = $gs->rev_db_get($r_max)) { push @cmd, $c; } - } else { + } elsif (defined $r_max) { my ($c_min, $c_max); $c_max = $gs->rev_db_get($r_max); $c_min = $gs->rev_db_get($r_min); @@ -3537,7 +3543,7 @@ sub git_svn_log_cmd { push @cmd, $c_min; } } - return @cmd; + return (@cmd, @files); } # adapted from pager.c @@ -3702,7 +3708,7 @@ sub cmd_show_log { } config_pager(); - @args = (git_svn_log_cmd($r_min, $r_max, @args), @args); + @args = git_svn_log_cmd($r_min, $r_max, @args); my $log = command_output_pipe(@args); run_pager(); my (@k, $c, $d, $stat); diff --git a/git-svnimport.perl b/git-svnimport.perl index b73d6494d8..8c17fb5ae2 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -49,7 +49,7 @@ getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:SP:R:uv") or usage(); usage if $opt_h; my $tag_name = $opt_t || "tags"; -my $trunk_name = $opt_T || "trunk"; +my $trunk_name = defined $opt_T ? $opt_T : "trunk"; my $branch_name = $opt_b || "branches"; my $project_name = $opt_P || ""; $project_name = "/" . $project_name if ($project_name); diff --git a/git.c b/git.c index 9908a60007..644befb723 100644 --- a/git.c +++ b/git.c @@ -319,7 +319,8 @@ static void handle_internal_command(int argc, const char **argv) { "branch", cmd_branch, RUN_SETUP }, { "bundle", cmd_bundle }, { "cat-file", cmd_cat_file, RUN_SETUP }, - { "checkout-index", cmd_checkout_index, RUN_SETUP }, + { "checkout-index", cmd_checkout_index, + RUN_SETUP | NEED_WORK_TREE}, { "check-ref-format", cmd_check_ref_format }, { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE }, { "cherry", cmd_cherry, RUN_SETUP }, @@ -328,7 +329,7 @@ static void handle_internal_command(int argc, const char **argv) { "config", cmd_config }, { "count-objects", cmd_count_objects, RUN_SETUP }, { "describe", cmd_describe, RUN_SETUP }, - { "diff", cmd_diff, USE_PAGER }, + { "diff", cmd_diff }, { "diff-files", cmd_diff_files }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, @@ -372,11 +373,13 @@ static void handle_internal_command(int argc, const char **argv) { "show", cmd_show, RUN_SETUP | USE_PAGER }, { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, + { "tag", cmd_tag, RUN_SETUP }, { "tar-tree", cmd_tar_tree }, { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, { "update-index", cmd_update_index, RUN_SETUP }, { "update-ref", cmd_update_ref, RUN_SETUP }, { "upload-archive", cmd_upload_archive }, + { "verify-tag", cmd_verify_tag, RUN_SETUP }, { "version", cmd_version }, { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, { "write-tree", cmd_write_tree, RUN_SETUP }, diff --git a/gitk b/gitk index 1103bafc25..c38a80c592 100755 --- a/gitk +++ b/gitk @@ -296,7 +296,7 @@ proc readcommit {id} { proc updatecommits {} { global viewdata curview phase displayorder - global children commitrow selectedline thickerline + global children commitrow selectedline thickerline showneartags if {$phase ne {}} { stop_rev_list @@ -313,7 +313,9 @@ proc updatecommits {} { catch {unset viewdata($n)} readrefs changedrefs - regetallcommits + if {$showneartags} { + getallcommits + } showview $n } @@ -427,7 +429,7 @@ proc readrefs {} { lappend idotherrefs($id) $name } } - close $refd + catch {close $refd} set mainhead {} set mainheadid {} catch { @@ -823,8 +825,13 @@ proc makewindow {} { pack .ctop -fill both -expand 1 bindall <1> {selcanvline %W %x %y} #bindall {selcanvline %W %x %y} - bindall "allcanvs yview scroll -5 units" - bindall "allcanvs yview scroll 5 units" + if {[tk windowingsystem] == "win32"} { + bind . { windows_mousewheel_redirector %W %X %Y %D } + bind $ctext { windows_mousewheel_redirector %W %X %Y %D ; break } + } else { + bindall "allcanvs yview scroll -5 units" + bindall "allcanvs yview scroll 5 units" + } bindall <2> "canvscan mark %W %x %y" bindall "canvscan dragto %W %x %y" bindkey selfirstline @@ -879,6 +886,7 @@ proc makewindow {} { bind $cflist <1> {sel_flist %W %x %y; break} bind $cflist {sel_flist %W %x %y; break} bind $cflist {treeclick %W %x %y} + bind $cflist {pop_flist_menu %W %X %Y %x %y} set maincursor [. cget -cursor] set textcursor [$ctext cget -cursor] @@ -916,6 +924,32 @@ proc makewindow {} { -command cobranch $headctxmenu add command -label "Remove this branch" \ -command rmbranch + + global flist_menu + set flist_menu .flistctxmenu + menu $flist_menu -tearoff 0 + $flist_menu add command -label "Highlight this too" \ + -command {flist_hl 0} + $flist_menu add command -label "Highlight this only" \ + -command {flist_hl 1} +} + +# Windows sends all mouse wheel events to the current focused window, not +# the one where the mouse hovers, so bind those events here and redirect +# to the correct window +proc windows_mousewheel_redirector {W X Y D} { + global canv canv2 canv3 + set w [winfo containing -displayof $W $X $Y] + if {$w ne ""} { + set u [expr {$D < 0 ? 5 : -5}] + if {$w == $canv || $w == $canv2 || $w == $canv3} { + allcanvs yview scroll $u units + } else { + catch { + $w yview scroll $u units + } + } + } } # mouse-2 makes all windows scan vertically, but only the one @@ -955,8 +989,8 @@ proc bindkey {ev script} { # set the focus back to the toplevel for any click outside # the entry widgets proc click {w} { - global entries - foreach e $entries { + global ctext entries + foreach e [concat $entries $ctext] { if {$w == $e} return } focus . @@ -1500,6 +1534,33 @@ proc sel_flist {w x y} { } } +proc pop_flist_menu {w X Y x y} { + global ctext cflist cmitmode flist_menu flist_menu_file + global treediffs diffids + + set l [lindex [split [$w index "@$x,$y"] "."] 0] + if {$l <= 1} return + if {$cmitmode eq "tree"} { + set e [linetoelt $l] + if {[string index $e end] eq "/"} return + } else { + set e [lindex $treediffs($diffids) [expr {$l-2}]] + } + set flist_menu_file $e + tk_popup $flist_menu $X $Y +} + +proc flist_hl {only} { + global flist_menu_file highlight_files + + set x [shellquote $flist_menu_file] + if {$only || $highlight_files eq {}} { + set highlight_files $x + } else { + append highlight_files " " $x + } +} + # Functions for adding and removing shell-type quoting proc shellquote {str} { @@ -2837,17 +2898,12 @@ proc layoutrows {row endrow last} { set offs [lindex $rowoffsets $row] while {$row < $endrow} { set id [lindex $displayorder $row] - set oldolds {} - set newolds {} + set nev [expr {[llength $idlist] - $maxwidth + 1}] foreach p [lindex $parentlist $row] { - if {![info exists idinlist($p)]} { - lappend newolds $p - } elseif {!$idinlist($p)} { - lappend oldolds $p + if {![info exists idinlist($p)] || !$idinlist($p)} { + incr nev } } - set nev [expr {[llength $idlist] + [llength $newolds] - + [llength $oldolds] - $maxwidth + 1}] if {$nev > 0} { if {!$last && $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break @@ -2866,12 +2922,22 @@ proc layoutrows {row endrow last} { if {[incr nev -1] <= 0} break continue } - set rowchk($id) [expr {$row + $r}] + set rowchk($i) [expr {$row + $r}] } } lset rowidlist $row $idlist lset rowoffsets $row $offs } + set oldolds {} + set newolds {} + foreach p [lindex $parentlist $row] { + if {![info exists idinlist($p)]} { + lappend newolds $p + } elseif {!$idinlist($p)} { + lappend oldolds $p + } + set idinlist($p) 1 + } set col [lsearch -exact $idlist $id] if {$col < 0} { set col [llength $idlist] @@ -2917,12 +2983,10 @@ proc layoutrows {row endrow last} { lset offs $col {} } foreach i $newolds { - set idinlist($i) 1 set idrowranges($i) $id } incr col $l foreach oid $oldolds { - set idinlist($oid) 1 set idlist [linsert $idlist $col $oid] set offs [linsert $offs $col $o] makeuparrow $oid $col $row $o @@ -2963,7 +3027,7 @@ proc layouttail {} { set col [expr {[llength $idlist] - 1}] set id [lindex $idlist $col] addextraid $id $row - unset idinlist($id) + catch {unset idinlist($id)} lappend idrowranges($id) $id lappend rowrangelist $idrowranges($id) unset idrowranges($id) @@ -4566,6 +4630,7 @@ proc sellastline {} { proc selnextline {dir} { global selectedline + focus . if {![info exists selectedline]} return set l [expr {$selectedline + $dir}] unmarkmatches @@ -4646,6 +4711,7 @@ proc godo {elt} { proc goback {} { global history historyindex + focus . if {$historyindex > 1} { incr historyindex -1 @@ -4659,6 +4725,7 @@ proc goback {} { proc goforw {} { global history historyindex + focus . if {$historyindex < [llength $history]} { set cmd [lindex $history $historyindex] @@ -6135,17 +6202,13 @@ proc rmbranch {} { proc getallcommits {} { global allcommits allids nbmp nextarc seeds - set allids {} - set nbmp 0 - set nextarc 0 - set allcommits 0 - set seeds {} - regetallcommits -} - -# Called when the graph might have changed -proc regetallcommits {} { - global allcommits seeds + if {![info exists allcommits]} { + set allids {} + set nbmp 0 + set nextarc 0 + set allcommits 0 + set seeds {} + } set cmd [concat | git rev-list --all --parents] foreach id $seeds { @@ -7576,7 +7639,10 @@ catch {source ~/.gitk} font create optionfont -family sans-serif -size -12 # check that we can find a .git directory somewhere... -set gitdir [gitdir] +if {[catch {set gitdir [gitdir]}]} { + show_error {} . "Cannot find a git repository here." + exit 1 +} if {![file isdirectory $gitdir]} { show_error {} . "Cannot find the git directory \"$gitdir\"." exit 1 diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 077eb2f4ca..f282a677aa 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -5366,7 +5366,7 @@ sub git_feed { # log/feed of current (HEAD) branch, log of given branch, history of file/directory my $head = $hash || 'HEAD'; - my @commitlist = parse_commits($head, 150); + my @commitlist = parse_commits($head, 150, 0, undef, $file_name); my %latest_commit; my %latest_date; diff --git a/ident.c b/ident.c index 6656d2e7f9..1f99984d76 100644 --- a/ident.c +++ b/ident.c @@ -191,11 +191,11 @@ static const char *env_hint = "\n" "Run\n" "\n" -" git config user.email \"you@email.com\"\n" -" git config user.name \"Your Name\"\n" +" git config --global user.email \"you@email.com\"\n" +" git config --global user.name \"Your Name\"\n" "\n" -"To set the identity in this repository.\n" -"Add --global to set your account\'s default\n" +"to set your account\'s default identity.\n" +"Omit --global to set the identity only in this repository.\n" "\n"; const char *fmt_ident(const char *name, const char *email, diff --git a/index-pack.c b/index-pack.c index 8403c36b63..db58e05041 100644 --- a/index-pack.c +++ b/index-pack.c @@ -114,7 +114,7 @@ static const char *open_pack_file(const char *pack_name) static char tmpfile[PATH_MAX]; snprintf(tmpfile, sizeof(tmpfile), "%s/tmp_pack_XXXXXX", get_object_directory()); - output_fd = mkstemp(tmpfile); + output_fd = xmkstemp(tmpfile); pack_name = xstrdup(tmpfile); } else output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600); diff --git a/log-tree.c b/log-tree.c index 8624d5a39c..a6423718e7 100644 --- a/log-tree.c +++ b/log-tree.c @@ -295,6 +295,9 @@ void show_log(struct rev_info *opt, const char *sep) if (opt->add_signoff) len = append_signoff(&msgbuf, &msgbuf_len, len, opt->add_signoff); + if (opt->show_log_size) + printf("log size %i\n", len); + printf("%s%s%s", msgbuf, extra, sep); free(msgbuf); } diff --git a/merge-recursive.c b/merge-recursive.c index c8539ec0ba..16f6a0f98b 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -216,13 +216,19 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, */ static int index_only = 0; +static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree) +{ + parse_tree(tree); + init_tree_desc(desc, tree->buffer, tree->size); +} + static int git_merge_trees(int index_only, struct tree *common, struct tree *head, struct tree *merge) { int rc; - struct object_list *trees = NULL; + struct tree_desc t[3]; struct unpack_trees_options opts; memset(&opts, 0, sizeof(opts)); @@ -234,11 +240,11 @@ static int git_merge_trees(int index_only, opts.head_idx = 2; opts.fn = threeway_merge; - object_list_append(&common->object, &trees); - object_list_append(&head->object, &trees); - object_list_append(&merge->object, &trees); + init_tree_desc_from_tree(t+0, common); + init_tree_desc_from_tree(t+1, head); + init_tree_desc_from_tree(t+2, merge); - rc = unpack_trees(trees, &opts); + rc = unpack_trees(3, t, &opts); cache_tree_free(&active_cache_tree); return rc; } @@ -671,6 +677,26 @@ struct ll_merge_driver { /* * Built-in low-levels */ +static int ll_binary_merge(const struct ll_merge_driver *drv_unused, + const char *path_unused, + mmfile_t *orig, + mmfile_t *src1, const char *name1, + mmfile_t *src2, const char *name2, + mmbuffer_t *result) +{ + /* + * 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 = index_only ? orig : src1; + + result->ptr = stolen->ptr; + result->size = stolen->size; + stolen->ptr = NULL; + return 1; +} + static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, const char *path_unused, mmfile_t *orig, @@ -681,10 +707,15 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, xpparam_t xpp; if (buffer_is_binary(orig->ptr, orig->size) || - buffer_is_binary(src1->ptr, src1->size) || - buffer_is_binary(src2->ptr, src2->size)) - return error("Cannot merge binary files: %s vs. %s\n", + buffer_is_binary(src1->ptr, src1->size) || + buffer_is_binary(src2->ptr, src2->size)) { + warning("Cannot merge binary files: %s vs. %s\n", name1, name2); + return ll_binary_merge(drv_unused, path_unused, + orig, src1, name1, + src2, name2, + result); + } memset(&xpp, 0, sizeof(xpp)); return xdl_merge(orig, @@ -737,26 +768,6 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused, return 0; } -static int ll_binary_merge(const struct ll_merge_driver *drv_unused, - const char *path_unused, - mmfile_t *orig, - mmfile_t *src1, const char *name1, - mmfile_t *src2, const char *name2, - mmbuffer_t *result) -{ - /* - * 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 = index_only ? orig : src1; - - result->ptr = stolen->ptr; - result->size = stolen->size; - stolen->ptr = NULL; - return 1; -} - #define LL_BINARY_MERGE 0 #define LL_TEXT_MERGE 1 #define LL_UNION_MERGE 2 @@ -771,9 +782,7 @@ static void create_temp(mmfile_t *src, char *path) int fd; strcpy(path, ".merge_file_XXXXXX"); - fd = mkstemp(path); - if (fd < 0) - die("unable to create temp-file"); + fd = xmkstemp(path); if (write_in_full(fd, src->ptr, src->size) != src->size) die("unable to write temp-file"); close(fd); diff --git a/pack-write.c b/pack-write.c index 1cf5f7c9f0..e59b197e5e 100644 --- a/pack-write.c +++ b/pack-write.c @@ -45,7 +45,7 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec static char tmpfile[PATH_MAX]; snprintf(tmpfile, sizeof(tmpfile), "%s/tmp_idx_XXXXXX", get_object_directory()); - fd = mkstemp(tmpfile); + fd = xmkstemp(tmpfile); index_name = xstrdup(tmpfile); } else { unlink(index_name); diff --git a/pager.c b/pager.c index 13d99d882f..a55f4e9d1e 100644 --- a/pager.c +++ b/pager.c @@ -44,8 +44,11 @@ void setup_pager(void) if (!isatty(1)) return; - if (!pager) + if (!pager) { + if (!pager_program) + git_config(git_default_config); pager = pager_program; + } if (!pager) pager = getenv("PAGER"); if (!pager) diff --git a/path-list.c b/path-list.c index dcb4b3ac13..3d83b7ba9e 100644 --- a/path-list.c +++ b/path-list.c @@ -76,16 +76,18 @@ struct path_list_item *path_list_lookup(const char *path, struct path_list *list return list->items + i; } -void path_list_clear(struct path_list *list, int free_items) +void path_list_clear(struct path_list *list, int free_util) { if (list->items) { int i; - if (free_items) - for (i = 0; i < list->nr; i++) { - if (list->strdup_paths) - free(list->items[i].path); + if (list->strdup_paths) { + for (i = 0; i < list->nr; i++) + free(list->items[i].path); + } + if (free_util) { + for (i = 0; i < list->nr; i++) free(list->items[i].util); - } + } free(list->items); } list->items = NULL; diff --git a/path-list.h b/path-list.h index ce5ffabcce..5931e2cc0c 100644 --- a/path-list.h +++ b/path-list.h @@ -15,7 +15,7 @@ struct path_list void print_path_list(const char *text, const struct path_list *p); int path_list_has_path(const struct path_list *list, const char *path); -void path_list_clear(struct path_list *list, int free_items); +void path_list_clear(struct path_list *list, int free_util); struct path_list_item *path_list_insert(const char *path, struct path_list *list); struct path_list_item *path_list_lookup(const char *path, struct path_list *list); diff --git a/read-cache.c b/read-cache.c index e060392d1d..8b1c94e0e3 100644 --- a/read-cache.c +++ b/read-cache.c @@ -7,6 +7,7 @@ #include "cache.h" #include "cache-tree.h" #include "refs.h" +#include "dir.h" /* Index extensions. * @@ -665,7 +666,7 @@ static int check_file_directory_conflict(struct index_state *istate, return retval + has_dir_name(istate, ce, pos, ok_to_replace); } -int add_index_entry(struct index_state *istate, struct cache_entry *ce, int option) +static int add_index_entry_with_check(struct index_state *istate, struct cache_entry *ce, int option) { int pos; int ok_to_add = option & ADD_CACHE_OK_TO_ADD; @@ -707,6 +708,22 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti pos = index_name_pos(istate, ce->name, ntohs(ce->ce_flags)); pos = -pos-1; } + return pos + 1; +} + +int add_index_entry(struct index_state *istate, struct cache_entry *ce, int option) +{ + int pos; + + if (option & ADD_CACHE_JUST_APPEND) + pos = istate->cache_nr; + else { + int ret; + ret = add_index_entry_with_check(istate, ce, option); + if (ret <= 0) + return ret; + pos = ret - 1; + } /* Make sure the array is big enough .. */ if (istate->cache_nr == istate->cache_alloc) { @@ -717,7 +734,7 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti /* Add it in.. */ istate->cache_nr++; - if (istate->cache_nr > pos) + if (istate->cache_nr > pos + 1) memmove(istate->cache + pos + 1, istate->cache + pos, (istate->cache_nr - pos - 1) * sizeof(ce)); @@ -782,7 +799,7 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, return updated; } -int refresh_index(struct index_state *istate, unsigned int flags) +int refresh_index(struct index_state *istate, unsigned int flags, const char **pathspec, char *seen) { int i; int has_errors = 0; @@ -808,6 +825,9 @@ int refresh_index(struct index_state *istate, unsigned int flags) continue; } + if (pathspec && !match_pathspec(pathspec, ce->name, strlen(ce->name), 0, seen)) + continue; + new = refresh_cache_ent(istate, ce, really, &cache_errno); if (new == ce) continue; diff --git a/refs.c b/refs.c index 9c07f83b72..774e9f9e0e 100644 --- a/refs.c +++ b/refs.c @@ -869,6 +869,7 @@ static int repack_without_ref(const char *refname) die("too long a refname '%s'", list->name); write_or_die(fd, line, len); } + close(fd); return commit_lock_file(&packlock); } diff --git a/revision.c b/revision.c index 038693caba..7d32a89b0e 100644 --- a/revision.c +++ b/revision.c @@ -1150,6 +1150,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch die("unknown date format %s", arg); continue; } + if (!strcmp(arg, "--log-size")) { + revs->show_log_size = 1; + continue; + } /* * Grepping the commit log diff --git a/revision.h b/revision.h index f46b4d55a2..98a0a8f3fa 100644 --- a/revision.h +++ b/revision.h @@ -81,6 +81,7 @@ struct rev_info { const char *log_reencode; const char *subject_prefix; int no_inline; + int show_log_size; /* Filter by commit log message */ struct grep_opt *grep_filter; diff --git a/setup.c b/setup.c index 15100bc1b7..d575529ac9 100644 --- a/setup.c +++ b/setup.c @@ -141,7 +141,7 @@ void verify_non_filename(const char *prefix, const char *arg) if (!lstat(name, &st)) die("ambiguous argument '%s': both revision and filename\n" "Use '--' to separate filenames from revisions", arg); - if (errno != ENOENT) + if (errno != ENOENT && errno != ENOTDIR) die("'%s': %s", arg, strerror(errno)); } @@ -223,53 +223,21 @@ int is_inside_work_tree(void) } /* - * If no worktree was given, and we are outside of a default work tree, - * now is the time to set it. - * - * In other words, if the user calls git with something like - * - * git --git-dir=/some/where/else/.git bla - * - * default to /some/where/else as working directory; if the specified - * git-dir does not end in "/.git", the cwd is used as working directory. + * set_work_tree() is only ever called if you set GIT_DIR explicitely. + * 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. */ -const char *set_work_tree(const char *dir) +static const char *set_work_tree(const char *dir) { - char dir_buffer[PATH_MAX], *rel = NULL; - static char buffer[PATH_MAX + 1]; - int len, suffix_len = strlen(DEFAULT_GIT_DIR_ENVIRONMENT) + 1; + char buffer[PATH_MAX + 1]; - /* strip the variable 'dir' of the postfix "/.git" if it has it */ - len = strlen(dir); - if (len > suffix_len && - !strcmp(dir + len - suffix_len, "/" DEFAULT_GIT_DIR_ENVIRONMENT)) { - if ((len - suffix_len) >= sizeof(dir_buffer)) - die("directory name too long"); - memcpy(dir_buffer, dir, len - suffix_len); - dir_buffer[len - suffix_len] = '\0'; - - /* are we inside the default work tree? */ - rel = get_relative_cwd(buffer, sizeof(buffer), dir_buffer); - } - - /* if rel is set, the cwd is _not_ the current working tree */ - if (rel && *rel) { - if (!is_absolute_path(dir)) - set_git_dir(make_absolute_path(dir)); - dir = dir_buffer; - if (chdir(dir)) - die("cannot chdir to %s: %s", dir, strerror(errno)); - else - strcat(rel, "/"); - inside_git_dir = 0; - } else { - rel = NULL; - dir = getcwd(buffer, sizeof(buffer)); - } - git_work_tree_cfg = xstrdup(dir); + if (!getcwd(buffer, sizeof(buffer))) + die ("Could not get the current working directory"); + git_work_tree_cfg = xstrdup(buffer); inside_work_tree = 1; - return rel; + return NULL; } /* diff --git a/sha1_file.c b/sha1_file.c index 4629fe81a4..6d795b7612 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -505,7 +505,7 @@ static int check_packed_git_idx(const char *path, struct packed_git *p) */ if (idx_size != 4*256 + nr * 24 + 20 + 20) { munmap(idx_map, idx_size); - return error("wrong index file size in %s", path); + return error("wrong index v1 file size in %s", path); } } else if (version == 2) { /* @@ -527,7 +527,7 @@ static int check_packed_git_idx(const char *path, struct packed_git *p) max_size += (nr - 1)*8; if (idx_size < min_size || idx_size > max_size) { munmap(idx_map, idx_size); - return error("wrong index file size in %s", path); + return error("wrong index v2 file size in %s", path); } if (idx_size != min_size) { /* make sure we can deal with large pack offsets */ diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index fe1dfd08a0..0807d9f01a 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -290,4 +290,85 @@ test_expect_success '.gitattributes says two and three are text' ' fi ' +test_expect_success 'in-tree .gitattributes (1)' ' + + echo "one -crlf" >>.gitattributes && + git add .gitattributes && + git commit -m "Add .gitattributes" && + + rm -rf tmp one dir .gitattributes patch.file three && + git read-tree --reset -u HEAD && + + if remove_cr one >/dev/null + then + echo "Eh? one should not have CRLF" + false + else + : happy + fi && + remove_cr three >/dev/null || { + echo "Eh? three should still have CRLF" + false + } +' + +test_expect_success 'in-tree .gitattributes (2)' ' + + rm -rf tmp one dir .gitattributes patch.file three && + git read-tree --reset HEAD && + git checkout-index -f -q -u -a && + + if remove_cr one >/dev/null + then + echo "Eh? one should not have CRLF" + false + else + : happy + fi && + remove_cr three >/dev/null || { + echo "Eh? three should still have CRLF" + false + } +' + +test_expect_success 'in-tree .gitattributes (3)' ' + + rm -rf tmp one dir .gitattributes patch.file three && + git read-tree --reset HEAD && + git checkout-index -u .gitattributes && + git checkout-index -u one dir/two three && + + if remove_cr one >/dev/null + then + echo "Eh? one should not have CRLF" + false + else + : happy + fi && + remove_cr three >/dev/null || { + echo "Eh? three should still have CRLF" + false + } +' + +test_expect_success 'in-tree .gitattributes (4)' ' + + rm -rf tmp one dir .gitattributes patch.file three && + git read-tree --reset HEAD && + git checkout-index -u one dir/two three && + git checkout-index -u .gitattributes && + + if remove_cr one >/dev/null + then + echo "Eh? one should not have CRLF" + false + else + : happy + fi && + remove_cr three >/dev/null || { + echo "Eh? three should still have CRLF" + false + } +' + test_done diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh index b1c900379b..cad95f35ad 100755 --- a/t/t0030-stripspace.sh +++ b/t/t0030-stripspace.sh @@ -392,4 +392,9 @@ test_expect_success \ git diff expect actual ' +test_expect_success 'strip comments, too' ' + test ! -z "$(echo "# comment" | git stripspace)" && + test -z "$(echo "# comment" | git stripspace -s)" +' + test_done diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh index bb5f30220a..6bfe19a4e5 100755 --- a/t/t1301-shared-repo.sh +++ b/t/t1301-shared-repo.sh @@ -21,7 +21,16 @@ test_expect_success 'update-server-info honors core.sharedRepository' ' git commit -m a1 && umask 0277 && git update-server-info && - test 444 = $(stat -c %a .git/info/refs) + actual="$(ls -l .git/info/refs)" && + case "$actual" in + -r--r--r--*) + : happy + ;; + *) + echo Oops, .git/info/refs is not 0444 + false + ;; + esac ' test_done diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index bea40cba8d..e474b3f1d5 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh @@ -28,6 +28,8 @@ test_rev_parse() { [ $# -eq 0 ] && return } +# label is-bare is-inside-git is-inside-work prefix + test_rev_parse toplevel false false true '' cd .git || exit 1 @@ -53,13 +55,13 @@ export GIT_DIR=../.git export GIT_CONFIG="$(pwd)"/../.git/config git config core.bare false -test_rev_parse 'GIT_DIR=../.git, core.bare = false' false false true work/ +test_rev_parse 'GIT_DIR=../.git, core.bare = false' false false true '' git config core.bare true test_rev_parse 'GIT_DIR=../.git, core.bare = true' true false false '' git config --unset core.bare -test_rev_parse 'GIT_DIR=../.git, core.bare undefined' false false true work/ +test_rev_parse 'GIT_DIR=../.git, core.bare undefined' false false true '' mv ../.git ../repo.git || exit 1 export GIT_DIR=../repo.git diff --git a/t/t2050-git-dir-relative.sh b/t/t2050-git-dir-relative.sh new file mode 100755 index 0000000000..88f268b9d7 --- /dev/null +++ b/t/t2050-git-dir-relative.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +test_description='check problems with relative GIT_DIR + +This test creates a working tree state with a file and subdir: + + top (committed several times) + subdir (a subdirectory) + +It creates a commit-hook and tests it, then moves .git +into the subdir while keeping the worktree location, +and tries commits from the top and the subdir, checking +that the commit-hook still gets called.' + +. ./test-lib.sh + +COMMIT_FILE="$(pwd)/output" +export COMMIT_FILE + +test_expect_success 'Setting up post-commit hook' ' +mkdir -p .git/hooks && +echo >.git/hooks/post-commit "#!/bin/sh +touch \"\${COMMIT_FILE}\" +echo Post commit hook was called." && +chmod +x .git/hooks/post-commit' + +test_expect_success 'post-commit hook used ordinarily' ' +echo initial >top && +git-add top +git-commit -m initial && +test -r "${COMMIT_FILE}" +' + +rm -rf "${COMMIT_FILE}" +mkdir subdir +mv .git subdir + +test_expect_success 'post-commit-hook created and used from top dir' ' +echo changed >top && +git --git-dir subdir/.git add top && +git --git-dir subdir/.git commit -m topcommit && +test -r "${COMMIT_FILE}" +' + +rm -rf "${COMMIT_FILE}" + +test_expect_success 'post-commit-hook from sub dir' ' +echo changed again >top +cd subdir && +git --git-dir .git --work-tree .. add ../top && +git --git-dir .git --work-tree .. commit -m subcommit && +test -r "${COMMIT_FILE}" +' + +test_done diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh index 0a703af149..61d08bb431 100755 --- a/t/t2200-add-update.sh +++ b/t/t2200-add-update.sh @@ -13,26 +13,67 @@ only the updates to dir/sub.' . ./test-lib.sh -test_expect_success 'setup' ' -echo initial >top && -mkdir dir && -echo initial >dir/sub && -git add dir/sub top && -git-commit -m initial && -echo changed >top && -echo changed >dir/sub && -echo other >dir/other +test_expect_success setup ' + echo initial >check && + echo initial >top && + mkdir dir1 dir2 && + echo initial >dir1/sub1 && + echo initial >dir1/sub2 && + echo initial >dir2/sub3 && + git add check dir1 dir2 top && + test_tick + git-commit -m initial && + + echo changed >check && + echo changed >top && + echo changed >dir2/sub3 && + rm -f dir1/sub1 && + echo other >dir2/other ' -test_expect_success 'update' 'git add -u dir' +test_expect_success update ' + git add -u dir1 dir2 +' -test_expect_success 'update touched correct path' \ - 'test "`git diff-files --name-status dir/sub`" = ""' +test_expect_success 'update noticed a removal' ' + test "$(git-ls-files dir1/sub1)" = "" +' -test_expect_success 'update did not touch other tracked files' \ - 'test "`git diff-files --name-status top`" = "M top"' +test_expect_success 'update touched correct path' ' + test "$(git-diff-files --name-status dir2/sub3)" = "" +' -test_expect_success 'update did not touch untracked files' \ - 'test "`git diff-files --name-status dir/other`" = ""' +test_expect_success 'update did not touch other tracked files' ' + test "$(git-diff-files --name-status check)" = "M check" && + test "$(git-diff-files --name-status top)" = "M top" +' + +test_expect_success 'update did not touch untracked files' ' + test "$(git-ls-files dir2/other)" = "" +' + +test_expect_success 'cache tree has not been corrupted' ' + + git ls-files -s | + sed -e "s/ 0 / /" >expect && + git ls-tree -r $(git write-tree) | + sed -e "s/ blob / /" >current && + diff -u expect current + +' + +test_expect_success 'update from a subdirectory' ' + ( + cd dir1 && + echo more >sub2 && + git add -u sub2 + ) +' + +test_expect_success 'change gets noticed' ' + + test "$(git diff-files --name-status dir1)" = "" + +' test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index a9b552ff08..40d6799ed6 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -61,29 +61,31 @@ test_expect_success 'setup' ' git tag I ' -cat > fake-editor.sh << EOF +cat > fake-editor.sh <<\EOF #!/bin/sh -test "\$1" = .git/COMMIT_EDITMSG && { - test -z "\$FAKE_COMMIT_MESSAGE" || echo "\$FAKE_COMMIT_MESSAGE" > "\$1" - test -z "\$FAKE_COMMIT_AMEND" || echo "\$FAKE_COMMIT_AMEND" >> "\$1" +case "$1" in +*/COMMIT_EDITMSG) + test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1" + test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1" exit -} -test -z "\$EXPECT_COUNT" || - test "\$EXPECT_COUNT" = \$(grep -ve "^#" -e "^$" < "\$1" | wc -l) || + ;; +esac +test -z "$EXPECT_COUNT" || + test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) || exit -test -z "\$FAKE_LINES" && exit -grep -v "^#" < "\$1" > "\$1".tmp -rm "\$1" -cat "\$1".tmp +test -z "$FAKE_LINES" && exit +grep -v '^#' < "$1" > "$1".tmp +rm -f "$1" +cat "$1".tmp action=pick -for line in \$FAKE_LINES; do - case \$line in +for line in $FAKE_LINES; do + case $line in squash) - action="\$line";; + action="$line";; *) - echo sed -n "\${line}s/^pick/\$action/p" - sed -n "\${line}p" < "\$1".tmp - sed -n "\${line}s/^pick/\$action/p" < "\$1".tmp >> "\$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 diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 2356ca9da2..17cc800e3b 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -147,4 +147,16 @@ test_expect_success 'git add with filemode=0, symlinks=0 prefers stage 2 over st git ls-files --stage | grep "^120000 .* 0 symlink$" ' +test_expect_success 'git add --refresh' ' + >foo && git add foo && git commit -a -m "commit all" && + test -z "`git diff-index HEAD -- foo`" && + git read-tree HEAD && + case "`git diff-index HEAD -- foo`" in + :100644" "*"M foo") echo ok;; + *) echo fail; (exit 1);; + esac && + git add --refresh -- foo && + test -z "`git diff-index HEAD -- foo`" +' + test_done diff --git a/t/t3902-quoted.sh b/t/t3902-quoted.sh index e8ad85afe4..73da45f18c 100755 --- a/t/t3902-quoted.sh +++ b/t/t3902-quoted.sh @@ -7,6 +7,12 @@ test_description='quoted output' . ./test-lib.sh +P1='pathname with HT' +: >"$P1" 2>&1 && test -f "$P1" && rm -f "$P1" || { + echo >&2 'Filesystem does not support HT in names' + test_done +} + FN='濱野' GN='純' HT=' ' diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index 7da515361a..7b6798d8b5 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -129,7 +129,7 @@ pull_to_client 2nd "B" $((64*3)) pull_to_client 3rd "A" $((1*3)) # old fails -test_expect_success "clone shallow" "git-clone --depth 2 . shallow" +test_expect_success "clone shallow" "git-clone --depth 2 file://`pwd`/. shallow" (cd shallow; git count-objects -v) > count.shallow diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 426017e1d0..439430f569 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -145,4 +145,12 @@ test_expect_success 'bundle does not prerequisite objects' ' test 4 = $(git verify-pack -v bundle.pack | wc -l) ' +test_expect_success 'bundle should be able to create a full history' ' + + cd "$D" && + git tag -a -m '1.0' v1.0 master && + git bundle create bundle4 v1.0 + +' + test_done diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh index 6d43252593..4e93aaab02 100755 --- a/t/t5700-clone-reference.sh +++ b/t/t5700-clone-reference.sh @@ -51,7 +51,7 @@ diff expected current' cd "$base_dir" test_expect_success 'cloning with reference (no -l -s)' \ -'git clone --reference B A D' +'git clone --reference B file://`pwd`/A D' cd "$base_dir" diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh index b0933274db..822ac8c28e 100755 --- a/t/t5701-clone-local.sh +++ b/t/t5701-clone-local.sh @@ -8,13 +8,16 @@ D=`pwd` test_expect_success 'preparing origin repository' ' : >file && git add . && git commit -m1 && git clone --bare . a.git && - git clone --bare . x + git clone --bare . x && + test "$(GIT_CONFIG=a.git/config git config --bool core.bare)" = true && + test "$(GIT_CONFIG=x/config git config --bool core.bare)" = true ' test_expect_success 'local clone without .git suffix' ' cd "$D" && git clone -l -s a b && cd b && + test "$(GIT_CONFIG=.git/config git config --bool core.bare)" = false && git fetch ' @@ -43,4 +46,21 @@ test_expect_success 'local clone from x.git that does not exist' ' fi ' +test_expect_success 'With -no-hardlinks, local will make a copy' ' + cd "$D" && + git clone --bare --no-hardlinks x w && + cd w && + linked=$(find objects -type f ! -links 1 | wc -l) && + test 0 = $linked +' + +test_expect_success 'Even without -l, local will make a hardlink' ' + cd "$D" && + rm -fr w && + git clone -l --bare x w && + cd w && + copied=$(find objects -type f -links 1 | wc -l) && + test 0 = $copied +' + test_done diff --git a/t/t6027-merge-binary.sh b/t/t6027-merge-binary.sh new file mode 100755 index 0000000000..a7358f75b1 --- /dev/null +++ b/t/t6027-merge-binary.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +test_description='ask merge-recursive to merge binary files' + +. ./test-lib.sh + +test_expect_success setup ' + + cat ../test4012.png >m && + git add m && + git ls-files -s | sed -e "s/ 0 / 1 /" >E1 && + test_tick && + git commit -m "initial" && + + git branch side && + echo frotz >a && + git add a && + echo nitfol >>m && + git add a m && + git ls-files -s a >E0 && + git ls-files -s m | sed -e "s/ 0 / 3 /" >E3 && + test_tick && + git commit -m "master adds some" && + + git checkout side && + echo rezrov >>m && + git add m && + git ls-files -s m | sed -e "s/ 0 / 2 /" >E2 && + test_tick && + git commit -m "side modifies" && + + git tag anchor && + + cat E0 E1 E2 E3 >expect +' + +test_expect_success resolve ' + + rm -f a* m* && + git reset --hard anchor && + + if git merge -s resolve master + then + echo Oops, should not have succeeded + false + else + git ls-files -s >current + diff -u current expect + fi +' + +test_expect_success recursive ' + + rm -f a* m* && + git reset --hard anchor && + + if git merge -s recursive master + then + echo Oops, should not have succeeded + false + else + git ls-files -s >current + diff -u current expect + fi +' + +test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 17de2a90e6..c4fa4461f7 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -5,7 +5,7 @@ test_description='git-tag -Basic tests for operations with tags.' +Tests for operations with tags.' . ./test-lib.sh @@ -16,11 +16,15 @@ tag_exists () { } # todo: git tag -l now returns always zero, when fixed, change this test -test_expect_success 'listing all tags in an empty tree should succeed' \ - 'git tag -l' +test_expect_success 'listing all tags in an empty tree should succeed' ' + git tag -l && + git tag +' -test_expect_success 'listing all tags in an empty tree should output nothing' \ - 'test `git-tag -l | wc -l` -eq 0' +test_expect_success 'listing all tags in an empty tree should output nothing' ' + test `git-tag -l | wc -l` -eq 0 && + test `git-tag | wc -l` -eq 0 +' test_expect_failure 'looking for a tag in an empty tree should fail' \ 'tag_exists mytag' @@ -49,11 +53,15 @@ test_expect_success 'creating a tag using default HEAD should succeed' ' git tag mytag ' -test_expect_success 'listing all tags if one exists should succeed' \ - 'git-tag -l' +test_expect_success 'listing all tags if one exists should succeed' ' + git-tag -l && + git-tag +' -test_expect_success 'listing all tags if one exists should output that tag' \ - 'test `git-tag -l` = mytag' +test_expect_success 'listing all tags if one exists should output that tag' ' + test `git-tag -l` = mytag && + test `git-tag` = mytag +' # pattern matching: @@ -165,6 +173,8 @@ test_expect_success 'listing all tags should print them ordered' ' git tag v1.0 && git tag t210 && git tag -l > actual && + git diff expect actual && + git tag > actual && git diff expect actual ' @@ -264,6 +274,10 @@ test_expect_failure \ 'trying to verify a non-annotated and non-signed tag should fail' \ 'git-tag -v non-annotated-tag' +test_expect_failure \ + 'trying to verify many non-annotated or unknown tags, should fail' \ + 'git-tag -v unknown-tag1 non-annotated-tag unknown-tag2' + # creating annotated tags: get_tag_msg () { @@ -306,6 +320,45 @@ test_expect_success \ git diff expect actual ' +cat >inputmsg <expect +cat inputmsg >>expect +test_expect_success 'creating an annotated tag with -F - should succeed' ' + git-tag -F - stdin-annotated-tag actual && + git diff expect actual +' + +test_expect_success \ + 'trying to create a tag with a non-existing -F file should fail' ' + ! test -f nonexistingfile && + ! tag_exists notag && + ! git-tag -F nonexistingfile notag && + ! tag_exists notag +' + +test_expect_success \ + 'trying to create tags giving many -m or -F options should fail' ' + echo "message file 1" >msgfile1 && + echo "message file 2" >msgfile2 && + ! tag_exists msgtag && + ! git-tag -m "message 1" -m "message 2" msgtag && + ! tag_exists msgtag && + ! git-tag -F msgfile1 -F msgfile2 msgtag && + ! tag_exists msgtag && + ! git-tag -m "message 1" -F msgfile1 msgtag && + ! tag_exists msgtag && + ! git-tag -F msgfile1 -m "message 1" msgtag && + ! tag_exists msgtag && + ! git-tag -F msgfile1 -m "message 1" -F msgfile2 msgtag && + ! tag_exists msgtag && + ! git-tag -m "message 1" -F msgfile1 -m "message 2" msgtag && + ! tag_exists msgtag +' + # blank and empty messages: get_tag_header empty-annotated-tag $commit commit $time >expect @@ -551,6 +604,12 @@ test_expect_success \ ! git-tag -v file-annotated-tag ' +test_expect_success \ + 'trying to verify two annotated non-signed tags should fail' ' + tag_exists annotated-tag file-annotated-tag && + ! git-tag -v annotated-tag file-annotated-tag +' + # creating and verifying signed tags: gpg --version >/dev/null @@ -589,9 +648,55 @@ test_expect_success 'creating a signed tag with -m message should succeed' ' git diff expect actual ' +cat >sigmsgfile <expect +cat sigmsgfile >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success \ + 'creating a signed tag with -F messagefile should succeed' ' + git-tag -s -F sigmsgfile file-signed-tag && + get_tag_msg file-signed-tag >actual && + git diff expect actual +' + +cat >siginputmsg <expect +cat siginputmsg >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success 'creating a signed tag with -F - should succeed' ' + git-tag -s -F - stdin-signed-tag actual && + git diff expect actual +' + +test_expect_success \ + 'trying to create a signed tag with non-existing -F file should fail' ' + ! test -f nonexistingfile && + ! tag_exists nosigtag && + ! git-tag -s -F nonexistingfile nosigtag && + ! tag_exists nosigtag +' + test_expect_success 'verifying a signed tag should succeed' \ 'git-tag -v signed-tag' +test_expect_success 'verifying two signed tags in one command should succeed' \ + 'git-tag -v signed-tag file-signed-tag' + +test_expect_success \ + 'verifying many signed and non-signed tags should fail' ' + ! git-tag -v signed-tag annotated-tag && + ! git-tag -v file-annotated-tag file-signed-tag && + ! git-tag -v annotated-tag file-signed-tag file-annotated-tag && + ! git-tag -v signed-tag annotated-tag file-signed-tag +' + test_expect_success 'verifying a forged tag should fail' ' forged=$(git cat-file tag signed-tag | sed -e "s/signed-tag/forged-tag/" | diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index e8ce7cdb83..9d142ed649 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -175,4 +175,21 @@ test_expect_success 'checkout superproject with subproject already present' ' git-checkout master ' +test_expect_success 'apply submodule diff' ' + git branch second && + ( + cd lib && + echo s >s && + git add s && + git commit -m "change subproject" + ) && + git update-index --add lib && + git-commit -m "change lib" && + git-format-patch -1 --stdout >P.diff && + git checkout second && + git apply --index P.diff && + D=$(git diff --cached master) && + test -z "$D" +' + test_done diff --git a/t/t9116-git-svn-log.sh b/t/t9116-git-svn-log.sh new file mode 100755 index 0000000000..0d4e6b3f04 --- /dev/null +++ b/t/t9116-git-svn-log.sh @@ -0,0 +1,48 @@ +#!/bin/sh +# +# Copyright (c) 2007 Eric Wong +# + +test_description='git-svn log tests' +. ./lib-git-svn.sh + +test_expect_success 'setup repository and import' " + mkdir import && + cd import && + for i in trunk branches/a branches/b \ + tags/0.1 tags/0.2 tags/0.3; do + mkdir -p \$i && \ + echo hello >> \$i/README || exit 1 + done && \ + svn import -m test . $svnrepo + cd .. && + git-svn init $svnrepo -T trunk -b branches -t tags && + git-svn fetch && + git reset --hard trunk && + echo bye >> README && + git commit -a -m bye && + git svn dcommit && + git reset --hard a && + echo why >> FEEDME && + git update-index --add FEEDME && + git commit -m feedme && + git svn dcommit && + git reset --hard trunk && + echo aye >> README && + git commit -a -m aye && + git svn dcommit + " + +test_expect_success 'run log' " + git reset --hard a && + git svn log -r2 trunk | grep ^r2 && + git svn log -r4 trunk | grep ^r4 && + git svn log -r3 | grep ^r3 + " + +test_expect_success 'run log against a from trunk' " + git reset --hard trunk && + git svn log -r3 a | grep ^r3 + " + +test_done diff --git a/tree.c b/tree.c index 04fe653a8e..8c0819fa72 100644 --- a/tree.c +++ b/tree.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "cache-tree.h" #include "tree.h" #include "blob.h" #include "commit.h" @@ -7,7 +8,7 @@ const char *tree_type = "tree"; -static int read_one_entry(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage) +static int read_one_entry_opt(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage, int opt) { int len; unsigned int size; @@ -25,7 +26,23 @@ static int read_one_entry(const unsigned char *sha1, const char *base, int basel memcpy(ce->name, base, baselen); memcpy(ce->name + baselen, pathname, len+1); hashcpy(ce->sha1, sha1); - return add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); + return add_cache_entry(ce, opt); +} + +static int read_one_entry(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage) +{ + return read_one_entry_opt(sha1, base, baselen, pathname, mode, stage, + ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); +} + +/* + * This is used when the caller knows there is no existing entries at + * the stage that will conflict with the entry being added. + */ +static int read_one_entry_quick(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage) +{ + return read_one_entry_opt(sha1, base, baselen, pathname, mode, stage, + ADD_CACHE_JUST_APPEND); } static int match_tree_entry(const char *base, int baselen, const char *path, unsigned int mode, const char **paths) @@ -119,9 +136,55 @@ int read_tree_recursive(struct tree *tree, return 0; } +static int cmp_cache_name_compare(const void *a_, const void *b_) +{ + const struct cache_entry *ce1, *ce2; + + ce1 = *((const struct cache_entry **)a_); + ce2 = *((const struct cache_entry **)b_); + return cache_name_compare(ce1->name, ntohs(ce1->ce_flags), + ce2->name, ntohs(ce2->ce_flags)); +} + int read_tree(struct tree *tree, int stage, const char **match) { - return read_tree_recursive(tree, "", 0, stage, match, read_one_entry); + read_tree_fn_t fn = NULL; + int i, err; + + /* + * Currently the only existing callers of this function all + * call it with stage=1 and after making sure there is nothing + * at that stage; we could always use read_one_entry_quick(). + * + * But when we decide to straighten out git-read-tree not to + * use unpack_trees() in some cases, this will probably start + * to matter. + */ + + /* + * See if we have cache entry at the stage. If so, + * do it the original slow way, otherwise, append and then + * sort at the end. + */ + for (i = 0; !fn && i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce) == stage) + fn = read_one_entry; + } + + if (!fn) + fn = read_one_entry_quick; + err = read_tree_recursive(tree, "", 0, stage, match, fn); + if (fn == read_one_entry || err) + return err; + + /* + * Sort the cache entry -- we need to nuke the cache tree, though. + */ + cache_tree_free(&active_cache_tree); + qsort(active_cache, active_nr, sizeof(active_cache[0]), + cmp_cache_name_compare); + return 0; } struct tree *lookup_tree(const unsigned char *sha1) diff --git a/unpack-file.c b/unpack-file.c index 25c56b374a..65c66eb0bf 100644 --- a/unpack-file.c +++ b/unpack-file.c @@ -14,9 +14,7 @@ static char *create_temp_file(unsigned char *sha1) die("unable to read blob object %s", sha1_to_hex(sha1)); strcpy(path, ".merge_file_XXXXXX"); - fd = mkstemp(path); - if (fd < 0) - die("unable to create temp-file"); + fd = xmkstemp(path); if (write_in_full(fd, buf, size) != size) die("unable to write temp-file"); close(fd); diff --git a/unpack-trees.c b/unpack-trees.c index 3b32718436..ccfeb6e245 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -16,19 +16,13 @@ struct tree_entry_list { const unsigned char *sha1; }; -static struct tree_entry_list *create_tree_entry_list(struct tree *tree) +static struct tree_entry_list *create_tree_entry_list(struct tree_desc *desc) { - struct tree_desc desc; struct name_entry one; struct tree_entry_list *ret = NULL; struct tree_entry_list **list_p = &ret; - if (!tree->object.parsed) - parse_tree(tree); - - init_tree_desc(&desc, tree->buffer, tree->size); - - while (tree_entry(&desc, &one)) { + while (tree_entry(desc, &one)) { struct tree_entry_list *entry; entry = xmalloc(sizeof(struct tree_entry_list)); @@ -64,10 +58,17 @@ static int entcmp(const char *name1, int dir1, const char *name2, int dir2) return ret; } +static inline void remove_entry(int remove) +{ + if (remove >= 0) + remove_cache_entry_at(remove); +} + static int unpack_trees_rec(struct tree_entry_list **posns, int len, const char *base, struct unpack_trees_options *o, struct tree_entry_list *df_conflict_list) { + int remove; int baselen = strlen(base); int src_size = len + 1; int i_stk = i_stk; @@ -151,10 +152,11 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, subposns = xcalloc(len, sizeof(struct tree_list_entry *)); + remove = -1; if (cache_name && !strcmp(cache_name, first)) { any_files = 1; src[0] = active_cache[o->pos]; - remove_cache_entry_at(o->pos); + remove = o->pos; } for (i = 0; i < len; i++) { @@ -173,9 +175,11 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, if (S_ISDIR(posns[i]->mode)) { struct tree *tree = lookup_tree(posns[i]->sha1); + struct tree_desc t; any_dirs = 1; parse_tree(tree); - subposns[i] = create_tree_entry_list(tree); + init_tree_desc(&t, tree->buffer, tree->size); + subposns[i] = create_tree_entry_list(&t); posns[i] = posns[i]->next; src[i + o->merge] = o->df_conflict_entry; continue; @@ -218,13 +222,14 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, printf("\n"); } #endif - ret = o->fn(src, o); + ret = o->fn(src, o, remove); #if DBRT_DEBUG > 1 printf("Added %d entries\n", ret); #endif o->pos += ret; } else { + remove_entry(remove); for (i = 0; i < src_size; i++) { if (src[i]) { add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); @@ -331,12 +336,10 @@ static void check_updates(struct cache_entry **src, int nr, stop_progress(&progress);; } -int unpack_trees(struct object_list *trees, struct unpack_trees_options *o) +int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o) { - unsigned len = object_list_length(trees); struct tree_entry_list **posns; int i; - struct object_list *posn = trees; struct tree_entry_list df_conflict_list; static struct cache_entry *dfc; @@ -356,10 +359,9 @@ int unpack_trees(struct object_list *trees, struct unpack_trees_options *o) if (len) { posns = xmalloc(len * sizeof(struct tree_entry_list *)); - for (i = 0; i < len; i++) { - posns[i] = create_tree_entry_list((struct tree *) posn->item); - posn = posn->next; - } + for (i = 0; i < len; i++) + posns[i] = create_tree_entry_list(t+i); + if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "", o, &df_conflict_list)) return -1; @@ -407,6 +409,15 @@ static void verify_uptodate(struct cache_entry *ce, unsigned changed = ce_match_stat(ce, &st, 1); if (!changed) return; + /* + * NEEDSWORK: the current default policy is to allow + * submodule to be out of sync wrt the supermodule + * index. This needs to be tightened later for + * submodules that are marked to be automatically + * checked out. + */ + if (S_ISGITLINK(ntohl(ce->ce_mode))) + return; errno = 0; } if (errno == ENOENT) @@ -639,7 +650,8 @@ static void show_stage_entry(FILE *o, #endif int threeway_merge(struct cache_entry **stages, - struct unpack_trees_options *o) + struct unpack_trees_options *o, + int remove) { struct cache_entry *index; struct cache_entry *head; @@ -717,8 +729,10 @@ int threeway_merge(struct cache_entry **stages, } /* #1 */ - if (!head && !remote && any_anc_missing) + if (!head && !remote && any_anc_missing) { + remove_entry(remove); return 0; + } /* Under the new "aggressive" rule, we resolve mostly trivial * cases that we historically had git-merge-one-file resolve. @@ -750,6 +764,7 @@ int threeway_merge(struct cache_entry **stages, if ((head_deleted && remote_deleted) || (head_deleted && remote && remote_match) || (remote_deleted && head && head_match)) { + remove_entry(remove); if (index) return deleted_entry(index, index, o); else if (ce && !head_deleted) @@ -772,6 +787,7 @@ int threeway_merge(struct cache_entry **stages, verify_uptodate(index, o); } + remove_entry(remove); o->nontrivial_merge = 1; /* #2, #3, #4, #6, #7, #9, #10, #11. */ @@ -807,7 +823,8 @@ int threeway_merge(struct cache_entry **stages, * */ int twoway_merge(struct cache_entry **src, - struct unpack_trees_options *o) + struct unpack_trees_options *o, + int remove) { struct cache_entry *current = src[0]; struct cache_entry *oldtree = src[1]; @@ -835,6 +852,7 @@ int twoway_merge(struct cache_entry **src, } else if (oldtree && !newtree && same(current, oldtree)) { /* 10 or 11 */ + remove_entry(remove); return deleted_entry(oldtree, current, o); } else if (oldtree && newtree && @@ -844,6 +862,7 @@ int twoway_merge(struct cache_entry **src, } else { /* all other failures */ + remove_entry(remove); if (oldtree) reject_merge(oldtree); if (current) @@ -855,8 +874,8 @@ int twoway_merge(struct cache_entry **src, } else if (newtree) return merged_entry(newtree, current, o); - else - return deleted_entry(oldtree, current, o); + remove_entry(remove); + return deleted_entry(oldtree, current, o); } /* @@ -866,7 +885,8 @@ int twoway_merge(struct cache_entry **src, * stage0 does not have anything there. */ int bind_merge(struct cache_entry **src, - struct unpack_trees_options *o) + struct unpack_trees_options *o, + int remove) { struct cache_entry *old = src[0]; struct cache_entry *a = src[1]; @@ -889,7 +909,8 @@ int bind_merge(struct cache_entry **src, * - take the stat information from stage0, take the data from stage1 */ int oneway_merge(struct cache_entry **src, - struct unpack_trees_options *o) + struct unpack_trees_options *o, + int remove) { struct cache_entry *old = src[0]; struct cache_entry *a = src[1]; @@ -898,8 +919,10 @@ int oneway_merge(struct cache_entry **src, return error("Cannot do a oneway merge of %d trees", o->merge_size); - if (!a) + if (!a) { + remove_entry(remove); return deleted_entry(old, old, o); + } if (old && same(old, a)) { if (o->reset) { struct stat st; diff --git a/unpack-trees.h b/unpack-trees.h index fee7da4382..5517faafad 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -4,7 +4,8 @@ struct unpack_trees_options; typedef int (*merge_fn_t)(struct cache_entry **src, - struct unpack_trees_options *options); + struct unpack_trees_options *options, + int remove); struct unpack_trees_options { int reset; @@ -26,12 +27,12 @@ struct unpack_trees_options { struct cache_entry *df_conflict_entry; }; -extern int unpack_trees(struct object_list *trees, +extern int unpack_trees(unsigned n, struct tree_desc *t, struct unpack_trees_options *options); -int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o); -int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o); -int bind_merge(struct cache_entry **src, struct unpack_trees_options *o); -int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o); +int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o, int); +int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o, int); +int bind_merge(struct cache_entry **src, struct unpack_trees_options *o, int); +int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o, int); #endif