diff options
154 files changed, 3116 insertions, 805 deletions
diff --git a/Documentation/RelNotes-1.5.5.1.txt b/Documentation/RelNotes-1.5.5.1.txt new file mode 100644 index 0000000000..7de419708f --- /dev/null +++ b/Documentation/RelNotes-1.5.5.1.txt @@ -0,0 +1,44 @@ +GIT v1.5.5.1 Release Notes +========================== + +Fixes since v1.5.5 +------------------ + + * "git archive --prefix=$path/" mishandled gitattributes. + + * "git fetch -v" that fetches into FETCH_HEAD did not report the summary + the same way as done for updating the tracking refs. + + * "git svn" misbehaved when the configuration file customized the "git + log" output format using format.pretty. + + * "git submodule status" leaked an unnecessary error message. + + * "git log --date-order --topo-order" did not override the earlier + date-order with topo-order as expected. + + * "git bisect good $this" did not check the validity of the revision + given properly. + + * "url.<there>.insteadOf" did not work correctly. + + * "git clean" ran inside subdirectory behaved as if the directory was + explicitly specified for removal by the end user from the top level. + + * "git bisect" from a detached head leaked an unnecessary error message. + + * "git bisect good $a $b" when $a is Ok but $b is bogus should have + atomically failed before marking $a as good. + + * "git fmt-merge-msg" did not clean up leading empty lines from commit + log messages like "git log" family does. + + * "git am" recorded a commit with empty Subject: line without + complaining. + + * when given a commit log message whose first paragraph consists of + multiple lines, "git rebase" squashed it into a single line. + + * "git remote add $bogus_name $url" did not complain properly. + +Also comes with various documentation updates. diff --git a/Documentation/RelNotes-1.5.6.txt b/Documentation/RelNotes-1.5.6.txt new file mode 100644 index 0000000000..f3256fb82c --- /dev/null +++ b/Documentation/RelNotes-1.5.6.txt @@ -0,0 +1,59 @@ +GIT v1.5.6 Release Notes +======================== + +Updates since v1.5.5 +-------------------- + +(subsystems) + + +(portability) + + +(performance) + +* "git rebase --onto $there $from $branch" used to switch to the tip of + $branch only to immediately reset back to $from, smudging work tree + files unnecessarily. This has been optimized. + +(usability, bells and whistles) + +* "git add -p" (and the "patch" subcommand of "git add -i") can choose to + apply (or not apply) mode changes independently from contents changes. + +* "git bisect help" gives longer and more helpful usage information. + +* "git diff/log --dirstat" output is consistent between binary and textual + changes. + +* "git gc --auto" honors a new pre-aut-gc hook to temporarily disable it. + +* "git log --pretty=tformat:<custom format>" gives a LF after each entry, + instead of giving a LF between each pair of entries which is how + "git log --pretty=format:<custom format>" works. + +* "git send-email" now can send out messages outside a git repository. + +* "git status" can optionally include output from "git submodule + summary". + +* "gitweb" can read from a system-wide configuration file. + +(internal) + +* "git unpack-objects" and "git receive-pack" is now more strict about + detecting breakage in the objects they receive over the wire. + + +Fixes since v1.5.5 +------------------ + +All of the fixes in v1.5.5 maintenance series are included in +this release, unless otherwise noted. + + +-- +exec >/var/tmp/1 +O=v1.5.5-56-g5f0734f +echo O=`git describe refs/heads/master` +git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint diff --git a/Documentation/config.txt b/Documentation/config.txt index fe43b12572..a6fc5a2cfd 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -234,7 +234,13 @@ core.worktree:: used in combination with repositories found automatically in a .git directory (i.e. $GIT_DIR is not set). This can be overridden by the GIT_WORK_TREE environment - variable and the '--work-tree' command line option. + variable and the '--work-tree' command line option. It can be + a absolute path or relative path to the directory specified by + --git-dir or GIT_DIR. + Note: If --git-dir or GIT_DIR are specified but none of + --work-tree, GIT_WORK_TREE and core.worktree is specified, + the current working directory is regarded as the top directory + of your working tree. core.logAllRefUpdates:: Enable the reflog. Updates to a ref <ref> is logged to the file @@ -261,7 +267,12 @@ core.sharedRepository:: group-writable). When 'all' (or 'world' or 'everybody'), the repository will be readable by all users, additionally to being group-shareable. When 'umask' (or 'false'), git will use permissions - reported by umask(2). See linkgit:git-init[1]. False by default. + reported by umask(2). When '0xxx', where '0xxx' is an octal number, + files in the repository will have this mode value. '0xxx' will override + user's umask value, and thus, users with a safe umask (0077) can use + this option. Examples: '0660' is equivalent to 'group'. '0640' is a + repository that is group-readable but not group-writable. + See linkgit:git-init[1]. False by default. core.warnAmbiguousRefs:: If true, git will warn you if the ref name you passed it is ambiguous @@ -415,7 +426,8 @@ branch.<name>.mergeoptions:: branch.<name>.rebase:: When true, rebase the branch <name> on top of the fetched branch, - instead of merging the default branch from the default remote. + instead of merging the default branch from the default remote when + "git pull" is run. *NOTE*: this is a possibly dangerous operation; do *not* use it unless you understand the implications (see linkgit:git-rebase[1] for details). @@ -673,6 +685,36 @@ specified as 'gitcvs.<access_method>.<varname>' (where 'access_method' is one of "ext" and "pserver") to make them apply only for the given access method. +gui.commitmsgwidth:: + Defines how wide the commit message window is in the + linkgit:git-gui[1]. "75" is the default. + +gui.diffcontext:: + Specifies how many context lines should be used in calls to diff + made by the linkgit:git-gui[1]. The default is "5". + +gui.matchtrackingbranch:: + Determines if new branches created with linkgit:git-gui[1] should + default to tracking remote branches with matching names or + not. Default: "false". + +gui.newbranchtemplate:: + Is used as suggested name when creating new branches using the + linkgit:git-gui[1]. + +gui.pruneduringfetch:: + "true" if linkgit:git-gui[1] should prune tracking branches when + performing a fetch. The default value is "false". + +gui.trustmtime:: + Determines if linkgit:git-gui[1] should trust the file modification + timestamp or not. By default the timestamps are not trusted. + +gui.spellingdictionary:: + Specifies the dictionary used for spell checking commit messages in + the linkgit:git-gui[1]. When set to "none" spell checking is turned + off. + help.browser:: Specify the browser that will be used to display help in the 'web' format. See linkgit:git-help[1]. @@ -768,37 +810,16 @@ man.viewer:: Specify the programs that may be used to display help in the 'man' format. See linkgit:git-help[1]. -merge.summary:: - Whether to include summaries of merged commits in newly created - merge commit messages. False by default. - -merge.tool:: - Controls which merge resolution program is used by - linkgit:git-mergetool[1]. Valid built-in values are: "kdiff3", - "tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", and - "opendiff". Any other value is treated is custom merge tool - and there must be a corresponing mergetool.<tool>.cmd option. - -merge.verbosity:: - Controls the amount of output shown by the recursive merge - strategy. Level 0 outputs nothing except a final error - message if conflicts were detected. Level 1 outputs only - conflicts, 2 outputs conflicts and file changes. Level 5 and - above outputs debugging information. The default is level 2. - Can be overridden by 'GIT_MERGE_VERBOSITY' environment variable. - -merge.<driver>.name:: - Defines a human readable name for a custom low-level - merge driver. See linkgit:gitattributes[5] for details. - -merge.<driver>.driver:: - Defines the command that implements a custom low-level - merge driver. See linkgit:gitattributes[5] for details. - -merge.<driver>.recursive:: - Names a low-level merge driver to be used when - performing an internal merge between common ancestors. - See linkgit:gitattributes[5] for details. +include::merge-config.txt[] + +man.<tool>.cmd:: + Specify the command to invoke the specified man viewer. The + specified command is evaluated in shell with the man page + passed as argument. (See linkgit:git-help[1].) + +man.<tool>.path:: + Override the path for the given tool that may be used to + display help in the 'man' format. See linkgit:git-help[1]. mergetool.<tool>.path:: Override the path for the given tool. This is useful in case @@ -910,6 +931,10 @@ remote.<name>.push:: The default set of "refspec" for linkgit:git-push[1]. See linkgit:git-push[1]. +remote.<name>.mirror:: + If true, pushing to this remote will automatically behave + as if the `\--mirror` option was given on the command line. + remote.<name>.skipDefaultUpdate:: If true, this remote will be skipped by default when updating using the update subcommand of linkgit:git-remote[1]. diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt index aa40dfd36a..5a5531222d 100644 --- a/Documentation/core-tutorial.txt +++ b/Documentation/core-tutorial.txt @@ -535,18 +535,18 @@ with the associated patches use the more complex (and much more powerful) ---------------- -$ git-whatchanged -p --root +$ git-whatchanged -p ---------------- and you will see exactly what has changed in the repository over its short history. [NOTE] -The `\--root` flag is a flag to `git-diff-tree` to tell it to -show the initial aka 'root' commit too. Normally you'd probably not -want to see the initial import diff, but since the tutorial project -was started from scratch and is so small, we use it to make the result -a bit more interesting. +When using the above two commands, the initial commit will be shown. +If this is a problem because it is huge, you can hide it by setting +the log.showroot configuration variable to false. Having this, you +can still show it for each command just adding the `\--root` option, +which is a flag for `git-diff-tree` accepted by both commands. With that, you should now be having some inkling of what git does, and can explore on your own. diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt index ea98900228..00f2e36b2e 100644 --- a/Documentation/cvs-migration.txt +++ b/Documentation/cvs-migration.txt @@ -8,7 +8,8 @@ designating a single shared repository which people can synchronize with; this document explains how to do that. Some basic familiarity with git is required. This -link:tutorial.html[tutorial introduction to git] should be sufficient. +link:tutorial.html[tutorial introduction to git] and the +link:glossary.html[git glossary] should be sufficient. Developing against a shared repository -------------------------------------- diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 8dc5b001c4..13234fa280 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -58,6 +58,14 @@ endif::git-format-patch[] number of modified files, as well as number of added and deleted lines. +--dirstat[=limit]:: + Output only the sub-directories that are impacted by a diff, + and to what degree they are impacted. You can override the + default cut-off in percent (3) by "--dirstat=limit". If you + want to enable "cumulative" directory statistics, you can use + the "--cumulative" flag, which adds up percentages recursively + even when they have been already reported for a sub-directory. + --summary:: Output a condensed summary of extended header information such as creations, renames and mode changes. @@ -75,7 +83,8 @@ endif::git-format-patch[] Show only names of changed files. --name-status:: - Show only names and status of changed files. + Show only names and status of changed files. See the description + of the `--diff-filter` option on what the status letters mean. --color:: Show colored diff. diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt index fdbd15a181..e598cdda45 100644 --- a/Documentation/everyday.txt +++ b/Documentation/everyday.txt @@ -48,14 +48,12 @@ $ git gc <3> repository health reasonably well. <2> check how many loose objects there are and how much disk space is wasted by not repacking. -<3> repacks the local repository and performs other housekeeping tasks. Running -without `--prune` is a safe operation even while other ones are in progress. +<3> repacks the local repository and performs other housekeeping tasks. Repack a small project into single pack.:: + ------------ $ git gc <1> -$ git gc --prune ------------ + <1> pack all the objects reachable from the refs into one pack, @@ -182,7 +180,7 @@ $ git pull <3> $ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <4> $ git pull git://git.kernel.org/pub/.../jgarzik/libata-dev.git ALL <5> $ git reset --hard ORIG_HEAD <6> -$ git gc --prune <7> +$ git gc <7> $ git fetch --tags <8> ------------ + diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 35e67a06e4..e0e730b6c4 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -71,7 +71,9 @@ OPTIONS the specified filepatterns before exiting. -u:: - Update only files that git already knows about. This is similar + Update only files that git already knows about, staging modified + content for commit and marking deleted files for removal. This + is similar to what "git commit -a" does in preparation for making a commit, except that the update is limited to paths specified on the command line. If no paths are specified, all tracked files in the @@ -98,21 +100,27 @@ those in info/exclude. See link:repository-layout.html[repository layout]. EXAMPLES -------- -git-add Documentation/\\*.txt:: - Adds content from all `\*.txt` files under `Documentation` - directory and its subdirectories. +* Adds content from all `\*.txt` files under `Documentation` directory +and its subdirectories: ++ +------------ +$ git add Documentation/\\*.txt +------------ + Note that the asterisk `\*` is quoted from the shell in this example; this lets the command to include the files from subdirectories of `Documentation/` directory. -git-add git-*.sh:: - - Considers adding content from all git-*.sh scripts. - Because this example lets shell expand the asterisk - (i.e. you are listing the files explicitly), it does not - consider `subdir/git-foo.sh`. +* Considers adding content from all git-*.sh scripts: ++ +------------ +$ git add git-*.sh +------------ ++ +Because this example lets shell expand the asterisk (i.e. you are +listing the files explicitly), it does not consider +`subdir/git-foo.sh`. Interactive mode ---------------- diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index 96585ae8d9..539f37df26 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -15,6 +15,7 @@ DESCRIPTION The command takes various subcommands, and different options depending on the subcommand: + git bisect help git bisect start [<bad> [<good>...]] [--] [<paths>...] git bisect bad [<rev>] git bisect good [<rev>...] @@ -29,6 +30,12 @@ This command uses 'git-rev-list --bisect' option to help drive the binary search process to find which change introduced a bug, given an old "good" commit object name and a later "bad" commit object name. +Getting help +~~~~~~~~~~~~ + +Use "git bisect" to get a short usage description, and "git bisect +help" or "git bisect -h" to get a long usage description. + Basic bisect commands: start, bad, good ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -217,6 +224,55 @@ tree to the pristine state. Finally the "run" script can exit with the status of the real test to let "git bisect run" command loop to know the outcome. +EXAMPLES +-------- + +* Automatically bisect a broken build between v1.2 and HEAD: ++ +------------ +$ git bisect start HEAD v1.2 -- # HEAD is bad, v1.2 is good +$ git bisect run make # "make" builds the app +------------ + +* Automatically bisect a broken test suite: ++ +------------ +$ cat ~/test.sh +#!/bin/sh +make || exit 125 # this "skip"s broken builds +make test # "make test" runs the test suite +$ git bisect start v1.3 v1.1 -- # v1.3 is bad, v1.1 is good +$ git bisect run ~/test.sh +------------ ++ +Here we use a "test.sh" custom script. In this script, if "make" +fails, we "skip" the current commit. ++ +It's safer to use a custom script outside the repo to prevent +interactions between the bisect, make and test processes and the +script. ++ +And "make test" should "exit 0", if the test suite passes, and +"exit 1" (for example) otherwise. + +* Automatically bisect a broken test case: ++ +------------ +$ cat ~/test.sh +#!/bin/sh +make || exit 125 # this "skip"s broken builds +~/check_test_case.sh # does the test case passes ? +$ git bisect start HEAD HEAD~10 -- # culprit is among the last 10 +$ git bisect run ~/test.sh +------------ ++ +Here "check_test_case.sh" should "exit 0", if the test case passes, +and "exit 1" (for example) otherwise. ++ +It's safer if both "test.sh" and "check_test_case.sh" scripts are +outside the repo to prevent interactions between the bisect, make and +test processes and the scripts. + Author ------ Written by Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 6f07a17a2c..c824d88742 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -8,7 +8,7 @@ git-branch - List, create, or delete branches SYNOPSIS -------- [verse] -'git-branch' [--color | --no-color] [-r | -a] +'git-branch' [--color | --no-color] [-r | -a] [--merged | --no-merged] [-v [--abbrev=<length> | --no-abbrev]] [--contains <commit>] 'git-branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>] @@ -24,6 +24,8 @@ and option `-a` shows both. With `--contains <commit>`, shows only the branches that contains the named commit (in other words, the branches whose tip commits are descendant of the named commit). +With `--merged`, only branches merged into HEAD will be listed, and +with `--no-merged` only branches not merged into HEAD will be listed. In its second form, a new branch named <branchname> will be created. It will start out with a head equal to the one given as <start-point>. @@ -118,6 +120,15 @@ OPTIONS --no-track:: Ignore the branch.autosetupmerge configuration variable. +--contains <commit>:: + Only list branches which contain the specified commit. + +--merged:: + Only list branches which are fully contained by HEAD. + +--no-merged:: + Do not list branches which are fully contained by HEAD. + <branchname>:: The name of the branch to create or delete. The new branch name must pass all checks defined by @@ -175,6 +186,18 @@ If you are creating a branch that you want to immediately checkout, it's easier to use the git checkout command with its `-b` option to create a branch and check it out with a single command. +The options `--contains`, `--merged` and `--no-merged` serves three related +but different purposes: + +- `--contains <commit>` is used to find all branches which will need + special attention if <commit> were to be rebased or amended, since those + branches contain the specified <commit>. + +- `--merged` is used to find all branches which can be safely deleted, + since those branches are fully contained by HEAD. + +- `--no-merged` is used to find branches which are candidates for merging + into HEAD, since those branches are not fully contained by HEAD. Author ------ diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index e11cddbfc9..a644173e15 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -47,7 +47,7 @@ OPTIONS by linkgit:git-check-ref-format[1]. Some of these checks may restrict the characters allowed in a branch name. ---track:: +-t, --track:: When creating a new branch, set up configuration so that git-pull will automatically retrieve data from the start point, which must be a branch. Use this if you always pull from the same upstream branch diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index f0beb412e6..ca048f46f6 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -7,7 +7,7 @@ git-cherry-pick - Apply the change introduced by an existing commit SYNOPSIS -------- -'git-cherry-pick' [--edit] [-n] [-m parent-number] [-x] <commit> +'git-cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] <commit> DESCRIPTION ----------- @@ -64,6 +64,9 @@ OPTIONS This is useful when cherry-picking more than one commits' effect to your working tree in a row. +-s|--signoff:: + Add Signed-off-by line at the end of the commit message. + Author ------ diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 975824301a..9b564420c5 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -65,10 +65,13 @@ OPTIONS + *NOTE*: this is a possibly dangerous operation; do *not* use it unless you understand what it does. If you clone your -repository using this option, then delete branches in the -source repository and then run linkgit:git-gc[1] using the -'--prune' option in the source repository, it may remove -objects which are referenced by the cloned repository. +repository using this option and then delete branches (or use any +other git command that makes any existing commit unreferenced) in the +source repository, some objects may become unreferenced (or dangling). +These objects may be removed by normal git operations (such as git-commit[1]) +which automatically call git-gc[1]. If these objects are removed and +were referenced by the cloned repository, then the cloned repository +will become corrupt. @@ -79,6 +82,8 @@ objects which are referenced by the cloned repository. an already existing repository as an alternate will require fewer objects to be copied from the repository being cloned, reducing network and local storage costs. ++ +*NOTE*: see NOTE to --shared option. --quiet:: -q:: diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index b4ae61ff46..4bb51cc06e 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -139,6 +139,17 @@ but can be used to amend a merge commit. as well. This is usually not what you want unless you are concluding a conflicted merge. +-o|--only:: + Make a commit only from the paths specified on the + command line, disregarding any contents that have been + staged so far. This is the default mode of operation of + 'git commit' if any paths are given on the command line, + in which case this option can be omitted. + If this option is specified together with '--amend', then + no paths need be specified, which can be used to amend + the last commit without committing changes that have + already been staged. + -u|--untracked-files:: Show all untracked files, also those in uninteresting directories, in the "Untracked files:" section of commit diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index fa161718dd..5de5d051b7 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -144,6 +144,8 @@ See also <<FILES>>. "auto". If `stdout-is-tty` is missing, then checks the standard output of the command itself, and exits with status 0 if color is to be used, or exits with status 1 otherwise. + When the color setting for `name` is undefined, the command uses + `color.ui` as fallback. --get-color name default:: diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index 9cec8021b8..b1106714b2 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -110,7 +110,9 @@ cvs -d ":ext;CVS_SERVER=git-cvsserver:user@server/path/repo.git" co <HEAD_name> ------ This has the advantage that it will be saved in your 'CVS/Root' files and you don't need to worry about always setting the correct environment -variable. +variable. SSH users restricted to git-shell don't need to override the default +with CVS_SERVER (and shouldn't) as git-shell understands `cvs` to mean +git-cvsserver and pretends that the other end runs the real cvs better. -- 2. For each repo that you want accessible from CVS you need to edit config in the repo and add the following section. @@ -141,25 +143,29 @@ allowing access over SSH. enabled=1 ------ -- -3. On the client machine you need to set the following variables. - CVSROOT should be set as per normal, but the directory should point at the - appropriate git repo. For example: +3. If you didn't specify the CVSROOT/CVS_SERVER directly in the checkout command, + automatically saving it in your 'CVS/Root' files, then you need to set them + explicitly in your environment. CVSROOT should be set as per normal, but the + directory should point at the appropriate git repo. As above, for SSH clients + _not_ restricted to git-shell, CVS_SERVER should be set to git-cvsserver. + -- -For SSH access, CVS_SERVER should be set to git-cvsserver - -Example: - ------ export CVSROOT=:ext:user@server:/var/git/project.git export CVS_SERVER=git-cvsserver ------ -- -4. For SSH clients that will make commits, make sure their .bashrc file - sets the GIT_AUTHOR and GIT_COMMITTER variables. +4. For SSH clients that will make commits, make sure their server-side + .ssh/environment files (or .bashrc, etc., according to their specific shell) + export appropriate values for GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, + GIT_COMMITTER_NAME, and GIT_COMMITTER_EMAIL. For SSH clients whose login + shell is bash, .bashrc may be a reasonable alternative. 5. Clients should now be able to check out the project. Use the CVS 'module' - name to indicate what GIT 'head' you want to check out. Example: + name to indicate what GIT 'head' you want to check out. This also sets the + name of your newly checked-out directory, unless you tell it otherwise with + `-d <dir_name>`. For example, this checks out 'master' branch to the + `project-master` directory: + ------ cvs co -d project-master master diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index 2a78549be5..8d80f0d074 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -133,10 +133,16 @@ use "--tag-name-filter cat" to simply update the tags. In this case, be very careful and make sure you have the old tags backed up in case the conversion has run afoul. + -Note that there is currently no support for proper rewriting of -tag objects; in layman terms, if the tag has a message or signature -attached, the rewritten tag won't have it. Sorry. (It is by -definition impossible to preserve signatures at any rate.) +Nearly proper rewriting of tag objects is supported. If the tag has +a message attached, a new tag object will be created with the same message, +author, and timestamp. If the tag has a signature attached, the +signature will be stripped. It is by definition impossible to preserve +signatures. The reason this is "nearly" proper, is because ideally if +the tag did not change (points to the same object, has the same name, etc.) +it should retain any signature. That is not the case, signatures will always +be removed, buyer beware. There is also no support for changing the +author or timestamp (or the tag message for that matter). Tags which point +to other tags will be rewritten to point to the underlying commit. --subdirectory-filter <directory>:: Only look at the history which touches the given subdirectory. @@ -243,12 +249,12 @@ committed a merge between P1 and P2, it will be propagated properly and all children of the merge will become merge commits with P1,P2 as their parents instead of the merge commit. -You can rewrite the commit log messages using `--message-filter`. For +You can rewrite the commit log messages using `--msg-filter`. For example, `git-svn-id` strings in a repository created by `git-svn` can be removed this way: ------------------------------------------------------- -git filter-branch --message-filter ' +git filter-branch --msg-filter ' sed -e "/^git-svn-id:/d" ' ------------------------------------------------------- diff --git a/Documentation/git-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt index 8615ae353e..457cf42561 100644 --- a/Documentation/git-fmt-merge-msg.txt +++ b/Documentation/git-fmt-merge-msg.txt @@ -9,8 +9,8 @@ git-fmt-merge-msg - Produce a merge commit message SYNOPSIS -------- [verse] -git-fmt-merge-msg [--summary | --no-summary] <$GIT_DIR/FETCH_HEAD -git-fmt-merge-msg [--summary | --no-summary] -F <file> +git-fmt-merge-msg [--log | --no-log] <$GIT_DIR/FETCH_HEAD +git-fmt-merge-msg [--log | --no-log] -F <file> DESCRIPTION ----------- @@ -24,15 +24,19 @@ automatically invoking `git-merge`. OPTIONS ------- ---summary:: +--log:: In addition to branch names, populate the log message with one-line descriptions from the actual commits that are being merged. ---no-summary:: +--no-log:: Do not list one-line descriptions from the actual commits being merged. +--summary,--no-summary:: + Synonyms to --log and --no-log; these are deprecated and will be + removed in the future. + --file <file>, -F <file>:: Take the list of merged objects from <file> instead of stdin. @@ -40,10 +44,14 @@ OPTIONS CONFIGURATION ------------- -merge.summary:: +merge.log:: Whether to include summaries of merged commits in newly merge commit messages. False by default. +merge.summary:: + Synonym to `merge.log`; this is deprecated and will be removed in + the future. + SEE ALSO -------- linkgit:git-merge[1] diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index b5207b7604..87e491b59e 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -174,32 +174,47 @@ and file suffix, and number patches when outputting more than one. EXAMPLES -------- -git-format-patch -k --stdout R1..R2 | git-am -3 -k:: - Extract commits between revisions R1 and R2, and apply - them on top of the current branch using `git-am` to - cherry-pick them. - -git-format-patch origin:: - Extract all commits which are in the current branch but - not in the origin branch. For each commit a separate file - is created in the current directory. - -git-format-patch \--root origin:: - Extract all commits that lead to 'origin' since the - inception of the project. - -git-format-patch -M -B origin:: - The same as the previous one. Additionally, it detects - and handles renames and complete rewrites intelligently to - produce a renaming patch. A renaming patch reduces the - amount of text output, and generally makes it easier to - review it. Note that the "patch" program does not - understand renaming patches, so use it only when you know - the recipient uses git to apply your patch. - -git-format-patch -3:: - Extract three topmost commits from the current branch - and format them as e-mailable patches. +* Extract commits between revisions R1 and R2, and apply them on top of +the current branch using `git-am` to cherry-pick them: ++ +------------ +$ git format-patch -k --stdout R1..R2 | git-am -3 -k +------------ + +* Extract all commits which are in the current branch but not in the +origin branch: ++ +------------ +$ git format-patch origin +------------ ++ +For each commit a separate file is created in the current directory. + +* Extract all commits that lead to 'origin' since the inception of the +project: ++ +------------ +$ git format-patch \--root origin +------------ + +* The same as the previous one: ++ +------------ +$ git format-patch -M -B origin +------------ ++ +Additionally, it detects and handles renames and complete rewrites +intelligently to produce a renaming patch. A renaming patch reduces +the amount of text output, and generally makes it easier to review it. +Note that the "patch" program does not understand renaming patches, so +use it only when you know the recipient uses git to apply your patch. + +* Extract three topmost commits from the current branch and format them +as e-mailable patches: ++ +------------ +$ git format-patch -3 +------------ See Also -------- diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index f16cb98612..4cc26fb744 100644 --- a/Documentation/git-fsck.txt +++ b/Documentation/git-fsck.txt @@ -22,7 +22,8 @@ OPTIONS An object to treat as the head of an unreachability trace. + If no objects are given, git-fsck defaults to using the -index file and all SHA1 references in .git/refs/* as heads. +index file, all SHA1 references in .git/refs/*, and all reflogs (unless +--no-reflogs is given) as heads. --unreachable:: Print out objects that exist but that aren't readable from any diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index d424a4ecbe..b6b5ce1519 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -104,6 +104,21 @@ The optional configuration variable 'gc.pruneExpire' controls how old the unreferenced loose objects have to be before they are pruned. The default is "2 weeks ago". + +Notes +----- + +git-gc tries very hard to be safe about the garbage it collects. In +particular, it will keep not only objects referenced by your current set +of branches and tags, but also objects referenced by the index, remote +tracking branches, refs saved by linkgit:git-filter-branch[1] in +refs/original/, or reflogs (which may references commits in branches +that were later amended or rewound). + +If you are expecting some objects to be collected and they aren't, check +all of those locations and decide whether it makes sense in your case to +remove those references. + See Also -------- linkgit:git-prune[1] diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt index be2ae53b90..bfbba9e235 100644 --- a/Documentation/git-help.txt +++ b/Documentation/git-help.txt @@ -82,28 +82,75 @@ man.viewer ~~~~~~~~~~ The 'man.viewer' config variable will be checked if the 'man' format -is chosen. Only the following values are currently supported: +is chosen. The following values are currently supported: * "man": use the 'man' program as usual, * "woman": use 'emacsclient' to launch the "woman" mode in emacs (this only works starting with emacsclient versions 22), -* "konqueror": use a man KIO slave in konqueror. +* "konqueror": use 'kfmclient' to open the man page in a new konqueror +tab (see 'Note about konqueror' below). -Multiple values may be given to this configuration variable. Their -corresponding programs will be tried in the order listed in the -configuration file. +Values for other tools can be used if there is a corresponding +'man.<tool>.cmd' configuration entry (see below). + +Multiple values may be given to the 'man.viewer' configuration +variable. Their corresponding programs will be tried in the order +listed in the configuration file. For example, this configuration: +------------------------------------------------ [man] viewer = konqueror viewer = woman +------------------------------------------------ will try to use konqueror first. But this may fail (for example if DISPLAY is not set) and in that case emacs' woman mode will be tried. If everything fails the 'man' program will be tried anyway. +man.<tool>.path +~~~~~~~~~~~~~~~ + +You can explicitly provide a full path to your preferred man viewer by +setting the configuration variable 'man.<tool>.path'. For example, you +can configure the absolute path to konqueror by setting +'man.konqueror.path'. Otherwise, 'git help' assumes the tool is +available in PATH. + +man.<tool>.cmd +~~~~~~~~~~~~~~ + +When the man viewer, specified by the 'man.viewer' configuration +variables, is not among the supported ones, then the corresponding +'man.<tool>.cmd' configuration variable will be looked up. If this +variable exists then the specified tool will be treated as a custom +command and a shell eval will be used to run the command with the man +page passed as arguments. + +Note about konqueror +~~~~~~~~~~~~~~~~~~~~ + +When 'konqueror' is specified in the 'man.viewer' configuration +variable, we launch 'kfmclient' to try to open the man page on an +already opened konqueror in a new tab if possible. + +For consistency, we also try such a trick if 'man.konqueror.path' is +set to something like 'A_PATH_TO/konqueror'. That means we will try to +launch 'A_PATH_TO/kfmclient' instead. + +If you really want to use 'konqueror', then you can use something like +the following: + +------------------------------------------------ + [man] + viewer = konq + + [man "konq"] + cmd = A_PATH_TO/konqueror +------------------------------------------------ + Note about git config --global ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt index 62914da97b..b17ae8485c 100644 --- a/Documentation/git-init.txt +++ b/Documentation/git-init.txt @@ -31,7 +31,7 @@ structure, some suggested "exclude patterns", and copies of non-executing "hook" files. The suggested patterns and hook files are all modifiable and extensible. ---shared[={false|true|umask|group|all|world|everybody}]:: +--shared[={false|true|umask|group|all|world|everybody|0xxx}]:: Specify that the git repository is to be shared amongst several users. This allows users belonging to the same group to push into that @@ -52,6 +52,12 @@ is given: - 'all' (or 'world' or 'everybody'): Same as 'group', but make the repository readable by all users. + - '0xxx': '0xxx' is an octal number and each file will have mode '0xxx' + Any option except 'umask' can be set using this option. '0xxx' will + override users umask(2) value, and thus, users with a safe umask (0077) + can use this option. '0640' will create a repository which is group-readable + but not writable. '0660' is equivalent to 'group'. + By default, the configuration flag receive.denyNonFastForwards is enabled in shared repositories, so that you cannot force a non fast-forwarding push into it. diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index c136b10692..ef1f055c85 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -9,7 +9,7 @@ git-merge - Join two or more development histories together SYNOPSIS -------- [verse] -'git-merge' [-n] [--summary] [--no-commit] [--squash] [-s <strategy>]... +'git-merge' [-n] [--stat] [--no-commit] [--squash] [-s <strategy>]... [-m <msg>] <remote> <remote>... 'git-merge' <msg> HEAD <remote>... @@ -46,18 +46,7 @@ linkgit:git-reset[1]. CONFIGURATION ------------- - -merge.summary:: - Whether to include summaries of merged commits in newly - created merge commit. False by default. - -merge.verbosity:: - Controls the amount of output shown by the recursive merge - strategy. Level 0 outputs nothing except a final error - message if conflicts were detected. Level 1 outputs only - conflicts, 2 outputs conflicts and file changes. Level 5 and - above outputs debugging information. The default is level 2. - Can be overridden by 'GIT_MERGE_VERBOSITY' environment variable. +include::merge-config.txt[] branch.<name>.mergeoptions:: Sets default options for merging into branch <name>. The syntax and diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt index f151cff5d9..f92bb8cfa0 100644 --- a/Documentation/git-prune.txt +++ b/Documentation/git-prune.txt @@ -13,6 +13,9 @@ SYNOPSIS DESCRIPTION ----------- +NOTE: In most cases, users should run linkgit:git-gc[1], which calls +git-prune. See the section "NOTES", below. + This runs `git-fsck --unreachable` using all the refs available in `$GIT_DIR/refs`, optionally with additional set of objects specified on the command line, and prunes all @@ -50,6 +53,23 @@ borrows from your repository via its $ git prune $(cd ../another && $(git-rev-parse --all)) ------------ +Notes +----- + +In most cases, users will not need to call git-prune directly, but +should instead call linkgit:git-gc[1], which handles pruning along with +many other housekeeping tasks. + +For a description of which objects are considered for pruning, see +git-fsck's --unreachable option. + +See Also +-------- + +linkgit:git-fsck[1], +linkgit:git-gc[1], +linkgit:git-reflog[1] + Author ------ Written by Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 3405ca09e8..66304f0255 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -111,40 +111,58 @@ rules apply: EXAMPLES -------- -git pull, git pull origin:: - Update the remote-tracking branches for the repository - you cloned from, then merge one of them into your - current branch. Normally the branch merged in is - the HEAD of the remote repository, but the choice is - determined by the branch.<name>.remote and - branch.<name>.merge options; see linkgit:git-config[1] - for details. - -git pull origin next:: - Merge into the current branch the remote branch `next`; - leaves a copy of `next` temporarily in FETCH_HEAD, but - does not update any remote-tracking branches. - -git pull . fixes enhancements:: - Bundle local branch `fixes` and `enhancements` on top of - the current branch, making an Octopus merge. This `git pull .` - syntax is equivalent to `git merge`. - -git pull -s ours . obsolete:: - Merge local branch `obsolete` into the current branch, - using `ours` merge strategy. - -git pull --no-commit . maint:: - Merge local branch `maint` into the current branch, but - do not make a commit automatically. This can be used - when you want to include further changes to the merge, - or want to write your own merge commit message. +* Update the remote-tracking branches for the repository + you cloned from, then merge one of them into your + current branch: ++ +------------------------------------------------ +$ git pull, git pull origin +------------------------------------------------ ++ +Normally the branch merged in is the HEAD of the remote repository, +but the choice is determined by the branch.<name>.remote and +branch.<name>.merge options; see linkgit:git-config[1] for details. + +* Merge into the current branch the remote branch `next`: ++ +------------------------------------------------ +$ git pull origin next +------------------------------------------------ ++ +This leaves a copy of `next` temporarily in FETCH_HEAD, but +does not update any remote-tracking branches. + +* Bundle local branch `fixes` and `enhancements` on top of + the current branch, making an Octopus merge: ++ +------------------------------------------------ +$ git pull . fixes enhancements +------------------------------------------------ ++ +This `git pull .` syntax is equivalent to `git merge`. + +* Merge local branch `obsolete` into the current branch, using `ours` + merge strategy: ++ +------------------------------------------------ +$ git pull -s ours . obsolete +------------------------------------------------ + +* Merge local branch `maint` into the current branch, but do not make + a commit automatically: ++ +------------------------------------------------ +$ git pull --no-commit . maint +------------------------------------------------ ++ +This can be used when you want to include further changes to the +merge, or want to write your own merge commit message. + You should refrain from abusing this option to sneak substantial changes into a merge commit. Small fixups like bumping release/version name would be acceptable. -Command line pull of multiple branches from one repository:: +* Command line pull of multiple branches from one repository: + ------------------------------------------------ $ git checkout master @@ -152,12 +170,12 @@ $ git fetch origin +pu:pu maint:tmp $ git pull . tmp ------------------------------------------------ + -This updates (or creates, as necessary) branches `pu` and `tmp` -in the local repository by fetching from the branches -(respectively) `pu` and `maint` from the remote repository. +This updates (or creates, as necessary) branches `pu` and `tmp` in +the local repository by fetching from the branches (respectively) +`pu` and `maint` from the remote repository. + -The `pu` branch will be updated even if it is does not -fast-forward; the others will not be. +The `pu` branch will be updated even if it is does not fast-forward; +the others will not be. + The final command then merges the newly fetched `tmp` into master. diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 3128170bcd..f06d94e318 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -35,14 +35,15 @@ OPTIONS by the source ref, followed by a colon `:`, followed by the destination ref. + -The <src> side can be an -arbitrary "SHA1 expression" that can be used as an -argument to `git-cat-file -t`. E.g. `master~4` (push -four parents before the current master head). +The <src> side represents the source branch (or arbitrary +"SHA1 expression", such as `master~4` (four parents before the +tip of `master` branch); see linkgit:git-rev-parse[1]) that you +want to push. The <dst> side represents the destination location. + The local ref that matches <src> is used -to fast forward the remote ref that matches <dst>. If -the optional plus `+` is used, the remote ref is updated +to fast forward the remote ref that matches <dst> (or, if no <dst> was +specified, the same ref that <src> referred to locally). If +the optional leading plus `+` is used, the remote ref is updated even if it does not result in a fast forward update. + Note: If no explicit refspec is found, (that is neither @@ -69,7 +70,9 @@ the remote repository. be mirrored to the remote repository. Newly created local refs will be pushed to the remote end, locally updated refs will be force updated on the remote end, and deleted refs - will be removed from the remote end. + will be removed from the remote end. This is the default + if the configuration option `remote.<remote>.mirror` is + set. \--dry-run:: Do everything except actually send the updates. @@ -165,7 +168,8 @@ git push origin master:: Find a ref that matches `master` in the source repository (most likely, it would find `refs/heads/master`), and update the same ref (e.g. `refs/heads/master`) in `origin` repository - with it. + with it. If `master` did not exist remotely, it would be + created. git push origin :experimental:: Find a ref that matches `experimental` in the `origin` repository @@ -179,9 +183,10 @@ git push origin master:satellite/master:: git push origin master:refs/heads/experimental:: Create the branch `experimental` in the `origin` repository - by copying the current `master` branch. This form is usually - needed to create a new branch in the remote repository as - there is no `experimental` branch to match. + by copying the current `master` branch. This form is only + needed to create a new branch or tag in the remote repository when + the local name and the remote name are different; otherwise, + the ref name on its own will work. Author ------ diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 2cbd1f764b..b20e851973 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -47,9 +47,11 @@ With `-m <master>` option, `$GIT_DIR/remotes/<name>/HEAD` is set up to point at remote's `<master>` branch instead of whatever branch the `HEAD` at the remote repository actually points at. + -In mirror mode, enabled with `--mirror`, the refs will not be stored +In mirror mode, enabled with `\--mirror`, the refs will not be stored in the 'refs/remotes/' namespace, but in 'refs/heads/'. This option -only makes sense in bare repositories. +only makes sense in bare repositories. If a remote uses mirror +mode, furthermore, `git push` will always behave as if `\--mirror` +was passed. 'rm':: diff --git a/Documentation/git-request-pull.txt b/Documentation/git-request-pull.txt index 270df9b185..9a14c04e39 100644 --- a/Documentation/git-request-pull.txt +++ b/Documentation/git-request-pull.txt @@ -24,7 +24,7 @@ OPTIONS URL to include in the summary. <end>:: - Commit to send at; defaults to HEAD. + Commit to end at; defaults to HEAD. Author ------ diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 6513c2efe1..110e7ba71f 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -52,6 +52,11 @@ OPTIONS The parameter given must be usable as a single, valid object name. Otherwise barf and abort. +-q, --quiet:: + Only meaningful in `--verify` mode. Do not output an error + message if the first argument is not a valid object name; + instead exit with non-zero status silently. + --sq:: Usually the output is made one line per flag and parameter. This option makes output a single line, diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index 93e20f7752..13ceabbcc8 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -7,7 +7,7 @@ git-revert - Revert an existing commit SYNOPSIS -------- -'git-revert' [--edit | --no-edit] [-n] [-m parent-number] <commit> +'git-revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit> DESCRIPTION ----------- @@ -51,6 +51,9 @@ OPTIONS This is useful when reverting more than one commits' effect to your working tree in a row. +-s|--signoff:: + Add Signed-off-by line at the end of the commit message. + Author ------ diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt index dc36c662ae..9c81b72dbe 100644 --- a/Documentation/git-rm.txt +++ b/Documentation/git-rm.txt @@ -11,28 +11,37 @@ SYNOPSIS DESCRIPTION ----------- -Remove files from the working tree and from the index. The -files have to be identical to the tip of the branch, and no -updates to its contents must have been placed in the staging -area (aka index). When --cached is given, the staged content has to -match either the tip of the branch *or* the file on disk. +Remove files from the index, or from the working tree and the index. +`git rm` will not remove a file from just your working directory. +(There is no option to remove a file only from the work tree +and yet keep it in the index; use `/bin/rm` if you want to do that.) +The files being removed have to be identical to the tip of the branch, +and no updates to their contents can be staged in the index, +though that default behavior can be overridden with the `-f` option. +When '--cached' is given, the staged content has to +match either the tip of the branch or the file on disk, +allowing the file to be removed from just the index. OPTIONS ------- <file>...:: Files to remove. Fileglobs (e.g. `*.c`) can be given to - remove all matching files. Also a leading directory name - (e.g. `dir` to add `dir/file1` and `dir/file2`) can be - given to remove all files in the directory, recursively, - but this requires `-r` option to be given for safety. + remove all matching files. If you want git to expand + file glob characters, you may need to shell-escape them. + A leading directory name + (e.g. `dir` to remove `dir/file1` and `dir/file2`) can be + given to remove all files in the directory, and recursively + all sub-directories, + but this requires the `-r` option to be explicitly given. -f:: Override the up-to-date check. -n, \--dry-run:: - Don't actually remove the file(s), just show if they exist in - the index. + Don't actually remove any file(s). Instead, just show + if they exist in the index and would otherwise be removed + by the command. -r:: Allow recursive removal when a leading directory name is @@ -44,9 +53,9 @@ OPTIONS for command-line options). \--cached:: - This option can be used to tell the command to remove - the paths only from the index, leaving working tree - files. + Use this option to unstage and remove paths only from the index. + Working tree files, whether modified or not, will be + left alone. \--ignore-unmatch:: Exit with a zero status even if no files matched. @@ -59,11 +68,15 @@ OPTIONS DISCUSSION ---------- -The list of <file> given to the command can be exact pathnames, -file glob patterns, or leading directory name. The command -removes only the paths that is known to git. Giving the name of +The <file> list given to the command can be exact pathnames, +file glob patterns, or leading directory names. The command +removes only the paths that are known to git. Giving the name of a file that you have not told git about does not remove that file. +File globbing matches across directory boundaries. Thus, given +two directories `d` and `d2`, there is a difference between +using `git rm \'d\*\'` and `git rm \'d/\*\'`, as the former will +also remove all of directory `d2`. EXAMPLES -------- @@ -72,11 +85,10 @@ git-rm Documentation/\\*.txt:: `Documentation` directory and any of its subdirectories. + Note that the asterisk `\*` is quoted from the shell in this -example; this lets the command include the files from -subdirectories of `Documentation/` directory. +example; this lets git, and not the shell, expand the pathnames +of files and subdirectories under the `Documentation/` directory. git-rm -f git-*.sh:: - Remove all git-*.sh scripts that are in the index. Because this example lets the shell expand the asterisk (i.e. you are listing the files explicitly), it does not remove `subdir/git-foo.sh`. diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index c7752575d8..d7cb4c0468 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -8,8 +8,8 @@ git-shortlog - Summarize 'git log' output SYNOPSIS -------- [verse] -git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s] [-e] -git-shortlog [-n|--numbered] [-s|--summary] [-e|--email] [<committish>...] +git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s] [-e] [-w] +git-shortlog [-n|--numbered] [-s|--summary] [-e|--email] [-w[<width>[,<indent1>[,<indent2>]]]] [<committish>...] DESCRIPTION ----------- @@ -35,6 +35,12 @@ OPTIONS -e, \--email:: Show the email address of each author. +-w[<width>[,<indent1>[,<indent2>]]]:: + Linewrap the output by wrapping each line at `width`. The first + line of each entry is indented by `indent1` spaces, and the second + and subsequent lines are indented by `indent2` spaces. `width`, + `indent1`, and `indent2` default to 76, 6 and 9 respectively. + FILES ----- diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 3ea269aa7a..ea4376a17f 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -52,6 +52,11 @@ If the config variable `status.relativePaths` is set to false, then all paths shown are relative to the repository root, not to the current directory. +If `status.submodulesummary` is set to a non zero number or true (identical +to -1 or an unlimited number), the submodule summary will be enabled and a +summary of commits for modified submodules will be shown (see --summary-limit +option of linkgit:git-submodule[1]). + See Also -------- linkgit:gitignore[5] diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 41f9f63566..6ffd896fbc 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -70,7 +70,7 @@ OPTIONS -n, --summary-limit:: This option is only valid for the summary command. Limit the summary size (number of commits shown in total). - Giving 0 will disable the summary; a negative number means unlimted + Giving 0 will disable the summary; a negative number means unlimited (the default). This limit only applies to modified submodules. The size is always limited to 1 for added/deleted/typechanged submodules. diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index bec9accc89..f4ba1056f0 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -188,6 +188,12 @@ All arguments are passed directly to `git blame'. commit. All merging is assumed to have taken place independently of git-svn functions. +'create-ignore':: + + Recursively finds the svn:ignore property on directories and + creates matching .gitignore files. The resulting files are staged to + be committed, but are not committed. + 'show-ignore':: Recursively finds and lists the svn:ignore property on directories. The output is suitable for appending to diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index c22fb71176..9712392f79 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git-tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <name> [<head>] 'git-tag' -d <name>... -'git-tag' [-n [<num>]] -l [<pattern>] +'git-tag' [-n[<num>]] -l [<pattern>] 'git-tag' -v <name>... DESCRIPTION @@ -57,7 +57,7 @@ OPTIONS -v:: Verify the gpg signature of the given tag names. --n <num>:: +-n<num>:: <num> specifies how many lines from the annotation, if any, are printed when using -l. The default is not to print any annotation lines. @@ -233,14 +233,14 @@ the tag object affects, for example, the ordering of tags in the gitweb interface. To set the date used in future tag objects, set the environment -variable GIT_AUTHOR_DATE to one or more of the date and time. The +variable GIT_COMMITTER_DATE to one or more of the date and time. The date and time can be specified in a number of ways; the most common is "YYYY-MM-DD HH:MM". An example follows. ------------ -$ GIT_AUTHOR_DATE="2006-10-02 10:31" git tag -s v1.0.1 +$ GIT_COMMITTER_DATE="2006-10-02 10:31" git tag -s v1.0.1 ------------ diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt index 3697896a06..50947c50df 100644 --- a/Documentation/git-unpack-objects.txt +++ b/Documentation/git-unpack-objects.txt @@ -8,7 +8,7 @@ git-unpack-objects - Unpack objects from a packed archive SYNOPSIS -------- -'git-unpack-objects' [-n] [-q] [-r] <pack-file +'git-unpack-objects' [-n] [-q] [-r] [--strict] <pack-file DESCRIPTION diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt index ddbae5b194..92ef574565 100644 --- a/Documentation/git-web--browse.txt +++ b/Documentation/git-web--browse.txt @@ -20,7 +20,7 @@ The following browsers (or commands) are currently supported: * firefox (this is the default under X Window when not using KDE) * iceweasel -* konqueror (this is the default under KDE) +* konqueror (this is the default under KDE, see 'Note about konqueror' below) * w3m (this is the default outside graphical environments) * links * lynx @@ -71,6 +71,28 @@ variable exists then "git web--browse" will treat the specified tool as a custom command and will use a shell eval to run the command with the URLs passed as arguments. +Note about konqueror +-------------------- + +When 'konqueror' is specified by the a command line option or a +configuration variable, we launch 'kfmclient' to try to open the HTML +man page on an already opened konqueror in a new tab if possible. + +For consistency, we also try such a trick if 'brower.konqueror.path' is +set to something like 'A_PATH_TO/konqueror'. That means we will try to +launch 'A_PATH_TO/kfmclient' instead. + +If you really want to use 'konqueror', then you can use something like +the following: + +------------------------------------------------ + [web] + browser = konq + + [browser "konq"] + cmd = A_PATH_TO/konqueror +------------------------------------------------ + Note about git config --global ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/git.txt b/Documentation/git.txt index 336fe99cc7..6f445b1e3b 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -46,8 +46,11 @@ Documentation for older releases are available here: * link:v1.5.5/git.html[documentation for release 1.5.5] * release notes for + link:RelNotes-1.5.5.1.txt[1.5.5.1], link:RelNotes-1.5.5.txt[1.5.5]. +* link:v1.5.5.1/git.html[documentation for release 1.5.5.1] + * link:v1.5.4.5/git.html[documentation for release 1.5.4.5] * release notes for @@ -140,7 +143,8 @@ help ...'. --git-dir=<path>:: Set the path to the repository. This can also be controlled by - setting the GIT_DIR environment variable. + setting the GIT_DIR environment variable. It can be an absolute + path or relative path to current working directory. --work-tree=<path>:: Set the path to the working tree. The value will not be @@ -148,7 +152,12 @@ help ...'. a .git directory (i.e. $GIT_DIR is not set). This can also be controlled by setting the GIT_WORK_TREE environment variable and the core.worktree configuration - variable. + variable. It can be an absolute path or relative path to + the directory specified by --git-dir or GIT_DIR. + Note: If --git-dir or GIT_DIR are specified but none of + --work-tree, GIT_WORK_TREE and core.worktree is specified, + the current working directory is regarded as the top directory + of your working tree. --bare:: Treat the repository as a bare repository. If GIT_DIR diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index 29edafceda..50d12da89f 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -41,6 +41,12 @@ frequently used options. Show all branches. +--merge:: + + After an attempt to merge stops with conflicts, show the commits on + the history between two branches (i.e. the HEAD and the MERGE_HEAD) + that modify the conflicted files. + <revs>:: Limit the revisions to show. This can be either a single revision @@ -74,6 +80,11 @@ gitk --max-count=100 --all \-- Makefile:: Show at most 100 changes made to the file 'Makefile'. Instead of only looking for changes in the current branch look in all branches. +Files +----- +Gitk creates the .gitk file in your $HOME directory to store preferences +such as display options, font, and colors. + See Also -------- 'qgit(1)':: diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt index 76b8d77460..d89cc22261 100644 --- a/Documentation/hooks.txt +++ b/Documentation/hooks.txt @@ -28,10 +28,11 @@ The default 'applypatch-msg' hook, when enabled, runs the pre-applypatch -------------- -This hook is invoked by `git-am`. It takes no parameter, -and is invoked after the patch is applied, but before a commit -is made. Exiting with non-zero status causes the working tree -after application of the patch not committed. +This hook is invoked by `git-am`. It takes no parameter, and is +invoked after the patch is applied, but before a commit is made. + +If it exits with non-zero status, then the working tree will not be +committed after applying the patch. It can be used to inspect the current working tree and refuse to make a commit if it does not pass certain test. @@ -136,7 +137,8 @@ post-merge This hook is invoked by `git-merge`, which happens when a `git pull` is done on a local repository. The hook takes a single parameter, a status flag specifying whether or not the merge being done was a squash merge. -This hook cannot affect the outcome of `git-merge`. +This hook cannot affect the outcome of `git-merge` and is not executed, +if the merge failed due to conflicts. This hook can be used in conjunction with a corresponding pre-commit hook to save and restore any form of metadata associated with the working tree @@ -276,3 +278,10 @@ probably enable this hook. Both standard output and standard error output are forwarded to `git-send-pack` on the other end, so you can simply `echo` messages for the user. + +pre-auto-gc +----------- + +This hook is invoked by `git-gc --auto`. It takes no parameter, and +exiting with non-zero status from this script causes the `git-gc --auto` +to abort. diff --git a/Documentation/howto/setup-git-server-over-http.txt b/Documentation/howto/setup-git-server-over-http.txt index 8eadc20494..b7d09c1ec6 100644 --- a/Documentation/howto/setup-git-server-over-http.txt +++ b/Documentation/howto/setup-git-server-over-http.txt @@ -1,5 +1,5 @@ From: Rutger Nijlunsing <rutger@nospam.com> -Subject: Setting up a git repository which can be pushed into and pulled from over HTTP. +Subject: Setting up a git repository which can be pushed into and pulled from over HTTP(S). Date: Thu, 10 Aug 2006 22:00:26 +0200 Since Apache is one of those packages people like to compile @@ -40,9 +40,13 @@ What's needed: - have permissions to chown a directory -- have git installed at the server _and_ client +- have git installed on the client, and -In effect, this probably means you're going to be root. +- either have git installed on the server or have a webdav client on + the client. + +In effect, this means you're going to be root, or that you're using a +preconfigured WebDAV server. Step 1: setup a bare GIT repository @@ -50,9 +54,9 @@ Step 1: setup a bare GIT repository At the time of writing, git-http-push cannot remotely create a GIT repository. So we have to do that at the server side with git. Another -option would be to generate an empty repository at the client and copy -it to the server with WebDAV. But then you're probably the first to -try that out :) +option is to generate an empty bare repository at the client and copy +it to the server with a WebDAV client (which is the only option if Git +is not installed on the server). Create the directory under the DocumentRoot of the directories served by Apache. As an example we take /usr/local/apache2, but try "grep @@ -169,7 +173,9 @@ On Debian: Most tests should pass. -A command line tool to test WebDAV is cadaver. +A command line tool to test WebDAV is cadaver. If you prefer GUIs, for +example, konqueror can open WebDAV URLs as "webdav://..." or +"webdavs://...". If you're into Windows, from XP onwards Internet Explorer supports WebDAV. For this, do Internet Explorer -> Open Location -> @@ -179,8 +185,9 @@ http://<servername>/my-new-repo.git [x] Open as webfolder -> login . Step 3: setup the client ------------------------ -Make sure that you have HTTP support, i.e. your git was built with curl. -The easiest way to check is to look for the executable 'git-http-push'. +Make sure that you have HTTP support, i.e. your git was built with +curl (version more recent than 7.10). The command 'git http-push' with +no argument should display a usage message. Then, add the following to your $HOME/.netrc (you can do without, but will be asked to input your password a _lot_ of times): @@ -197,10 +204,10 @@ instead of the server name. To check whether all is OK, do: - curl --netrc --location -v http://<username>@<servername>/my-new-repo.git/ - -...this should give a directory listing in HTML of /var/www/my-new-repo.git . + curl --netrc --location -v http://<username>@<servername>/my-new-repo.git/HEAD +...this should give something like 'ref: refs/heads/master', which is +the content of the file HEAD on the server. Now, add the remote in your existing repository which contains the project you want to export: @@ -225,6 +232,15 @@ want to export) to repository called 'upload', which we previously defined with git-config. +Using a proxy: +-------------- + +If you have to access the WebDAV server from behind an HTTP(S) proxy, +set the variable 'all_proxy' to 'http://proxy-host.com:port', or +'http://login-on-proxy:passwd-on-proxy@proxy-host.com:port'. See 'man +curl' for details. + + Troubleshooting: ---------------- @@ -248,9 +264,14 @@ Reading /usr/local/apache2/logs/error_log is often helpful. On Debian: Read /var/log/apache2/error.log instead. +If you access HTTPS locations, git may fail verifying the SSL +certificate (this is return code 60). Setting http.sslVerify=false can +help diagnosing the problem, but removes security checks. + Debian References: http://www.debian-administration.org/articles/285 Authors Johannes Schindelin <Johannes.Schindelin@gmx.de> Rutger Nijlunsing <git@wingding.demon.nl> + Matthieu Moy <Matthieu.Moy@imag.fr> diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt new file mode 100644 index 0000000000..9719311b42 --- /dev/null +++ b/Documentation/merge-config.txt @@ -0,0 +1,35 @@ +merge.stat:: + Whether to print the diffstat berween ORIG_HEAD and merge result + at the end of the merge. True by default. + +merge.log:: + Whether to include summaries of merged commits in newly created + merge commit messages. False by default. + +merge.tool:: + Controls which merge resolution program is used by + linkgit:git-mergetool[1]. Valid built-in values are: "kdiff3", + "tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", and + "opendiff". Any other value is treated is custom merge tool + and there must be a corresponing mergetool.<tool>.cmd option. + +merge.verbosity:: + Controls the amount of output shown by the recursive merge + strategy. Level 0 outputs nothing except a final error + message if conflicts were detected. Level 1 outputs only + conflicts, 2 outputs conflicts and file changes. Level 5 and + above outputs debugging information. The default is level 2. + Can be overridden by 'GIT_MERGE_VERBOSITY' environment variable. + +merge.<driver>.name:: + Defines a human readable name for a custom low-level + merge driver. See linkgit:gitattributes[5] for details. + +merge.<driver>.driver:: + Defines the command that implements a custom low-level + merge driver. See linkgit:gitattributes[5] for details. + +merge.<driver>.recursive:: + Names a low-level merge driver to be used when + performing an internal merge between common ancestors. + See linkgit:gitattributes[5] for details. diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 9f1fc82550..f37a776489 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -1,10 +1,23 @@ ---summary:: +--stat:: Show a diffstat at the end of the merge. The diffstat is also - controlled by the configuration option merge.diffstat. + controlled by the configuration option merge.stat. --n, \--no-summary:: +-n, \--no-stat:: Do not show diffstat at the end of the merge. +--summary, \--no-summary:: + Synonyms to --stat and --no-stat; these are deprecated and will be + removed in the future. + +--log:: + In addition to branch names, populate the log message with + one-line descriptions from the actual commits that are being + merged. + +--no-log:: + Do not list one-line descriptions from the actual commits being + merged. + --no-commit:: Perform the merge but pretend the merge failed and do not autocommit, to give the user a chance to inspect and diff --git a/Documentation/repository-layout.txt b/Documentation/repository-layout.txt index 6939130094..bbaed2e129 100644 --- a/Documentation/repository-layout.txt +++ b/Documentation/repository-layout.txt @@ -3,7 +3,10 @@ git repository layout You may find these things in your git repository (`.git` directory for a repository associated with your working tree, or -`'project'.git` directory for a public 'bare' repository). +`'project'.git` directory for a public 'bare' repository. It is +also possible to have a working tree where `.git` is a plain +ascii file containing `gitdir: <path>`, i.e. the path to the +real git repository). objects:: Object store associated with this repository. Usually diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 565aeb9804..86b91a53e5 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -1548,22 +1548,7 @@ dangling tree b24c2473f1fd3d91352a624795be026d64c8841f Dangling objects are not a problem. At worst they may take up a little extra disk space. They can sometimes provide a last-resort method for -recovering lost work--see <<dangling-objects>> for details. However, if -you wish, you can remove them with linkgit:git-prune[1] or the `--prune` -option to linkgit:git-gc[1]: - -------------------------------------------------- -$ git gc --prune -------------------------------------------------- - -This may be time-consuming. Unlike most other git operations (including -git-gc when run without any options), it is not safe to prune while -other git operations are in progress in the same repository. - -If linkgit:git-fsck[1] complains about sha1 mismatches or missing -objects, you may have a much more serious problem; your best option is -probably restoring from backups. See -<<recovering-from-repository-corruption>> for a detailed discussion. +recovering lost work--see <<dangling-objects>> for details. [[recovering-lost-changes]] Recovering lost changes diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index f60bab896b..3cabc92e7a 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -11,7 +11,7 @@ LF=' if test -f version then VN=$(cat version) || VN="$DEF_VER" -elif test -d .git && +elif test -d .git -o -f .git && VN=$(git describe --abbrev=4 HEAD 2>/dev/null) && case "$VN" in *$LF*) (exit 1) ;; @@ -38,6 +38,10 @@ Issues of note: has been actively developed since 1997, and people have moved over to graphical file managers. + NOTE: As of gnuit-4.9.2, the GNU interactive tools package has been + renamed. You can compile gnuit with the --disable-transition + option and then it will not conflict with git. + - You can use git after building but without installing if you wanted to. Various git commands need to find other git commands and scripts to do their work, so you would need to @@ -366,6 +366,7 @@ LIB_H += refs.h LIB_H += remote.h LIB_H += revision.h LIB_H += run-command.h +LIB_H += sha1-lookup.h LIB_H += sideband.h LIB_H += strbuf.h LIB_H += tag.h @@ -447,6 +448,7 @@ LIB_OBJS += run-command.o LIB_OBJS += server-info.o LIB_OBJS += setup.o LIB_OBJS += sha1_file.o +LIB_OBJS += sha1-lookup.o LIB_OBJS += sha1_name.o LIB_OBJS += shallow.o LIB_OBJS += sideband.o @@ -1 +1 @@ -Documentation/RelNotes-1.5.5.txt
\ No newline at end of file +Documentation/RelNotes-1.5.6.txt
\ No newline at end of file diff --git a/archive-tar.c b/archive-tar.c index 30aa2e23fd..4add80284e 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -17,6 +17,7 @@ static time_t archive_time; static int tar_umask = 002; static int verbose; static const struct commit *commit; +static size_t base_len; /* writes out the whole block, but only if it is full */ static void write_if_needed(void) @@ -251,8 +252,8 @@ static int write_tar_entry(const unsigned char *sha1, buffer = NULL; size = 0; } else { - buffer = sha1_file_to_archive(path.buf, sha1, mode, &type, - &size, commit); + buffer = sha1_file_to_archive(path.buf + base_len, sha1, mode, + &type, &size, commit); if (!buffer) die("cannot read %s", sha1_to_hex(sha1)); } @@ -272,6 +273,7 @@ int write_tar_archive(struct archiver_args *args) archive_time = args->time; verbose = args->verbose; commit = args->commit; + base_len = args->base ? strlen(args->base) : 0; if (args->commit_sha1) write_global_extended_header(args->commit_sha1); diff --git a/archive-zip.c b/archive-zip.c index 74e30f6205..18c0f8710c 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -13,6 +13,7 @@ static int verbose; static int zip_date; static int zip_time; static const struct commit *commit; +static size_t base_len; static unsigned char *zip_dir; static unsigned int zip_dir_size; @@ -197,8 +198,8 @@ static int write_zip_entry(const unsigned char *sha1, if (S_ISREG(mode) && zlib_compression_level != 0) method = 8; result = 0; - buffer = sha1_file_to_archive(path, sha1, mode, &type, &size, - commit); + buffer = sha1_file_to_archive(path + base_len, sha1, mode, + &type, &size, commit); if (!buffer) die("cannot read %s", sha1_to_hex(sha1)); crc = crc32(crc, buffer, size); @@ -321,6 +322,7 @@ int write_zip_archive(struct archiver_args *args) zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; verbose = args->verbose; commit = args->commit; + base_len = args->base ? strlen(args->base) : 0; if (args->base && plen > 0 && args->base[plen - 1] == '/') { char *base = xstrdup(args->base); @@ -16,9 +16,9 @@ static void format_subst(const struct commit *commit, const char *b, *c; b = memmem(src, len, "$Format:", 8); - if (!b || src + len < b + 9) + if (!b) break; - c = memchr(b + 8, '$', len - 8); + c = memchr(b + 8, '$', (src + len) - b - 8); if (!c) break; @@ -546,7 +546,9 @@ static int path_matches(const char *pathname, int pathlen, (baselen && pathname[baselen] != '/') || strncmp(pathname, base, baselen)) return 0; - return fnmatch(pattern, pathname + baselen + 1, FNM_PATHNAME) == 0; + if (baselen != 0) + baselen++; + return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0; } static int fill_one(const char *what, struct match_attr *a, int rem) diff --git a/builtin-apply.c b/builtin-apply.c index abe73a0f81..caa3f2aa0c 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2981,12 +2981,8 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof) static int git_apply_config(const char *var, const char *value) { - if (!strcmp(var, "apply.whitespace")) { - if (!value) - return config_error_nonbool(var); - apply_default_whitespace = xstrdup(value); - return 0; - } + if (!strcmp(var, "apply.whitespace")) + return git_config_string(&apply_default_whitespace, var, value); return git_default_config(var, value); } @@ -3121,7 +3117,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) fd = open(arg, O_RDONLY); if (fd < 0) - usage(apply_usage); + die("can't open patch '%s': %s", arg, strerror(errno)); read_stdin = 0; set_default_whitespace_mode(whitespace_option); errs |= apply_patch(fd, arg, inaccurate_eof); diff --git a/builtin-branch.c b/builtin-branch.c index 5bc4526f64..19c508a608 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -15,7 +15,7 @@ #include "branch.h" static const char * const builtin_branch_usage[] = { - "git-branch [options] [-r | -a]", + "git-branch [options] [-r | -a] [--merged | --no-merged]", "git-branch [options] [-l] [-f] <branchname> [<start-point>]", "git-branch [options] [-r] (-d | -D) <branchname>", "git-branch [options] (-m | -M) [<oldbranch>] <newbranch>", @@ -46,6 +46,8 @@ enum color_branch { COLOR_BRANCH_CURRENT = 4, }; +static int mergefilter = -1; + static int parse_branch_color_slot(const char *var, int ofs) { if (!strcasecmp(var+ofs, "plain")) @@ -210,6 +212,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, struct ref_item *newitem; int kind = REF_UNKNOWN_TYPE; int len; + static struct commit_list branch; /* Detect kind */ if (!prefixcmp(refname, "refs/heads/")) { @@ -231,6 +234,16 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, if ((kind & ref_list->kinds) == 0) return 0; + if (mergefilter > -1) { + branch.item = lookup_commit_reference_gently(sha1, 1); + if (!branch.item) + die("Unable to lookup tip of branch %s", refname); + if (mergefilter == 0 && has_commit(head_sha1, &branch)) + return 0; + if (mergefilter == 1 && !has_commit(head_sha1, &branch)) + return 0; + } + /* Resize buffer */ if (ref_list->index >= ref_list->alloc) { ref_list->alloc = alloc_nr(ref_list->alloc); @@ -444,6 +457,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2), OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"), OPT_BOOLEAN('f', NULL, &force_create, "force creation (when already exists)"), + OPT_SET_INT(0, "merged", &mergefilter, "list only merged branches", 1), OPT_END(), }; diff --git a/builtin-checkout.c b/builtin-checkout.c index 7deb504837..10ec137cce 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -142,7 +142,7 @@ static void describe_detached_head(char *msg, struct commit *commit) struct strbuf sb; strbuf_init(&sb, 0); parse_commit(commit); - pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, "", "", 0, 0); + pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, NULL, NULL, 0, 0); fprintf(stderr, "%s %s... %s\n", msg, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf); strbuf_release(&sb); @@ -504,7 +504,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT__QUIET(&opts.quiet), OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"), OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"), - OPT_SET_INT( 0 , "track", &opts.track, "track", + OPT_SET_INT('t', "track", &opts.track, "track", BRANCH_TRACK_EXPLICIT), OPT_BOOLEAN('f', NULL, &opts.force, "force"), OPT_BOOLEAN('m', NULL, &opts.merge, "merge"), diff --git a/builtin-clean.c b/builtin-clean.c index fefec3010c..6778a03ae4 100644 --- a/builtin-clean.c +++ b/builtin-clean.c @@ -95,7 +95,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix) for (i = 0; i < dir.nr; i++) { struct dir_entry *ent = dir.entries[i]; - int len, pos, matches; + int len, pos; + int matches = 0; struct cache_entry *ce; struct stat st; @@ -127,18 +128,18 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (pathspec) { memset(seen, 0, argc > 0 ? argc : 1); - matches = match_pathspec(pathspec, ent->name, ent->len, + matches = match_pathspec(pathspec, ent->name, len, baselen, seen); - } else { - matches = 0; } if (S_ISDIR(st.st_mode)) { strbuf_addstr(&directory, ent->name); qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); - if (show_only && (remove_directories || matches)) { + if (show_only && (remove_directories || + (matches == MATCHED_EXACTLY))) { printf("Would remove %s\n", qname); - } else if (remove_directories || matches) { + } else if (remove_directories || + (matches == MATCHED_EXACTLY)) { if (!quiet) printf("Removing %s\n", qname); if (remove_dir_recursively(&directory, 0) != 0) { diff --git a/builtin-commit.c b/builtin-commit.c index 660a3458f7..256181a68b 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -90,7 +90,7 @@ static struct option builtin_commit_options[] = { OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m), OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit "), OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"), - OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by: header"), + OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_STRING('t', "template", &template_file, "FILE", "use specified template file"), OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"), @@ -98,7 +98,7 @@ static struct option builtin_commit_options[] = { OPT_BOOLEAN('a', "all", &all, "commit all changed files"), OPT_BOOLEAN('i', "include", &also, "add specified files to index for commit"), OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"), - OPT_BOOLEAN('o', "only", &only, ""), + OPT_BOOLEAN('o', "only", &only, "commit only specified files"), OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"), OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), OPT_BOOLEAN(0, "untracked-files", &untracked_files, "show all untracked files"), @@ -745,10 +745,8 @@ static int parse_and_validate_options(int argc, const char *argv[], die("No paths with --include/--only does not make sense."); if (argc == 0 && only && amend) only_include_assumed = "Clever... amending the last one with dirty index."; - if (argc > 0 && !also && !only) { + if (argc > 0 && !also && !only) only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths..."; - also = 0; - } if (!cleanup_arg || !strcmp(cleanup_arg, "default")) cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE; else if (!strcmp(cleanup_arg, "verbatim")) @@ -810,7 +808,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) rev.verbose_header = 1; rev.show_root_diff = 1; - rev.commit_format = get_commit_format("format:%h: %s"); + get_commit_format("format:%h: %s", &rev); rev.always_show_header = 0; rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = 100; diff --git a/builtin-config.c b/builtin-config.c index c34bc8b6a6..8ee01bdecd 100644 --- a/builtin-config.c +++ b/builtin-config.c @@ -3,7 +3,7 @@ #include "color.h" static const char git_config_set_usage[] = -"git-config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list | --get-color var [default] | --get-colorbool name [stdout-is-tty]"; +"git-config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int | --bool-or-int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list | --get-color var [default] | --get-colorbool name [stdout-is-tty]"; static char *key; static regex_t *key_regexp; @@ -16,7 +16,7 @@ static int seen; static char delim = '='; static char key_delim = ' '; static char term = '\n'; -static enum { T_RAW, T_INT, T_BOOL } type = T_RAW; +static enum { T_RAW, T_INT, T_BOOL, T_BOOL_OR_INT } type = T_RAW; static int show_all_config(const char *key_, const char *value_) { @@ -53,6 +53,14 @@ static int show_config(const char* key_, const char* value_) sprintf(value, "%d", git_config_int(key_, value_?value_:"")); else if (type == T_BOOL) vptr = git_config_bool(key_, value_) ? "true" : "false"; + else if (type == T_BOOL_OR_INT) { + int is_bool, v; + v = git_config_bool_or_int(key_, value_, &is_bool); + if (is_bool) + vptr = v ? "true" : "false"; + else + sprintf(value, "%d", v); + } else vptr = value_?value_:""; seen++; @@ -157,6 +165,14 @@ char *normalize_value(const char *key, const char *value) else if (type == T_BOOL) sprintf(normalized, "%s", git_config_bool(key, value) ? "true" : "false"); + else if (type == T_BOOL_OR_INT) { + int is_bool, v; + v = git_config_bool_or_int(key, value, &is_bool); + if (!is_bool) + sprintf(normalized, "%d", v); + else + sprintf(normalized, "%s", v ? "true" : "false"); + } } return normalized; @@ -224,6 +240,10 @@ static int git_get_colorbool_config(const char *var, const char *value) get_diff_color_found = git_config_colorbool(var, value, stdout_is_tty); } + if (!strcmp(var, "color.ui")) { + git_use_color_default = git_config_colorbool(var, value, stdout_is_tty); + return 0; + } return 0; } @@ -251,7 +271,7 @@ static int get_colorbool(int argc, const char **argv) if (!strcmp(get_color_slot, "color.diff")) get_colorbool_found = get_diff_color_found; if (get_colorbool_found < 0) - get_colorbool_found = 0; + get_colorbool_found = git_use_color_default; } if (argc == 1) { @@ -273,6 +293,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) type = T_INT; else if (!strcmp(argv[1], "--bool")) type = T_BOOL; + else if (!strcmp(argv[1], "--bool-or-int")) + type = T_BOOL_OR_INT; else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l")) { if (argc != 2) usage(git_config_set_usage); diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 65350ca522..c97a42739d 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -117,15 +117,15 @@ static const unsigned char* get_rev(void) while (commit == NULL) { unsigned int mark; - struct commit_list *parents = NULL; + struct commit_list *parents; if (rev_list == NULL || non_common_revs == 0) return NULL; commit = rev_list->item; - if (!(commit->object.parsed)) - if (!parse_commit(commit)) - parents = commit->parents; + if (!commit->object.parsed) + parse_commit(commit); + parents = commit->parents; commit->object.flags |= POPPED; if (!(commit->object.flags & COMMON)) diff --git a/builtin-fetch.c b/builtin-fetch.c index 5841b3e51a..e56617e32e 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -215,13 +215,6 @@ static int update_local_ref(struct ref *ref, if (type < 0) die("object %s not found", sha1_to_hex(ref->new_sha1)); - if (!*ref->name) { - /* Not storing */ - if (verbose) - sprintf(display, "* branch %s -> FETCH_HEAD", remote); - return 0; - } - if (!hashcmp(ref->old_sha1, ref->new_sha1)) { if (verbose) sprintf(display, "= %-*s %-*s -> %s", SUMMARY_WIDTH, @@ -365,16 +358,19 @@ static int store_updated_refs(const char *url, struct ref *ref_map) rm->merge ? "" : "not-for-merge", note); - if (ref) { + if (ref) update_local_ref(ref, what, verbose, note); - if (*note) { - if (!shown_url) { - fprintf(stderr, "From %.*s\n", - url_len, url); - shown_url = 1; - } - fprintf(stderr, " %s\n", note); + else + sprintf(note, "* %-*s %-*s -> FETCH_HEAD", + SUMMARY_WIDTH, *kind ? kind : "branch", + REFCOL_WIDTH, *what ? what : "HEAD"); + if (*note) { + if (!shown_url) { + fprintf(stderr, "From %.*s\n", + url_len, url); + shown_url = 1; } + fprintf(stderr, " %s\n", note); } } fclose(fp); @@ -579,8 +575,6 @@ static int do_fetch(struct transport *transport, free_refs(ref_map); } - transport_disconnect(transport); - return 0; } @@ -601,6 +595,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) int i; static const char **refs = NULL; int ref_nr = 0; + int exit_code; /* Record the command line for the reflog */ strbuf_addstr(&default_rla, "fetch"); @@ -654,6 +649,9 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) signal(SIGINT, unlock_pack_on_signal); atexit(unlock_pack); - return do_fetch(transport, + exit_code = do_fetch(transport, parse_fetch_refspec(ref_nr, refs), ref_nr); + transport_disconnect(transport); + transport = NULL; + return exit_code; } diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index ebb3f37cf1..b72cb59e6a 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -6,13 +6,18 @@ #include "tag.h" static const char *fmt_merge_msg_usage = - "git-fmt-merge-msg [--summary] [--no-summary] [--file <file>]"; + "git-fmt-merge-msg [--log] [--no-log] [--file <file>]"; static int merge_summary; static int fmt_merge_msg_config(const char *key, const char *value) { - if (!strcmp("merge.summary", key)) + static int found_merge_log = 0; + if (!strcmp("merge.log", key)) { + found_merge_log = 1; + merge_summary = git_config_bool(key, value); + } + if (!found_merge_log && !strcmp("merge.summary", key)) merge_summary = git_config_bool(key, value); return 0; } @@ -201,6 +206,15 @@ static void shortlog(const char *name, unsigned char *sha1, continue; bol = strstr(commit->buffer, "\n\n"); + if (bol) { + unsigned char c; + do { + c = *++bol; + } while (isspace(c)); + if (!c) + bol = NULL; + } + if (!bol) { append_to_list(&subjects, xstrdup(sha1_to_hex( commit->object.sha1)), @@ -208,7 +222,6 @@ static void shortlog(const char *name, unsigned char *sha1, continue; } - bol += 2; eol = strchr(bol, '\n'); if (eol) { oneline = xmemdupz(bol, eol - bol); @@ -250,9 +263,10 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) git_config(fmt_merge_msg_config); while (argc > 1) { - if (!strcmp(argv[1], "--summary")) + if (!strcmp(argv[1], "--log") || !strcmp(argv[1], "--summary")) merge_summary = 1; - else if (!strcmp(argv[1], "--no-summary")) + else if (!strcmp(argv[1], "--no-log") + || !strcmp(argv[1], "--no-summary")) merge_summary = 0; else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) { if (argc < 3) diff --git a/builtin-gc.c b/builtin-gc.c index 8cef36f6a4..f99ebc7926 100644 --- a/builtin-gc.c +++ b/builtin-gc.c @@ -157,6 +157,34 @@ static int too_many_packs(void) return gc_auto_pack_limit <= cnt; } +static int run_hook(void) +{ + const char *argv[2]; + struct child_process hook; + int ret; + + argv[0] = git_path("hooks/pre-auto-gc"); + argv[1] = NULL; + + if (access(argv[0], X_OK) < 0) + return 0; + + memset(&hook, 0, sizeof(hook)); + hook.argv = argv; + hook.no_stdin = 1; + hook.stdout_to_stderr = 1; + + ret = start_command(&hook); + if (ret) { + warning("Could not spawn %s", argv[0]); + return ret; + } + ret = finish_command(&hook); + if (ret == -ERR_RUN_COMMAND_WAITPID_SIGNAL) + warning("%s exited due to uncaught signal", argv[0]); + return ret; +} + static int need_to_gc(void) { /* @@ -176,6 +204,9 @@ static int need_to_gc(void) append_option(argv_repack, "-A", MAX_ADD); else if (!too_many_loose_objects()) return 0; + + if (run_hook()) + return 0; return 1; } diff --git a/builtin-init-db.c b/builtin-init-db.c index 2854868b4e..a76f5d3474 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -400,9 +400,16 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) char buf[10]; /* We do not spell "group" and such, so that * the configuration can be read by older version - * of git. + * of git. Note, we use octal numbers for new share modes, + * and compatibility values for PERM_GROUP and + * PERM_EVERYBODY. */ - sprintf(buf, "%d", shared_repository); + if (shared_repository == PERM_GROUP) + sprintf(buf, "%d", OLD_PERM_GROUP); + else if (shared_repository == PERM_EVERYBODY) + sprintf(buf, "%d", OLD_PERM_EVERYBODY); + else + sprintf(buf, "0%o", shared_repository); git_config_set("core.sharedrepository", buf); git_config_set("receive.denyNonFastforwards", "true"); } diff --git a/builtin-log.c b/builtin-log.c index 5c00725f03..256bbac93a 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -56,7 +56,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, rev->abbrev = DEFAULT_ABBREV; rev->commit_format = CMIT_FMT_DEFAULT; if (fmt_pretty) - rev->commit_format = get_commit_format(fmt_pretty); + get_commit_format(fmt_pretty, rev); rev->verbose_header = 1; DIFF_OPT_SET(&rev->diffopt, RECURSIVE); rev->show_root_diff = default_show_root; @@ -400,6 +400,7 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) * allow us to set a different default. */ rev.commit_format = CMIT_FMT_ONELINE; + rev.use_terminator = 1; rev.always_show_header = 1; /* @@ -769,7 +770,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.diff = 1; rev.combine_merges = 0; rev.ignore_merges = 1; - rev.diffopt.msg_sep = ""; DIFF_OPT_SET(&rev.diffopt, RECURSIVE); rev.subject_prefix = fmt_patch_subject_prefix; diff --git a/builtin-push.c b/builtin-push.c index b68c6813b8..b35aad68e9 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -56,6 +56,17 @@ static int do_push(const char *repo, int flags) if (!remote) die("bad repository '%s'", repo); + if (remote->mirror) + flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); + + if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) && refspec) + return -1; + + if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) == + (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) { + return error("--all and --mirror are incompatible"); + } + if (!refspec && !(flags & TRANSPORT_PUSH_ALL) && remote->push_refspec_nr) { @@ -95,6 +106,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) int dry_run = 0; int force = 0; int tags = 0; + int rc; const char *repo = NULL; /* default repository */ struct option options[] = { @@ -130,14 +142,10 @@ int cmd_push(int argc, const char **argv, const char *prefix) repo = argv[0]; set_refspecs(argv + 1, argc - 1); } - if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) && refspec) - usage_with_options(push_usage, options); - if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) == - (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) { - error("--all and --mirror are incompatible"); + rc = do_push(repo, flags); + if (rc == -1) usage_with_options(push_usage, options); - } - - return do_push(repo, flags); + else + return rc; } diff --git a/builtin-remote.c b/builtin-remote.c index d77f10a0ea..8b63619ef0 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -19,6 +19,8 @@ static const char * const builtin_remote_usage[] = { static int verbose; +static int show_all(void); + static inline int postfixcmp(const char *string, const char *postfix) { int len1 = strlen(string), len2 = strlen(postfix); @@ -88,19 +90,24 @@ static int add(int argc, const char **argv) strbuf_init(&buf, 0); strbuf_init(&buf2, 0); + strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name); + if (!valid_fetch_refspec(buf2.buf)) + die("'%s' is not a valid remote name", name); + strbuf_addf(&buf, "remote.%s.url", name); if (git_config_set(buf.buf, url)) return 1; + strbuf_reset(&buf); + strbuf_addf(&buf, "remote.%s.fetch", name); + if (track.nr == 0) path_list_append("*", &track); for (i = 0; i < track.nr; i++) { struct path_list_item *item = track.items + i; - strbuf_reset(&buf); - strbuf_addf(&buf, "remote.%s.fetch", name); - strbuf_reset(&buf2); + strbuf_addch(&buf2, '+'); if (mirror) strbuf_addf(&buf2, "refs/%s:refs/%s", item->path, item->path); @@ -111,6 +118,13 @@ static int add(int argc, const char **argv) return 1; } + if (mirror) { + strbuf_reset(&buf); + strbuf_addf(&buf, "remote.%s.mirror", name); + if (git_config_set(buf.buf, "yes")) + return 1; + } + if (fetch && fetch_remote(name)) return 1; @@ -380,8 +394,11 @@ static int show_or_prune(int argc, const char **argv, int prune) argc = parse_options(argc, argv, options, builtin_remote_usage, 0); - if (argc < 1) + if (argc < 1) { + if (!prune) + return show_all(); usage_with_options(builtin_remote_usage, options); + } memset(&states, 0, sizeof(states)); for (; argc; argc--, argv++) { diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 0351d54435..0e59707323 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -365,9 +365,17 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) return 0; } +static void die_no_single_rev(int quiet) +{ + if (quiet) + exit(1); + else + die("Needed a single revision"); +} + int cmd_rev_parse(int argc, const char **argv, const char *prefix) { - int i, as_is = 0, verify = 0; + int i, as_is = 0, verify = 0, quiet = 0; unsigned char sha1[20]; if (argc > 1 && !strcmp("--parseopt", argv[1])) @@ -432,6 +440,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) verify = 1; continue; } + if (!strcmp(arg, "--quiet") || !strcmp(arg, "-q")) { + quiet = 1; + continue; + } if (!strcmp(arg, "--short") || !prefixcmp(arg, "--short=")) { filter &= ~(DO_FLAGS|DO_NOREV); @@ -549,7 +561,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; } if (show_flag(arg) && verify) - die("Needed a single revision"); + die_no_single_rev(quiet); continue; } @@ -564,15 +576,15 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) show_rev(REVERSED, sha1, arg+1); continue; } + if (verify) + die_no_single_rev(quiet); as_is = 1; if (!show_file(arg)) continue; - if (verify) - die("Needed a single revision"); verify_filename(prefix, arg); } show_default(); if (verify && revs_count != 1) - die("Needed a single revision"); + die_no_single_rev(quiet); return 0; } diff --git a/builtin-revert.c b/builtin-revert.c index 607a2f0337..2b57525d72 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -33,7 +33,7 @@ static const char * const cherry_pick_usage[] = { NULL }; -static int edit, no_replay, no_commit, mainline; +static int edit, no_replay, no_commit, mainline, signoff; static enum { REVERT, CHERRY_PICK } action; static struct commit *commit; @@ -53,6 +53,7 @@ static void parse_args(int argc, const char **argv) OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"), OPT_BOOLEAN('x', NULL, &no_replay, "append commit name when cherry-picking"), OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"), + OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_INTEGER('m', "mainline", &mainline, "parent number"), OPT_END(), }; @@ -404,10 +405,19 @@ static int revert_or_cherry_pick(int argc, const char **argv) */ if (!no_commit) { - if (edit) - return execl_git_cmd("commit", "-n", NULL); - else - return execl_git_cmd("commit", "-n", "-F", defmsg, NULL); + /* 6 is max possible length of our args array including NULL */ + const char *args[6]; + int i = 0; + args[i++] = "commit"; + args[i++] = "-n"; + if (signoff) + args[i++] = "-s"; + if (!edit) { + args[i++] = "-F"; + args[i++] = defmsg; + } + args[i] = NULL; + return execv_git_cmd(args); } free(reencoded_message); diff --git a/builtin-shortlog.c b/builtin-shortlog.c index bd795b1db7..e6a2865019 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -9,7 +9,7 @@ #include "shortlog.h" static const char shortlog_usage[] = -"git-shortlog [-n] [-s] [-e] [<commit-id>... ]"; +"git-shortlog [-n] [-s] [-e] [-w] [<commit-id>... ]"; static int compare_by_number(const void *a1, const void *a2) { diff --git a/builtin-tag.c b/builtin-tag.c index 8dd959fe1c..129ff57f11 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -16,7 +16,7 @@ static const char * const git_tag_usage[] = { "git-tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]", "git-tag -d <tagname>...", - "git-tag -l [-n [<num>]] [<pattern>]", + "git-tag -l [-n[<num>]] [<pattern>]", "git-tag -v <tagname>...", NULL }; diff --git a/cache-tree.c b/cache-tree.c index 39da54d1e5..73cb340707 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -341,8 +341,11 @@ static int update_one(struct cache_tree *it, if (dryrun) hash_sha1_file(buffer.buf, buffer.len, tree_type, it->sha1); - else - write_sha1_file(buffer.buf, buffer.len, tree_type, it->sha1); + else if (write_sha1_file(buffer.buf, buffer.len, tree_type, it->sha1)) { + strbuf_release(&buffer); + return -1; + } + strbuf_release(&buffer); it->entry_count = i; #if DEBUG @@ -315,6 +315,7 @@ extern char *get_index_file(void); extern char *get_graft_file(void); extern int set_git_dir(const char *path); extern const char *get_git_work_tree(void); +extern const char *read_gitfile_gently(const char *path); #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES" @@ -479,10 +480,20 @@ static inline void hashclr(unsigned char *hash) int git_mkstemp(char *path, size_t n, const char *template); +/* + * NOTE NOTE NOTE!! + * + * PERM_UMASK, OLD_PERM_GROUP and OLD_PERM_EVERYBODY enumerations must + * not be changed. Old repositories have core.sharedrepository written in + * numeric format, and therefore these values are preserved for compatibility + * reasons. + */ enum sharedrepo { - PERM_UMASK = 0, - PERM_GROUP, - PERM_EVERYBODY + PERM_UMASK = 0, + OLD_PERM_GROUP = 1, + OLD_PERM_EVERYBODY = 2, + PERM_GROUP = 0660, + PERM_EVERYBODY = 0664, }; int git_config_perm(const char *var, const char *value); int adjust_shared_perm(const char *path); @@ -629,6 +640,7 @@ struct ref { struct ref *next; unsigned char old_sha1[20]; unsigned char new_sha1[20]; + char *symref; unsigned int force:1, merge:1, nonfastforward:1, @@ -697,6 +709,7 @@ extern int git_parse_long(const char *, long *); extern int git_parse_ulong(const char *, unsigned long *); extern int git_config_int(const char *, const char *); extern unsigned long git_config_ulong(const char *, const char *); +extern int git_config_bool_or_int(const char *, const char *, int *); extern int git_config_bool(const char *, const char *); extern int git_config_string(const char **, const char *, const char *); extern int git_config_set(const char *, const char *); @@ -720,8 +733,8 @@ extern const char *git_log_output_encoding; extern void maybe_flush_or_die(FILE *, const char *); extern int copy_fd(int ifd, int ofd); extern int copy_file(const char *dst, const char *src, int mode); -extern int read_in_full(int fd, void *buf, size_t count); -extern int write_in_full(int fd, const void *buf, size_t count); +extern ssize_t read_in_full(int fd, void *buf, size_t count); +extern ssize_t write_in_full(int fd, const void *buf, size_t count); extern void write_or_die(int fd, const void *buf, size_t count); extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg); extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg); diff --git a/combine-diff.c b/combine-diff.c index 0e19cbaacc..588c58bc55 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -701,7 +701,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, else if (0 <= (fd = open(elem->path, O_RDONLY)) && !fstat(fd, &st)) { size_t len = xsize_t(st.st_size); - size_t sz = 0; + ssize_t done; int is_file, i; elem->mode = canon_mode(st.st_mode); @@ -716,14 +716,13 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, result_size = len; result = xmalloc(len + 1); - while (sz < len) { - ssize_t done = xread(fd, result+sz, len-sz); - if (done == 0) - break; - if (done < 0) - die("read error '%s'", elem->path); - sz += done; - } + + done = read_in_full(fd, result, len); + if (done < 0) + die("read error '%s'", elem->path); + else if (done < len) + die("early EOF '%s'", elem->path); + result[len] = 0; } else { @@ -798,7 +797,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, int deleted = 0; if (rev->loginfo && !rev->no_commit_id) - show_log(rev, opt->msg_sep); + show_log(rev); dump_quoted_path(dense ? "diff --cc " : "diff --combined ", "", elem->path, c_meta, c_reset); printf("%sindex ", c_meta); @@ -881,7 +880,7 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re inter_name_termination = 0; if (rev->loginfo && !rev->no_commit_id) - show_log(rev, opt->msg_sep); + show_log(rev); if (opt->output_format & DIFF_FORMAT_RAW) { offset = strlen(COLONS) - num_parent; @@ -962,7 +961,7 @@ void diff_tree_combined(const unsigned char *sha1, paths = intersect_paths(paths, i, num_parent); if (show_log_first && i == 0) { - show_log(rev, opt->msg_sep); + show_log(rev); if (rev->verbose_header && opt->output_format) putchar(opt->line_termination); } @@ -63,7 +63,8 @@ enum cmit_fmt { }; extern int non_ascii(int); -extern enum cmit_fmt get_commit_format(const char *arg); +struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ +extern void get_commit_format(const char *arg, struct rev_info *); extern void format_commit_message(const struct commit *commit, const void *format, struct strbuf *sb); extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*, diff --git a/compat/fopen.c b/compat/fopen.c index ccb9e89fa4..b5ca142fed 100644 --- a/compat/fopen.c +++ b/compat/fopen.c @@ -1,5 +1,16 @@ +/* + * The order of the following two lines is important. + * + * FREAD_READS_DIRECTORIES is undefined before including git-compat-util.h + * to avoid the redefinition of fopen within git-compat-util.h. This is + * necessary since fopen is a macro on some platforms which may be set + * based on compiler options. For example, on AIX fopen is set to fopen64 + * when _LARGE_FILES is defined. The previous technique of merely undefining + * fopen after including git-compat-util.h is inadequate in this case. + */ +#undef FREAD_READS_DIRECTORIES #include "../git-compat-util.h" -#undef fopen + FILE *git_fopen(const char *path, const char *mode) { FILE *fp; @@ -303,8 +303,9 @@ unsigned long git_config_ulong(const char *name, const char *value) return ret; } -int git_config_bool(const char *name, const char *value) +int git_config_bool_or_int(const char *name, const char *value, int *is_bool) { + *is_bool = 1; if (!value) return 1; if (!*value) @@ -313,7 +314,14 @@ int git_config_bool(const char *name, const char *value) return 1; if (!strcasecmp(value, "false") || !strcasecmp(value, "no")) return 0; - return git_config_int(name, value) != 0; + *is_bool = 0; + return git_config_int(name, value); +} + +int git_config_bool(const char *name, const char *value) +{ + int discard; + return !!git_config_bool_or_int(name, value, &discard); } int git_config_string(const char **dest, const char *var, const char *value) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 4d81963b1d..16984632d9 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -152,7 +152,7 @@ __git_heads () done return fi - for i in $(git-ls-remote "$1" 2>/dev/null); do + for i in $(git ls-remote "$1" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; @@ -173,7 +173,7 @@ __git_tags () done return fi - for i in $(git-ls-remote "$1" 2>/dev/null); do + for i in $(git ls-remote "$1" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; @@ -200,7 +200,7 @@ __git_refs () done return fi - for i in $(git-ls-remote "$dir" 2>/dev/null); do + for i in $(git ls-remote "$dir" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; @@ -223,7 +223,7 @@ __git_refs2 () __git_refs_remotes () { local cmd i is_hash=y - for i in $(git-ls-remote "$1" 2>/dev/null); do + for i in $(git ls-remote "$1" 2>/dev/null); do case "$is_hash,$i" in n,refs/heads/*) is_hash=y @@ -641,6 +641,7 @@ _git_diff () --ignore-all-space --exit-code --quiet --ext-diff --no-ext-diff --no-prefix --src-prefix= --dst-prefix= + --base --ours --theirs " return ;; @@ -779,7 +780,7 @@ _git_merge () ;; --*) __gitcomp " - --no-commit --no-summary --squash --strategy + --no-commit --no-stat --log --no-log --squash --strategy " return esac @@ -1052,6 +1053,7 @@ _git_remote () local subcommands="add rm show prune update" local subcommand="$(__git_find_subcommand "$subcommands")" if [ -z "$subcommand" ]; then + __gitcomp "$subcommands" return fi @@ -1344,9 +1346,14 @@ _git () _gitk () { local cur="${COMP_WORDS[COMP_CWORD]}" + local g="$(git rev-parse --git-dir 2>/dev/null)" + local merge="" + if [ -f $g/MERGE_HEAD ]; then + merge="--merge" + fi case "$cur" in --*) - __gitcomp "--not --all" + __gitcomp "--not --all $merge" return ;; esac diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 4fa853fae7..2557a7667f 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -232,10 +232,8 @@ and returns the process output as a string, or nil if the git failed." (defun git-run-command-region (buffer start end env &rest args) "Run a git command with specified buffer region as input." - (unless (eq 0 (if env - (git-run-process-region - buffer start end "env" - (append (git-get-env-strings env) (list "git") args)) + (unless (eq 0 (let ((process-environment (append (git-get-env-strings env) + process-environment))) (git-run-process-region buffer start end "git" args))) (error "Failed to run \"git %s\":\n%s" (mapconcat (lambda (x) x) args " ") (buffer-string)))) @@ -250,9 +248,8 @@ and returns the process output as a string, or nil if the git failed." (erase-buffer) (cd dir) (setq status - (if env - (apply #'call-process "env" nil (list buffer t) nil - (append (git-get-env-strings env) (list hook-name) args)) + (let ((process-environment (append (git-get-env-strings env) + process-environment))) (apply #'call-process hook-name nil (list buffer t) nil args)))) (display-message-or-buffer buffer) (eq 0 status))))) diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 62a740c482..41368950d6 100644 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -202,11 +202,12 @@ generate_email_header() generate_email_footer() { + SPACE=" " cat <<-EOF hooks/post-receive - -- + --${SPACE} $projectdesc EOF } diff --git a/contrib/hooks/pre-auto-gc-battery b/contrib/hooks/pre-auto-gc-battery new file mode 100644 index 0000000000..0096f57b7e --- /dev/null +++ b/contrib/hooks/pre-auto-gc-battery @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to verify if you are on battery, in case you +# are running Linux. Called by git-gc --auto with no arguments. The hook +# should exit with non-zero status after issuing an appropriate message +# if it wants to stop the auto repacking. +# +# This hook is stored in the contrib/hooks directory. Your distribution +# may have put this somewhere else. If you want to use this hook, you +# should make this script executable then link to it in the repository +# you would like to use it in. +# +# For example, if the hook is stored in +# /usr/share/git-core/contrib/hooks/pre-auto-gc-battery: +# +# chmod a+x pre-auto-gc-battery +# cd /path/to/your/repository.git +# ln -sf /usr/share/git-core/contrib/hooks/pre-auto-gc-battery \ +# hooks/pre-auto-gc + +if test -x /sbin/on_ac_power && /sbin/on_ac_power +then + exit 0 +elif test "$(cat /sys/class/power_supply/AC/online 2>/dev/null)" = 1 +then + exit 0 +elif grep -q 'on-line' /proc/acpi/ac_adapter/AC/state 2>/dev/null +then + exit 0 +elif grep -q '0x01$' /proc/apm 2>/dev/null +then + exit 0 +fi + +echo "Auto packing deferred; not on AC" +exit 1 @@ -9,8 +9,7 @@ int copy_fd(int ifd, int ofd) if (!len) break; if (len < 0) { - int read_error; - read_error = errno; + int read_error = errno; close(ifd); return error("copy-fd: read returned %s", strerror(read_error)); @@ -25,9 +24,10 @@ int copy_fd(int ifd, int ofd) close(ifd); return error("copy-fd: write returned 0"); } else { + int write_error = errno; close(ifd); return error("copy-fd: write returned %s", - strerror(errno)); + strerror(write_error)); } } } @@ -48,7 +48,7 @@ int copy_file(const char *dst, const char *src, int mode) } status = copy_fd(fdi, fdo); if (close(fdo) != 0) - return error("%s: write error: %s", dst, strerror(errno)); + return error("%s: close error: %s", dst, strerror(errno)); if (!status && adjust_shared_perm(dst)) return -1; diff --git a/diff-lib.c b/diff-lib.c index 069e4507ae..9139e45fb9 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -264,6 +264,9 @@ int setup_diff_no_index(struct rev_info *revs, DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS); break; } + if (nongit && argc != i + 2) + die("git diff [--no-index] takes two paths"); + if (argc != i + 2 || (!is_outside_repo(argv[i + 1], nongit, prefix) && !is_outside_repo(argv[i], nongit, prefix))) return -1; @@ -476,8 +479,11 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } changed = ce_match_stat(ce, &st, ce_option); - if (!changed && !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) - continue; + if (!changed) { + ce_mark_uptodate(ce); + if (!DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) + continue; + } oldmode = ce->ce_mode; newmode = ce_mode_from_stat(ce, st.st_mode); diff_change(&revs->diffopt, oldmode, newmode, @@ -991,18 +991,23 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options) } } -struct diffstat_dir { - struct diffstat_file **files; - int nr, percent, cumulative; +struct dirstat_file { + const char *name; + unsigned long changed; }; -static long gather_dirstat(FILE *file, struct diffstat_dir *dir, unsigned long changed, const char *base, int baselen) +struct dirstat_dir { + struct dirstat_file *files; + int alloc, nr, percent, cumulative; +}; + +static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long changed, const char *base, int baselen) { unsigned long this_dir = 0; unsigned int sources = 0; while (dir->nr) { - struct diffstat_file *f = *dir->files; + struct dirstat_file *f = dir->files; int namelen = strlen(f->name); unsigned long this; char *slash; @@ -1017,10 +1022,7 @@ static long gather_dirstat(FILE *file, struct diffstat_dir *dir, unsigned long c this = gather_dirstat(file, dir, changed, f->name, newbaselen); sources++; } else { - if (f->is_unmerged || f->is_binary) - this = 0; - else - this = f->added + f->deleted; + this = f->changed; dir->files++; dir->nr--; sources += 2; @@ -1048,19 +1050,58 @@ static long gather_dirstat(FILE *file, struct diffstat_dir *dir, unsigned long c return this_dir; } -static void show_dirstat(struct diffstat_t *data, struct diff_options *options) +static void show_dirstat(struct diff_options *options) { int i; unsigned long changed; - struct diffstat_dir dir; + struct dirstat_dir dir; + struct diff_queue_struct *q = &diff_queued_diff; + + dir.files = NULL; + dir.alloc = 0; + dir.nr = 0; + dir.percent = options->dirstat_percent; + dir.cumulative = options->output_format & DIFF_FORMAT_CUMULATIVE; - /* Calculate total changes */ changed = 0; - for (i = 0; i < data->nr; i++) { - if (data->files[i]->is_binary || data->files[i]->is_unmerged) + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + const char *name; + unsigned long copied, added, damage; + + name = p->one->path ? p->one->path : p->two->path; + + if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) { + diff_populate_filespec(p->one, 0); + diff_populate_filespec(p->two, 0); + diffcore_count_changes(p->one, p->two, NULL, NULL, 0, + &copied, &added); + diff_free_filespec_data(p->one); + diff_free_filespec_data(p->two); + } else if (DIFF_FILE_VALID(p->one)) { + diff_populate_filespec(p->one, 1); + copied = added = 0; + diff_free_filespec_data(p->one); + } else if (DIFF_FILE_VALID(p->two)) { + diff_populate_filespec(p->two, 1); + copied = 0; + added = p->two->size; + diff_free_filespec_data(p->two); + } else continue; - changed += data->files[i]->added; - changed += data->files[i]->deleted; + + /* + * Original minus copied is the removed material, + * added is the new material. They are both damages + * made to the preimage. + */ + damage = (p->one->size - copied) + added; + + ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc); + dir.files[dir.nr].name = name; + dir.files[dir.nr].changed = damage; + changed += damage; + dir.nr++; } /* This can happen even with many files, if everything was renames */ @@ -1068,10 +1109,6 @@ static void show_dirstat(struct diffstat_t *data, struct diff_options *options) return; /* Show all directories with more than x% of the changes */ - dir.files = data->files; - dir.nr = data->nr; - dir.percent = options->dirstat_percent; - dir.cumulative = options->output_format & DIFF_FORMAT_CUMULATIVE; gather_dirstat(options->file, &dir, changed, "", 0); } @@ -2183,7 +2220,6 @@ void diff_setup(struct diff_options *options) options->rename_limit = -1; options->dirstat_percent = 3; options->context = 3; - options->msg_sep = ""; options->change = diff_change; options->add_remove = diff_addremove; @@ -3095,7 +3131,7 @@ void diff_flush(struct diff_options *options) separator++; } - if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIRSTAT)) { + if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) { struct diffstat_t diffstat; memset(&diffstat, 0, sizeof(struct diffstat_t)); @@ -3105,8 +3141,6 @@ void diff_flush(struct diff_options *options) if (check_pair_status(p)) diff_flush_stat(p, options, &diffstat); } - if (output_format & DIFF_FORMAT_DIRSTAT) - show_dirstat(&diffstat, options); if (output_format & DIFF_FORMAT_NUMSTAT) show_numstat(&diffstat, options); if (output_format & DIFF_FORMAT_DIFFSTAT) @@ -3116,6 +3150,8 @@ void diff_flush(struct diff_options *options) free_diffstat_info(&diffstat); separator++; } + if (output_format & DIFF_FORMAT_DIRSTAT) + show_dirstat(options); if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) { for (i = 0; i < q->nr; i++) @@ -88,7 +88,6 @@ struct diff_options { int abbrev; const char *prefix; int prefix_length; - const char *msg_sep; const char *stat_sep; long xdl_opts; @@ -52,6 +52,11 @@ int common_prefix(const char **pathspec) return prefix; } +static inline int special_char(unsigned char c1) +{ + return !c1 || c1 == '*' || c1 == '[' || c1 == '?'; +} + /* * Does 'match' matches the given name? * A match is found if @@ -69,18 +74,31 @@ static int match_one(const char *match, const char *name, int namelen) int matchlen; /* If the match was just the prefix, we matched */ - matchlen = strlen(match); - if (!matchlen) + if (!*match) return MATCHED_RECURSIVELY; + for (;;) { + unsigned char c1 = *match; + unsigned char c2 = *name; + if (special_char(c1)) + break; + if (c1 != c2) + return 0; + match++; + name++; + namelen--; + } + + /* * If we don't match the matchstring exactly, * we need to match by fnmatch */ + matchlen = strlen(match); if (strncmp(match, name, matchlen)) return !fnmatch(match, name, 0) ? MATCHED_FNMATCH : 0; - if (!name[matchlen]) + if (namelen == matchlen) return MATCHED_EXACTLY; if (match[matchlen-1] == '/' || name[matchlen] == '/') return MATCHED_RECURSIVELY; diff --git a/environment.c b/environment.c index 3c81682429..945574169b 100644 --- a/environment.c +++ b/environment.c @@ -51,6 +51,8 @@ static void setup_git_env(void) { git_dir = getenv(GIT_DIR_ENVIRONMENT); if (!git_dir) + git_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); + if (!git_dir) git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; git_object_dir = getenv(DB_ENVIRONMENT); if (!git_object_dir) { @@ -107,7 +107,7 @@ It does not apply to blobs recorded in its index." # patch did not touch, so recursive ends up canceling them, # saying that we reverted all those changes. - eval GITHEAD_$his_tree='"$SUBJECT"' + eval GITHEAD_$his_tree='"$FIRSTLINE"' export GITHEAD_$his_tree git-merge-recursive $orig_tree -- HEAD $his_tree || { git rerere @@ -117,10 +117,6 @@ It does not apply to blobs recorded in its index." unset GITHEAD_$his_tree } -reread_subject () { - git stripspace <"$1" | sed -e 1q -} - prec=4 dotest=".dotest" sign= utf8=t keep= skip= interactive= resolved= binary= rebasing= @@ -331,7 +327,20 @@ do echo "Patch is empty. Was it split wrong?" stop_here $this } - git stripspace < "$dotest/msg" > "$dotest/msg-clean" + if test -f "$dotest/rebasing" && + commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \ + -e q "$dotest/$msgnum") && + test "$(git cat-file -t "$commit")" = commit + then + git cat-file commit "$commit" | + sed -e '1,/^$/d' >"$dotest/msg-clean" + else + SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")" + case "$keep_subject" in -k) SUBJECT="[PATCH] $SUBJECT" ;; esac + + (printf '%s\n\n' "$SUBJECT"; cat "$dotest/msg") | + git stripspace > "$dotest/msg-clean" + fi ;; esac @@ -347,9 +356,6 @@ do export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE - SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")" - case "$keep_subject" in -k) SUBJECT="[PATCH] $SUBJECT" ;; esac - case "$resume" in '') if test '' != "$SIGNOFF" @@ -368,10 +374,8 @@ do ADD_SIGNOFF= fi { - printf '%s\n' "$SUBJECT" if test -s "$dotest/msg-clean" then - echo cat "$dotest/msg-clean" fi if test '' != "$ADD_SIGNOFF" @@ -408,7 +412,6 @@ do [aA]*) action=yes interactive= ;; [nN]*) action=skip ;; [eE]*) git_editor "$dotest/final-commit" - SUBJECT=$(reread_subject "$dotest/final-commit") action=again ;; [vV]*) action=again LESS=-S ${PAGER:-less} "$dotest/patch" ;; @@ -418,6 +421,7 @@ do else action=yes fi + FIRSTLINE=$(head -1 "$dotest/final-commit") if test $action = skip then @@ -431,7 +435,7 @@ do stop_here $this fi - printf 'Applying %s\n' "$SUBJECT" + printf 'Applying %s\n' "$FIRSTLINE" case "$resolved" in '') @@ -489,7 +493,7 @@ do tree=$(git write-tree) && parent=$(git rev-parse --verify HEAD) && commit=$(git commit-tree $tree -p $parent <"$dotest/final-commit") && - git update-ref -m "$GIT_REFLOG_ACTION: $SUBJECT" HEAD $commit $parent || + git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent || stop_here $this if test -x "$GIT_DIR"/hooks/post-applypatch diff --git a/git-bisect.sh b/git-bisect.sh index 48fb92d612..d8d9bfde4c 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -1,7 +1,9 @@ #!/bin/sh -USAGE='[start|bad|good|skip|next|reset|visualize|replay|log|run]' -LONG_USAGE='git bisect start [<bad> [<good>...]] [--] [<pathspec>...] +USAGE='[help|start|bad|good|skip|next|reset|visualize|replay|log|run]' +LONG_USAGE='git bisect help + print this long help message. +git bisect start [<bad> [<good>...]] [--] [<pathspec>...] reset bisect state and start bisection. git bisect bad [<rev>] mark <rev> a known-bad revision. @@ -20,7 +22,9 @@ git bisect replay <logfile> git bisect log show bisect log. git bisect run <cmd>... - use <cmd>... to automatically bisect.' + use <cmd>... to automatically bisect. + +Please use "git help bisect" to get the full man page.' OPTIONS_SPEC= . git-sh-setup @@ -62,9 +66,10 @@ bisect_start() { # Verify HEAD. If we were bisecting before this, reset to the # top-of-line master first! # - head=$(GIT_DIR="$GIT_DIR" git symbolic-ref HEAD) || + head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) || head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) || die "Bad HEAD - I need a HEAD" + start_head='' case "$head" in refs/heads/bisect) if [ -s "$GIT_DIR/BISECT_START" ]; then @@ -78,7 +83,7 @@ bisect_start() { # This error message should only be triggered by cogito usage, # and cogito users should understand it relates to cg-seek. [ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree" - echo "${head#refs/heads/}" >"$GIT_DIR/BISECT_START" + start_head="${head#refs/heads/}" ;; *) die "Bad HEAD - strange symbolic ref" @@ -99,6 +104,7 @@ bisect_start() { done orig_args=$(sq "$@") bad_seen=0 + eval='' while [ $# -gt 0 ]; do arg="$1" case "$arg" in @@ -116,13 +122,15 @@ bisect_start() { 0) state='bad' ; bad_seen=1 ;; *) state='good' ;; esac - bisect_write "$state" "$rev" 'nolog' + eval="$eval bisect_write '$state' '$rev' 'nolog'; " shift ;; esac done sq "$@" >"$GIT_DIR/BISECT_NAMES" + test -n "$start_head" && echo "$start_head" >"$GIT_DIR/BISECT_START" + eval "$eval" echo "git-bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" bisect_auto_next } @@ -151,20 +159,18 @@ bisect_state() { rev=$(git rev-parse --verify HEAD) || die "Bad rev input: HEAD" bisect_write "$state" "$rev" ;; - 2,bad) - rev=$(git rev-parse --verify "$2^{commit}") || - die "Bad rev input: $2" - bisect_write "$state" "$rev" ;; - *,good|*,skip) + 2,bad|*,good|*,skip) shift - revs=$(git rev-parse --revs-only --no-flags "$@") && - test '' != "$revs" || die "Bad rev input: $@" - for rev in $revs + eval='' + for rev in "$@" do - rev=$(git rev-parse --verify "$rev^{commit}") || - die "Bad rev commit: $rev^{commit}" - bisect_write "$state" "$rev" - done ;; + sha=$(git rev-parse --verify "$rev^{commit}") || + die "Bad rev input: $rev" + eval="$eval bisect_write '$state' '$sha'; " + done + eval "$eval" ;; + *,bad) + die "'git bisect bad' can take only one argument." ;; *) usage ;; esac @@ -465,6 +471,8 @@ case "$#" in cmd="$1" shift case "$cmd" in + help) + git bisect -h ;; start) bisect_start "$@" ;; bad|good|skip) diff --git a/git-clone.sh b/git-clone.sh index 2636159aaa..9d88d1ce60 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -219,6 +219,7 @@ fi if test -n "$2" then dir="$2" + test $# = 2 || die "excess parameter to git-clone" else # Derive one from the repository name # Try using "humanish" part of source repo if user didn't specify one @@ -333,7 +334,10 @@ yes) fi fi && cd "$repo" && - find objects -depth -print | cpio $cpio_quiet_flag -pumd$l "$GIT_DIR/" || \ + # Create dirs using umask and permissions and destination + find objects -type d -print | (cd "$GIT_DIR" && xargs mkdir -p) && + # Copy existing 0444 permissions on content + find objects ! -type d -print | cpio $cpio_quiet_flag -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 a18235e6d0..01c4045e89 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -206,6 +206,9 @@ void *gitmemmem(const void *haystack, size_t haystacklen, #endif #ifdef FREAD_READS_DIRECTORIES +#ifdef fopen +#undef fopen +#endif #define fopen(a,b) git_fopen(a,b) extern FILE *git_fopen(const char*, const char*); #endif @@ -268,6 +271,12 @@ static inline void *xmalloc(size_t size) return ret; } +/* + * xmemdupz() allocates (len + 1) bytes of memory, duplicates "len" bytes of + * "data" to the allocated memory, zero terminates the allocated memory, + * and returns a pointer to the allocated memory. If the allocation fails, + * the program dies. + */ static inline void *xmemdupz(const void *data, size_t len) { char *p = xmalloc(len + 1); @@ -329,6 +338,11 @@ static inline void *xmmap(void *start, size_t length, return ret; } +/* + * xread() is the same a read(), but it automatically restarts read() + * operations with a recoverable error (EAGAIN and EINTR). xread() + * DOES NOT GUARANTEE that "len" bytes is read even if the data is available. + */ static inline ssize_t xread(int fd, void *buf, size_t len) { ssize_t nr; @@ -340,6 +354,11 @@ static inline ssize_t xread(int fd, void *buf, size_t len) } } +/* + * xwrite() is the same a write(), but it automatically restarts write() + * operations with a recoverable error (EAGAIN and EINTR). xwrite() DOES NOT + * GUARANTEE that "len" bytes is written even if the operation is successful. + */ static inline ssize_t xwrite(int fd, const void *buf, size_t len) { ssize_t nr; diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 95c5eec51e..bdac5d51b6 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -772,7 +772,7 @@ sub commit { waitpid($pid,0); die "Error running git-commit-tree: $?\n" if $?; - system("git-update-ref $remote/$branch $cid") == 0 + system('git-update-ref', "$remote/$branch", $cid) == 0 or die "Cannot write branch $branch for update: $!\n"; if ($tag) { diff --git a/git-filter-branch.sh b/git-filter-branch.sh index ea59015baa..333f6a8f3b 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -406,8 +406,22 @@ if [ "$filter_tag_name" ]; then echo "$ref -> $new_ref ($sha1 -> $new_sha1)" if [ "$type" = "tag" ]; then - # Warn that we are not rewriting the tag object itself. - warn "unreferencing tag object $sha1t" + new_sha1=$(git cat-file tag "$ref" | + sed -n \ + -e "1,/^$/{ + s/^object .*/object $new_sha1/ + s/^type .*/type commit/ + s/^tag .*/tag $new_ref/ + }" \ + -e '/^-----BEGIN PGP SIGNATURE-----/q' \ + -e 'p' | + git mktag) || + die "Could not create new tag object for $ref" + if git cat-file tag "$ref" | \ + grep '^-----BEGIN PGP SIGNATURE-----' >/dev/null 2>&1 + then + warn "gpg signature stripped from tag object $sha1t" + fi fi git update-ref "refs/tags/$new_ref" "$new_sha1" || diff --git a/git-merge.sh b/git-merge.sh index 7dbbb1d79d..69b35d87e6 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -8,8 +8,12 @@ OPTIONS_SPEC="\ git-merge [options] <remote>... git-merge [options] <msg> HEAD <remote> -- -summary show a diffstat at the end of the merge -n,no-summary don't show a diffstat at the end of the merge +stat show a diffstat at the end of the merge +n,no-stat don't show a diffstat at the end of the merge +summary (synonym to --stat) +no-summary (synonym to --no-stat) +log add list of one-line log to merge commit message +no-log don't add list of one-line log to merge commit message squash create a single commit instead of doing a merge commit perform a commit if the merge sucesses (default) ff allow fast forward (default) @@ -37,7 +41,7 @@ use_strategies= allow_fast_forward=t allow_trivial_merge=t -squash= no_commit= +squash= no_commit= log_arg= dropsave() { rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \ @@ -148,10 +152,12 @@ merge_name () { parse_config () { while test $# != 0; do case "$1" in - -n|--no-summary) + -n|--no-stat|--no-summary) show_diffstat=false ;; - --summary) + --stat|--summary) show_diffstat=t ;; + --log|--no-log) + log_arg=$1 ;; --squash) test "$allow_fast_forward" = t || die "You cannot combine --squash with --no-ff." @@ -210,6 +216,7 @@ while test $args_left -lt $#; do shift; done if test -z "$show_diffstat"; then test "$(git config --bool merge.diffstat)" = false && show_diffstat=false + test "$(git config --bool merge.stat)" = false && show_diffstat=false test -z "$show_diffstat" && show_diffstat=t fi @@ -258,7 +265,7 @@ else merge_name=$(for remote do merge_name "$remote" - done | git fmt-merge-msg + done | git fmt-merge-msg $log_arg ) merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name" fi diff --git a/git-pull.sh b/git-pull.sh index 3ce32b5f21..bf0c2985af 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -4,7 +4,7 @@ # # Fetch one or more remote refs and merge it/them into the current HEAD. -USAGE='[-n | --no-summary] [--[no-]commit] [--[no-]squash] [--[no-]ff] [-s strategy]... [<fetch-options>] <repo> <head>...' +USAGE='[-n | --no-stat] [--[no-]commit] [--[no-]squash] [--[no-]ff] [-s strategy]... [<fetch-options>] <repo> <head>...' LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEAD.' SUBDIRECTORY_OK=Yes OPTIONS_SPEC= @@ -16,19 +16,19 @@ cd_to_toplevel test -z "$(git ls-files -u)" || die "You are in the middle of a conflicted merge." -strategy_args= no_summary= no_commit= squash= no_ff= +strategy_args= no_stat= no_commit= squash= no_ff= log_arg= curr_branch=$(git symbolic-ref -q HEAD) curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||") rebase=$(git config --bool branch.$curr_branch_short.rebase) while : do case "$1" in - -n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\ - --no-summa|--no-summar|--no-summary) - no_summary=-n ;; - --summary) - no_summary=$1 - ;; + -n|--no-stat|--no-summary) + no_stat=-n ;; + --stat|--summary) + no_stat=$1 ;; + --log|--no-log) + log_arg=$1 ;; --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit) no_commit=--no-commit ;; --c|--co|--com|--comm|--commi|--commit) @@ -172,9 +172,9 @@ then exit fi -merge_name=$(git fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit +merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit test true = "$rebase" && exec git-rebase $strategy_args --onto $merge_head \ ${oldremoteref:-$merge_head} -exec git-merge $no_summary $no_commit $squash $no_ff $strategy_args \ +exec git-merge $no_stat $no_commit $squash $no_ff $log_arg $strategy_args \ "$merge_name" HEAD $merge_head diff --git a/git-submodule.sh b/git-submodule.sh index 56ec3536e0..67f7a28cb3 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -300,7 +300,7 @@ cmd_update() continue fi - if ! test -d "$path"/.git + if ! test -d "$path"/.git -o -f "$path"/.git then module_clone "$path" "$url" || exit subsha1= @@ -327,7 +327,8 @@ set_name_rev () { cd "$1" && { git describe "$2" 2>/dev/null || git describe --tags "$2" 2>/dev/null || - git describe --contains --tags "$2" + git describe --contains "$2" 2>/dev/null || + git describe --all --always "$2" } ) ) test -z "$revname" || revname=" ($revname)" @@ -342,6 +343,7 @@ set_name_rev () { # cmd_summary() { summary_limit=-1 + for_status= # parse $args after "submodule ... summary". while test $# -ne 0 @@ -350,6 +352,9 @@ cmd_summary() { --cached) cached="$1" ;; + --for-status) + for_status="$1" + ;; -n|--summary-limit) if summary_limit=$(($2 + 0)) 2>/dev/null && test "$summary_limit" = "$2" then @@ -397,7 +402,8 @@ cmd_summary() { done ) - test -n "$modules" && + test -z "$modules" && return + git diff-index $cached --raw $head -- $modules | grep -e '^:160000' -e '^:[0-7]* 160000' | cut -c2- | @@ -499,7 +505,14 @@ cmd_summary() { echo fi echo - done + done | + if test -n "$for_status"; then + echo "# Modified submodules:" + echo "#" + sed -e 's|^|# |' -e 's|^# $|#|' + else + cat + fi } # # List all submodules, prefixed with: @@ -542,7 +555,7 @@ cmd_status() do name=$(module_name "$path") || exit url=$(git config submodule."$name".url) - if test -z "$url" || ! test -d "$path"/.git + if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git then say "-$sha1 $path" continue; diff --git a/git-svn.perl b/git-svn.perl index 81afb5cfcd..e47b1ea6c1 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -410,10 +410,12 @@ sub cmd_dcommit { $head ||= 'HEAD'; my @refs; my ($url, $rev, $uuid, $gs) = working_head_info($head, \@refs); - print "Committing to $url ...\n"; + if ($url) { + print "Committing to $url ...\n"; + } unless ($gs) { die "Unable to determine upstream SVN information from ", - "$head history\n"; + "$head history.\nPerhaps the repository is empty."; } my $last_rev; my ($linear_refs, $parents) = linearize_history($gs, \@refs); @@ -612,7 +614,7 @@ sub cmd_create_ignore { print GITIGNORE "$s\n"; close(GITIGNORE) or fatal("Failed to close `$ignore': $!"); - command_noisy('add', $ignore); + command_noisy('add', '-f', $ignore); }); } @@ -1120,7 +1122,7 @@ sub cmt_metadata { sub working_head_info { my ($head, $refs) = @_; - my @args = ('log', '--no-color', '--first-parent'); + my @args = ('log', '--no-color', '--first-parent', '--pretty=medium'); my ($fh, $ctx) = command_output_pipe(@args, $head); my $hash; my %max; @@ -2375,8 +2377,7 @@ sub check_author { my ($author) = @_; if (!defined $author || length $author == 0) { $author = '(no author)'; - } - if (defined $::_authors && ! defined $::users{$author}) { + } elsif (defined $::_authors && ! defined $::users{$author}) { die "Author: $author not defined in $::_authors file\n"; } $author; @@ -2427,13 +2428,15 @@ sub make_log_entry { $name_field = $1; } if (!defined $name_field) { - # + if (!defined $email) { + $email = $name; + } } elsif ($name_field =~ /(.*?)\s+<(.*)>/) { ($name, $email) = ($1, $2); } elsif ($name_field =~ /(.*)@/) { ($name, $email) = ($1, $name_field); } else { - ($name, $email) = ($name_field, 'unknown'); + ($name, $email) = ($name_field, $name_field); } } if (defined $headrev && $self->use_svm_props) { @@ -2519,6 +2522,7 @@ sub rebuild_from_rev_db { my ($self, $path) = @_; my $r = -1; open my $fh, '<', $path or croak "open: $!"; + binmode $fh or croak "binmode: $!"; while (<$fh>) { length($_) == 41 or croak "inconsistent size in ($_) != 41"; chomp($_); @@ -2616,6 +2620,7 @@ sub rebuild { sub _rev_map_set { my ($fh, $rev, $commit) = @_; + binmode $fh or croak "binmode: $!"; my $size = (stat($fh))[7]; ($size % 24) == 0 or croak "inconsistent size: $size"; @@ -2719,6 +2724,7 @@ sub rev_map_max { my $map_path = $self->map_path; stat $map_path or return $want_commit ? (0, undef) : 0; sysopen(my $fh, $map_path, O_RDONLY) or croak "open: $!"; + binmode $fh or croak "binmode: $!"; my $size = (stat($fh))[7]; ($size % 24) == 0 or croak "inconsistent size: $size"; @@ -2751,6 +2757,7 @@ sub rev_map_get { return undef unless -e $map_path; sysopen(my $fh, $map_path, O_RDONLY) or croak "open: $!"; + binmode $fh or croak "binmode: $!"; my $size = (stat($fh))[7]; ($size % 24) == 0 or croak "inconsistent size: $size"; @@ -347,7 +347,7 @@ static void handle_internal_command(int argc, const char **argv) { "shortlog", cmd_shortlog, USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, - { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, + { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE | USE_PAGER }, { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, { "tag", cmd_tag, RUN_SETUP }, diff --git a/gitweb/INSTALL b/gitweb/INSTALL index 743f2d4442..f7194dbef7 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -29,40 +29,40 @@ Build time configuration See also "How to configure gitweb for your local system" in README file for gitweb (in gitweb/README). -- There are many configuration variables which affects building of +- There are many configuration variables which affect building of gitweb.cgi; see "default configuration for gitweb" section in main (top dir) Makefile, and instructions for building gitweb/gitweb.cgi target. - One of most important is where to find git wrapper binary. Gitweb - tries to find git wrapper at $(bindir)/git, so you have to set $bindir + One of the most important is where to find the git wrapper binary. Gitweb + tries to find the git wrapper at $(bindir)/git, so you have to set $bindir when building gitweb.cgi, or $prefix from which $bindir is derived. If - you build and install gitweb together with the rest of git suite, + you build and install gitweb together with the rest of the git suite, there should be no problems. Otherwise, if git was for example installed from a binary package, you have to set $prefix (or $bindir) accordingly. - Another important issue is where are git repositories you want to make - available to gitweb. By default gitweb search for repositories under + available to gitweb. By default gitweb searches for repositories under /pub/git; if you want to have projects somewhere else, like /home/git, use GITWEB_PROJECTROOT build configuration variable. By default all git repositories under projectroot are visible and - available to gitweb. List of projects is generated by default by + available to gitweb. The list of projects is generated by default by scanning the projectroot directory for git repositories. This can be changed (configured) as described in "Gitweb repositories" section below. - Note that gitweb deals directly with object database, and does not - need working directory; the name of the project is the name of its + Note that gitweb deals directly with the object database, and does not + need a working directory; the name of the project is the name of its repository object database, usually projectname.git for bare repositories. If you want to provide gitweb access to non-bare (live) - repository, you can make projectname.git symbolic link under + repositories, you can make projectname.git a symbolic link under projectroot linking to projectname/.git (but it is just a suggestion). - You can control where gitweb tries to find its main CSS style file, - its favicon and logo with GITWEB_CSS, GITWEB_FAVICON and GITWEB_LOGO + its favicon and logo with the GITWEB_CSS, GITWEB_FAVICON and GITWEB_LOGO build configuration variables. By default gitweb tries to find them in the same directory as gitweb.cgi script. @@ -91,17 +91,17 @@ Gitweb config file See also "Runtime gitweb configuration" section in README file for gitweb (in gitweb/README). -- You can configure gitweb further using gitweb configuration file; - by default it is file named gitweb_config.perl in the same place as - gitweb.cgi script. You can control default place for config file - using GITWEB_CONFIG build configuration variable, and you can set it - using GITWEB_CONFIG environmental variable. If this file does not +- You can configure gitweb further using the gitweb configuration file; + by default this is a file named gitweb_config.perl in the same place as + gitweb.cgi script. You can control the default place for the config file + using the GITWEB_CONFIG build configuration variable, and you can set it + using the GITWEB_CONFIG environment variable. If this file does not exist, gitweb looks for a system-wide configuration file, normally /etc/gitweb.conf. You can change the default using the GITWEB_CONFIG_SYSTEM build configuration variable, and override it - through GITWEB_CONFIG_SYSTEM environmental variable. + through the GITWEB_CONFIG_SYSTEM environment variable. -- Gitweb config file is [fragment] of perl code. You can set variables +- The gitweb config file is a fragment of perl code. You can set variables using "our $variable = value"; text from "#" character until the end of a line is ignored. See perlsyn(1) for details. @@ -128,36 +128,37 @@ Gitweb repositories ------------------- - By default all git repositories under projectroot are visible and - available to gitweb. List of projects is generated by default by + available to gitweb. The list of projects is generated by default by scanning the projectroot directory for git repositories (for object databases to be more exact). - You can provide pre-generated list of [visible] repositories, + You can provide a pre-generated list of [visible] repositories, together with information about their owners (the project ownership - is taken from owner of repository directory otherwise), by setting - GITWEB_LIST build configuration variable (or $projects_list variable - in gitweb config file) to point to a plain file. - - Each line of projects list file should consist of url-encoded path - to project repository database (relative to projectroot) separated - by space from url-encoded project owner; spaces in both project path - and project owner have to be encoded as either '%20' or '+'. - - You can generate projects list index file using project_index action - (the 'TXT' link on projects list page) directly from gitweb. - -- By default even if project is not visible on projects list page, you - can view it nevertheless by hand-crafting gitweb URL. You can set - GITWEB_STRICT_EXPORT build configuration variable (or $strict_export - variable in gitweb config file) to only allow viewing of + defaults to the owner of the repository directory otherwise), by setting + the GITWEB_LIST build configuration variable (or the $projects_list + variable in the gitweb config file) to point to a plain file. + + Each line of the projects list file should consist of the url-encoded path + to the project repository database (relative to projectroot), followed + by the url-encoded project owner on the same line (separated by a space). + Spaces in both project path and project owner have to be encoded as either + '%20' or '+'. + + You can generate the projects list index file using the project_index + action (the 'TXT' link on projects list page) directly from gitweb. + +- By default, even if a project is not visible on projects list page, you + can view it nevertheless by hand-crafting a gitweb URL. You can set the + GITWEB_STRICT_EXPORT build configuration variable (or the $strict_export + variable in the gitweb config file) to only allow viewing of repositories also shown on the overview page. - Alternatively, you can configure gitweb to only list and allow - viewing of the explicitly exported repositories, via - GITWEB_EXPORT_OK build configuration variable (or $export_ok + viewing of the explicitly exported repositories, via the + GITWEB_EXPORT_OK build configuration variable (or the $export_ok variable in gitweb config file). If it evaluates to true, gitweb - show repository only if this file exists in its object database - (if directory has the magic file $export_ok). + shows repositories only if this file exists in its object database + (if directory has the magic file named $export_ok). Generating projects list using gitweb ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/gitweb/README b/gitweb/README index 8dfe335f73..8f7ea367ba 100644 --- a/gitweb/README +++ b/gitweb/README @@ -2,7 +2,7 @@ GIT web Interface ================= The one working on: - http://www.kernel.org/git/ + http://git.kernel.org/ From the git version 1.4.0 gitweb is bundled with git. @@ -10,13 +10,13 @@ From the git version 1.4.0 gitweb is bundled with git. How to configure gitweb for your local system --------------------------------------------- -See also "Build time configuration" section in INSTALL +See also the "Build time configuration" section in the INSTALL file for gitweb (in gitweb/INSTALL). You can specify the following configuration variables when building GIT: * GIT_BINDIR - Points out where to find git executable. You should set up it to - the place where git binary was installed (usually /usr/bin) if you + Points where to find the git executable. You should set it up to + the place where the git binary was installed (usually /usr/bin) if you don't install git from sources together with gitweb. [Default: $(bindir)] * GITWEB_SITENAME Shown in the title of all generated pages, defaults to the server name @@ -24,13 +24,13 @@ You can specify the following configuration variables when building GIT: * GITWEB_PROJECTROOT The root directory for all projects shown by gitweb. Must be set correctly for gitweb to find repositories to display. See also - "Gitweb repositories" in INSTALL file for gitweb. [Default: /pub/git] + "Gitweb repositories" in the INSTALL file for gitweb. [Default: /pub/git] * GITWEB_PROJECT_MAXDEPTH - The filesystem traversing limit for getting projects list; the number + The filesystem traversing limit for getting the project list; the number is taken as depth relative to the projectroot. It is used when GITWEB_LIST is a directory (or is not set; then project root is used). Is is meant to speed up project listing on large work trees by limiting - find depth. [Default: 2007] + search depth. [Default: 2007] * GITWEB_LIST Points to a directory to scan for projects (defaults to project root if not set / if empty) or to a file with explicit listing of projects @@ -72,15 +72,15 @@ You can specify the following configuration variables when building GIT: Git base URLs used for URL to where fetch project from, i.e. full URL is "$git_base_url/$project". Shown on projects summary page. Repository URL for project can be also configured per repository; this - takes precendence over URL composed from base URL and project name. + takes precedence over URLs composed from base URL and a project name. Note that you can setup multiple base URLs (for example one for - git:// protocol access, one for http:// access) from gitweb config - file. [No default] + git:// protocol access, another for http:// access) from the gitweb + config file. [No default] * GITWEB_CSS Points to the location where you put gitweb.css on your web server - (or to be more generic URI of gitweb stylesheet). Relative to base - URI of gitweb. Note that you can setup multiple stylesheets from - gitweb config file. [Default: gitweb.css] + (or to be more generic, the URI of gitweb stylesheet). Relative to the + base URI of gitweb. Note that you can setup multiple stylesheets from + the gitweb config file. [Default: gitweb.css] * GITWEB_LOGO Points to the location where you put git-logo.png on your web server (or to be more generic URI of logo, 72x27 size, displayed in top right @@ -121,15 +121,15 @@ Ultimate description on how to reconfigure the default features setting in your `GITWEB_CONFIG` or per-project in `project.git/config` can be found as comments inside 'gitweb.cgi'. -See also "Gitweb config file" (with example of gitweb config file), and -"Gitweb repositories" sections in INSTALL file for gitweb. +See also the "Gitweb config file" (with an example of config file), and +the "Gitweb repositories" sections in INSTALL file for gitweb. -Gitweb config file is [fragment] of perl code. You can set variables +The gitweb config file is a fragment of perl code. You can set variables using "our $variable = value"; text from "#" character until the end of a line is ignored. See perlsyn(1) man page for details. -Below there is list of vaiables which you might want to set in gitweb config. +Below is the list of variables which you might want to set in gitweb config. See the top of 'gitweb.cgi' for the full list of variables and their descriptions. @@ -140,7 +140,7 @@ You can set, among others, the following variables in gitweb config files (with the exception of $projectroot and $projects_list this list does not include variables usually directly set during build): * $GIT - Cure git executable to use. By default set to "$GIT_BINDIR/git", which + Core git executable to use. By default set to "$GIT_BINDIR/git", which in turn is by default set to "$(bindir)/git". If you use git from binary package, set this to "/usr/bin/git". This can just be "git" if your webserver has a sensible PATH. If you have multiple git versions @@ -176,7 +176,7 @@ not include variables usually directly set during build): to make it easier to upgrade gitweb. You can add 'site' stylesheet for example by using push @stylesheets, "gitweb-site.css"; - in gitweb config file. + in the gitweb config file. * $logo_url, $logo_label URI and label (title) of GIT logo link (or your site logo, if you choose to use different logo image). By default they point to git homepage; @@ -198,12 +198,12 @@ not include variables usually directly set during build): Default mimetype for blob_plain (raw) view, if mimetype checking doesn't result in some other type; by default 'text/plain'. * $default_text_plain_charset - Default charset for text files. If not set, web serwer configuration + Default charset for text files. If not set, web server configuration would be used. * $mimetypes_file File to use for (filename extension based) guessing of MIME types before - trying /etc/mime.types. Path, if relative, is taken currently as taken - relative to current git repositoy. + trying /etc/mime.types. Path, if relative, is taken currently as + relative to the current git repository. * $fallback_encoding Gitweb assumes this charset if line contains non-UTF-8 characters. Fallback decoding is used without error checking, so it can be even @@ -232,14 +232,14 @@ You can use the following files in repository: single line description of a project (of a repository). Plain text file; HTML will be escaped. By default set to Unnamed repository; edit this file to name it for gitweb. - from the template during creating repository. You can use + from the template during repository creation. You can use the gitweb.description repo configuration variable, but the file takes - precendence. + precedence. * cloneurl (or multiple-valued gitweb.url) File with repository URL (used for clone and fetch), one per line. Displayed in the project summary page. You can use multiple-valued gitweb.url repository configuration variable for that, but the file - takes precendence. + takes precedence. * gitweb.owner You can use the gitweb.owner repository configuration variable to set repository's owner. It is displayed in the project list and summary diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 446a1c333b..aa0eeca247 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -464,6 +464,14 @@ a.rss_logo:hover { background-color: #ee5500; } +a.rss_logo.generic { + background-color: #ff8800; +} + +a.rss_logo.generic:hover { + background-color: #ee7700; +} + span.refs span { padding: 0px 4px; font-size: 70%; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index e69d7fd07b..2facf2db7a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -511,7 +511,7 @@ sub evaluate_path_info { } # do not change any parameters if an action is given using the query string return if $action; - $path_info =~ s,^$project/*,,; + $path_info =~ s,^\Q$project\E/*,,; my ($refname, $pathname) = split(/:/, $path_info, 2); if (defined $pathname) { # we got "project.git/branch:filename" or "project.git/branch:dir/" @@ -592,7 +592,7 @@ exit; ## ====================================================================== ## action links -sub href(%) { +sub href (%) { my %params = @_; # default is to use -absolute url() i.e. $my_uri my $href = $params{-full} ? $my_url : $my_uri; @@ -633,7 +633,7 @@ sub href(%) { my ($use_pathinfo) = gitweb_check_feature('pathinfo'); if ($use_pathinfo) { # use PATH_INFO for project name - $href .= "/$params{'project'}" if defined $params{'project'}; + $href .= "/".esc_url($params{'project'}) if defined $params{'project'}; delete $params{'project'}; # Summary just uses the project path URL @@ -1448,6 +1448,46 @@ sub format_snapshot_links { } } +## ...................................................................... +## functions returning values to be passed, perhaps after some +## transformation, to other functions; e.g. returning arguments to href() + +# returns hash to be passed to href to generate gitweb URL +# in -title key it returns description of link +sub get_feed_info { + my $format = shift || 'Atom'; + my %res = (action => lc($format)); + + # feed links are possible only for project views + return unless (defined $project); + # some views should link to OPML, or to generic project feed, + # or don't have specific feed yet (so they should use generic) + return if ($action =~ /^(?:tags|heads|forks|tag|search)$/x); + + my $branch; + # branches refs uses 'refs/heads/' prefix (fullname) to differentiate + # from tag links; this also makes possible to detect branch links + if ((defined $hash_base && $hash_base =~ m!^refs/heads/(.*)$!) || + (defined $hash && $hash =~ m!^refs/heads/(.*)$!)) { + $branch = $1; + } + # find log type for feed description (title) + my $type = 'log'; + if (defined $file_name) { + $type = "history of $file_name"; + $type .= "/" if ($action eq 'tree'); + $type .= " on '$branch'" if (defined $branch); + } else { + $type = "log of $branch" if (defined $branch); + } + + $res{-title} = $type; + $res{'hash'} = (defined $branch ? "refs/heads/$branch" : undef); + $res{'file_name'} = $file_name; + + return %res; +} + ## ---------------------------------------------------------------------- ## git utility subroutines, invoking git commands @@ -2510,30 +2550,49 @@ EOF } } if (defined $project) { - printf('<link rel="alternate" title="%s log RSS feed" '. - 'href="%s" type="application/rss+xml" />'."\n", - esc_param($project), href(action=>"rss")); - printf('<link rel="alternate" title="%s log RSS feed (no merges)" '. - 'href="%s" type="application/rss+xml" />'."\n", - esc_param($project), href(action=>"rss", - extra_options=>"--no-merges")); - printf('<link rel="alternate" title="%s log Atom feed" '. - 'href="%s" type="application/atom+xml" />'."\n", - esc_param($project), href(action=>"atom")); - printf('<link rel="alternate" title="%s log Atom feed (no merges)" '. - 'href="%s" type="application/atom+xml" />'."\n", - esc_param($project), href(action=>"atom", - extra_options=>"--no-merges")); + my %href_params = get_feed_info(); + if (!exists $href_params{'-title'}) { + $href_params{'-title'} = 'log'; + } + + foreach my $format qw(RSS Atom) { + my $type = lc($format); + my %link_attr = ( + '-rel' => 'alternate', + '-title' => "$project - $href_params{'-title'} - $format feed", + '-type' => "application/$type+xml" + ); + + $href_params{'action'} = $type; + $link_attr{'-href'} = href(%href_params); + print "<link ". + "rel=\"$link_attr{'-rel'}\" ". + "title=\"$link_attr{'-title'}\" ". + "href=\"$link_attr{'-href'}\" ". + "type=\"$link_attr{'-type'}\" ". + "/>\n"; + + $href_params{'extra_options'} = '--no-merges'; + $link_attr{'-href'} = href(%href_params); + $link_attr{'-title'} .= ' (no merges)'; + print "<link ". + "rel=\"$link_attr{'-rel'}\" ". + "title=\"$link_attr{'-title'}\" ". + "href=\"$link_attr{'-href'}\" ". + "type=\"$link_attr{'-type'}\" ". + "/>\n"; + } + } else { printf('<link rel="alternate" title="%s projects list" '. - 'href="%s" type="text/plain; charset=utf-8"/>'."\n", + 'href="%s" type="text/plain; charset=utf-8" />'."\n", $site_name, href(project=>undef, action=>"project_index")); printf('<link rel="alternate" title="%s projects feeds" '. - 'href="%s" type="text/x-opml"/>'."\n", + 'href="%s" type="text/x-opml" />'."\n", $site_name, href(project=>undef, action=>"opml")); } if (defined $favicon) { - print qq(<link rel="shortcut icon" href="$favicon" type="image/png"/>\n); + print qq(<link rel="shortcut icon" href="$favicon" type="image/png" />\n); } print "</head>\n" . @@ -2575,7 +2634,7 @@ EOF my $action = $my_uri; my ($use_pathinfo) = gitweb_check_feature('pathinfo'); if ($use_pathinfo) { - $action .= "/$project"; + $action .= "/".esc_url($project); } else { $cgi->param("p", $project); } @@ -2601,23 +2660,35 @@ EOF } sub git_footer_html { + my $feed_class = 'rss_logo'; + print "<div class=\"page_footer\">\n"; if (defined $project) { my $descr = git_get_project_description($project); if (defined $descr) { print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n"; } - print $cgi->a({-href => href(action=>"rss"), - -class => "rss_logo"}, "RSS") . " "; - print $cgi->a({-href => href(action=>"atom"), - -class => "rss_logo"}, "Atom") . "\n"; + + my %href_params = get_feed_info(); + if (!%href_params) { + $feed_class .= ' generic'; + } + $href_params{'-title'} ||= 'log'; + + foreach my $format qw(RSS Atom) { + $href_params{'action'} = lc($format); + print $cgi->a({-href => href(%href_params), + -title => "$href_params{'-title'} $format feed", + -class => $feed_class}, $format)."\n"; + } + } else { print $cgi->a({-href => href(project=>undef, action=>"opml"), - -class => "rss_logo"}, "OPML") . " "; + -class => $feed_class}, "OPML") . " "; print $cgi->a({-href => href(project=>undef, action=>"project_index"), - -class => "rss_logo"}, "TXT") . "\n"; + -class => $feed_class}, "TXT") . "\n"; } - print "</div>\n" ; + print "</div>\n"; # class="page_footer" if (-f $site_footer) { open (my $fd, $site_footer); @@ -5176,14 +5247,26 @@ sub git_history { my $refs = git_get_references(); my $limit = sprintf("--max-count=%i", (100 * ($page+1))); + my @commitlist = parse_commits($hash_base, 101, (100 * $page), + $file_name, "--full-history"); + if (!@commitlist) { + die_error('404 Not Found', "No such file or directory on given branch"); + } + if (!defined $hash && defined $file_name) { - $hash = git_get_hash_by_path($hash_base, $file_name); + # some commits could have deleted file in question, + # and not have it in tree, but one of them has to have it + for (my $i = 0; $i <= @commitlist; $i++) { + $hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name); + last if defined $hash; + } } if (defined $hash) { $ftype = git_get_type($hash); } - - my @commitlist = parse_commits($hash_base, 101, (100 * $page), $file_name, "--full-history"); + if (!defined $ftype) { + die_error(undef, "Unknown type of object"); + } my $paging_nav = ''; if ($page > 0) { @@ -11,10 +11,16 @@ #include "run-command.h" static struct man_viewer_list { - void (*exec)(const char *); struct man_viewer_list *next; + char name[FLEX_ARRAY]; } *man_viewer_list; +static struct man_viewer_info_list { + struct man_viewer_info_list *next; + const char *info; + char name[FLEX_ARRAY]; +} *man_viewer_info_list; + enum help_format { HELP_FORMAT_MAN, HELP_FORMAT_INFO, @@ -49,6 +55,18 @@ static enum help_format parse_help_format(const char *format) die("unrecognized help format '%s'", format); } +static const char *get_man_viewer_info(const char *name) +{ + struct man_viewer_info_list *viewer; + + for (viewer = man_viewer_info_list; viewer; viewer = viewer->next) + { + if (!strcasecmp(name, viewer->name)) + return viewer->info; + } + return NULL; +} + static int check_emacsclient_version(void) { struct strbuf buffer = STRBUF_INIT; @@ -95,56 +113,145 @@ static int check_emacsclient_version(void) return 0; } -static void exec_woman_emacs(const char *page) +static void exec_woman_emacs(const char* path, const char *page) { if (!check_emacsclient_version()) { /* This works only with emacsclient version >= 22. */ struct strbuf man_page = STRBUF_INIT; + + if (!path) + path = "emacsclient"; strbuf_addf(&man_page, "(woman \"%s\")", page); - execlp("emacsclient", "emacsclient", "-e", man_page.buf, NULL); + execlp(path, "emacsclient", "-e", man_page.buf, NULL); + warning("failed to exec '%s': %s", path, strerror(errno)); } } -static void exec_man_konqueror(const char *page) +static void exec_man_konqueror(const char* path, const char *page) { const char *display = getenv("DISPLAY"); if (display && *display) { struct strbuf man_page = STRBUF_INIT; + const char *filename = "kfmclient"; + + /* It's simpler to launch konqueror using kfmclient. */ + if (path) { + const char *file = strrchr(path, '/'); + if (file && !strcmp(file + 1, "konqueror")) { + char *new = xstrdup(path); + char *dest = strrchr(new, '/'); + + /* strlen("konqueror") == strlen("kfmclient") */ + strcpy(dest + 1, "kfmclient"); + path = new; + } + if (file) + filename = file; + } else + path = "kfmclient"; strbuf_addf(&man_page, "man:%s(1)", page); - execlp("kfmclient", "kfmclient", "newTab", man_page.buf, NULL); + execlp(path, filename, "newTab", man_page.buf, NULL); + warning("failed to exec '%s': %s", path, strerror(errno)); } } -static void exec_man_man(const char *page) +static void exec_man_man(const char* path, const char *page) +{ + if (!path) + path = "man"; + execlp(path, "man", page, NULL); + warning("failed to exec '%s': %s", path, strerror(errno)); +} + +static void exec_man_cmd(const char *cmd, const char *page) { - execlp("man", "man", page, NULL); + struct strbuf shell_cmd = STRBUF_INIT; + strbuf_addf(&shell_cmd, "%s %s", cmd, page); + execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL); + warning("failed to exec '%s': %s", cmd, strerror(errno)); } -static void do_add_man_viewer(void (*exec)(const char *)) +static void add_man_viewer(const char *name) { struct man_viewer_list **p = &man_viewer_list; + size_t len = strlen(name); while (*p) p = &((*p)->next); - *p = xmalloc(sizeof(**p)); - (*p)->next = NULL; - (*p)->exec = exec; + *p = xcalloc(1, (sizeof(**p) + len + 1)); + strncpy((*p)->name, name, len); } -static int add_man_viewer(const char *value) +static int supported_man_viewer(const char *name, size_t len) { - if (!strcasecmp(value, "man")) - do_add_man_viewer(exec_man_man); - else if (!strcasecmp(value, "woman")) - do_add_man_viewer(exec_woman_emacs); - else if (!strcasecmp(value, "konqueror")) - do_add_man_viewer(exec_man_konqueror); + return (!strncasecmp("man", name, len) || + !strncasecmp("woman", name, len) || + !strncasecmp("konqueror", name, len)); +} + +static void do_add_man_viewer_info(const char *name, + size_t len, + const char *value) +{ + struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1); + + strncpy(new->name, name, len); + new->info = xstrdup(value); + new->next = man_viewer_info_list; + man_viewer_info_list = new; +} + +static int add_man_viewer_path(const char *name, + size_t len, + const char *value) +{ + if (supported_man_viewer(name, len)) + do_add_man_viewer_info(name, len, value); else - warning("'%s': unsupported man viewer.", value); + warning("'%s': path for unsupported man viewer.\n" + "Please consider using 'man.<tool>.cmd' instead.", + name); return 0; } +static int add_man_viewer_cmd(const char *name, + size_t len, + const char *value) +{ + if (supported_man_viewer(name, len)) + warning("'%s': cmd for supported man viewer.\n" + "Please consider using 'man.<tool>.path' instead.", + name); + else + do_add_man_viewer_info(name, len, value); + + return 0; +} + +static int add_man_viewer_info(const char *var, const char *value) +{ + const char *name = var + 4; + const char *subkey = strrchr(name, '.'); + + if (!subkey) + return error("Config with no key for man viewer: %s", name); + + if (!strcmp(subkey, ".path")) { + if (!value) + return config_error_nonbool(var); + return add_man_viewer_path(name, subkey - name, value); + } + if (!strcmp(subkey, ".cmd")) { + if (!value) + return config_error_nonbool(var); + return add_man_viewer_cmd(name, subkey - name, value); + } + + warning("'%s': unsupported man viewer sub key.", subkey); + return 0; +} + static int git_help_config(const char *var, const char *value) { if (!strcmp(var, "help.format")) { @@ -156,8 +263,12 @@ static int git_help_config(const char *var, const char *value) if (!strcmp(var, "man.viewer")) { if (!value) return config_error_nonbool(var); - return add_man_viewer(value); + add_man_viewer(value); + return 0; } + if (!prefixcmp(var, "man.")) + return add_man_viewer_info(var, value); + return git_default_config(var, value); } @@ -453,6 +564,22 @@ static void setup_man_path(void) strbuf_release(&new_path); } +static void exec_viewer(const char *name, const char *page) +{ + const char *info = get_man_viewer_info(name); + + if (!strcasecmp(name, "man")) + exec_man_man(info, page); + else if (!strcasecmp(name, "woman")) + exec_woman_emacs(info, page); + else if (!strcasecmp(name, "konqueror")) + exec_man_konqueror(info, page); + else if (info) + exec_man_cmd(info, page); + else + warning("'%s': unknown man viewer.", name); +} + static void show_man_page(const char *git_cmd) { struct man_viewer_list *viewer; @@ -461,9 +588,9 @@ static void show_man_page(const char *git_cmd) setup_man_path(); for (viewer = man_viewer_list; viewer; viewer = viewer->next) { - viewer->exec(page); /* will return when unable */ + exec_viewer(viewer->name, page); /* will return when unable */ } - exec_man_man(page); + exec_viewer("man", page); die("no man viewer handled the request"); } diff --git a/http-push.c b/http-push.c index 5b230380cc..939a764602 100644 --- a/http-push.c +++ b/http-push.c @@ -1759,15 +1759,16 @@ static int one_local_ref(const char *refname, const unsigned char *sha1, int fla static void one_remote_ref(char *refname) { struct ref *ref; - unsigned char remote_sha1[20]; struct object *obj; - int len = strlen(refname) + 1; - if (http_fetch_ref(remote->url, refname + 5 /* "refs/" */, - remote_sha1) != 0) { + ref = alloc_ref(strlen(refname) + 1); + strcpy(ref->name, refname); + + if (http_fetch_ref(remote->url, ref) != 0) { fprintf(stderr, "Unable to fetch ref %s from %s\n", refname, remote->url); + free(ref); return; } @@ -1775,18 +1776,15 @@ static void one_remote_ref(char *refname) * Fetch a copy of the object if it doesn't exist locally - it * may be required for updating server info later. */ - if (remote->can_update_info_refs && !has_sha1_file(remote_sha1)) { - obj = lookup_unknown_object(remote_sha1); + if (remote->can_update_info_refs && !has_sha1_file(ref->old_sha1)) { + obj = lookup_unknown_object(ref->old_sha1); if (obj) { fprintf(stderr, " fetch %s for %s\n", - sha1_to_hex(remote_sha1), refname); + sha1_to_hex(ref->old_sha1), refname); add_fetch_request(obj); } } - ref = xcalloc(1, sizeof(*ref) + len); - hashcpy(ref->old_sha1, remote_sha1); - memcpy(ref->name, refname, len); *remote_tail = ref; remote_tail = &ref->next; } @@ -1891,33 +1889,37 @@ static void mark_edges_uninteresting(struct commit_list *list) static void add_remote_info_ref(struct remote_ls_ctx *ls) { struct strbuf *buf = (struct strbuf *)ls->userData; - unsigned char remote_sha1[20]; struct object *o; int len; char *ref_info; + struct ref *ref; + + ref = alloc_ref(strlen(ls->dentry_name) + 1); + strcpy(ref->name, ls->dentry_name); - if (http_fetch_ref(remote->url, ls->dentry_name + 5 /* "refs/" */, - remote_sha1) != 0) { + if (http_fetch_ref(remote->url, ref) != 0) { fprintf(stderr, "Unable to fetch ref %s from %s\n", ls->dentry_name, remote->url); aborted = 1; + free(ref); return; } - o = parse_object(remote_sha1); + o = parse_object(ref->old_sha1); if (!o) { fprintf(stderr, "Unable to parse object %s for remote ref %s\n", - sha1_to_hex(remote_sha1), ls->dentry_name); + sha1_to_hex(ref->old_sha1), ls->dentry_name); aborted = 1; + free(ref); return; } len = strlen(ls->dentry_name) + 42; ref_info = xcalloc(len + 1, 1); sprintf(ref_info, "%s %s\n", - sha1_to_hex(remote_sha1), ls->dentry_name); + sha1_to_hex(ref->old_sha1), ls->dentry_name); fwrite_buffer(ref_info, 1, len, buf); free(ref_info); @@ -1932,6 +1934,7 @@ static void add_remote_info_ref(struct remote_ls_ctx *ls) free(ref_info); } } + free(ref); } static void update_remote_info_refs(struct remote_lock *lock) diff --git a/http-walker.c b/http-walker.c index 7bda34d914..99f397e32b 100644 --- a/http-walker.c +++ b/http-walker.c @@ -888,10 +888,10 @@ static int fetch(struct walker *walker, unsigned char *sha1) data->alt->base); } -static int fetch_ref(struct walker *walker, char *ref, unsigned char *sha1) +static int fetch_ref(struct walker *walker, struct ref *ref) { struct walker_data *data = walker->data; - return http_fetch_ref(data->alt->base, ref, sha1); + return http_fetch_ref(data->alt->base, ref); } static void cleanup(struct walker *walker) @@ -589,8 +589,9 @@ static char *quote_ref_url(const char *base, const char *ref) len += 2; /* extra two hex plus replacement % */ qref = xmalloc(len); memcpy(qref, base, baselen); - memcpy(qref + baselen, "/refs/", 6); - for (cp = ref, dp = qref + baselen + 6; (ch = *cp) != 0; cp++) { + dp = qref + baselen; + *(dp++) = '/'; + for (cp = ref; (ch = *cp) != 0; cp++) { if (needs_quote(ch)) { *dp++ = '%'; *dp++ = hex((ch >> 4) & 0xF); @@ -604,7 +605,7 @@ static char *quote_ref_url(const char *base, const char *ref) return qref; } -int http_fetch_ref(const char *base, const char *ref, unsigned char *sha1) +int http_fetch_ref(const char *base, struct ref *ref) { char *url; struct strbuf buffer = STRBUF_INIT; @@ -612,7 +613,7 @@ int http_fetch_ref(const char *base, const char *ref, unsigned char *sha1) struct slot_results results; int ret; - url = quote_ref_url(base, ref); + url = quote_ref_url(base, ref->name); slot = get_active_slot(); slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); @@ -624,12 +625,15 @@ int http_fetch_ref(const char *base, const char *ref, unsigned char *sha1) if (results.curl_result == CURLE_OK) { strbuf_rtrim(&buffer); if (buffer.len == 40) - ret = get_sha1_hex(buffer.buf, sha1); - else + ret = get_sha1_hex(buffer.buf, ref->old_sha1); + else if (!prefixcmp(buffer.buf, "ref: ")) { + ref->symref = xstrdup(buffer.buf + 5); + ret = 0; + } else ret = 1; } else { ret = error("Couldn't get %s for %s\n%s", - url, ref, curl_errorstr); + url, ref->name, curl_errorstr); } } else { ret = error("Unable to start request"); @@ -105,6 +105,6 @@ static inline int missing__target(int code, int result) #define missing_target(a) missing__target((a)->http_code, (a)->curl_result) -extern int http_fetch_ref(const char *base, const char *ref, unsigned char *sha1); +extern int http_fetch_ref(const char *base, struct ref *ref); #endif /* HTTP_H */ diff --git a/imap-send.c b/imap-send.c index 04afbc4924..db6559725e 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1303,8 +1303,11 @@ main(int argc, char **argv) return 1; } if (!server.host) { - fprintf( stderr, "no imap host specified\n" ); - return 1; + if (!server.tunnel) { + fprintf( stderr, "no imap host specified\n" ); + return 1; + } + server.host = "tunnel"; } /* read the messages */ diff --git a/log-tree.c b/log-tree.c index 9d54061601..d3fb0e520c 100644 --- a/log-tree.c +++ b/log-tree.c @@ -208,14 +208,13 @@ void log_write_email_headers(struct rev_info *opt, const char *name, *extra_headers_p = extra_headers; } -void show_log(struct rev_info *opt, const char *sep) +void show_log(struct rev_info *opt) { struct strbuf msgbuf; struct log_info *log = opt->loginfo; struct commit *commit = log->commit, *parent = log->parent; int abbrev = opt->diffopt.abbrev; int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40; - const char *extra; const char *subject = NULL, *extra_headers = opt->extra_headers; int need_8bit_cte = 0; @@ -240,18 +239,11 @@ void show_log(struct rev_info *opt, const char *sep) } /* - * The "oneline" format has several special cases: - * - The pretty-printed commit lacks a newline at the end - * of the buffer, but we do want to make sure that we - * have a newline there. If the separator isn't already - * a newline, add an extra one. - * - unlike other log messages, the one-line format does - * not have an empty line between entries. + * If use_terminator is set, add a newline at the end of the entry. + * Otherwise, add a diffopt.line_termination character before all + * entries but the first. (IOW, as a separator between entries) */ - extra = ""; - if (*sep != '\n' && opt->commit_format == CMIT_FMT_ONELINE) - extra = "\n"; - if (opt->shown_one && opt->commit_format != CMIT_FMT_ONELINE) + if (opt->shown_one && !opt->use_terminator) putchar(opt->diffopt.line_termination); opt->shown_one = 1; @@ -292,10 +284,8 @@ void show_log(struct rev_info *opt, const char *sep) show_reflog_message(opt->reflog_info, opt->commit_format == CMIT_FMT_ONELINE, opt->date_mode); - if (opt->commit_format == CMIT_FMT_ONELINE) { - printf("%s", sep); + if (opt->commit_format == CMIT_FMT_ONELINE) return; - } } } @@ -317,10 +307,10 @@ void show_log(struct rev_info *opt, const char *sep) if (opt->show_log_size) printf("log size %i\n", (int)msgbuf.len); - if (msgbuf.len) { + if (msgbuf.len) fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout); - printf("%s%s", extra, sep); - } + if (opt->use_terminator) + putchar('\n'); strbuf_release(&msgbuf); } @@ -342,7 +332,7 @@ int log_tree_diff_flush(struct rev_info *opt) * an extra newline between the end of log and the * output for readability. */ - show_log(opt, opt->diffopt.msg_sep); + show_log(opt); if ((opt->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) && opt->verbose_header && opt->commit_format != CMIT_FMT_ONELINE) { @@ -430,7 +420,7 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit) shown = log_tree_diff(opt, commit, &log); if (!shown && opt->loginfo && opt->always_show_header) { log.parent = NULL; - show_log(opt, ""); + show_log(opt); shown = 1; } opt->loginfo = NULL; diff --git a/log-tree.h b/log-tree.h index 8946ff377c..59ba4c48b7 100644 --- a/log-tree.h +++ b/log-tree.h @@ -11,7 +11,7 @@ void init_log_tree_opt(struct rev_info *); int log_tree_diff_flush(struct rev_info *); int log_tree_commit(struct rev_info *, struct commit *); int log_tree_opt_parse(struct rev_info *, const char **, int); -void show_log(struct rev_info *opt, const char *sep); +void show_log(struct rev_info *opt); void show_decorations(struct commit *commit); void log_write_email_headers(struct rev_info *opt, const char *name, const char **subject_p, diff --git a/pack-write.c b/pack-write.c index 665e2b29b8..c66c8af725 100644 --- a/pack-write.c +++ b/pack-write.c @@ -183,7 +183,6 @@ void fixup_pack_header_footer(int pack_fd, char *index_pack_lockfile(int ip_out) { - int len, s; char packname[46]; /* @@ -193,11 +192,8 @@ char *index_pack_lockfile(int ip_out) * case, we need it to remove the corresponding .keep file * later on. If we don't get that then tough luck with it. */ - for (len = 0; - len < 46 && (s = xread(ip_out, packname+len, 46-len)) > 0; - len += s); - if (len == 46 && packname[45] == '\n' && - memcmp(packname, "keep\t", 5) == 0) { + if (read_in_full(ip_out, packname, 46) == 46 && packname[45] == '\n' && + memcmp(packname, "keep\t", 5) == 0) { char path[PATH_MAX]; packname[45] = 0; snprintf(path, sizeof(path), "%s/pack/pack-%s.keep", diff --git a/parse-options.c b/parse-options.c index e87cafbe41..acf3fe3a1a 100644 --- a/parse-options.c +++ b/parse-options.c @@ -344,7 +344,7 @@ void usage_with_options_internal(const char * const *usagestr, break; case OPTION_INTEGER: if (opts->flags & PARSE_OPT_OPTARG) - pos += fprintf(stderr, " [<n>]"); + pos += fprintf(stderr, "[<n>]"); else pos += fprintf(stderr, " <n>"); break; @@ -91,7 +91,8 @@ int validate_headref(const char *path) struct stat st; char *buf, buffer[256]; unsigned char sha1[20]; - int len, fd; + int fd; + ssize_t len; if (lstat(path, &st) < 0) return -1; @@ -266,24 +267,25 @@ int adjust_shared_perm(const char *path) if (lstat(path, &st) < 0) return -1; mode = st.st_mode; - if (mode & S_IRUSR) - mode |= (shared_repository == PERM_GROUP - ? S_IRGRP - : (shared_repository == PERM_EVERYBODY - ? (S_IRGRP|S_IROTH) - : 0)); - - if (mode & S_IWUSR) - mode |= S_IWGRP; - - if (mode & S_IXUSR) - mode |= (shared_repository == PERM_GROUP - ? S_IXGRP - : (shared_repository == PERM_EVERYBODY - ? (S_IXGRP|S_IXOTH) - : 0)); - if (S_ISDIR(mode)) + + if (shared_repository) { + int tweak = shared_repository; + if (!(mode & S_IWUSR)) + tweak &= ~0222; + mode = (mode & ~0777) | tweak; + } else { + /* Preserve old PERM_UMASK behaviour */ + if (mode & S_IWUSR) + mode |= S_IWGRP; + } + + if (S_ISDIR(mode)) { mode |= FORCE_DIR_SET_GID; + + /* Copy read bits to execute bits */ + mode |= (shared_repository & 0444) >> 2; + } + if ((mode & st.st_mode) != mode && chmod(path, mode) < 0) return -2; return 0; diff --git a/pkt-line.c b/pkt-line.c index 355546a1ad..f5d00863a6 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -65,16 +65,11 @@ void packet_write(int fd, const char *fmt, ...) static void safe_read(int fd, void *buffer, unsigned size) { - size_t n = 0; - - while (n < size) { - ssize_t ret = xread(fd, (char *) buffer + n, size - n); - if (ret < 0) - die("read error (%s)", strerror(errno)); - if (!ret) - die("The remote end hung up unexpectedly"); - n += ret; - } + ssize_t ret = read_in_full(fd, buffer, size); + if (ret < 0) + die("read error (%s)", strerror(errno)); + else if (ret < size) + die("The remote end hung up unexpectedly"); } int packet_read_line(int fd, char *buffer, unsigned size) @@ -4,40 +4,49 @@ #include "diff.h" #include "revision.h" -static struct cmt_fmt_map { - const char *n; - size_t cmp_len; - enum cmit_fmt v; -} cmt_fmts[] = { - { "raw", 1, CMIT_FMT_RAW }, - { "medium", 1, CMIT_FMT_MEDIUM }, - { "short", 1, CMIT_FMT_SHORT }, - { "email", 1, CMIT_FMT_EMAIL }, - { "full", 5, CMIT_FMT_FULL }, - { "fuller", 5, CMIT_FMT_FULLER }, - { "oneline", 1, CMIT_FMT_ONELINE }, - { "format:", 7, CMIT_FMT_USERFORMAT}, -}; - static char *user_format; -enum cmit_fmt get_commit_format(const char *arg) +void get_commit_format(const char *arg, struct rev_info *rev) { int i; - - if (!arg || !*arg) - return CMIT_FMT_DEFAULT; + static struct cmt_fmt_map { + const char *n; + size_t cmp_len; + enum cmit_fmt v; + } cmt_fmts[] = { + { "raw", 1, CMIT_FMT_RAW }, + { "medium", 1, CMIT_FMT_MEDIUM }, + { "short", 1, CMIT_FMT_SHORT }, + { "email", 1, CMIT_FMT_EMAIL }, + { "full", 5, CMIT_FMT_FULL }, + { "fuller", 5, CMIT_FMT_FULLER }, + { "oneline", 1, CMIT_FMT_ONELINE }, + }; + + rev->use_terminator = 0; + if (!arg || !*arg) { + rev->commit_format = CMIT_FMT_DEFAULT; + return; + } if (*arg == '=') arg++; - if (!prefixcmp(arg, "format:")) { + if (!prefixcmp(arg, "format:") || !prefixcmp(arg, "tformat:")) { + const char *cp = strchr(arg, ':') + 1; free(user_format); - user_format = xstrdup(arg + 7); - return CMIT_FMT_USERFORMAT; + user_format = xstrdup(cp); + if (arg[0] == 't') + rev->use_terminator = 1; + rev->commit_format = CMIT_FMT_USERFORMAT; + return; } for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) { if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) && - !strncmp(arg, cmt_fmts[i].n, strlen(arg))) - return cmt_fmts[i].v; + !strncmp(arg, cmt_fmts[i].n, strlen(arg))) { + if (cmt_fmts[i].v == CMIT_FMT_ONELINE) + rev->use_terminator = 1; + rev->commit_format = cmt_fmts[i].v; + return; + } } die("invalid --pretty format: %s", arg); diff --git a/read-cache.c b/read-cache.c index 6b7d16c554..3b20a142ea 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1344,7 +1344,7 @@ int write_index(const struct index_state *istate, int newfd) struct cache_entry *ce = cache[i]; if (ce->ce_flags & CE_REMOVE) continue; - if (is_racy_timestamp(istate, ce)) + if (!ce_uptodate(ce) && is_racy_timestamp(istate, ce)) ce_smudge_racily_clean_entry(ce); if (ce_write_entry(&c, newfd, ce) < 0) return -1; @@ -352,6 +352,7 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *re { int len = strlen(path), retval; char *gitdir; + const char *tmp; while (len && path[len-1] == '/') len--; @@ -359,16 +360,27 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *re return -1; gitdir = xmalloc(len + MAXREFLEN + 8); memcpy(gitdir, path, len); - memcpy(gitdir + len, "/.git/", 7); - - retval = resolve_gitlink_ref_recursive(gitdir, len+6, refname, result, 0); + memcpy(gitdir + len, "/.git", 6); + len += 5; + + tmp = read_gitfile_gently(gitdir); + if (tmp) { + free(gitdir); + len = strlen(tmp); + gitdir = xmalloc(len + MAXREFLEN + 3); + memcpy(gitdir, tmp, len); + } + gitdir[len] = '/'; + gitdir[++len] = '\0'; + retval = resolve_gitlink_ref_recursive(gitdir, len, refname, result, 0); free(gitdir); return retval; } const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag) { - int depth = MAXDEPTH, len; + int depth = MAXDEPTH; + ssize_t len; char buffer[256]; static char ref_buffer[256]; @@ -315,7 +315,7 @@ static int handle_config(const char *key, const char *value) } if (!prefixcmp(key, "url.")) { struct rewrite *rewrite; - name = key + 5; + name = key + 4; subkey = strrchr(name, '.'); if (!subkey) return 0; @@ -337,44 +337,49 @@ static int handle_config(const char *key, const char *value) return 0; } remote = make_remote(name, subkey - name); - if (!value) { - /* if we ever have a boolean variable, e.g. "remote.*.disabled" - * [remote "frotz"] - * disabled - * is a valid way to set it to true; we get NULL in value so - * we need to handle it here. - * - * if (!strcmp(subkey, ".disabled")) { - * val = git_config_bool(key, value); - * return 0; - * } else - * - */ - return 0; /* ignore unknown booleans */ - } - if (!strcmp(subkey, ".url")) { - add_url(remote, xstrdup(value)); + if (!strcmp(subkey, ".mirror")) + remote->mirror = git_config_bool(key, value); + else if (!strcmp(subkey, ".skipdefaultupdate")) + remote->skip_default_update = git_config_bool(key, value); + + else if (!strcmp(subkey, ".url")) { + const char *v; + if (git_config_string(&v, key, value)) + return -1; + add_url(remote, v); } else if (!strcmp(subkey, ".push")) { - add_push_refspec(remote, xstrdup(value)); + const char *v; + if (git_config_string(&v, key, value)) + return -1; + add_push_refspec(remote, v); } else if (!strcmp(subkey, ".fetch")) { - add_fetch_refspec(remote, xstrdup(value)); + const char *v; + if (git_config_string(&v, key, value)) + return -1; + add_fetch_refspec(remote, v); } else if (!strcmp(subkey, ".receivepack")) { + const char *v; + if (git_config_string(&v, key, value)) + return -1; if (!remote->receivepack) - remote->receivepack = xstrdup(value); + remote->receivepack = v; else error("more than one receivepack given, using the first"); } else if (!strcmp(subkey, ".uploadpack")) { + const char *v; + if (git_config_string(&v, key, value)) + return -1; if (!remote->uploadpack) - remote->uploadpack = xstrdup(value); + remote->uploadpack = v; else error("more than one uploadpack given, using the first"); } else if (!strcmp(subkey, ".tagopt")) { if (!strcmp(value, "--no-tags")) remote->fetch_tags = -1; } else if (!strcmp(subkey, ".proxy")) { - remote->http_proxy = xstrdup(value); - } else if (!strcmp(subkey, ".skipdefaultupdate")) - remote->skip_default_update = 1; + return git_config_string((const char **)&remote->http_proxy, + key, value); + } return 0; } @@ -409,7 +414,7 @@ static void read_config(void) alias_all_urls(); } -static struct refspec *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch) +static struct refspec *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch, int verify) { int i; int st; @@ -519,17 +524,32 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp return rs; invalid: + if (verify) { + free(rs); + return NULL; + } die("Invalid refspec '%s'", refspec[i]); } +int valid_fetch_refspec(const char *fetch_refspec_str) +{ + const char *fetch_refspec[] = { fetch_refspec_str }; + struct refspec *refspec; + + refspec = parse_refspec_internal(1, fetch_refspec, 1, 1); + if (refspec) + free(refspec); + return !!refspec; +} + struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec) { - return parse_refspec_internal(nr_refspec, refspec, 1); + return parse_refspec_internal(nr_refspec, refspec, 1, 0); } struct refspec *parse_push_refspec(int nr_refspec, const char **refspec) { - return parse_refspec_internal(nr_refspec, refspec, 0); + return parse_refspec_internal(nr_refspec, refspec, 0, 0); } static int valid_remote_nick(const char *name) @@ -691,13 +711,22 @@ struct ref *copy_ref_list(const struct ref *ref) return ret; } +void free_ref(struct ref *ref) +{ + if (!ref) + return; + free(ref->remote_status); + free(ref->symref); + free(ref); +} + void free_refs(struct ref *ref) { struct ref *next; while (ref) { next = ref->next; free(ref->peer_ref); - free(ref); + free_ref(ref); ref = next; } } @@ -797,6 +826,26 @@ static struct ref *make_linked_ref(const char *name, struct ref ***tail) return ret; } +static char *guess_ref(const char *name, struct ref *peer) +{ + struct strbuf buf = STRBUF_INIT; + unsigned char sha1[20]; + + const char *r = resolve_ref(peer->name, sha1, 1, NULL); + if (!r) + return NULL; + + if (!prefixcmp(r, "refs/heads/")) + strbuf_addstr(&buf, "refs/heads/"); + else if (!prefixcmp(r, "refs/tags/")) + strbuf_addstr(&buf, "refs/tags/"); + else + return NULL; + + strbuf_addstr(&buf, name); + return strbuf_detach(&buf, NULL); +} + static int match_explicit(struct ref *src, struct ref *dst, struct ref ***dst_tail, struct refspec *rs, @@ -805,6 +854,7 @@ static int match_explicit(struct ref *src, struct ref *dst, struct ref *matched_src, *matched_dst; const char *dst_value = rs->dst; + char *dst_guess; if (rs->pattern) return errs; @@ -851,10 +901,15 @@ static int match_explicit(struct ref *src, struct ref *dst, case 0: if (!memcmp(dst_value, "refs/", 5)) matched_dst = make_linked_ref(dst_value, dst_tail); + else if((dst_guess = guess_ref(dst_value, matched_src))) + matched_dst = make_linked_ref(dst_guess, dst_tail); else - error("dst refspec %s does not match any " - "existing ref on the remote and does " - "not start with refs/.", dst_value); + error("unable to push to unqualified destination: %s\n" + "The destination refspec neither matches an " + "existing ref on the remote nor\n" + "begins with refs/, and we are unable to " + "guess a prefix based on the source ref.", + dst_value); break; default: matched_dst = NULL; @@ -1131,3 +1186,15 @@ int get_fetch_map(const struct ref *remote_refs, return 0; } + +int resolve_remote_symref(struct ref *ref, struct ref *list) +{ + if (!ref->symref) + return 0; + for (; list; list = list->next) + if (!strcmp(ref->symref, list->name)) { + hashcpy(ref->old_sha1, list->old_sha1); + return 0; + } + return 1; +} @@ -26,6 +26,7 @@ struct remote { */ int fetch_tags; int skip_default_update; + int mirror; const char *receivepack; const char *uploadpack; @@ -62,11 +63,14 @@ int check_ref_type(const struct ref *ref, int flags); */ void free_refs(struct ref *ref); +int resolve_remote_symref(struct ref *ref, struct ref *list); + /* * Removes and frees any duplicate refs in the map. */ void ref_remove_duplicates(struct ref *ref_map); +int valid_fetch_refspec(const char *refspec); struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec); struct refspec *parse_push_refspec(int nr_refspec, const char **refspec); diff --git a/revision.c b/revision.c index 196fedc9d1..4231ea2cce 100644 --- a/revision.c +++ b/revision.c @@ -1083,6 +1083,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch continue; } if (!strcmp(arg, "--topo-order")) { + revs->lifo = 1; revs->topo_order = 1; continue; } @@ -1198,7 +1199,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch } if (!prefixcmp(arg, "--pretty")) { revs->verbose_header = 1; - revs->commit_format = get_commit_format(arg+8); + get_commit_format(arg+8, revs); continue; } if (!strcmp(arg, "--root")) { diff --git a/revision.h b/revision.h index c8b3b948ec..31217f8c67 100644 --- a/revision.h +++ b/revision.h @@ -64,7 +64,8 @@ struct rev_info { /* Format info */ unsigned int shown_one:1, - abbrev_commit:1; + abbrev_commit:1, + use_terminator:1; enum date_mode date_mode; const char **ignore_packed; /* pretend objects in these are unpacked */ @@ -315,6 +315,44 @@ static int check_repository_format_gently(int *nongit_ok) } /* + * Try to read the location of the git directory from the .git file, + * return path to git directory if found. + */ +const char *read_gitfile_gently(const char *path) +{ + char *buf; + struct stat st; + int fd; + size_t len; + + if (stat(path, &st)) + return NULL; + if (!S_ISREG(st.st_mode)) + return NULL; + fd = open(path, O_RDONLY); + if (fd < 0) + die("Error opening %s: %s", path, strerror(errno)); + buf = xmalloc(st.st_size + 1); + len = read_in_full(fd, buf, st.st_size); + close(fd); + if (len != st.st_size) + die("Error reading %s", path); + buf[len] = '\0'; + if (prefixcmp(buf, "gitdir: ")) + die("Invalid gitfile format: %s", path); + while (buf[len - 1] == '\n' || buf[len - 1] == '\r') + len--; + if (len < 9) + die("No path in gitfile: %s", path); + buf[len] = '\0'; + if (!is_git_directory(buf + 8)) + die("Not a git repository: %s", buf + 8); + path = make_absolute_path(buf + 8); + free(buf); + return path; +} + +/* * We cannot decide in this function whether we are in the work tree or * not, since the config can only be read _after_ this function was called. */ @@ -323,6 +361,7 @@ const char *setup_git_directory_gently(int *nongit_ok) const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT); static char cwd[PATH_MAX+1]; const char *gitdirenv; + const char *gitfile_dir; int len, offset; /* @@ -377,8 +416,10 @@ const char *setup_git_directory_gently(int *nongit_ok) /* * Test in the following order (relative to the cwd): + * - .git (file containing "gitdir: <path>") * - .git/ * - ./ (bare) + * - ../.git * - ../.git/ * - ../ (bare) * - ../../.git/ @@ -386,6 +427,12 @@ const char *setup_git_directory_gently(int *nongit_ok) */ offset = len = strlen(cwd); for (;;) { + gitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); + if (gitfile_dir) { + if (set_git_dir(gitfile_dir)) + die("Repository setup failed"); + break; + } if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT)) break; if (is_git_directory(".")) { @@ -428,21 +475,53 @@ const char *setup_git_directory_gently(int *nongit_ok) int git_config_perm(const char *var, const char *value) { - if (value) { - int i; - if (!strcmp(value, "umask")) - return PERM_UMASK; - if (!strcmp(value, "group")) - return PERM_GROUP; - if (!strcmp(value, "all") || - !strcmp(value, "world") || - !strcmp(value, "everybody")) - return PERM_EVERYBODY; - i = atoi(value); - if (i > 1) - return i; + int i; + char *endptr; + + if (value == NULL) + return PERM_GROUP; + + if (!strcmp(value, "umask")) + return PERM_UMASK; + if (!strcmp(value, "group")) + return PERM_GROUP; + if (!strcmp(value, "all") || + !strcmp(value, "world") || + !strcmp(value, "everybody")) + return PERM_EVERYBODY; + + /* Parse octal numbers */ + i = strtol(value, &endptr, 8); + + /* If not an octal number, maybe true/false? */ + if (*endptr != 0) + return git_config_bool(var, value) ? PERM_GROUP : PERM_UMASK; + + /* + * Treat values 0, 1 and 2 as compatibility cases, otherwise it is + * a chmod value. + */ + switch (i) { + case PERM_UMASK: /* 0 */ + return PERM_UMASK; + case OLD_PERM_GROUP: /* 1 */ + return PERM_GROUP; + case OLD_PERM_EVERYBODY: /* 2 */ + return PERM_EVERYBODY; } - return git_config_bool(var, value); + + /* A filemode value was given: 0xxx */ + + if ((i & 0600) != 0600) + die("Problem with core.sharedRepository filemode value " + "(0%.3o).\nThe owner of files must always have " + "read and write permissions.", i); + + /* + * Mask filemode value. Others can not get write permission. + * x flags for directories are handled separately. + */ + return i & 0666; } int check_repository_format_version(const char *var, const char *value) diff --git a/sha1-lookup.c b/sha1-lookup.c new file mode 100644 index 0000000000..da357479cf --- /dev/null +++ b/sha1-lookup.c @@ -0,0 +1,171 @@ +#include "cache.h" +#include "sha1-lookup.h" + +/* + * Conventional binary search loop looks like this: + * + * unsigned lo, hi; + * do { + * unsigned mi = (lo + hi) / 2; + * int cmp = "entry pointed at by mi" minus "target"; + * if (!cmp) + * return (mi is the wanted one) + * if (cmp > 0) + * hi = mi; "mi is larger than target" + * else + * lo = mi+1; "mi is smaller than target" + * } while (lo < hi); + * + * The invariants are: + * + * - When entering the loop, lo points at a slot that is never + * above the target (it could be at the target), hi points at a + * slot that is guaranteed to be above the target (it can never + * be at the target). + * + * - We find a point 'mi' between lo and hi (mi could be the same + * as lo, but never can be as same as hi), and check if it hits + * the target. There are three cases: + * + * - if it is a hit, we are happy. + * + * - if it is strictly higher than the target, we set it to hi, + * and repeat the search. + * + * - if it is strictly lower than the target, we update lo to + * one slot after it, because we allow lo to be at the target. + * + * If the loop exits, there is no matching entry. + * + * When choosing 'mi', we do not have to take the "middle" but + * anywhere in between lo and hi, as long as lo <= mi < hi is + * satisfied. When we somehow know that the distance between the + * target and lo is much shorter than the target and hi, we could + * pick mi that is much closer to lo than the midway. + * + * Now, we can take advantage of the fact that SHA-1 is a good hash + * function, and as long as there are enough entries in the table, we + * can expect uniform distribution. An entry that begins with for + * example "deadbeef..." is much likely to appear much later than in + * the midway of the table. It can reasonably be expected to be near + * 87% (222/256) from the top of the table. + * + * However, we do not want to pick "mi" too precisely. If the entry at + * the 87% in the above example turns out to be higher than the target + * we are looking for, we would end up narrowing the search space down + * only by 13%, instead of 50% we would get if we did a simple binary + * search. So we would want to hedge our bets by being less aggressive. + * + * The table at "table" holds at least "nr" entries of "elem_size" + * bytes each. Each entry has the SHA-1 key at "key_offset". The + * table is sorted by the SHA-1 key of the entries. The caller wants + * to find the entry with "key", and knows that the entry at "lo" is + * not higher than the entry it is looking for, and that the entry at + * "hi" is higher than the entry it is looking for. + */ +int sha1_entry_pos(const void *table, + size_t elem_size, + size_t key_offset, + unsigned lo, unsigned hi, unsigned nr, + const unsigned char *key) +{ + const unsigned char *base = table; + const unsigned char *hi_key, *lo_key; + unsigned ofs_0; + static int debug_lookup = -1; + + if (debug_lookup < 0) + debug_lookup = !!getenv("GIT_DEBUG_LOOKUP"); + + if (!nr || lo >= hi) + return -1; + + if (nr == hi) + hi_key = NULL; + else + hi_key = base + elem_size * hi + key_offset; + lo_key = base + elem_size * lo + key_offset; + + ofs_0 = 0; + do { + int cmp; + unsigned ofs, mi, range; + unsigned lov, hiv, kyv; + const unsigned char *mi_key; + + range = hi - lo; + if (hi_key) { + for (ofs = ofs_0; ofs < 20; ofs++) + if (lo_key[ofs] != hi_key[ofs]) + break; + ofs_0 = ofs; + /* + * byte 0 thru (ofs-1) are the same between + * lo and hi; ofs is the first byte that is + * different. + */ + hiv = hi_key[ofs_0]; + if (ofs_0 < 19) + hiv = (hiv << 8) | hi_key[ofs_0+1]; + } else { + hiv = 256; + if (ofs_0 < 19) + hiv <<= 8; + } + lov = lo_key[ofs_0]; + kyv = key[ofs_0]; + if (ofs_0 < 19) { + lov = (lov << 8) | lo_key[ofs_0+1]; + kyv = (kyv << 8) | key[ofs_0+1]; + } + assert(lov < hiv); + + if (kyv < lov) + return -1 - lo; + if (hiv < kyv) + return -1 - hi; + + /* + * Even if we know the target is much closer to 'hi' + * than 'lo', if we pick too precisely and overshoot + * (e.g. when we know 'mi' is closer to 'hi' than to + * 'lo', pick 'mi' that is higher than the target), we + * end up narrowing the search space by a smaller + * amount (i.e. the distance between 'mi' and 'hi') + * than what we would have (i.e. about half of 'lo' + * and 'hi'). Hedge our bets to pick 'mi' less + * aggressively, i.e. make 'mi' a bit closer to the + * middle than we would otherwise pick. + */ + kyv = (kyv * 6 + lov + hiv) / 8; + if (lov < hiv - 1) { + if (kyv == lov) + kyv++; + else if (kyv == hiv) + kyv--; + } + mi = (range - 1) * (kyv - lov) / (hiv - lov) + lo; + + if (debug_lookup) { + printf("lo %u hi %u rg %u mi %u ", lo, hi, range, mi); + printf("ofs %u lov %x, hiv %x, kyv %x\n", + ofs_0, lov, hiv, kyv); + } + if (!(lo <= mi && mi < hi)) + die("assertion failure lo %u mi %u hi %u %s", + lo, mi, hi, sha1_to_hex(key)); + + mi_key = base + elem_size * mi + key_offset; + cmp = memcmp(mi_key + ofs_0, key + ofs_0, 20 - ofs_0); + if (!cmp) + return mi; + if (cmp > 0) { + hi = mi; + hi_key = mi_key; + } else { + lo = mi + 1; + lo_key = mi_key + elem_size; + } + } while (lo < hi); + return -lo-1; +} diff --git a/sha1-lookup.h b/sha1-lookup.h new file mode 100644 index 0000000000..3249a81b3d --- /dev/null +++ b/sha1-lookup.h @@ -0,0 +1,9 @@ +#ifndef SHA1_LOOKUP_H +#define SHA1_LOOKUP_H + +extern int sha1_entry_pos(const void *table, + size_t elem_size, + size_t key_offset, + unsigned lo, unsigned hi, unsigned nr, + const unsigned char *key); +#endif diff --git a/sha1_file.c b/sha1_file.c index 445a871db3..3516777bc7 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -15,6 +15,7 @@ #include "tree.h" #include "refs.h" #include "pack-revindex.h" +#include "sha1-lookup.h" #ifndef O_NOATIME #if defined(__linux__) && (defined(__i386__) || defined(__PPC__)) @@ -1675,7 +1676,12 @@ off_t find_pack_entry_one(const unsigned char *sha1, { const uint32_t *level1_ofs = p->index_data; const unsigned char *index = p->index_data; - unsigned hi, lo; + unsigned hi, lo, stride; + static int use_lookup = -1; + static int debug_lookup = -1; + + if (debug_lookup < 0) + debug_lookup = !!getenv("GIT_DEBUG_LOOKUP"); if (!index) { if (open_pack_index(p)) @@ -1690,11 +1696,34 @@ off_t find_pack_entry_one(const unsigned char *sha1, index += 4 * 256; hi = ntohl(level1_ofs[*sha1]); lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1])); + if (p->index_version > 1) { + stride = 20; + } else { + stride = 24; + index += 4; + } + + if (debug_lookup) + printf("%02x%02x%02x... lo %u hi %u nr %u\n", + sha1[0], sha1[1], sha1[2], lo, hi, p->num_objects); + + if (use_lookup < 0) + use_lookup = !!getenv("GIT_USE_LOOKUP"); + if (use_lookup) { + int pos = sha1_entry_pos(index, stride, 0, + lo, hi, p->num_objects, sha1); + if (pos < 0) + return 0; + return nth_packed_object_offset(p, pos); + } do { unsigned mi = (lo + hi) / 2; - unsigned x = (p->index_version > 1) ? (mi * 20) : (mi * 24 + 4); - int cmp = hashcmp(index + x, sha1); + int cmp = hashcmp(index + mi * stride, sha1); + + if (debug_lookup) + printf("lo %u hi %u rg %u mi %u\n", + lo, hi, hi - lo, mi); if (!cmp) return nth_packed_object_offset(p, mi); if (cmp > 0) @@ -2437,16 +2466,10 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write int read_pack_header(int fd, struct pack_header *header) { - char *c = (char*)header; - ssize_t remaining = sizeof(struct pack_header); - do { - ssize_t r = xread(fd, c, remaining); - if (r <= 0) - /* "eof before pack header was fully read" */ - return PH_ERROR_EOF; - remaining -= r; - c += r; - } while (remaining > 0); + if (read_in_full(fd, header, sizeof(*header)) < sizeof(*header)) + /* "eof before pack header was fully read" */ + return PH_ERROR_EOF; + if (header->hdr_signature != htonl(PACK_SIGNATURE)) /* "protocol error (pack signature mismatch detected)" */ return PH_ERROR_PACK_SIGNATURE; diff --git a/sha1_name.c b/sha1_name.c index 491d2e7ebf..b0b2167578 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -351,8 +351,11 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) } if (0 <= nth) at_time = 0; - else - at_time = approxidate(str + at + 2); + else { + char *tmp = xstrndup(str + at + 2, reflog_len); + at_time = approxidate(tmp); + free(tmp); + } if (read_ref_at(real_ref, at_time, nth, sha1, NULL, &co_time, &co_tz, &co_cnt)) { if (at_time) diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh new file mode 100755 index 0000000000..c5dbc724b6 --- /dev/null +++ b/t/t0002-gitfile.sh @@ -0,0 +1,103 @@ +#!/bin/sh + +test_description='.git file + +Verify that plumbing commands work when .git is a file +' +. ./test-lib.sh + +objpath() { + echo "$1" | sed -e 's|\(..\)|\1/|' +} + +objck() { + p=$(objpath "$1") + if test ! -f "$REAL/objects/$p" + then + echo "Object not found: $REAL/objects/$p" + false + fi +} + + +test_expect_success 'initial setup' ' + REAL="$(pwd)/.real" && + mv .git "$REAL" +' + +test_expect_success 'bad setup: invalid .git file format' ' + echo "gitdir $REAL" >.git && + if git rev-parse 2>.err + then + echo "git rev-parse accepted an invalid .git file" + false + fi && + if ! grep -qe "Invalid gitfile format" .err + then + echo "git rev-parse returned wrong error" + false + fi +' + +test_expect_success 'bad setup: invalid .git file path' ' + echo "gitdir: $REAL.not" >.git && + if git rev-parse 2>.err + then + echo "git rev-parse accepted an invalid .git file path" + false + fi && + if ! grep -qe "Not a git repository" .err + then + echo "git rev-parse returned wrong error" + false + fi +' + +test_expect_success 'final setup + check rev-parse --git-dir' ' + echo "gitdir: $REAL" >.git && + test "$REAL" = "$(git rev-parse --git-dir)" +' + +test_expect_success 'check hash-object' ' + echo "foo" >bar && + SHA=$(cat bar | git hash-object -w --stdin) && + objck $SHA +' + +test_expect_success 'check cat-file' ' + git cat-file blob $SHA >actual && + diff -u bar actual +' + +test_expect_success 'check update-index' ' + if test -f "$REAL/index" + then + echo "Hmm, $REAL/index exists?" + false + fi && + rm -f "$REAL/objects/$(objpath $SHA)" && + git update-index --add bar && + if ! test -f "$REAL/index" + then + echo "$REAL/index not found" + false + fi && + objck $SHA +' + +test_expect_success 'check write-tree' ' + SHA=$(git write-tree) && + objck $SHA +' + +test_expect_success 'check commit-tree' ' + SHA=$(echo "commit bar" | git commit-tree $SHA) && + objck $SHA +' + +test_expect_success 'check rev-list' ' + echo $SHA >"$REAL/HEAD" && + test "$SHA" = "$(git rev-list HEAD)" +' + +test_done diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index 3faf135e38..c56d2fbaba 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -21,6 +21,7 @@ test_expect_success 'setup' ' mkdir -p a/b/d a/c && ( echo "f test=f" + echo "a/i test=a/i" ) >.gitattributes && ( echo "g test=a/g" && @@ -46,4 +47,11 @@ test_expect_success 'attribute test' ' ' +test_expect_success 'root subdir attribute test' ' + + attr_check a/i a/i && + attr_check subdir/a/i unspecified + +' + test_done diff --git a/t/t0004-unwritable.sh b/t/t0004-unwritable.sh new file mode 100755 index 0000000000..9255c63c08 --- /dev/null +++ b/t/t0004-unwritable.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +test_description='detect unwritable repository and fail correctly' + +. ./test-lib.sh + +test_expect_success setup ' + + >file && + git add file && + git commit -m initial && + echo >file && + git add file + +' + +test_expect_success 'write-tree should notice unwritable repository' ' + + ( + chmod a-w .git/objects + test_must_fail git write-tree + ) + status=$? + chmod 775 .git/objects + (exit $status) + +' + +test_expect_success 'commit should notice unwritable repository' ' + + ( + chmod a-w .git/objects + test_must_fail git commit -m second + ) + status=$? + chmod 775 .git/objects + (exit $status) + +' + +test_expect_success 'update-index should notice unwritable repository' ' + + ( + echo a >file && + chmod a-w .git/objects + test_must_fail git update-index file + ) + status=$? + chmod 775 .git/objects + (exit $status) + +' + +test_expect_success 'add should notice unwritable repository' ' + + ( + echo b >file && + chmod a-w .git/objects + test_must_fail git add file + ) + status=$? + chmod 775 .git/objects + (exit $status) + +' + +test_done diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index b36a9012ec..a675cbb51b 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -595,6 +595,64 @@ test_expect_success 'set --int' ' rm .git/config +cat >expect <<\EOF +[bool] + true1 = true + true2 = true + false1 = false + false2 = false +[int] + int1 = 0 + int2 = 1 + int3 = -1 +EOF + +test_expect_success 'get --bool-or-int' ' + ( + echo "[bool]" + echo true1 + echo true2 = true + echo false = false + echo "[int]" + echo int1 = 0 + echo int2 = 1 + echo int3 = -1 + ) >>.git/config && + test $(git config --bool-or-int bool.true1) = true && + test $(git config --bool-or-int bool.true2) = true && + test $(git config --bool-or-int bool.false) = false && + test $(git config --bool-or-int int.int1) = 0 && + test $(git config --bool-or-int int.int2) = 1 && + test $(git config --bool-or-int int.int3) = -1 + +' + +rm .git/config +cat >expect <<\EOF +[bool] + true1 = true + false1 = false + true2 = true + false2 = false +[int] + int1 = 0 + int2 = 1 + int3 = -1 +EOF + +test_expect_success 'set --bool-or-int' ' + git config --bool-or-int bool.true1 true && + git config --bool-or-int bool.false1 false && + git config --bool-or-int bool.true2 yes && + git config --bool-or-int bool.false2 no && + git config --bool-or-int int.int1 0 && + git config --bool-or-int int.int2 1 && + git config --bool-or-int int.int3 -1 && + test_cmp expect .git/config +' + +rm .git/config + git config quote.leading " test" git config quote.ending "test " git config quote.semicolon "test;test" diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh index 6bfe19a4e5..5e4252a320 100755 --- a/t/t1301-shared-repo.sh +++ b/t/t1301-shared-repo.sh @@ -7,6 +7,16 @@ test_description='Test shared repository initialization' . ./test-lib.sh +# User must have read permissions to the repo -> failure on --shared=0400 +test_expect_success 'shared = 0400 (faulty permission u-w)' ' + mkdir sub && ( + cd sub && git init --shared=0400 + ) + ret="$?" + rm -rf sub + test $ret != "0" +' + test_expect_success 'shared=all' ' mkdir sub && cd sub && @@ -33,4 +43,44 @@ test_expect_success 'update-server-info honors core.sharedRepository' ' esac ' +for u in 0660:rw-rw---- \ + 0640:rw-r----- \ + 0600:rw------- \ + 0666:rw-rw-rw- \ + 0664:rw-rw-r-- +do + x=$(expr "$u" : ".*:\([rw-]*\)") && + y=$(echo "$x" | sed -e "s/w/-/g") && + u=$(expr "$u" : "\([0-7]*\)") && + git config core.sharedrepository "$u" && + umask 0277 && + + test_expect_success "shared = $u ($y) ro" ' + + rm -f .git/info/refs && + git update-server-info && + actual="$(ls -l .git/info/refs)" && + actual=${actual%% *} && + test "x$actual" = "x-$y" || { + ls -lt .git/info + false + } + ' + + umask 077 && + test_expect_success "shared = $u ($x) rw" ' + + rm -f .git/info/refs && + git update-server-info && + actual="$(ls -l .git/info/refs)" && + actual=${actual%% *} && + test "x$actual" = "x-$x" || { + ls -lt .git/info + false + } + + ' + +done + test_done diff --git a/t/t2002-checkout-cache-u.sh b/t/t2002-checkout-cache-u.sh index 0f441bcef7..70361c806e 100755 --- a/t/t2002-checkout-cache-u.sh +++ b/t/t2002-checkout-cache-u.sh @@ -21,13 +21,13 @@ test_expect_success \ rm -f path0 && git read-tree $t && git checkout-index -f -a && -! git diff-files | diff - /dev/null' +test_must_fail git diff-files --exit-code' test_expect_success \ 'with -u, git checkout-index picks up stat information from new files.' ' rm -f path0 && git read-tree $t && git checkout-index -u -f -a && -git diff-files | diff - /dev/null' +git diff-files --exit-code' test_done diff --git a/t/t3201-branch-contains.sh b/t/t3201-branch-contains.sh index b4cf628d22..f86f4bc5eb 100755 --- a/t/t3201-branch-contains.sh +++ b/t/t3201-branch-contains.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='branch --contains <commit>' +test_description='branch --contains <commit>, --merged, and --no-merged' . ./test-lib.sh @@ -55,4 +55,44 @@ test_expect_success 'branch --contains=side' ' ' +test_expect_success 'side: branch --merged' ' + + git branch --merged >actual && + { + echo " master" && + echo "* side" + } >expect && + test_cmp expect actual + +' + +test_expect_success 'side: branch --no-merged' ' + + git branch --no-merged >actual && + >expect && + test_cmp expect actual + +' + +test_expect_success 'master: branch --merged' ' + + git checkout master && + git branch --merged >actual && + { + echo "* master" + } >expect && + test_cmp expect actual + +' + +test_expect_success 'master: branch --no-merged' ' + + git branch --no-merged >actual && + { + echo " side" + } >expect && + test_cmp expect actual + +' + test_done diff --git a/t/t3408-rebase-multi-line.sh b/t/t3408-rebase-multi-line.sh new file mode 100755 index 0000000000..e12cd578e8 --- /dev/null +++ b/t/t3408-rebase-multi-line.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +test_description='rebasing a commit with multi-line first paragraph.' + +. ./test-lib.sh + +test_expect_success setup ' + + >file && + git add file && + test_tick && + git commit -m initial && + + echo hello >file && + test_tick && + git commit -a -m "A sample commit log message that has a long +summary that spills over multiple lines. + +But otherwise with a sane description." + + git branch side && + + git reset --hard HEAD^ && + >elif && + git add elif && + test_tick && + git commit -m second + +' + +test_expect_success rebase ' + + git checkout side && + git rebase master && + git cat-file commit HEAD | sed -e "1,/^$/d" >actual && + git cat-file commit side@{1} | sed -e "1,/^$/d" >expect && + test_cmp expect actual + +' + +test_done diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index dca2067b2d..fa62b6aa21 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -109,9 +109,10 @@ test_expect_success \ 'diff -r a c/prefix/a' test_expect_success \ - 'create an archive with a substfiles' \ + 'create archives with substfiles' \ 'echo "substfile?" export-subst >a/.gitattributes && git archive HEAD >f.tar && + git archive --prefix=prefix/ HEAD >g.tar && rm a/.gitattributes' test_expect_success \ @@ -127,6 +128,18 @@ test_expect_success \ ' test_expect_success \ + 'extract substfiles from archive with prefix' \ + '(mkdir g && cd g && $TAR xf -) <g.tar' + +test_expect_success \ + 'validate substfile contents from archive with prefix' \ + 'git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \ + >g/prefix/a/substfile1.expected && + diff g/prefix/a/substfile1.expected g/prefix/a/substfile1 && + diff a/substfile2 g/prefix/a/substfile2 +' + +test_expect_success \ 'git archive --format=zip' \ 'git archive --format=zip HEAD >d.zip' diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 0a7fea865d..48ff2d424d 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -77,6 +77,16 @@ test_expect_success 'add another remote' ' ) ' +test_expect_success 'remote forces tracking branches' ' +( + cd test && + case `git config remote.second.fetch` in + +*) true ;; + *) false ;; + esac +) +' + test_expect_success 'remove remote' ' ( cd test && @@ -253,4 +263,10 @@ test_expect_success '"remote show" does not show symbolic refs' ' ' +test_expect_success 'reject adding remote with an invalid name' ' + + ! git remote add some:url desired-name + +' + test_done diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 793ffc6600..0a757d5b9f 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -103,9 +103,9 @@ test_expect_success 'fetch with wildcard' ' test_expect_success 'fetch with insteadOf' ' mk_empty && ( - TRASH=$(pwd) && + TRASH=$(pwd)/ && cd testrepo && - git config url./$TRASH/.insteadOf trash/ + git config url.$TRASH.insteadOf trash/ git config remote.up.url trash/. && git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" && git fetch up && @@ -145,8 +145,8 @@ test_expect_success 'push with wildcard' ' test_expect_success 'push with insteadOf' ' mk_empty && - TRASH=$(pwd) && - git config url./$TRASH/.insteadOf trash/ && + TRASH=$(pwd)/ && + git config url.$TRASH.insteadOf trash/ && git push trash/testrepo refs/heads/master:refs/remotes/origin/master && ( cd testrepo && @@ -209,19 +209,7 @@ test_expect_success 'push with weak ambiguity (2)' ' ' -test_expect_success 'push with ambiguity (1)' ' - - mk_test remotes/origin/master remotes/frotz/master && - if git push testrepo master:master - then - echo "Oops, should have failed" - false - else - check_push_result $the_first_commit remotes/origin/master remotes/frotz/master - fi -' - -test_expect_success 'push with ambiguity (2)' ' +test_expect_success 'push with ambiguity' ' mk_test heads/frotz tags/frotz && if git push testrepo master:frotz @@ -285,6 +273,37 @@ test_expect_success 'push with colon-less refspec (4)' ' ' +test_expect_success 'push head with non-existant, incomplete dest' ' + + mk_test && + git push testrepo master:branch && + check_push_result $the_commit heads/branch + +' + +test_expect_success 'push tag with non-existant, incomplete dest' ' + + mk_test && + git tag -f v1.0 && + git push testrepo v1.0:tag && + check_push_result $the_commit tags/tag + +' + +test_expect_success 'push sha1 with non-existant, incomplete dest' ' + + mk_test && + test_must_fail git push testrepo `git rev-parse master`:foo + +' + +test_expect_success 'push ref expression with non-existant, incomplete dest' ' + + mk_test && + test_must_fail git push testrepo master^:branch + +' + test_expect_success 'push with HEAD' ' mk_test heads/master && @@ -323,6 +342,15 @@ test_expect_success 'push with +HEAD' ' ' +test_expect_success 'push HEAD with non-existant, incomplete dest' ' + + mk_test && + git checkout master && + git push testrepo HEAD:branch && + check_push_result $the_commit heads/branch + +' + test_expect_success 'push with config remote.*.push = HEAD' ' mk_test heads/local && diff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh index ed3fec192a..ea49dedbf8 100755 --- a/t/t5517-push-mirror.sh +++ b/t/t5517-push-mirror.sh @@ -25,7 +25,7 @@ mk_repo_pair () { ( cd master && git init && - git config remote.up.url ../mirror + git remote add $1 up ../mirror ) } @@ -225,4 +225,43 @@ test_expect_success 'push mirror adds, updates and removes tags together' ' ' +test_expect_success 'remote.foo.mirror adds and removes branches' ' + + mk_repo_pair --mirror && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git branch keep master && + git branch remove master && + git push up && + git branch -D remove + git push up + ) && + ( + cd mirror && + git show-ref -s --verify refs/heads/keep && + invert git show-ref -s --verify refs/heads/remove + ) + +' + +test_expect_success 'remote.foo.mirror=no has no effect' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git config --add remote.up.mirror no && + git branch keep master && + git push --mirror up && + git branch -D keep && + git push up + ) && + ( + cd mirror && + git show-ref -s --verify refs/heads/keep + ) + +' + test_done diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh new file mode 100755 index 0000000000..dc9d63dbf9 --- /dev/null +++ b/t/t5601-clone.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +test_description=clone + +. ./test-lib.sh + +test_expect_success setup ' + + rm -fr .git && + test_create_repo src && + ( + cd src + >file + git add file + git commit -m initial + ) + +' + +test_expect_success 'clone with excess parameters' ' + + test_must_fail git clone -n "file://$(pwd)/src" dst junk + +' + +test_done diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index f471c1526f..5e3e5445c7 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -71,6 +71,24 @@ test_expect_success 'bisect start with one bad and good' ' git bisect next ' +test_expect_success 'bisect fails if given any junk instead of revs' ' + git bisect reset && + test_must_fail git bisect start foo $HASH1 -- && + test_must_fail git bisect start $HASH4 $HASH1 bar -- && + test -z "$(git for-each-ref "refs/bisect/*")" && + test_must_fail ls .git/BISECT_* && + git bisect start && + test_must_fail git bisect good foo $HASH1 && + test_must_fail git bisect good $HASH1 bar && + test_must_fail git bisect bad frotz && + test_must_fail git bisect bad $HASH3 $HASH4 && + test_must_fail git bisect skip bar $HASH3 && + test_must_fail git bisect skip $HASH1 foo && + test -z "$(git for-each-ref "refs/bisect/*")" && + git bisect good $HASH1 && + git bisect bad $HASH4 +' + test_expect_success 'bisect reset: back in the master branch' ' git bisect reset && echo "* master" > branch.expect && diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh index 526d7d1c44..bd4e49bf1e 100755 --- a/t/t6200-fmt-merge-msg.sh +++ b/t/t6200-fmt-merge-msg.sh @@ -106,8 +106,24 @@ Merge branch 'left' Common #1 EOF -test_expect_success 'merge-msg test #3' ' +test_expect_success 'merge-msg test #3-1' ' + git config --unset-all merge.log + git config --unset-all merge.summary + git config merge.log true && + + git checkout master && + setdate && + git fetch . left && + + git fmt-merge-msg <.git/FETCH_HEAD >actual && + git diff actual expected +' + +test_expect_success 'merge-msg test #3-2' ' + + git config --unset-all merge.log + git config --unset-all merge.summary git config merge.summary true && git checkout master && @@ -136,8 +152,24 @@ Merge branches 'left' and 'right' Common #1 EOF -test_expect_success 'merge-msg test #4' ' +test_expect_success 'merge-msg test #4-1' ' + + git config --unset-all merge.log + git config --unset-all merge.summary + git config merge.log true && + + git checkout master && + setdate && + git fetch . left right && + + git fmt-merge-msg <.git/FETCH_HEAD >actual && + git diff actual expected +' + +test_expect_success 'merge-msg test #4-2' ' + git config --unset-all merge.log + git config --unset-all merge.summary git config merge.summary true && git checkout master && @@ -148,8 +180,24 @@ test_expect_success 'merge-msg test #4' ' git diff actual expected ' -test_expect_success 'merge-msg test #5' ' +test_expect_success 'merge-msg test #5-1' ' + + git config --unset-all merge.log + git config --unset-all merge.summary + git config merge.log yes && + + git checkout master && + setdate && + git fetch . left right && + + git fmt-merge-msg <.git/FETCH_HEAD >actual && + git diff actual expected +' + +test_expect_success 'merge-msg test #5-2' ' + git config --unset-all merge.log + git config --unset-all merge.summary git config merge.summary yes && git checkout master && diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index efd658adb6..16df3d4adb 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -219,4 +219,36 @@ test_expect_success 'Subdirectory filter with disappearing trees' ' test $(git rev-list master | wc -l) = 3 ' +test_expect_success 'Tag name filtering retains tag message' ' + git tag -m atag T && + git cat-file tag T > expect && + git filter-branch -f --tag-name-filter cat && + git cat-file tag T > actual && + git diff expect actual +' + +faux_gpg_tag='object XXXXXX +type commit +tag S +tagger T A Gger <tagger@example.com> 1206026339 -0500 + +This is a faux gpg signed tag. +-----BEGIN PGP SIGNATURE----- +Version: FauxGPG v0.0.0 (FAUX/Linux) + +gdsfoewhxu/6l06f1kxyxhKdZkrcbaiOMtkJUA9ITAc1mlamh0ooasxkH1XwMbYQ +acmwXaWET20H0GeAGP+7vow= +=agpO +-----END PGP SIGNATURE----- +' +test_expect_success 'Tag name filtering strips gpg signature' ' + sha1=$(git rev-parse HEAD) && + sha1t=$(echo "$faux_gpg_tag" | sed -e s/XXXXXX/$sha1/ | git mktag) && + git update-ref "refs/tags/S" "$sha1t" && + echo "$faux_gpg_tag" | sed -e s/XXXXXX/$sha1/ | head -n 6 > expect && + git filter-branch -f --tag-name-filter cat && + git cat-file tag S > actual && + git diff expect actual +' + test_done diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index afccfc9973..a50492f7c0 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -75,8 +75,8 @@ test_expect_success 'git-clean src/ src/' ' test_expect_success 'git-clean with prefix' ' - mkdir -p build docs && - touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + mkdir -p build docs src/test && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so src/test/1.c && (cd src/ && git-clean) && test -f Makefile && test -f README && @@ -84,6 +84,7 @@ test_expect_success 'git-clean with prefix' ' test -f src/part2.c && test -f a.out && test ! -f src/part3.c && + test -f src/test/1.c && test -f docs/manual.txt && test -f obj.o && test -f build/lib.so diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh index 0f3c42ab35..bf12dbdeef 100755 --- a/t/t7401-submodule-summary.sh +++ b/t/t7401-submodule-summary.sh @@ -30,7 +30,7 @@ commit_file () { } test_create_repo sm1 && -add_file . foo +add_file . foo >/dev/null head1=$(add_file sm1 foo1 foo2) @@ -192,4 +192,17 @@ test_expect_success 'given commit' " EOF " +test_expect_success '--for-status' " + git submodule summary --for-status HEAD^ >actual && + test_cmp actual - <<EOF +# Modified submodules: +# +# * sm1 $head6...0000000: +# +# * sm2 0000000...$head7 (2): +# > Add foo9 +# +EOF +" + test_done diff --git a/t/t7502-status.sh b/t/t7502-status.sh index cd08516e6d..e4bfcaece0 100755 --- a/t/t7502-status.sh +++ b/t/t7502-status.sh @@ -149,4 +149,138 @@ test_expect_success 'status of partial commit excluding new file in index' ' test_cmp expect output ' +test_expect_success 'setup status submodule summary' ' + test_create_repo sm && ( + cd sm && + >foo && + git add foo && + git commit -m "Add foo" + ) && + git add sm +' + +cat >expect <<EOF +# On branch master +# Changes to be committed: +# (use "git reset HEAD <file>..." to unstage) +# +# new file: dir2/added +# new file: sm +# +# Changed but not updated: +# (use "git add <file>..." to update what will be committed) +# +# modified: dir1/modified +# +# Untracked files: +# (use "git add <file>..." to include in what will be committed) +# +# dir1/untracked +# dir2/modified +# dir2/untracked +# expect +# output +# untracked +EOF +test_expect_success 'status submodule summary is disabled by default' ' + git status >output && + test_cmp expect output +' + +head=$(cd sm && git rev-parse --short=7 --verify HEAD) + +cat >expect <<EOF +# On branch master +# Changes to be committed: +# (use "git reset HEAD <file>..." to unstage) +# +# new file: dir2/added +# new file: sm +# +# Changed but not updated: +# (use "git add <file>..." to update what will be committed) +# +# modified: dir1/modified +# +# Modified submodules: +# +# * sm 0000000...$head (1): +# > Add foo +# +# Untracked files: +# (use "git add <file>..." to include in what will be committed) +# +# dir1/untracked +# dir2/modified +# dir2/untracked +# expect +# output +# untracked +EOF +test_expect_success 'status submodule summary' ' + git config status.submodulesummary 10 && + git status >output && + test_cmp expect output +' + + +cat >expect <<EOF +# On branch master +# Changed but not updated: +# (use "git add <file>..." to update what will be committed) +# +# modified: dir1/modified +# +# Untracked files: +# (use "git add <file>..." to include in what will be committed) +# +# dir1/untracked +# dir2/modified +# dir2/untracked +# expect +# output +# untracked +no changes added to commit (use "git add" and/or "git commit -a") +EOF +test_expect_success 'status submodule summary (clean submodule)' ' + git commit -m "commit submodule" && + git config status.submodulesummary 10 && + test_must_fail git status >output && + test_cmp expect output +' + +cat >expect <<EOF +# On branch master +# Changes to be committed: +# (use "git reset HEAD^1 <file>..." to unstage) +# +# new file: dir2/added +# new file: sm +# +# Changed but not updated: +# (use "git add <file>..." to update what will be committed) +# +# modified: dir1/modified +# +# Modified submodules: +# +# * sm 0000000...$head (1): +# > Add foo +# +# Untracked files: +# (use "git add <file>..." to include in what will be committed) +# +# dir1/untracked +# dir2/modified +# dir2/untracked +# expect +# output +# untracked +EOF +test_expect_success 'status submodule summary (--amend)' ' + git config status.submodulesummary 10 && + git status --amend >output && + test_cmp expect output +' + test_done diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 56869aceed..d21cd290d3 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -104,7 +104,11 @@ create_merge_msgs() { git log --no-merges ^HEAD c2 >>squash.1-5 && echo "Squashed commit of the following:" >squash.1-5-9 && echo >>squash.1-5-9 && - git log --no-merges ^HEAD c2 c3 >>squash.1-5-9 + git log --no-merges ^HEAD c2 c3 >>squash.1-5-9 && + echo > msg.nolog && + echo "* commit 'c3':" >msg.log && + echo " commit 3" >>msg.log && + echo >>msg.log } verify_diff() { @@ -364,7 +368,7 @@ test_expect_success 'merge c1 with c2 (squash in config)' ' test_debug 'gitk --all' -test_expect_success 'override config option -n' ' +test_expect_success 'override config option -n with --summary' ' git reset --hard c1 && git config branch.master.mergeoptions "-n" && test_tick && @@ -373,15 +377,30 @@ test_expect_success 'override config option -n' ' verify_parents $c1 $c2 && if ! grep "^ file | *2 +-$" diffstat.txt then - echo "[OOPS] diffstat was not generated" + echo "[OOPS] diffstat was not generated with --summary" + false + fi +' + +test_expect_success 'override config option -n with --stat' ' + git reset --hard c1 && + git config branch.master.mergeoptions "-n" && + test_tick && + git merge --stat c2 >diffstat.txt && + verify_merge file result.1-5 msg.1-5 && + verify_parents $c1 $c2 && + if ! grep "^ file | *2 +-$" diffstat.txt + then + echo "[OOPS] diffstat was not generated with --stat" + false fi ' test_debug 'gitk --all' -test_expect_success 'override config option --summary' ' +test_expect_success 'override config option --stat' ' git reset --hard c1 && - git config branch.master.mergeoptions "--summary" && + git config branch.master.mergeoptions "--stat" && test_tick && git merge -n c2 >diffstat.txt && verify_merge file result.1-5 msg.1-5 && @@ -441,6 +460,16 @@ test_expect_success 'merge c0 with c1 (ff overrides no-ff)' ' verify_head $c1 ' +test_expect_success 'merge log message' ' + git reset --hard c0 && + git merge --no-log c2 && + git show -s --pretty=format:%b HEAD >msg.act && + verify_diff msg.nolog msg.act "[OOPS] bad merge log message" && + git merge --log c3 && + git show -s --pretty=format:%b HEAD >msg.act && + verify_diff msg.log msg.act "[OOPS] bad merge log message" +' + test_debug 'gitk --all' test_done diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 796cd7dba0..061a2596d3 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -483,6 +483,22 @@ test_expect_success \ 'gitweb_run "p=.git;a=history;f=file"' test_debug 'cat gitweb.log' +test_expect_success \ + 'logs: history (implicit HEAD, non-existent file)' \ + 'gitweb_run "p=.git;a=history;f=non-existent"' +test_debug 'cat gitweb.log' + +test_expect_success \ + 'logs: history (implicit HEAD, deleted file)' \ + 'git checkout master && + echo "to be deleted" > deleted_file && + git add deleted_file && + git commit -m "Add file to be deleted" && + git rm deleted_file && + git commit -m "Delete file" && + gitweb_run "p=.git;a=history;f=deleted_file"' +test_debug 'cat gitweb.log' + # ---------------------------------------------------------------------- # feed generation diff --git a/transport.c b/transport.c index 393e0e8fe2..b012a28338 100644 --- a/transport.c +++ b/transport.c @@ -441,10 +441,14 @@ static struct ref *get_refs_via_curl(struct transport *transport) struct ref *ref = NULL; struct ref *last_ref = NULL; + struct walker *walker; + if (!transport->data) transport->data = get_http_walker(transport->url, transport->remote); + walker = transport->data; + refs_url = xmalloc(strlen(transport->url) + 11); sprintf(refs_url, "%s/info/refs", transport->url); @@ -500,6 +504,16 @@ static struct ref *get_refs_via_curl(struct transport *transport) strbuf_release(&buffer); + ref = alloc_ref(strlen("HEAD") + 1); + strcpy(ref->name, "HEAD"); + if (!walker->fetch_ref(walker, ref) && + !resolve_remote_symref(ref, refs)) { + ref->next = refs; + refs = ref; + } else { + free(ref); + } + return refs; } @@ -190,9 +190,14 @@ static int interpret_target(struct walker *walker, char *target, unsigned char * if (!get_sha1_hex(target, sha1)) return 0; if (!check_ref_format(target)) { - if (!walker->fetch_ref(walker, target, sha1)) { + struct ref *ref = alloc_ref(strlen(target)); + strcpy(ref->name, target); + if (!walker->fetch_ref(walker, ref)) { + hashcpy(sha1, ref->old_sha1); + free(ref); return 0; } + free(ref); } return -1; } @@ -5,7 +5,7 @@ struct walker { void *data; - int (*fetch_ref)(struct walker *, char *ref, unsigned char *sha1); + int (*fetch_ref)(struct walker *, struct ref *ref); void (*prefetch)(struct walker *, unsigned char *sha1); int (*fetch)(struct walker *, unsigned char *sha1); void (*cleanup)(struct walker *); diff --git a/write_or_die.c b/write_or_die.c index e125e11d3b..32f9914020 100644 --- a/write_or_die.c +++ b/write_or_die.c @@ -40,7 +40,7 @@ void maybe_flush_or_die(FILE *f, const char *desc) } } -int read_in_full(int fd, void *buf, size_t count) +ssize_t read_in_full(int fd, void *buf, size_t count) { char *p = buf; ssize_t total = 0; @@ -57,7 +57,7 @@ int read_in_full(int fd, void *buf, size_t count) return total; } -int write_in_full(int fd, const void *buf, size_t count) +ssize_t write_in_full(int fd, const void *buf, size_t count) { const char *p = buf; ssize_t total = 0; diff --git a/wt-status.c b/wt-status.c index b3fd57b79d..532b4ea2c1 100644 --- a/wt-status.c +++ b/wt-status.c @@ -8,9 +8,11 @@ #include "revision.h" #include "diffcore.h" #include "quote.h" +#include "run-command.h" int wt_status_relative_paths = 1; int wt_status_use_color = -1; +int wt_status_submodule_summary; static char wt_status_colors[][COLOR_MAXLEN] = { "", /* WT_STATUS_HEADER: normal */ "\033[32m", /* WT_STATUS_UPDATED: green */ @@ -220,6 +222,36 @@ static void wt_status_print_changed(struct wt_status *s) run_diff_files(&rev, 0); } +static void wt_status_print_submodule_summary(struct wt_status *s) +{ + struct child_process sm_summary; + char summary_limit[64]; + char index[PATH_MAX]; + const char *env[] = { index, NULL }; + const char *argv[] = { + "submodule", + "summary", + "--cached", + "--for-status", + "--summary-limit", + summary_limit, + s->amend ? "HEAD^" : "HEAD", + NULL + }; + + sprintf(summary_limit, "%d", wt_status_submodule_summary); + snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file); + + memset(&sm_summary, 0, sizeof(sm_summary)); + sm_summary.argv = argv; + sm_summary.env = env; + sm_summary.git_cmd = 1; + sm_summary.no_stdin = 1; + fflush(s->fp); + sm_summary.out = dup(fileno(s->fp)); /* run_command closes it */ + run_command(&sm_summary); +} + static void wt_status_print_untracked(struct wt_status *s) { struct dir_struct dir; @@ -308,6 +340,8 @@ void wt_status_print(struct wt_status *s) } wt_status_print_changed(s); + if (wt_status_submodule_summary) + wt_status_print_submodule_summary(s); wt_status_print_untracked(s); if (s->verbose && !s->is_initial) @@ -330,6 +364,13 @@ void wt_status_print(struct wt_status *s) int git_status_config(const char *k, const char *v) { + if (!strcmp(k, "status.submodulesummary")) { + int is_bool; + wt_status_submodule_summary = git_config_bool_or_int(k, v, &is_bool); + if (is_bool && wt_status_submodule_summary) + wt_status_submodule_summary = -1; + return 0; + } if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { wt_status_use_color = git_config_colorbool(k, v, -1); return 0; |