diff options
102 files changed, 2543 insertions, 685 deletions
diff --git a/Documentation/RelNotes/1.7.6.4.txt b/Documentation/RelNotes/1.7.6.4.txt new file mode 100644 index 0000000000..e19acac2da --- /dev/null +++ b/Documentation/RelNotes/1.7.6.4.txt @@ -0,0 +1,32 @@ +Git v1.7.6.4 Release Notes +========================== + +Fixes since v1.7.6.3 +-------------------- + + * The error reporting logic of "git am" when the command is fed a file + whose mail-storage format is unknown was fixed. + + * "git branch --set-upstream @{-1} foo" did not expand @{-1} correctly. + + * "git check-ref-format --print" used to parrot a candidate string that + began with a slash (e.g. /refs/heads/master) without stripping it, to make + the result a suitably normalized string the caller can append to "$GIT_DIR/". + + * "git clone" failed to clone locally from a ".git" file that itself + is not a directory but is a pointer to one. + + * "git clone" from a local repository that borrows from another + object store using a relative path in its objects/info/alternates + file did not adjust the alternates in the resulting repository. + + * "git describe --dirty" did not refresh the index before checking the + state of the working tree files. + + * "git ls-files ../$path" that is run from a subdirectory reported errors + incorrectly when there is no such path that matches the given pathspec. + + * "git mergetool" could loop forever prompting when nothing can be read + from the standard input. + +Also contains minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.7.txt b/Documentation/RelNotes/1.7.7.txt index f10ff28820..7655cccfaa 100644 --- a/Documentation/RelNotes/1.7.7.txt +++ b/Documentation/RelNotes/1.7.7.txt @@ -130,43 +130,5 @@ Fixes since v1.7.6 Unless otherwise noted, all fixes in the 1.7.6.X maintenance track are included in this release. - * The error reporting logic of "git am" when the command is fed a file - whose mail-storage format is unknown was fixed. - (merge dff4b0e gb/maint-am-patch-format-error-message later to 'maint'). - - * "git branch --set-upstream @{-1} foo" did not expand @{-1} correctly. - (merge e9d4f74 mg/branch-set-upstream-previous later to 'maint'). - * "git branch -m" and "git checkout -b" incorrectly allowed the tip of the branch that is currently checked out updated. - (merge 55c4a67 ci/forbid-unwanted-current-branch-update later to 'maint'). - - * "git check-ref-format --print" used to parrot a candidate string that - began with a slash (e.g. /refs/heads/master) without stripping it, to make - the result a suitably normalized string the caller can append to "$GIT_DIR/". - (merge f3738c1 mh/check-ref-format-print-normalize later to 'maint'). - - * "git clone" failed to clone locally from a ".git" file that itself - is not a directory but is a pointer to one. - (merge 9b0ebc7 nd/maint-clone-gitdir later to 'maint'). - - * "git clone" from a local repository that borrows from another - object store using a relative path in its objects/info/alternates - file did not adjust the alternates in the resulting repository. - (merge e6baf4a1 jc/maint-clone-alternates later to 'maint'). - - * "git describe --dirty" did not refresh the index before checking the - state of the working tree files. - (cherry-pick bb57148 ac/describe-dirty-refresh later to 'maint'). - - * "git ls-files ../$path" that is run from a subdirectory reported errors - incorrectly when there is no such path that matches the given pathspec. - (merge 0f64bfa cb/maint-ls-files-error-report later to 'maint'). - --- -exec >/var/tmp/1 -echo O=$(git describe master) -O=v1.7.7-rc2 -git log --first-parent --oneline $O..master -echo -git shortlog --no-merges ^maint ^$O master diff --git a/Documentation/config.txt b/Documentation/config.txt index 0658ffb889..03296b7eb8 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -147,7 +147,7 @@ advice.*:: core.fileMode:: If false, the executable bit differences between the index and - the working copy are ignored; useful on broken filesystems like FAT. + the working tree are ignored; useful on broken filesystems like FAT. See linkgit:git-update-index[1]. + The default is true, except linkgit:git-clone[1] or linkgit:git-init[1] @@ -179,7 +179,7 @@ is created. core.trustctime:: If false, the ctime differences between the index and the - working copy are ignored; useful when the inode change time + working tree are ignored; useful when the inode change time is regularly modified by something outside Git (file system crawlers and some backup systems). See linkgit:git-update-index[1]. True by default. @@ -292,7 +292,7 @@ core.ignoreStat:: If true, commands which modify both the working tree and the index will mark the updated paths with the "assume unchanged" bit in the index. These marked files are then assumed to stay unchanged in the - working copy, until you mark them otherwise manually - Git will not + working tree, until you mark them otherwise manually - Git will not detect the file changes by lstat() calls. This is useful on systems where those are very slow, such as Microsoft Windows. See linkgit:git-update-index[1]. @@ -857,6 +857,13 @@ fetch.recurseSubmodules:: when its superproject retrieves a commit that updates the submodule's reference. +fetch.fsckObjects:: + If it is set to true, git-fetch-pack will check all fetched + objects. It will abort in the case of a malformed object or a + broken link. The result of an abort are only dangling objects. + Defaults to false. If not set, the value of `transfer.fsckObjects` + is used instead. + fetch.unpackLimit:: If the number of objects fetched over the git native transfer is below this @@ -1453,7 +1460,8 @@ notes.rewriteRef:: You may also specify this configuration several times. + Does not have a default value; you must configure this variable to -enable note rewriting. +enable note rewriting. Set it to `refs/notes/commits` to enable +rewriting for the default commit notes. + This setting can be overridden with the `GIT_NOTES_REWRITE_REF` environment variable, which must be a colon separated list of refs or @@ -1595,7 +1603,8 @@ receive.fsckObjects:: If it is set to true, git-receive-pack will check all received objects. It will abort in the case of a malformed object or a broken link. The result of an abort are only dangling objects. - Defaults to false. + Defaults to false. If not set, the value of `transfer.fsckObjects` + is used instead. receive.unpackLimit:: If the number of objects received in a push is below this @@ -1830,6 +1839,11 @@ tar.umask:: archiving user's umask will be used instead. See umask(2) and linkgit:git-archive[1]. +transfer.fsckObjects:: + When `fetch.fsckObjects` or `receive.fsckObjects` are + not set, the value of this variable is used instead. + Defaults to false. + transfer.unpackLimit:: When `fetch.unpackLimit` or `receive.unpackLimit` are not set, the value of this variable is used instead. diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 507b8d0ab2..f46013c91f 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -9,8 +9,8 @@ SYNOPSIS -------- [verse] 'git branch' [--color[=<when>] | --no-color] [-r | -a] - [-v [--abbrev=<length> | --no-abbrev]] - [(--merged | --no-merged | --contains) [<commit>]] + [--list] [-v [--abbrev=<length> | --no-abbrev]] + [(--merged | --no-merged | --contains) [<commit>]] [<pattern>...] 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>] 'git branch' (-m | -M) [<oldbranch>] <newbranch> 'git branch' (-d | -D) [-r] <branchname>... @@ -20,7 +20,11 @@ DESCRIPTION With no arguments, existing branches are listed and the current branch will be highlighted with an asterisk. Option `-r` causes the remote-tracking -branches to be listed, and option `-a` shows both. +branches to be listed, and option `-a` shows both. This list mode is also +activated by the `--list` option (see below). +<pattern> restricts the output to matching branches, the pattern is a shell +wildcard (i.e., matched using fnmatch(3)) +Multiple patterns may be given; if any of them matches, the tag is shown. With `--contains`, shows only the branches that contain the named commit (in other words, the branches whose tip commits are descendants of the @@ -64,6 +68,7 @@ way to clean up all obsolete remote-tracking branches. OPTIONS ------- -d:: +--delete:: Delete a branch. The branch must be fully merged in its upstream branch, or in `HEAD` if no upstream was set with `--track` or `--set-upstream`. @@ -72,6 +77,7 @@ OPTIONS Delete a branch irrespective of its merged status. -l:: +--create-reflog:: Create the branch's reflog. This activates recording of all changes made to the branch ref, enabling use of date based sha1 expressions such as "<branchname>@\{yesterday}". @@ -84,6 +90,7 @@ OPTIONS already. Without `-f` 'git branch' refuses to change an existing branch. -m:: +--move:: Move/rename a branch and the corresponding reflog. -M:: @@ -100,14 +107,21 @@ OPTIONS Same as `--color=never`. -r:: +--remotes:: List or delete (if used with -d) the remote-tracking branches. -a:: +--all:: List both remote-tracking branches and local branches. +--list:: + Activate the list mode. `git branch <pattern>` would try to create a branch, + use `git branch --list <pattern>` to list matching branches. + -v:: --verbose:: - Show sha1 and commit subject line for each head, along with + When in list mode, + show sha1 and commit subject line for each head, along with relationship to upstream branch (if any). If given twice, print the name of the upstream branch, as well. diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 7cfa3d92ac..2660a842fc 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -9,6 +9,8 @@ SYNOPSIS -------- [verse] 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>... +'git cherry-pick' --reset +'git cherry-pick' --continue DESCRIPTION ----------- @@ -110,6 +112,10 @@ effect to your index in a row. Pass the merge strategy-specific option through to the merge strategy. See linkgit:git-merge[1] for details. +SEQUENCER SUBCOMMANDS +--------------------- +include::sequencer.txt[] + EXAMPLES -------- `git cherry-pick master`:: diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 152e695c81..c872b883ba 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -101,9 +101,10 @@ Fields that have name-email-date tuple as its value (`author`, `committer`, and `tagger`) can be suffixed with `name`, `email`, and `date` to extract the named component. -The first line of the message in a commit and tag object is -`subject`, the remaining lines are `body`. The whole message -is `contents`. +The complete message in a commit and tag object is `contents`. +Its first line is `contents:subject`, the remaining lines +are `contents:body` and the optional GPG signature +is `contents:signature`. For sorting purposes, fields with numeric values sort in numeric order (`objectsize`, `authordate`, `committerdate`, `taggerdate`). diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index c45d53c6e1..1bd0317a42 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -47,7 +47,7 @@ OPTIONS -i:: Usually a merge requires the index file as well as the - files in the working tree are up to date with the + files in the working tree to be up to date with the current head commit, in order not to lose local changes. This flag disables the check with the working tree and is meant to be used when creating a merge of @@ -71,21 +71,21 @@ OPTIONS --aggressive:: Usually a three-way merge by 'git read-tree' resolves the merge for really trivial cases and leaves other - cases unresolved in the index, so that Porcelains can + cases unresolved in the index, so that porcelains can implement different merge policies. This flag makes the - command to resolve a few more cases internally: + command resolve a few more cases internally: + * when one side removes a path and the other side leaves the path unmodified. The resolution is to remove that path. * when both sides remove a path. The resolution is to remove that path. -* when both sides adds a path identically. The resolution +* when both sides add a path identically. The resolution is to add that path. --prefix=<prefix>/:: Keep the current index contents, and read the contents - of named tree-ish under directory at `<prefix>`. The + of the named tree-ish under the directory at `<prefix>`. The original index file cannot have anything at the path - `<prefix>` itself, and have nothing in `<prefix>/` + `<prefix>` itself, nor anything in the `<prefix>/` directory. Note that the `<prefix>/` value must end with a slash. @@ -379,36 +379,36 @@ have finished your work-in-progress), attempt the merge again. Sparse checkout --------------- -"Sparse checkout" allows to sparsely populate working directory. -It uses skip-worktree bit (see linkgit:git-update-index[1]) to tell -Git whether a file on working directory is worth looking at. +"Sparse checkout" allows populating the working directory sparsely. +It uses the skip-worktree bit (see linkgit:git-update-index[1]) to tell +Git whether a file in the working directory is worth looking at. -"git read-tree" and other merge-based commands ("git merge", "git -checkout"...) can help maintaining skip-worktree bitmap and working +'git read-tree' and other merge-based commands ('git merge', 'git +checkout'...) can help maintaining the skip-worktree bitmap and working directory update. `$GIT_DIR/info/sparse-checkout` is used to -define the skip-worktree reference bitmap. When "git read-tree" needs -to update working directory, it will reset skip-worktree bit in index +define the skip-worktree reference bitmap. When 'git read-tree' needs +to update the working directory, it resets the skip-worktree bit in the index based on this file, which uses the same syntax as .gitignore files. -If an entry matches a pattern in this file, skip-worktree will be -set on that entry. Otherwise, skip-worktree will be unset. +If an entry matches a pattern in this file, skip-worktree will not be +set on that entry. Otherwise, skip-worktree will be set. Then it compares the new skip-worktree value with the previous one. If -skip-worktree turns from unset to set, it will add the corresponding -file back. If it turns from set to unset, that file will be removed. +skip-worktree turns from set to unset, it will add the corresponding +file back. If it turns from unset to set, that file will be removed. While `$GIT_DIR/info/sparse-checkout` is usually used to specify what -files are in. You can also specify what files are _not_ in, using -negate patterns. For example, to remove file "unwanted": +files are in, you can also specify what files are _not_ in, using +negate patterns. For example, to remove the file `unwanted`: ---------------- * !unwanted ---------------- -Another tricky thing is fully repopulating working directory when you +Another tricky thing is fully repopulating the working directory when you no longer want sparse checkout. You cannot just disable "sparse -checkout" because skip-worktree are still in the index and you working -directory is still sparsely populated. You should re-populate working +checkout" because skip-worktree bits are still in the index and your working +directory is still sparsely populated. You should re-populate the working directory with the `$GIT_DIR/info/sparse-checkout` file content as follows: @@ -416,8 +416,8 @@ follows: * ---------------- -Then you can disable sparse checkout. Sparse checkout support in "git -read-tree" and similar commands is disabled by default. You need to +Then you can disable sparse checkout. Sparse checkout support in 'git +read-tree' and similar commands is disabled by default. You need to turn `core.sparseCheckout` on in order to have sparse checkout support. diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index b311d59c7c..f3519413e7 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -9,6 +9,8 @@ SYNOPSIS -------- [verse] 'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>... +'git revert' --reset +'git revert' --continue DESCRIPTION ----------- @@ -91,6 +93,10 @@ effect to your index in a row. Pass the merge strategy-specific option through to the merge strategy. See linkgit:git-merge[1] for details. +SEQUENCER SUBCOMMANDS +--------------------- +include::sequencer.txt[] + EXAMPLES -------- `git revert HEAD~3`:: diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 08cad6d2b6..f977e8780b 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -318,7 +318,7 @@ Any other arguments are passed directly to 'git log' Show what revision and author last modified each line of a file. The output of this mode is format-compatible with the output of `svn blame' by default. Like the SVN blame command, - local uncommitted changes in the working copy are ignored; + local uncommitted changes in the working tree are ignored; the version of the file in the HEAD revision is annotated. Unknown arguments are passed directly to 'git blame'. + diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index fb1c0ac694..c83cb13de6 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -43,12 +43,15 @@ GnuPG key for signing. OPTIONS ------- -a:: +--annotate:: Make an unsigned, annotated tag object -s:: +--sign:: Make a GPG-signed tag, using the default e-mail address's key -u <key-id>:: +--local-user=<key-id>:: Make a GPG-signed tag, using the given key -f:: @@ -56,9 +59,11 @@ OPTIONS Replace an existing tag with the given name (instead of failing) -d:: +--delete:: Delete existing tags with the given names. -v:: +--verify:: Verify the gpg signature of the given tag names. -n<num>:: @@ -69,6 +74,7 @@ OPTIONS If the tag is not annotated, the commit message is displayed instead. -l <pattern>:: +--list <pattern>:: List tags with names that match the given pattern (or all if no pattern is given). Running "git tag" without arguments also lists all tags. The pattern is a shell wildcard (i.e., matched @@ -79,6 +85,7 @@ OPTIONS Only list tags which contain the specified commit. -m <msg>:: +--message=<msg>:: Use the given tag message (instead of prompting). If multiple `-m` options are given, their values are concatenated as separate paragraphs. @@ -86,6 +93,7 @@ OPTIONS is given. -F <file>:: +--file=<file>:: Take the tag message from the given file. Use '-' to read the message from the standard input. Implies `-a` if none of `-a`, `-s`, or `-u <key-id>` diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index d3931294d1..a3081f4e23 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -264,7 +264,9 @@ tree files, you have to explicitly tell git about it by dropping "assume unchanged" bit, either before or after you modify them. In order to set "assume unchanged" bit, use `--assume-unchanged` -option. To unset, use `--no-assume-unchanged`. +option. To unset, use `--no-assume-unchanged`. To see which files +have the "assume unchanged" bit set, use `git ls-files -v` +(see linkgit:git-ls-files[1]). The command looks at `core.ignorestat` configuration variable. When this is true, paths updated with `git update-index paths...` and @@ -363,7 +365,8 @@ ctime for marking files processed) (see linkgit:git-config[1]). SEE ALSO -------- linkgit:git-config[1], -linkgit:git-add[1] +linkgit:git-add[1], +linkgit:git-ls-files[1] GIT --- diff --git a/Documentation/git.txt b/Documentation/git.txt index 4ae21a3cc7..cbc51d5a94 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -44,9 +44,15 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.7.6.3/git.html[documentation for release 1.7.6.3] +* link:v1.7.7/git.html[documentation for release 1.7.7] * release notes for + link:RelNotes/1.7.7.txt[1.7.7]. + +* link:v1.7.6.4/git.html[documentation for release 1.7.6.4] + +* release notes for + link:RelNotes/1.7.6.4.txt[1.7.6.4], link:RelNotes/1.7.6.3.txt[1.7.6.3], link:RelNotes/1.7.6.2.txt[1.7.6.2], link:RelNotes/1.7.6.1.txt[1.7.6.1], diff --git a/Documentation/gitnamespaces.txt b/Documentation/gitnamespaces.txt index ed8924e856..c6713cf5d7 100644 --- a/Documentation/gitnamespaces.txt +++ b/Documentation/gitnamespaces.txt @@ -5,6 +5,13 @@ NAME ---- gitnamespaces - Git namespaces +SYNOPSIS +-------- +[verse] +GIT_NAMESPACE=<namespace> 'git upload-pack' +GIT_NAMESPACE=<namespace> 'git receive-pack' + + DESCRIPTION ----------- diff --git a/Documentation/sequencer.txt b/Documentation/sequencer.txt new file mode 100644 index 0000000000..3e6df338be --- /dev/null +++ b/Documentation/sequencer.txt @@ -0,0 +1,9 @@ +--reset:: + Forget about the current operation in progress. Can be used + to clear the sequencer state after a failed cherry-pick or + revert. + +--continue:: + Continue the operation in progress using the information in + '.git/sequencer'. Can be used to continue after resolving + conflicts in a failed cherry-pick or revert. diff --git a/Documentation/technical/api-argv-array.txt b/Documentation/technical/api-argv-array.txt new file mode 100644 index 0000000000..49b3d52952 --- /dev/null +++ b/Documentation/technical/api-argv-array.txt @@ -0,0 +1,46 @@ +argv-array API +============== + +The argv-array API allows one to dynamically build and store +NULL-terminated lists. An argv-array maintains the invariant that the +`argv` member always points to a non-NULL array, and that the array is +always NULL-terminated at the element pointed to by `argv[argc]`. This +makes the result suitable for passing to functions expecting to receive +argv from main(), or the link:api-run-command.html[run-command API]. + +The link:api-string-list.html[string-list API] is similar, but cannot be +used for these purposes; instead of storing a straight string pointer, +it contains an item structure with a `util` field that is not compatible +with the traditional argv interface. + +Each `argv_array` manages its own memory. Any strings pushed into the +array are duplicated, and all memory is freed by argv_array_clear(). + +Data Structures +--------------- + +`struct argv_array`:: + + A single array. This should be initialized by assignment from + `ARGV_ARRAY_INIT`, or by calling `argv_array_init`. The `argv` + member contains the actual array; the `argc` member contains the + number of elements in the array, not including the terminating + NULL. + +Functions +--------- + +`argv_array_init`:: + Initialize an array. This is no different than assigning from + `ARGV_ARRAY_INIT`. + +`argv_array_push`:: + Push a copy of a string onto the end of the array. + +`argv_array_pushf`:: + Format a string and push it onto the end of the array. This is a + convenience wrapper combining `strbuf_addf` and `argv_array_push`. + +`argv_array_clear`:: + Free all memory associated with the array and return it to the + initial, empty state. diff --git a/Documentation/technical/api-sha1-array.txt b/Documentation/technical/api-sha1-array.txt new file mode 100644 index 0000000000..4a4bae8109 --- /dev/null +++ b/Documentation/technical/api-sha1-array.txt @@ -0,0 +1,79 @@ +sha1-array API +============== + +The sha1-array API provides storage and manipulation of sets of SHA1 +identifiers. The emphasis is on storage and processing efficiency, +making them suitable for large lists. Note that the ordering of items is +not preserved over some operations. + +Data Structures +--------------- + +`struct sha1_array`:: + + A single array of SHA1 hashes. This should be initialized by + assignment from `SHA1_ARRAY_INIT`. The `sha1` member contains + the actual data. The `nr` member contains the number of items in + the set. The `alloc` and `sorted` members are used internally, + and should not be needed by API callers. + +Functions +--------- + +`sha1_array_append`:: + Add an item to the set. The sha1 will be placed at the end of + the array (but note that some operations below may lose this + ordering). + +`sha1_array_sort`:: + Sort the elements in the array. + +`sha1_array_lookup`:: + Perform a binary search of the array for a specific sha1. + If found, returns the offset (in number of elements) of the + sha1. If not found, returns a negative integer. If the array is + not sorted, this function has the side effect of sorting it. + +`sha1_array_clear`:: + Free all memory associated with the array and return it to the + initial, empty state. + +`sha1_array_for_each_unique`:: + Efficiently iterate over each unique element of the list, + executing the callback function for each one. If the array is + not sorted, this function has the side effect of sorting it. + +Examples +-------- + +----------------------------------------- +void print_callback(const unsigned char sha1[20], + void *data) +{ + printf("%s\n", sha1_to_hex(sha1)); +} + +void some_func(void) +{ + struct sha1_array hashes = SHA1_ARRAY_INIT; + unsigned char sha1[20]; + + /* Read objects into our set */ + while (read_object_from_stdin(sha1)) + sha1_array_append(&hashes, sha1); + + /* Check if some objects are in our set */ + while (read_object_from_stdin(sha1)) { + if (sha1_array_lookup(&hashes, sha1) >= 0) + printf("it's in there!\n"); + + /* + * Print the unique set of objects. We could also have + * avoided adding duplicate objects in the first place, + * but we would end up re-sorting the array repeatedly. + * Instead, this will sort once and then skip duplicates + * in linear time. + */ + sha1_array_for_each_unique(&hashes, print_callback, NULL); +} +----------------------------------------- diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 647dc603ab..f1dc5faead 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.7-rc2 +DEF_VER=v1.7.7 LF=' ' @@ -250,10 +250,6 @@ all:: # DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR', # DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork' # -# Define COMPUTE_HEADER_DEPENDENCIES if your compiler supports the -MMD option -# and you want to avoid rebuilding objects when an unrelated header file -# changes. -# # Define CHECK_HEADER_DEPENDENCIES to check for problems in the hard-coded # dependency rules. # @@ -505,6 +501,7 @@ VCSSVN_LIB=vcs-svn/lib.a LIB_H += advice.h LIB_H += archive.h +LIB_H += argv-array.h LIB_H += attr.h LIB_H += blob.h LIB_H += builtin.h @@ -520,6 +517,7 @@ LIB_H += compat/win32/pthread.h LIB_H += compat/win32/syslog.h LIB_H += compat/win32/sys/poll.h LIB_H += compat/win32/dirent.h +LIB_H += connected.h LIB_H += csum-file.h LIB_H += decorate.h LIB_H += delta.h @@ -561,6 +559,7 @@ LIB_H += rerere.h LIB_H += resolve-undo.h LIB_H += revision.h LIB_H += run-command.h +LIB_H += sequencer.h LIB_H += sha1-array.h LIB_H += sha1-lookup.h LIB_H += sideband.h @@ -586,6 +585,7 @@ LIB_OBJS += alloc.o LIB_OBJS += archive.o LIB_OBJS += archive-tar.o LIB_OBJS += archive-zip.o +LIB_OBJS += argv-array.o LIB_OBJS += attr.o LIB_OBJS += base85.o LIB_OBJS += bisect.o @@ -599,6 +599,7 @@ LIB_OBJS += commit.o LIB_OBJS += compat/obstack.o LIB_OBJS += config.o LIB_OBJS += connect.o +LIB_OBJS += connected.o LIB_OBJS += convert.o LIB_OBJS += copy.o LIB_OBJS += csum-file.o @@ -668,6 +669,7 @@ LIB_OBJS += revision.o LIB_OBJS += run-command.o LIB_OBJS += server-info.o LIB_OBJS += setup.o +LIB_OBJS += sequencer.o LIB_OBJS += sha1-array.o LIB_OBJS += sha1-lookup.o LIB_OBJS += sha1_file.o @@ -1242,6 +1244,15 @@ endif ifdef CHECK_HEADER_DEPENDENCIES COMPUTE_HEADER_DEPENDENCIES = USE_COMPUTED_HEADER_DEPENDENCIES = +else +ifndef COMPUTE_HEADER_DEPENDENCIES +dep_check = $(shell $(CC) $(ALL_CFLAGS) \ + -c -MF /dev/null -MMD -MP -x c /dev/null -o /dev/null 2>&1; \ + echo $$?) +ifeq ($(dep_check),0) +COMPUTE_HEADER_DEPENDENCIES=YesPlease +endif +endif endif ifdef COMPUTE_HEADER_DEPENDENCIES @@ -1894,7 +1905,7 @@ dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS)))) ifdef COMPUTE_HEADER_DEPENDENCIES $(dep_dirs): - mkdir -p $@ + @mkdir -p $@ missing_dep_dirs := $(filter-out $(wildcard $(dep_dirs)),$(dep_dirs)) dep_file = $(dir $@).depend/$(notdir $@).d @@ -19,6 +19,15 @@ static struct { { "detachedhead", &advice_detached_head }, }; +void advise(const char *advice, ...) +{ + va_list params; + + va_start(params, advice); + vreportf("hint: ", advice, params); + va_end(params); +} + int git_default_advice_config(const char *var, const char *value) { const char *k = skip_prefix(var, "advice."); @@ -34,16 +43,24 @@ int git_default_advice_config(const char *var, const char *value) return 0; } -void NORETURN die_resolve_conflict(const char *me) +int error_resolve_conflict(const char *me) { - if (advice_resolve_conflict) + error("'%s' is not possible because you have unmerged files.", me); + if (advice_resolve_conflict) { /* * Message used both when 'git commit' fails and when * other commands doing a merge do. */ - die("'%s' is not possible because you have unmerged files.\n" - "Please, fix them up in the work tree, and then use 'git add/rm <file>' as\n" - "appropriate to mark resolution and make a commit, or use 'git commit -a'.", me); - else - die("'%s' is not possible because you have unmerged files.", me); + advise("Fix them up in the work tree,"); + advise("and then use 'git add/rm <file>' as"); + advise("appropriate to mark resolution and make a commit,"); + advise("or use 'git commit -a'."); + } + return -1; +} + +void NORETURN die_resolve_conflict(const char *me) +{ + error_resolve_conflict(me); + die("Exiting because of an unresolved conflict."); } @@ -11,7 +11,8 @@ extern int advice_implicit_identity; extern int advice_detached_head; int git_default_advice_config(const char *var, const char *value); - +void advise(const char *advice, ...); +int error_resolve_conflict(const char *me); extern void NORETURN die_resolve_conflict(const char *me); #endif /* ADVICE_H */ diff --git a/argv-array.c b/argv-array.c new file mode 100644 index 0000000000..a4e04201e6 --- /dev/null +++ b/argv-array.c @@ -0,0 +1,51 @@ +#include "cache.h" +#include "argv-array.h" +#include "strbuf.h" + +static const char *empty_argv_storage = NULL; +const char **empty_argv = &empty_argv_storage; + +void argv_array_init(struct argv_array *array) +{ + array->argv = empty_argv; + array->argc = 0; + array->alloc = 0; +} + +static void argv_array_push_nodup(struct argv_array *array, const char *value) +{ + if (array->argv == empty_argv) + array->argv = NULL; + + ALLOC_GROW(array->argv, array->argc + 2, array->alloc); + array->argv[array->argc++] = value; + array->argv[array->argc] = NULL; +} + +void argv_array_push(struct argv_array *array, const char *value) +{ + argv_array_push_nodup(array, xstrdup(value)); +} + +void argv_array_pushf(struct argv_array *array, const char *fmt, ...) +{ + va_list ap; + struct strbuf v = STRBUF_INIT; + + va_start(ap, fmt); + strbuf_vaddf(&v, fmt, ap); + va_end(ap); + + argv_array_push_nodup(array, strbuf_detach(&v, NULL)); +} + +void argv_array_clear(struct argv_array *array) +{ + if (array->argv != empty_argv) { + int i; + for (i = 0; i < array->argc; i++) + free((char **)array->argv[i]); + free(array->argv); + } + argv_array_init(array); +} diff --git a/argv-array.h b/argv-array.h new file mode 100644 index 0000000000..74dd2b1bc0 --- /dev/null +++ b/argv-array.h @@ -0,0 +1,20 @@ +#ifndef ARGV_ARRAY_H +#define ARGV_ARRAY_H + +extern const char **empty_argv; + +struct argv_array { + const char **argv; + int argc; + int alloc; +}; + +#define ARGV_ARRAY_INIT { empty_argv, 0, 0 } + +void argv_array_init(struct argv_array *); +void argv_array_push(struct argv_array *, const char *); +__attribute__((format (printf,2,3))) +void argv_array_pushf(struct argv_array *, const char *fmt, ...); +void argv_array_clear(struct argv_array *); + +#endif /* ARGV_ARRAY_H */ @@ -10,18 +10,13 @@ #include "log-tree.h" #include "bisect.h" #include "sha1-array.h" +#include "argv-array.h" static struct sha1_array good_revs; static struct sha1_array skipped_revs; static const unsigned char *current_bad_sha1; -struct argv_array { - const char **argv; - int argv_nr; - int argv_alloc; -}; - static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL}; static const char *argv_show_branch[] = {"show-branch", NULL, NULL}; static const char *argv_update_ref[] = {"update-ref", "--no-deref", "BISECT_HEAD", NULL, NULL}; @@ -405,21 +400,6 @@ struct commit_list *find_bisection(struct commit_list *list, return best; } -static void argv_array_push(struct argv_array *array, const char *string) -{ - ALLOC_GROW(array->argv, array->argv_nr + 1, array->argv_alloc); - array->argv[array->argv_nr++] = string; -} - -static void argv_array_push_sha1(struct argv_array *array, - const unsigned char *sha1, - const char *format) -{ - struct strbuf buf = STRBUF_INIT; - strbuf_addf(&buf, format, sha1_to_hex(sha1)); - argv_array_push(array, strbuf_detach(&buf, NULL)); -} - static int register_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { @@ -449,16 +429,10 @@ static void read_bisect_paths(struct argv_array *array) die_errno("Could not open file '%s'", filename); while (strbuf_getline(&str, fp, '\n') != EOF) { - char *quoted; - int res; - strbuf_trim(&str); - quoted = strbuf_detach(&str, NULL); - res = sq_dequote_to_argv(quoted, &array->argv, - &array->argv_nr, &array->argv_alloc); - if (res) + if (sq_dequote_to_argv_array(str.buf, array)) die("Badly quoted content in file '%s': %s", - filename, quoted); + filename, str.buf); } strbuf_release(&str); @@ -623,7 +597,7 @@ static void bisect_rev_setup(struct rev_info *revs, const char *prefix, const char *bad_format, const char *good_format, int read_paths) { - struct argv_array rev_argv = { NULL, 0, 0 }; + struct argv_array rev_argv = ARGV_ARRAY_INIT; int i; init_revisions(revs, prefix); @@ -631,17 +605,17 @@ static void bisect_rev_setup(struct rev_info *revs, const char *prefix, revs->commit_format = CMIT_FMT_UNSPECIFIED; /* rev_argv.argv[0] will be ignored by setup_revisions */ - argv_array_push(&rev_argv, xstrdup("bisect_rev_setup")); - argv_array_push_sha1(&rev_argv, current_bad_sha1, bad_format); + argv_array_push(&rev_argv, "bisect_rev_setup"); + argv_array_pushf(&rev_argv, bad_format, sha1_to_hex(current_bad_sha1)); for (i = 0; i < good_revs.nr; i++) - argv_array_push_sha1(&rev_argv, good_revs.sha1[i], - good_format); - argv_array_push(&rev_argv, xstrdup("--")); + argv_array_pushf(&rev_argv, good_format, + sha1_to_hex(good_revs.sha1[i])); + argv_array_push(&rev_argv, "--"); if (read_paths) read_bisect_paths(&rev_argv); - argv_array_push(&rev_argv, NULL); - setup_revisions(rev_argv.argv_nr, rev_argv.argv, revs, NULL); + setup_revisions(rev_argv.argc, rev_argv.argv, revs, NULL); + /* XXX leak rev_argv, as "revs" may still be pointing to it */ } static void bisect_common(struct rev_info *revs) @@ -3,6 +3,7 @@ #include "refs.h" #include "remote.h" #include "commit.h" +#include "sequencer.h" struct tracking { struct refspec spec; @@ -245,4 +246,5 @@ void remove_branch_state(void) unlink(git_path("MERGE_MSG")); unlink(git_path("MERGE_MODE")); unlink(git_path("SQUASH_MSG")); + remove_sequencer_state(0); } diff --git a/builtin/branch.c b/builtin/branch.c index f49596f826..009b7138ac 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -260,9 +260,22 @@ static char *resolve_symref(const char *src, const char *prefix) struct append_ref_cb { struct ref_list *ref_list; + const char **pattern; int ret; }; +static int match_patterns(const char **pattern, const char *refname) +{ + if (!*pattern) + return 1; /* no pattern always matches */ + while (*pattern) { + if (!fnmatch(*pattern, refname, 0)) + return 1; + pattern++; + } + return 0; +} + static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data); @@ -297,6 +310,9 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, if ((kind & ref_list->kinds) == 0) return 0; + if (!match_patterns(cb->pattern, refname)) + return 0; + commit = NULL; if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) { commit = lookup_commit_reference_gently(sha1, 1); @@ -492,7 +508,7 @@ static void show_detached(struct ref_list *ref_list) } } -static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit) +static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern) { int i; struct append_ref_cb cb; @@ -506,6 +522,7 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru if (merge_filter != NO_FILTER) init_revisions(&ref_list.revs, NULL); cb.ref_list = &ref_list; + cb.pattern = pattern; cb.ret = 0; for_each_rawref(append_ref, &cb); if (merge_filter != NO_FILTER) { @@ -523,7 +540,7 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); detached = (detached && (kinds & REF_LOCAL_BRANCH)); - if (detached) + if (detached && match_patterns(pattern, "HEAD")) show_detached(&ref_list); for (i = 0; i < ref_list.index; i++) { @@ -608,7 +625,7 @@ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int int cmd_branch(int argc, const char **argv, const char *prefix) { - int delete = 0, rename = 0, force_create = 0; + int delete = 0, rename = 0, force_create = 0, list = 0; int verbose = 0, abbrev = -1, detached = 0; int reflog = 0; enum branch_track track; @@ -624,7 +641,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_SET_INT( 0, "set-upstream", &track, "change upstream info", BRANCH_TRACK_OVERRIDE), OPT__COLOR(&branch_use_color, "use colored output"), - OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", + OPT_SET_INT('r', "remotes", &kinds, "act on remote-tracking branches", REF_REMOTE_BRANCH), { OPTION_CALLBACK, 0, "contains", &with_commit, "commit", @@ -641,13 +658,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT__ABBREV(&abbrev), OPT_GROUP("Specific git-branch actions:"), - OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches", + OPT_SET_INT('a', "all", &kinds, "list both remote-tracking and local branches", REF_REMOTE_BRANCH | REF_LOCAL_BRANCH), - OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1), + OPT_BIT('d', "delete", &delete, "delete fully merged branch", 1), OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2), - OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1), + OPT_BIT('m', "move", &rename, "move/rename a branch and its reflog", 1), 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(0, "list", &list, "list branch names"), + OPT_BOOLEAN('l', "create-reflog", &reflog, "create the branch's reflog"), OPT__FORCE(&force_create, "force creation (when already exists)"), { OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, @@ -686,7 +704,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, 0); - if (!!delete + !!rename + !!force_create > 1) + + if (!delete && !rename && !force_create && argc == 0) + list = 1; + + if (!!delete + !!rename + !!force_create + !!list > 1) usage_with_options(builtin_branch_usage, options); if (abbrev == -1) @@ -694,8 +716,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (delete) return delete_branches(argc, argv, delete > 1, kinds); - else if (argc == 0) - return print_ref_list(kinds, detached, verbose, abbrev, with_commit); + else if (list) + return print_ref_list(kinds, detached, verbose, abbrev, + with_commit, argv); else if (rename && (argc == 1)) rename_branch(head, argv[0], rename > 1); else if (rename && (argc == 2)) diff --git a/builtin/bundle.c b/builtin/bundle.c index 81046a9cb8..92a8a6026a 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -58,7 +58,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) } else if (!strcmp(cmd, "unbundle")) { if (!startup_info->have_repository) die(_("Need a repository to unbundle.")); - return !!unbundle(&header, bundle_fd) || + return !!unbundle(&header, bundle_fd, 0) || list_bundle_refs(&header, argc, argv); } else usage(builtin_bundle_usage); diff --git a/builtin/checkout.c b/builtin/checkout.c index 5e356a6c61..8210ccc13d 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -19,6 +19,7 @@ #include "ll-merge.h" #include "resolve-undo.h" #include "submodule.h" +#include "argv-array.h" static const char * const checkout_usage[] = { "git checkout [options] <branch>", @@ -588,24 +589,12 @@ static void update_refs_for_switch(struct checkout_opts *opts, report_tracking(new); } -struct rev_list_args { - int argc; - int alloc; - const char **argv; -}; - -static void add_one_rev_list_arg(struct rev_list_args *args, const char *s) -{ - ALLOC_GROW(args->argv, args->argc + 1, args->alloc); - args->argv[args->argc++] = s; -} - static int add_one_ref_to_rev_list_arg(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { - add_one_rev_list_arg(cb_data, refname); + argv_array_push(cb_data, refname); return 0; } @@ -685,15 +674,14 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) */ static void orphaned_commit_warning(struct commit *commit) { - struct rev_list_args args = { 0, 0, NULL }; + struct argv_array args = ARGV_ARRAY_INIT; struct rev_info revs; - add_one_rev_list_arg(&args, "(internal)"); - add_one_rev_list_arg(&args, sha1_to_hex(commit->object.sha1)); - add_one_rev_list_arg(&args, "--not"); + argv_array_push(&args, "(internal)"); + argv_array_push(&args, sha1_to_hex(commit->object.sha1)); + argv_array_push(&args, "--not"); for_each_ref(add_one_ref_to_rev_list_arg, &args); - add_one_rev_list_arg(&args, "--"); - add_one_rev_list_arg(&args, NULL); + argv_array_push(&args, "--"); init_revisions(&revs, NULL); if (setup_revisions(args.argc - 1, args.argv, &revs, NULL) != 1) @@ -705,6 +693,7 @@ static void orphaned_commit_warning(struct commit *commit) else describe_detached_head(_("Previous HEAD position was"), commit); + argv_array_clear(&args); clear_commit_marks(commit, -1); for_each_ref(clear_commit_marks_from_one_ref, NULL); } diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 412bd327b5..c8bf9b85b0 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -15,7 +15,9 @@ static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; static int unpack_limit = 100; static int prefer_ofs_delta = 1; -static int no_done = 0; +static int no_done; +static int fetch_fsck_objects = -1; +static int transfer_fsck_objects = -1; static struct fetch_pack_args args = { /* .uploadpack = */ "git-upload-pack", }; @@ -734,6 +736,12 @@ static int get_pack(int xd[2], char **pack_lockfile) } if (*hdr_arg) *av++ = hdr_arg; + if (fetch_fsck_objects >= 0 + ? fetch_fsck_objects + : transfer_fsck_objects >= 0 + ? transfer_fsck_objects + : 0) + *av++ = "--strict"; *av++ = NULL; cmd.in = demux.out; @@ -853,6 +861,16 @@ static int fetch_pack_config(const char *var, const char *value, void *cb) return 0; } + if (!strcmp(var, "fetch.fsckobjects")) { + fetch_fsck_objects = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "transfer.fsckobjects")) { + transfer_fsck_objects = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); } diff --git a/builtin/fetch.c b/builtin/fetch.c index e422ced929..7a4e41cca7 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -13,6 +13,7 @@ #include "sigchain.h" #include "transport.h" #include "submodule.h" +#include "connected.h" static const char * const builtin_fetch_usage[] = { "git fetch [<options>] [<repository> [<refspec>...]]", @@ -345,6 +346,18 @@ static int update_local_ref(struct ref *ref, } } +static int iterate_ref_map(void *cb_data, unsigned char sha1[20]) +{ + struct ref **rm = cb_data; + struct ref *ref = *rm; + + if (!ref) + return -1; /* end of the list */ + *rm = ref->next; + hashcpy(sha1, ref->old_sha1); + return 0; +} + static int store_updated_refs(const char *raw_url, const char *remote_name, struct ref *ref_map) { @@ -364,6 +377,11 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, url = transport_anonymize_url(raw_url); else url = xstrdup("foreign"); + + rm = ref_map; + if (check_everything_connected(iterate_ref_map, 0, &rm)) + return error(_("%s did not send all necessary objects\n"), url); + for (rm = ref_map; rm; rm = rm->next) { struct ref *ref = NULL; @@ -457,23 +475,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, * We would want to bypass the object transfer altogether if * everything we are going to fetch already exists and is connected * locally. - * - * The refs we are going to fetch are in ref_map. If running - * - * $ git rev-list --objects --stdin --not --all - * - * (feeding all the refs in ref_map on its standard input) - * does not error out, that means everything reachable from the - * refs we are going to fetch exists and is connected to some of - * our existing refs. */ static int quickfetch(struct ref *ref_map) { - struct child_process revlist; - struct ref *ref; - int err; - const char *argv[] = {"rev-list", - "--quiet", "--objects", "--stdin", "--not", "--all", NULL}; + struct ref *rm = ref_map; /* * If we are deepening a shallow clone we already have these @@ -484,47 +489,7 @@ static int quickfetch(struct ref *ref_map) */ if (depth) return -1; - - if (!ref_map) - return 0; - - memset(&revlist, 0, sizeof(revlist)); - revlist.argv = argv; - revlist.git_cmd = 1; - revlist.no_stdout = 1; - revlist.no_stderr = 1; - revlist.in = -1; - - err = start_command(&revlist); - if (err) { - error(_("could not run rev-list")); - return err; - } - - /* - * If rev-list --stdin encounters an unknown commit, it terminates, - * which will cause SIGPIPE in the write loop below. - */ - sigchain_push(SIGPIPE, SIG_IGN); - - for (ref = ref_map; ref; ref = ref->next) { - if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 || - write_str_in_full(revlist.in, "\n") < 0) { - if (errno != EPIPE && errno != EINVAL) - error(_("failed write to rev-list: %s"), strerror(errno)); - err = -1; - break; - } - } - - if (close(revlist.in)) { - error(_("failed to close rev-list's stdin: %s"), strerror(errno)); - err = -1; - } - - sigchain_pop(SIGPIPE); - - return finish_command(&revlist) || err; + return check_everything_connected(iterate_ref_map, 1, &rm); } static int fetch_refs(struct transport *transport, struct ref *ref_map) diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 89e75c6894..d90e5d2b29 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -69,6 +69,9 @@ static struct { { "subject" }, { "body" }, { "contents" }, + { "contents:subject" }, + { "contents:body" }, + { "contents:signature" }, { "upstream" }, { "symref" }, { "flag" }, @@ -361,6 +364,18 @@ static const char *copy_email(const char *buf) return xmemdupz(email, eoemail + 1 - email); } +static char *copy_subject(const char *buf, unsigned long len) +{ + char *r = xmemdupz(buf, len); + int i; + + for (i = 0; i < len; i++) + if (r[i] == '\n') + r[i] = ' '; + + return r; +} + static void grab_date(const char *buf, struct atom_value *v, const char *atomname) { const char *eoemail = strstr(buf, "> "); @@ -458,38 +473,56 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru } } -static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body) +static void find_subpos(const char *buf, unsigned long sz, + const char **sub, unsigned long *sublen, + const char **body, unsigned long *bodylen, + unsigned long *nonsiglen, + const char **sig, unsigned long *siglen) { - while (*buf) { - const char *eol = strchr(buf, '\n'); - if (!eol) - return; - if (eol[1] == '\n') { - buf = eol + 1; - break; /* found end of header */ - } - buf = eol + 1; + const char *eol; + /* skip past header until we hit empty line */ + while (*buf && *buf != '\n') { + eol = strchrnul(buf, '\n'); + if (*eol) + eol++; + buf = eol; } + /* skip any empty lines */ while (*buf == '\n') buf++; - if (!*buf) - return; - *sub = buf; /* first non-empty line */ - buf = strchr(buf, '\n'); - if (!buf) { - *body = ""; - return; /* no body */ + + /* parse signature first; we might not even have a subject line */ + *sig = buf + parse_signature(buf, strlen(buf)); + *siglen = strlen(*sig); + + /* subject is first non-empty line */ + *sub = buf; + /* subject goes to first empty line */ + while (buf < *sig && *buf && *buf != '\n') { + eol = strchrnul(buf, '\n'); + if (*eol) + eol++; + buf = eol; } + *sublen = buf - *sub; + /* drop trailing newline, if present */ + if (*sublen && (*sub)[*sublen - 1] == '\n') + *sublen -= 1; + + /* skip any empty lines */ while (*buf == '\n') - buf++; /* skip blank between subject and body */ + buf++; *body = buf; + *bodylen = strlen(buf); + *nonsiglen = *sig - buf; } /* See grab_values */ static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { int i; - const char *subpos = NULL, *bodypos = NULL; + const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL; + unsigned long sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0; for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; @@ -500,17 +533,27 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj name++; if (strcmp(name, "subject") && strcmp(name, "body") && - strcmp(name, "contents")) + strcmp(name, "contents") && + strcmp(name, "contents:subject") && + strcmp(name, "contents:body") && + strcmp(name, "contents:signature")) continue; if (!subpos) - find_subpos(buf, sz, &subpos, &bodypos); - if (!subpos) - return; + find_subpos(buf, sz, + &subpos, &sublen, + &bodypos, &bodylen, &nonsiglen, + &sigpos, &siglen); if (!strcmp(name, "subject")) - v->s = copy_line(subpos); + v->s = copy_subject(subpos, sublen); + else if (!strcmp(name, "contents:subject")) + v->s = copy_subject(subpos, sublen); else if (!strcmp(name, "body")) - v->s = xstrdup(bodypos); + v->s = xmemdupz(bodypos, bodylen); + else if (!strcmp(name, "contents:body")) + v->s = xmemdupz(bodypos, nonsiglen); + else if (!strcmp(name, "contents:signature")) + v->s = xmemdupz(sigpos, siglen); else if (!strcmp(name, "contents")) v->s = xstrdup(subpos); } diff --git a/builtin/fsck.c b/builtin/fsck.c index 5ae0366bc8..df1a88b51a 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -231,12 +231,9 @@ static void check_unreachable_object(struct object *obj) unsigned long size; char *buf = read_sha1_file(obj->sha1, &type, &size); - if (buf) { - if (fwrite(buf, size, 1, f) != 1) - die_errno("Could not write '%s'", - filename); - free(buf); - } + if (buf && fwrite(buf, 1, size, f) != size) + die_errno("Could not write '%s'", filename); + free(buf); } else fprintf(f, "%s\n", sha1_to_hex(obj->sha1)); if (fclose(f)) diff --git a/builtin/grep.c b/builtin/grep.c index 1c359c2671..a286692e46 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -598,8 +598,11 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, struct strbuf base; int hit, len; + read_sha1_lock(); data = read_object_with_reference(obj->sha1, tree_type, &size, NULL); + read_sha1_unlock(); + if (!data) die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1)); diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 10223092a9..41c88a98a2 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -43,6 +43,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) struct transport *transport; const struct ref *ref; + if (argc == 2 && !strcmp("-h", argv[1])) + usage(ls_remote_usage); + for (i = 1; i < argc; i++) { const char *arg = argv[i]; diff --git a/builtin/merge.c b/builtin/merge.c index ab4077f272..ee56974371 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -403,6 +403,16 @@ static void finish(const unsigned char *new_head, const char *msg) strbuf_release(&reflog_message); } +static struct object *want_commit(const char *name) +{ + struct object *obj; + unsigned char sha1[20]; + if (get_sha1(name, sha1)) + return NULL; + obj = parse_object(sha1); + return peel_to_type(name, 0, obj, OBJ_COMMIT); +} + /* Get the name for the merge commit's message. */ static void merge_name(const char *remote, struct strbuf *msg) { @@ -418,7 +428,7 @@ static void merge_name(const char *remote, struct strbuf *msg) remote = bname.buf; memset(branch_head, 0, sizeof(branch_head)); - remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT); + remote_head = want_commit(remote); if (!remote_head) die(_("'%s' does not point to a commit"), remote); @@ -1124,7 +1134,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (!allow_fast_forward) die(_("Non-fast-forward commit does not make sense into " "an empty head")); - remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT); + remote_head = want_commit(argv[0]); if (!remote_head) die(_("%s - not something we can merge"), argv[0]); read_empty(remote_head->sha1, 0); @@ -1170,7 +1180,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) struct object *o; struct commit *commit; - o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT); + o = want_commit(argv[i]); if (!o) die(_("%s - not something we can merge"), argv[i]); commit = lookup_commit(o->sha1); @@ -1238,8 +1248,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (have_message) strbuf_addstr(&msg, " (no commit created; -m option ignored)"); - o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1), - 0, NULL, OBJ_COMMIT); + o = want_commit(sha1_to_hex(remoteheads->item->object.sha1)); if (!o) return 1; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index a9c67c18ba..2b18de5dc3 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2073,7 +2073,9 @@ static void show_commit(struct commit *commit, void *data) commit->object.flags |= OBJECT_ADDED; } -static void show_object(struct object *obj, const struct name_path *path, const char *last) +static void show_object(struct object *obj, + const struct name_path *path, const char *last, + void *data) { char *name = path_name(path, last); diff --git a/builtin/patch-id.c b/builtin/patch-id.c index f821eb3f0b..3cfe02d5a5 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -56,13 +56,13 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) return 1; } -static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx) +static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct strbuf *line_buf) { - static char line[1000]; int patchlen = 0, found_next = 0; int before = -1, after = -1; - while (fgets(line, sizeof(line), stdin) != NULL) { + while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) { + char *line = line_buf->buf; char *p = line; int len; @@ -133,14 +133,16 @@ static void generate_id_list(void) unsigned char sha1[20], n[20]; git_SHA_CTX ctx; int patchlen; + struct strbuf line_buf = STRBUF_INIT; git_SHA1_Init(&ctx); hashclr(sha1); while (!feof(stdin)) { - patchlen = get_one_patchid(n, &ctx); + patchlen = get_one_patchid(n, &ctx, &line_buf); flush_current_id(patchlen, sha1, &ctx); hashcpy(sha1, n); } + strbuf_release(&line_buf); } static const char patch_id_usage[] = "git patch-id < patch"; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index ae164da4d5..c1c5bac79e 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -11,6 +11,7 @@ #include "transport.h" #include "string-list.h" #include "sha1-array.h" +#include "connected.h" static const char receive_pack_usage[] = "git receive-pack <git-dir>"; @@ -25,7 +26,8 @@ static int deny_deletes; static int deny_non_fast_forwards; static enum deny_action deny_current_branch = DENY_UNCONFIGURED; static enum deny_action deny_delete_current = DENY_UNCONFIGURED; -static int receive_fsck_objects; +static int receive_fsck_objects = -1; +static int transfer_fsck_objects = -1; static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; static int unpack_limit = 100; @@ -79,6 +81,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "transfer.fsckobjects") == 0) { + transfer_fsck_objects = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "receive.denycurrentbranch")) { deny_current_branch = parse_deny_action(var, value); return 0; @@ -205,21 +212,15 @@ static int copy_to_sideband(int in, int out, void *arg) return 0; } -static int run_receive_hook(struct command *commands, const char *hook_name) +typedef int (*feed_fn)(void *, const char **, size_t *); +static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state) { - static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4]; - struct command *cmd; struct child_process proc; struct async muxer; const char *argv[2]; - int have_input = 0, code; - - for (cmd = commands; !have_input && cmd; cmd = cmd->next) { - if (!cmd->error_string) - have_input = 1; - } + int code; - if (!have_input || access(hook_name, X_OK) < 0) + if (access(hook_name, X_OK) < 0) return 0; argv[0] = hook_name; @@ -247,15 +248,13 @@ static int run_receive_hook(struct command *commands, const char *hook_name) return code; } - for (cmd = commands; cmd; cmd = cmd->next) { - if (!cmd->error_string) { - size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n", - sha1_to_hex(cmd->old_sha1), - sha1_to_hex(cmd->new_sha1), - cmd->ref_name); - if (write_in_full(proc.in, buf, n) != n) - break; - } + while (1) { + const char *buf; + size_t n; + if (feed(feed_state, &buf, &n)) + break; + if (write_in_full(proc.in, buf, n) != n) + break; } close(proc.in); if (use_sideband) @@ -263,6 +262,47 @@ static int run_receive_hook(struct command *commands, const char *hook_name) return finish_command(&proc); } +struct receive_hook_feed_state { + struct command *cmd; + struct strbuf buf; +}; + +static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep) +{ + struct receive_hook_feed_state *state = state_; + struct command *cmd = state->cmd; + + while (cmd && cmd->error_string) + cmd = cmd->next; + if (!cmd) + return -1; /* EOF */ + strbuf_reset(&state->buf); + strbuf_addf(&state->buf, "%s %s %s\n", + sha1_to_hex(cmd->old_sha1), sha1_to_hex(cmd->new_sha1), + cmd->ref_name); + state->cmd = cmd->next; + if (bufp) { + *bufp = state->buf.buf; + *sizep = state->buf.len; + } + return 0; +} + +static int run_receive_hook(struct command *commands, const char *hook_name) +{ + struct receive_hook_feed_state state; + int status; + + strbuf_init(&state.buf, 0); + state.cmd = commands; + if (feed_receive_hook(&state, NULL, NULL)) + return 0; + state.cmd = commands; + status = run_and_feed_hook(hook_name, feed_receive_hook, &state); + strbuf_release(&state.buf); + return status; +} + static int run_update_hook(struct command *cmd) { static const char update_hook[] = "hooks/update"; @@ -579,6 +619,43 @@ static void check_aliased_updates(struct command *commands) string_list_clear(&ref_list, 0); } +static int command_singleton_iterator(void *cb_data, unsigned char sha1[20]) +{ + struct command **cmd_list = cb_data; + struct command *cmd = *cmd_list; + + if (!cmd) + return -1; /* end of list */ + *cmd_list = NULL; /* this returns only one */ + hashcpy(sha1, cmd->new_sha1); + return 0; +} + +static void set_connectivity_errors(struct command *commands) +{ + struct command *cmd; + + for (cmd = commands; cmd; cmd = cmd->next) { + struct command *singleton = cmd; + if (!check_everything_connected(command_singleton_iterator, + 0, &singleton)) + continue; + cmd->error_string = "missing necessary objects"; + } +} + +static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20]) +{ + struct command **cmd_list = cb_data; + struct command *cmd = *cmd_list; + + if (!cmd) + return -1; /* end of list */ + *cmd_list = cmd->next; + hashcpy(sha1, cmd->new_sha1); + return 0; +} + static void execute_commands(struct command *commands, const char *unpacker_error) { struct command *cmd; @@ -590,6 +667,11 @@ static void execute_commands(struct command *commands, const char *unpacker_erro return; } + cmd = commands; + if (check_everything_connected(iterate_receive_command_list, + 0, &cmd)) + set_connectivity_errors(commands); + if (run_receive_hook(commands, pre_receive_hook)) { for (cmd = commands; cmd; cmd = cmd->next) cmd->error_string = "pre-receive hook declined"; @@ -674,6 +756,11 @@ static const char *unpack(void) struct pack_header hdr; const char *hdr_err; char hdr_arg[38]; + int fsck_objects = (receive_fsck_objects >= 0 + ? receive_fsck_objects + : transfer_fsck_objects >= 0 + ? transfer_fsck_objects + : 0); hdr_err = parse_pack_header(&hdr); if (hdr_err) @@ -686,7 +773,7 @@ static const char *unpack(void) int code, i = 0; const char *unpacker[4]; unpacker[i++] = "unpack-objects"; - if (receive_fsck_objects) + if (fsck_objects) unpacker[i++] = "--strict"; unpacker[i++] = hdr_arg; unpacker[i++] = NULL; @@ -706,7 +793,7 @@ static const char *unpack(void) keeper[i++] = "index-pack"; keeper[i++] = "--stdin"; - if (receive_fsck_objects) + if (fsck_objects) keeper[i++] = "--strict"; keeper[i++] = "--fix-thin"; keeper[i++] = hdr_arg; diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 56727e8c1d..ab3be7ca82 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -168,29 +168,24 @@ static void finish_commit(struct commit *commit, void *data) commit->buffer = NULL; } -static void finish_object(struct object *obj, const struct name_path *path, const char *name) +static void finish_object(struct object *obj, + const struct name_path *path, const char *name, + void *cb_data) { if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1)) die("missing blob object '%s'", sha1_to_hex(obj->sha1)); } -static void show_object(struct object *obj, const struct name_path *path, const char *component) +static void show_object(struct object *obj, + const struct name_path *path, const char *component, + void *cb_data) { - char *name = path_name(path, component); - /* An object with name "foo\n0000000..." can be used to - * confuse downstream "git pack-objects" very badly. - */ - const char *ep = strchr(name, '\n'); + struct rev_info *info = cb_data; - finish_object(obj, path, name); - if (ep) { - printf("%s %.*s\n", sha1_to_hex(obj->sha1), - (int) (ep - name), - name); - } - else - printf("%s %s\n", sha1_to_hex(obj->sha1), name); - free(name); + finish_object(obj, path, component, cb_data); + if (info->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT) + parse_object(obj->sha1); + show_object_with_name(stdout, obj, path, component); } static void show_edge(struct commit *commit) diff --git a/builtin/revert.c b/builtin/revert.c index 3117776c2c..ba27cf15ee 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -13,6 +13,8 @@ #include "rerere.h" #include "merge-recursive.h" #include "refs.h" +#include "dir.h" +#include "sequencer.h" /* * This implements the builtins revert and cherry-pick. @@ -27,85 +29,189 @@ static const char * const revert_usage[] = { "git revert [options] <commit-ish>", + "git revert <subcommand>", NULL }; static const char * const cherry_pick_usage[] = { "git cherry-pick [options] <commit-ish>", + "git cherry-pick <subcommand>", NULL }; -static int edit, no_replay, no_commit, mainline, signoff, allow_ff; -static enum { REVERT, CHERRY_PICK } action; -static struct commit *commit; -static int commit_argc; -static const char **commit_argv; -static int allow_rerere_auto; - -static const char *me; - -/* Merge strategy. */ -static const char *strategy; -static const char **xopts; -static size_t xopts_nr, xopts_alloc; +enum replay_action { REVERT, CHERRY_PICK }; +enum replay_subcommand { REPLAY_NONE, REPLAY_RESET, REPLAY_CONTINUE }; + +struct replay_opts { + enum replay_action action; + enum replay_subcommand subcommand; + + /* Boolean options */ + int edit; + int record_origin; + int no_commit; + int signoff; + int allow_ff; + int allow_rerere_auto; + + int mainline; + int commit_argc; + const char **commit_argv; + + /* Merge strategy */ + const char *strategy; + const char **xopts; + size_t xopts_nr, xopts_alloc; +}; #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" +static const char *action_name(const struct replay_opts *opts) +{ + return opts->action == REVERT ? "revert" : "cherry-pick"; +} + static char *get_encoding(const char *message); -static const char * const *revert_or_cherry_pick_usage(void) +static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts) { - return action == REVERT ? revert_usage : cherry_pick_usage; + return opts->action == REVERT ? revert_usage : cherry_pick_usage; } static int option_parse_x(const struct option *opt, const char *arg, int unset) { + struct replay_opts **opts_ptr = opt->value; + struct replay_opts *opts = *opts_ptr; + if (unset) return 0; - ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc); - xopts[xopts_nr++] = xstrdup(arg); + ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc); + opts->xopts[opts->xopts_nr++] = xstrdup(arg); return 0; } -static void parse_args(int argc, const char **argv) +static void verify_opt_compatible(const char *me, const char *base_opt, ...) +{ + const char *this_opt; + va_list ap; + + va_start(ap, base_opt); + while ((this_opt = va_arg(ap, const char *))) { + if (va_arg(ap, int)) + break; + } + va_end(ap); + + if (this_opt) + die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt); +} + +static void verify_opt_mutually_compatible(const char *me, ...) +{ + const char *opt1, *opt2; + va_list ap; + + va_start(ap, me); + while ((opt1 = va_arg(ap, const char *))) { + if (va_arg(ap, int)) + break; + } + if (opt1) { + while ((opt2 = va_arg(ap, const char *))) { + if (va_arg(ap, int)) + break; + } + } + + if (opt1 && opt2) + die(_("%s: %s cannot be used with %s"), me, opt1, opt2); +} + +static void parse_args(int argc, const char **argv, struct replay_opts *opts) { - const char * const * usage_str = revert_or_cherry_pick_usage(); + const char * const * usage_str = revert_or_cherry_pick_usage(opts); + const char *me = action_name(opts); int noop; + int reset = 0; + int contin = 0; struct option options[] = { - OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"), - OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"), + OPT_BOOLEAN(0, "reset", &reset, "forget the current operation"), + OPT_BOOLEAN(0, "continue", &contin, "continue the current operation"), + OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"), + OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"), { OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)", PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 0 }, - OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), - OPT_INTEGER('m', "mainline", &mainline, "parent number"), - OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), - OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"), - OPT_CALLBACK('X', "strategy-option", &xopts, "option", + OPT_BOOLEAN('s', "signoff", &opts->signoff, "add Signed-off-by:"), + OPT_INTEGER('m', "mainline", &opts->mainline, "parent number"), + OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto), + OPT_STRING(0, "strategy", &opts->strategy, "strategy", "merge strategy"), + OPT_CALLBACK('X', "strategy-option", &opts, "option", "option for merge strategy", option_parse_x), OPT_END(), OPT_END(), OPT_END(), }; - if (action == CHERRY_PICK) { + if (opts->action == CHERRY_PICK) { struct option cp_extra[] = { - OPT_BOOLEAN('x', NULL, &no_replay, "append commit name"), - OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"), + OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"), + OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"), OPT_END(), }; if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra)) die(_("program error")); } - commit_argc = parse_options(argc, argv, NULL, options, usage_str, - PARSE_OPT_KEEP_ARGV0 | - PARSE_OPT_KEEP_UNKNOWN); - if (commit_argc < 2) + opts->commit_argc = parse_options(argc, argv, NULL, options, usage_str, + PARSE_OPT_KEEP_ARGV0 | + PARSE_OPT_KEEP_UNKNOWN); + + /* Check for incompatible subcommands */ + verify_opt_mutually_compatible(me, + "--reset", reset, + "--continue", contin, + NULL); + + /* Set the subcommand */ + if (reset) + opts->subcommand = REPLAY_RESET; + else if (contin) + opts->subcommand = REPLAY_CONTINUE; + else + opts->subcommand = REPLAY_NONE; + + /* Check for incompatible command line arguments */ + if (opts->subcommand != REPLAY_NONE) { + char *this_operation; + if (opts->subcommand == REPLAY_RESET) + this_operation = "--reset"; + else + this_operation = "--continue"; + + verify_opt_compatible(me, this_operation, + "--no-commit", opts->no_commit, + "--signoff", opts->signoff, + "--mainline", opts->mainline, + "--strategy", opts->strategy ? 1 : 0, + "--strategy-option", opts->xopts ? 1 : 0, + "-x", opts->record_origin, + "--ff", opts->allow_ff, + NULL); + } + + else if (opts->commit_argc < 2) usage_with_options(usage_str, options); - commit_argv = argv; + if (opts->allow_ff) + verify_opt_compatible(me, "--ff", + "--signoff", opts->signoff, + "--no-commit", opts->no_commit, + "-x", opts->record_origin, + "--edit", opts->edit, + NULL); + opts->commit_argv = argv; } struct commit_message { @@ -116,25 +222,25 @@ struct commit_message { const char *message; }; -static int get_message(const char *raw_message, struct commit_message *out) +static int get_message(struct commit *commit, struct commit_message *out) { const char *encoding; const char *abbrev, *subject; int abbrev_len, subject_len; char *q; - if (!raw_message) + if (!commit->buffer) return -1; - encoding = get_encoding(raw_message); + encoding = get_encoding(commit->buffer); if (!encoding) encoding = "UTF-8"; if (!git_commit_encoding) git_commit_encoding = "UTF-8"; out->reencoded_message = NULL; - out->message = raw_message; + out->message = commit->buffer; if (strcmp(encoding, git_commit_encoding)) - out->reencoded_message = reencode_string(raw_message, + out->reencoded_message = reencode_string(commit->buffer, git_commit_encoding, encoding); if (out->reencoded_message) out->message = out->reencoded_message; @@ -167,9 +273,6 @@ static char *get_encoding(const char *message) { const char *p = message, *eol; - if (!p) - die (_("Could not read commit message of %s"), - sha1_to_hex(commit->object.sha1)); while (*p && *p != '\n') { for (eol = p + 1; *eol && *eol != '\n'; eol++) ; /* do nothing */ @@ -185,20 +288,7 @@ static char *get_encoding(const char *message) return NULL; } -static void add_message_to_msg(struct strbuf *msgbuf, const char *message) -{ - const char *p = message; - while (*p && (*p != '\n' || p[1] != '\n')) - p++; - - if (!*p) - strbuf_addstr(msgbuf, sha1_to_hex(commit->object.sha1)); - - p += 2; - strbuf_addstr(msgbuf, p); -} - -static void write_cherry_pick_head(void) +static void write_cherry_pick_head(struct commit *commit) { int fd; struct strbuf buf = STRBUF_INIT; @@ -214,15 +304,6 @@ static void write_cherry_pick_head(void) strbuf_release(&buf); } -static void advise(const char *advice, ...) -{ - va_list params; - - va_start(params, advice); - vreportf("hint: ", advice, params); - va_end(params); -} - static void print_advice(void) { char *msg = getenv("GIT_CHERRY_PICK_HELP"); @@ -261,25 +342,20 @@ static struct tree *empty_tree(void) return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN); } -static NORETURN void die_dirty_index(const char *me) +static int error_dirty_index(struct replay_opts *opts) { - if (read_cache_unmerged()) { - die_resolve_conflict(me); - } else { - if (advice_commit_before_merge) { - if (action == REVERT) - die(_("Your local changes would be overwritten by revert.\n" - "Please, commit your changes or stash them to proceed.")); - else - die(_("Your local changes would be overwritten by cherry-pick.\n" - "Please, commit your changes or stash them to proceed.")); - } else { - if (action == REVERT) - die(_("Your local changes would be overwritten by revert.\n")); - else - die(_("Your local changes would be overwritten by cherry-pick.\n")); - } - } + if (read_cache_unmerged()) + return error_resolve_conflict(action_name(opts)); + + /* Different translation strings for cherry-pick and revert */ + if (opts->action == CHERRY_PICK) + error(_("Your local changes would be overwritten by cherry-pick.")); + else + error(_("Your local changes would be overwritten by revert.")); + + if (advice_commit_before_merge) + advise(_("Commit your changes or stash them to proceed.")); + return -1; } static int fast_forward_to(const unsigned char *to, const unsigned char *from) @@ -295,7 +371,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from) static int do_recursive_merge(struct commit *base, struct commit *next, const char *base_label, const char *next_label, - unsigned char *head, struct strbuf *msgbuf) + unsigned char *head, struct strbuf *msgbuf, + struct replay_opts *opts) { struct merge_options o; struct tree *result, *next_tree, *base_tree, *head_tree; @@ -316,7 +393,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, next_tree = next ? next->tree : empty_tree(); base_tree = base ? base->tree : empty_tree(); - for (xopt = xopts; xopt != xopts + xopts_nr; xopt++) + for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++) parse_merge_opt(&o, *xopt); clean = merge_trees(&o, @@ -327,7 +404,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, (write_cache(index_fd, active_cache, active_nr) || commit_locked_index(&index_lock))) /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ - die(_("%s: Unable to write new index file"), me); + die(_("%s: Unable to write new index file"), action_name(opts)); rollback_lock_file(&index_lock); if (!clean) { @@ -356,7 +433,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, * If we are revert, or if our cherry-pick results in a hand merge, * we had better say that the current user is responsible for that. */ -static int run_git_commit(const char *defmsg) +static int run_git_commit(const char *defmsg, struct replay_opts *opts) { /* 6 is max possible length of our args array including NULL */ const char *args[6]; @@ -364,9 +441,9 @@ static int run_git_commit(const char *defmsg) args[i++] = "commit"; args[i++] = "-n"; - if (signoff) + if (opts->signoff) args[i++] = "-s"; - if (!edit) { + if (!opts->edit) { args[i++] = "-F"; args[i++] = defmsg; } @@ -375,7 +452,7 @@ static int run_git_commit(const char *defmsg) return run_command_v_opt(args, RUN_GIT_CMD); } -static int do_pick_commit(void) +static int do_pick_commit(struct commit *commit, struct replay_opts *opts) { unsigned char head[20]; struct commit *base, *next, *parent; @@ -385,7 +462,7 @@ static int do_pick_commit(void) struct strbuf msgbuf = STRBUF_INIT; int res; - if (no_commit) { + if (opts->no_commit) { /* * We do not intend to commit immediately. We just want to * merge the differences in, so let's compute the tree @@ -396,9 +473,9 @@ static int do_pick_commit(void) die (_("Your index file is unmerged.")); } else { if (get_sha1("HEAD", head)) - die (_("You do not have a valid HEAD")); + return error(_("You do not have a valid HEAD")); if (index_differs_from("HEAD", 0)) - die_dirty_index(me); + return error_dirty_index(opts); } discard_cache(); @@ -410,36 +487,36 @@ static int do_pick_commit(void) int cnt; struct commit_list *p; - if (!mainline) - die(_("Commit %s is a merge but no -m option was given."), - sha1_to_hex(commit->object.sha1)); + if (!opts->mainline) + return error(_("Commit %s is a merge but no -m option was given."), + sha1_to_hex(commit->object.sha1)); for (cnt = 1, p = commit->parents; - cnt != mainline && p; + cnt != opts->mainline && p; cnt++) p = p->next; - if (cnt != mainline || !p) - die(_("Commit %s does not have parent %d"), - sha1_to_hex(commit->object.sha1), mainline); + if (cnt != opts->mainline || !p) + return error(_("Commit %s does not have parent %d"), + sha1_to_hex(commit->object.sha1), opts->mainline); parent = p->item; - } else if (0 < mainline) - die(_("Mainline was specified but commit %s is not a merge."), - sha1_to_hex(commit->object.sha1)); + } else if (0 < opts->mainline) + return error(_("Mainline was specified but commit %s is not a merge."), + sha1_to_hex(commit->object.sha1)); else parent = commit->parents->item; - if (allow_ff && parent && !hashcmp(parent->object.sha1, head)) + if (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head)) return fast_forward_to(commit->object.sha1, head); if (parent && parse_commit(parent) < 0) /* TRANSLATORS: The first %s will be "revert" or "cherry-pick", the second %s a SHA1 */ - die(_("%s: cannot parse parent commit %s"), - me, sha1_to_hex(parent->object.sha1)); + return error(_("%s: cannot parse parent commit %s"), + action_name(opts), sha1_to_hex(parent->object.sha1)); - if (get_message(commit->buffer, &msg) != 0) - die(_("Cannot get commit message for %s"), - sha1_to_hex(commit->object.sha1)); + if (get_message(commit, &msg) != 0) + return error(_("Cannot get commit message for %s"), + sha1_to_hex(commit->object.sha1)); /* * "commit" is an existing commit. We would want to apply @@ -450,7 +527,7 @@ static int do_pick_commit(void) defmsg = git_pathdup("MERGE_MSG"); - if (action == REVERT) { + if (opts->action == REVERT) { base = commit; base_label = msg.label; next = parent; @@ -466,23 +543,36 @@ static int do_pick_commit(void) } strbuf_addstr(&msgbuf, ".\n"); } else { + const char *p; + base = parent; base_label = msg.parent_label; next = commit; next_label = msg.label; - add_message_to_msg(&msgbuf, msg.message); - if (no_replay) { + + /* + * Append the commit log message to msgbuf; it starts + * after the tree, parent, author, committer + * information followed by "\n\n". + */ + p = strstr(msg.message, "\n\n"); + if (p) { + p += 2; + strbuf_addstr(&msgbuf, p); + } + + if (opts->record_origin) { strbuf_addstr(&msgbuf, "(cherry picked from commit "); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); strbuf_addstr(&msgbuf, ")\n"); } - if (!no_commit) - write_cherry_pick_head(); + if (!opts->no_commit) + write_cherry_pick_head(commit); } - if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) { + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REVERT) { res = do_recursive_merge(base, next, base_label, next_label, - head, &msgbuf); + head, &msgbuf, opts); write_message(&msgbuf, defmsg); } else { struct commit_list *common = NULL; @@ -492,23 +582,23 @@ static int do_pick_commit(void) commit_list_insert(base, &common); commit_list_insert(next, &remotes); - res = try_merge_command(strategy, xopts_nr, xopts, common, - sha1_to_hex(head), remotes); + res = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts, + common, sha1_to_hex(head), remotes); free_commit_list(common); free_commit_list(remotes); } if (res) { - error(action == REVERT + error(opts->action == REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), msg.subject); print_advice(); - rerere(allow_rerere_auto); + rerere(opts->allow_rerere_auto); } else { - if (!no_commit) - res = run_git_commit(defmsg); + if (!opts->no_commit) + res = run_git_commit(defmsg, opts); } free_message(&msg); @@ -517,18 +607,18 @@ static int do_pick_commit(void) return res; } -static void prepare_revs(struct rev_info *revs) +static void prepare_revs(struct rev_info *revs, struct replay_opts *opts) { int argc; init_revisions(revs, NULL); revs->no_walk = 1; - if (action != REVERT) + if (opts->action != REVERT) revs->reverse = 1; - argc = setup_revisions(commit_argc, commit_argv, revs, NULL); + argc = setup_revisions(opts->commit_argc, opts->commit_argv, revs, NULL); if (argc > 1) - usage(*revert_or_cherry_pick_usage()); + usage(*revert_or_cherry_pick_usage(opts)); if (prepare_revision_walk(revs)) die(_("revision walk setup failed")); @@ -537,64 +627,403 @@ static void prepare_revs(struct rev_info *revs) die(_("empty commit set passed")); } -static void read_and_refresh_cache(const char *me) +static void read_and_refresh_cache(struct replay_opts *opts) { static struct lock_file index_lock; int index_fd = hold_locked_index(&index_lock, 0); if (read_index_preload(&the_index, NULL) < 0) - die(_("git %s: failed to read the index"), me); + die(_("git %s: failed to read the index"), action_name(opts)); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); if (the_index.cache_changed) { if (write_index(&the_index, index_fd) || commit_locked_index(&index_lock)) - die(_("git %s: failed to refresh the index"), me); + die(_("git %s: failed to refresh the index"), action_name(opts)); } rollback_lock_file(&index_lock); } -static int revert_or_cherry_pick(int argc, const char **argv) +/* + * Append a commit to the end of the commit_list. + * + * next starts by pointing to the variable that holds the head of an + * empty commit_list, and is updated to point to the "next" field of + * the last item on the list as new commits are appended. + * + * Usage example: + * + * struct commit_list *list; + * struct commit_list **next = &list; + * + * next = commit_list_append(c1, next); + * next = commit_list_append(c2, next); + * assert(commit_list_count(list) == 2); + * return list; + */ +static struct commit_list **commit_list_append(struct commit *commit, + struct commit_list **next) +{ + struct commit_list *new = xmalloc(sizeof(struct commit_list)); + new->item = commit; + *next = new; + new->next = NULL; + return &new->next; +} + +static int format_todo(struct strbuf *buf, struct commit_list *todo_list, + struct replay_opts *opts) +{ + struct commit_list *cur = NULL; + struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; + const char *sha1_abbrev = NULL; + const char *action_str = opts->action == REVERT ? "revert" : "pick"; + + for (cur = todo_list; cur; cur = cur->next) { + sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV); + if (get_message(cur->item, &msg)) + return error(_("Cannot get commit message for %s"), sha1_abbrev); + strbuf_addf(buf, "%s %s %s\n", action_str, sha1_abbrev, msg.subject); + } + return 0; +} + +static struct commit *parse_insn_line(char *start, struct replay_opts *opts) +{ + unsigned char commit_sha1[20]; + char sha1_abbrev[40]; + enum replay_action action; + int insn_len = 0; + char *p, *q; + + if (!prefixcmp(start, "pick ")) { + action = CHERRY_PICK; + insn_len = strlen("pick"); + p = start + insn_len + 1; + } else if (!prefixcmp(start, "revert ")) { + action = REVERT; + insn_len = strlen("revert"); + p = start + insn_len + 1; + } else + return NULL; + + q = strchr(p, ' '); + if (!q) + return NULL; + q++; + + strlcpy(sha1_abbrev, p, q - p); + + /* + * Verify that the action matches up with the one in + * opts; we don't support arbitrary instructions + */ + if (action != opts->action) { + const char *action_str; + action_str = action == REVERT ? "revert" : "cherry-pick"; + error(_("Cannot %s during a %s"), action_str, action_name(opts)); + return NULL; + } + + if (get_sha1(sha1_abbrev, commit_sha1) < 0) + return NULL; + + return lookup_commit_reference(commit_sha1); +} + +static int parse_insn_buffer(char *buf, struct commit_list **todo_list, + struct replay_opts *opts) +{ + struct commit_list **next = todo_list; + struct commit *commit; + char *p = buf; + int i; + + for (i = 1; *p; i++) { + commit = parse_insn_line(p, opts); + if (!commit) + return error(_("Could not parse line %d."), i); + next = commit_list_append(commit, next); + p = strchrnul(p, '\n'); + if (*p) + p++; + } + if (!*todo_list) + return error(_("No commits parsed.")); + return 0; +} + +static void read_populate_todo(struct commit_list **todo_list, + struct replay_opts *opts) +{ + const char *todo_file = git_path(SEQ_TODO_FILE); + struct strbuf buf = STRBUF_INIT; + int fd, res; + + fd = open(todo_file, O_RDONLY); + if (fd < 0) + die_errno(_("Could not open %s."), todo_file); + if (strbuf_read(&buf, fd, 0) < 0) { + close(fd); + strbuf_release(&buf); + die(_("Could not read %s."), todo_file); + } + close(fd); + + res = parse_insn_buffer(buf.buf, todo_list, opts); + strbuf_release(&buf); + if (res) + die(_("Unusable instruction sheet: %s"), todo_file); +} + +static int populate_opts_cb(const char *key, const char *value, void *data) +{ + struct replay_opts *opts = data; + int error_flag = 1; + + if (!value) + error_flag = 0; + else if (!strcmp(key, "options.no-commit")) + opts->no_commit = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.edit")) + opts->edit = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.signoff")) + opts->signoff = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.record-origin")) + opts->record_origin = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.allow-ff")) + opts->allow_ff = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.mainline")) + opts->mainline = git_config_int(key, value); + else if (!strcmp(key, "options.strategy")) + git_config_string(&opts->strategy, key, value); + else if (!strcmp(key, "options.strategy-option")) { + ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc); + opts->xopts[opts->xopts_nr++] = xstrdup(value); + } else + return error(_("Invalid key: %s"), key); + + if (!error_flag) + return error(_("Invalid value for %s: %s"), key, value); + + return 0; +} + +static void read_populate_opts(struct replay_opts **opts_ptr) +{ + const char *opts_file = git_path(SEQ_OPTS_FILE); + + if (!file_exists(opts_file)) + return; + if (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0) + die(_("Malformed options sheet: %s"), opts_file); +} + +static void walk_revs_populate_todo(struct commit_list **todo_list, + struct replay_opts *opts) { struct rev_info revs; + struct commit *commit; + struct commit_list **next; - git_config(git_default_config, NULL); - me = action == REVERT ? "revert" : "cherry-pick"; - setenv(GIT_REFLOG_ACTION, me, 0); - parse_args(argc, argv); - - if (allow_ff) { - if (signoff) - die(_("cherry-pick --ff cannot be used with --signoff")); - if (no_commit) - die(_("cherry-pick --ff cannot be used with --no-commit")); - if (no_replay) - die(_("cherry-pick --ff cannot be used with -x")); - if (edit) - die(_("cherry-pick --ff cannot be used with --edit")); + prepare_revs(&revs, opts); + + next = todo_list; + while ((commit = get_revision(&revs))) + next = commit_list_append(commit, next); +} + +static int create_seq_dir(void) +{ + const char *seq_dir = git_path(SEQ_DIR); + + if (file_exists(seq_dir)) + return error(_("%s already exists."), seq_dir); + else if (mkdir(seq_dir, 0777) < 0) + die_errno(_("Could not create sequencer directory '%s'."), seq_dir); + return 0; +} + +static void save_head(const char *head) +{ + const char *head_file = git_path(SEQ_HEAD_FILE); + static struct lock_file head_lock; + struct strbuf buf = STRBUF_INIT; + int fd; + + fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR); + strbuf_addf(&buf, "%s\n", head); + if (write_in_full(fd, buf.buf, buf.len) < 0) + die_errno(_("Could not write to %s."), head_file); + if (commit_lock_file(&head_lock) < 0) + die(_("Error wrapping up %s."), head_file); +} + +static void save_todo(struct commit_list *todo_list, struct replay_opts *opts) +{ + const char *todo_file = git_path(SEQ_TODO_FILE); + static struct lock_file todo_lock; + struct strbuf buf = STRBUF_INIT; + int fd; + + fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR); + if (format_todo(&buf, todo_list, opts) < 0) + die(_("Could not format %s."), todo_file); + if (write_in_full(fd, buf.buf, buf.len) < 0) { + strbuf_release(&buf); + die_errno(_("Could not write to %s."), todo_file); + } + if (commit_lock_file(&todo_lock) < 0) { + strbuf_release(&buf); + die(_("Error wrapping up %s."), todo_file); } + strbuf_release(&buf); +} - read_and_refresh_cache(me); +static void save_opts(struct replay_opts *opts) +{ + const char *opts_file = git_path(SEQ_OPTS_FILE); + + if (opts->no_commit) + git_config_set_in_file(opts_file, "options.no-commit", "true"); + if (opts->edit) + git_config_set_in_file(opts_file, "options.edit", "true"); + if (opts->signoff) + git_config_set_in_file(opts_file, "options.signoff", "true"); + if (opts->record_origin) + git_config_set_in_file(opts_file, "options.record-origin", "true"); + if (opts->allow_ff) + git_config_set_in_file(opts_file, "options.allow-ff", "true"); + if (opts->mainline) { + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "%d", opts->mainline); + git_config_set_in_file(opts_file, "options.mainline", buf.buf); + strbuf_release(&buf); + } + if (opts->strategy) + git_config_set_in_file(opts_file, "options.strategy", opts->strategy); + if (opts->xopts) { + int i; + for (i = 0; i < opts->xopts_nr; i++) + git_config_set_multivar_in_file(opts_file, + "options.strategy-option", + opts->xopts[i], "^$", 0); + } +} - prepare_revs(&revs); +static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) +{ + struct commit_list *cur; + int res; - while ((commit = get_revision(&revs))) { - int res = do_pick_commit(); - if (res) + setenv(GIT_REFLOG_ACTION, action_name(opts), 0); + if (opts->allow_ff) + assert(!(opts->signoff || opts->no_commit || + opts->record_origin || opts->edit)); + read_and_refresh_cache(opts); + + for (cur = todo_list; cur; cur = cur->next) { + save_todo(cur, opts); + res = do_pick_commit(cur->item, opts); + if (res) { + if (!cur->next) + /* + * An error was encountered while + * picking the last commit; the + * sequencer state is useless now -- + * the user simply needs to resolve + * the conflict and commit + */ + remove_sequencer_state(0); return res; + } } + /* + * Sequence of picks finished successfully; cleanup by + * removing the .git/sequencer directory + */ + remove_sequencer_state(1); return 0; } +static int pick_revisions(struct replay_opts *opts) +{ + struct commit_list *todo_list = NULL; + unsigned char sha1[20]; + + read_and_refresh_cache(opts); + + /* + * Decide what to do depending on the arguments; a fresh + * cherry-pick should be handled differently from an existing + * one that is being continued + */ + if (opts->subcommand == REPLAY_RESET) { + remove_sequencer_state(1); + return 0; + } else if (opts->subcommand == REPLAY_CONTINUE) { + if (!file_exists(git_path(SEQ_TODO_FILE))) + goto error; + read_populate_opts(&opts); + read_populate_todo(&todo_list, opts); + + /* Verify that the conflict has been resolved */ + if (!index_differs_from("HEAD", 0)) + todo_list = todo_list->next; + } else { + /* + * Start a new cherry-pick/ revert sequence; but + * first, make sure that an existing one isn't in + * progress + */ + + walk_revs_populate_todo(&todo_list, opts); + if (create_seq_dir() < 0) { + error(_("A cherry-pick or revert is in progress.")); + advise(_("Use --continue to continue the operation")); + advise(_("or --reset to forget about it")); + return -1; + } + if (get_sha1("HEAD", sha1)) { + if (opts->action == REVERT) + return error(_("Can't revert as initial commit")); + return error(_("Can't cherry-pick into empty head")); + } + save_head(sha1_to_hex(sha1)); + save_opts(opts); + } + return pick_commits(todo_list, opts); +error: + return error(_("No %s in progress"), action_name(opts)); +} + int cmd_revert(int argc, const char **argv, const char *prefix) { + struct replay_opts opts; + int res; + + memset(&opts, 0, sizeof(opts)); if (isatty(0)) - edit = 1; - action = REVERT; - return revert_or_cherry_pick(argc, argv); + opts.edit = 1; + opts.action = REVERT; + git_config(git_default_config, NULL); + parse_args(argc, argv, &opts); + res = pick_revisions(&opts); + if (res < 0) + die(_("revert failed")); + return res; } int cmd_cherry_pick(int argc, const char **argv, const char *prefix) { - action = CHERRY_PICK; - return revert_or_cherry_pick(argc, argv); + struct replay_opts opts; + int res; + + memset(&opts, 0, sizeof(opts)); + opts.action = CHERRY_PICK; + git_config(git_default_config, NULL); + parse_args(argc, argv, &opts); + res = pick_revisions(&opts); + if (res < 0) + die(_("cherry-pick failed")); + return res; } diff --git a/builtin/tag.c b/builtin/tag.c index 667515e527..9d89616863 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -429,21 +429,21 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct msg_arg msg = { 0, STRBUF_INIT }; struct commit_list *with_commit = NULL; struct option options[] = { - OPT_BOOLEAN('l', NULL, &list, "list tag names"), + OPT_BOOLEAN('l', "list", &list, "list tag names"), { OPTION_INTEGER, 'n', NULL, &lines, "n", "print <n> lines of each tag message", PARSE_OPT_OPTARG, NULL, 1 }, - OPT_BOOLEAN('d', NULL, &delete, "delete tags"), - OPT_BOOLEAN('v', NULL, &verify, "verify tags"), + OPT_BOOLEAN('d', "delete", &delete, "delete tags"), + OPT_BOOLEAN('v', "verify", &verify, "verify tags"), OPT_GROUP("Tag creation options"), - OPT_BOOLEAN('a', NULL, &annotate, + OPT_BOOLEAN('a', "annotate", &annotate, "annotated tag, needs a message"), - OPT_CALLBACK('m', NULL, &msg, "message", + OPT_CALLBACK('m', "message", &msg, "message", "tag message", parse_msg_arg), - OPT_FILENAME('F', NULL, &msgfile, "read message from file"), - OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"), - OPT_STRING('u', NULL, &keyid, "key-id", + OPT_FILENAME('F', "file", &msgfile, "read message from file"), + OPT_BOOLEAN('s', "sign", &sign, "annotated and GPG-signed tag"), + OPT_STRING('u', "local-user", &keyid, "key-id", "use another key to sign the tag"), OPT__FORCE(&force, "replace the tag if exists"), @@ -380,12 +380,15 @@ int create_bundle(struct bundle_header *header, const char *path, return 0; } -int unbundle(struct bundle_header *header, int bundle_fd) +int unbundle(struct bundle_header *header, int bundle_fd, int flags) { const char *argv_index_pack[] = {"index-pack", - "--fix-thin", "--stdin", NULL}; + "--fix-thin", "--stdin", NULL, NULL}; struct child_process ip; + if (flags & BUNDLE_VERBOSE) + argv_index_pack[3] = "-v"; + if (verify_bundle(header, 0)) return -1; memset(&ip, 0, sizeof(ip)); @@ -18,7 +18,8 @@ int read_bundle_header(const char *path, struct bundle_header *header); int create_bundle(struct bundle_header *header, const char *path, int argc, const char **argv); int verify_bundle(struct bundle_header *header, int verbose); -int unbundle(struct bundle_header *header, int bundle_fd); +#define BUNDLE_VERBOSE 1 +int unbundle(struct bundle_header *header, int bundle_fd, int flags); int list_bundle_refs(struct bundle_header *header, int argc, const char **argv); @@ -1076,9 +1076,11 @@ extern int git_config_bool(const char *, const char *); extern int git_config_maybe_bool(const char *, const char *); extern int git_config_string(const char **, const char *, const char *); extern int git_config_pathname(const char **, const char *, const char *); +extern int git_config_set_in_file(const char *, const char *, const char *); extern int git_config_set(const char *, const char *); extern int git_config_parse_key(const char *, char **, int *); extern int git_config_set_multivar(const char *, const char *, const char *, int); +extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int); extern int git_config_rename_section(const char *, const char *); extern const char *git_etc_gitconfig(void); extern int check_repository_format_version(const char *var, const char *value, void *cb); diff --git a/compat/obstack.c b/compat/obstack.c index a89ab5b8e8..e276ccd7b3 100644 --- a/compat/obstack.c +++ b/compat/obstack.c @@ -173,7 +173,7 @@ _obstack_begin (struct obstack *h, alignment - 1); h->chunk_limit = chunk->limit = (char *) chunk + h->chunk_size; - chunk->prev = 0; + chunk->prev = NULL; /* The initial chunk now contains no empty object. */ h->maybe_empty_object = 0; h->alloc_failed = 0; @@ -221,7 +221,7 @@ _obstack_begin_1 (struct obstack *h, int size, int alignment, alignment - 1); h->chunk_limit = chunk->limit = (char *) chunk + h->chunk_size; - chunk->prev = 0; + chunk->prev = NULL; /* The initial chunk now contains no empty object. */ h->maybe_empty_object = 0; h->alloc_failed = 0; @@ -321,12 +321,12 @@ _obstack_allocated_p (struct obstack *h, void *obj) /* We use >= rather than > since the object cannot be exactly at the beginning of the chunk but might be an empty object exactly at the end of an adjacent chunk. */ - while (lp != 0 && ((void *) lp >= obj || (void *) (lp)->limit < obj)) + while (lp != NULL && ((void *) lp >= obj || (void *) (lp)->limit < obj)) { plp = lp->prev; lp = plp; } - return lp != 0; + return lp != NULL; } /* Free objects in obstack H, including OBJ and everything allocate @@ -344,7 +344,7 @@ obstack_free (struct obstack *h, void *obj) /* We use >= because there cannot be an object at the beginning of a chunk. But there can be an empty object at that address at the end of another chunk. */ - while (lp != 0 && ((void *) lp >= obj || (void *) (lp)->limit < obj)) + while (lp != NULL && ((void *) lp >= obj || (void *) (lp)->limit < obj)) { plp = lp->prev; CALL_FREEFUN (h, lp); @@ -359,7 +359,7 @@ obstack_free (struct obstack *h, void *obj) h->chunk_limit = lp->limit; h->chunk = lp; } - else if (obj != 0) + else if (obj != NULL) /* obj is not in any of the chunks! */ abort (); } @@ -376,7 +376,7 @@ _obstack_memory_used (struct obstack *h) register struct _obstack_chunk* lp; register int nbytes = 0; - for (lp = h->chunk; lp != 0; lp = lp->prev) + for (lp = h->chunk; lp != NULL; lp = lp->prev) { nbytes += lp->limit - (char *) lp; } @@ -395,7 +395,6 @@ _obstack_memory_used (struct obstack *h) # endif static void -__attribute__ ((noreturn)) print_and_abort (void) { /* Don't change any of these strings. Yes, it would be possible to add @@ -1095,6 +1095,12 @@ contline: return offset; } +int git_config_set_in_file(const char *config_filename, + const char *key, const char *value) +{ + return git_config_set_multivar_in_file(config_filename, key, value, NULL, 0); +} + int git_config_set(const char *key, const char *value) { return git_config_set_multivar(key, value, NULL, 0); @@ -1192,19 +1198,14 @@ out_free_ret_1: * - the config file is removed and the lock file rename()d to it. * */ -int git_config_set_multivar(const char *key, const char *value, - const char *value_regex, int multi_replace) +int git_config_set_multivar_in_file(const char *config_filename, + const char *key, const char *value, + const char *value_regex, int multi_replace) { int fd = -1, in_fd; int ret; - char *config_filename; struct lock_file *lock = NULL; - if (config_exclusive_filename) - config_filename = xstrdup(config_exclusive_filename); - else - config_filename = git_pathdup("config"); - /* parse-key returns negative; flip the sign to feed exit(3) */ ret = 0 - git_config_parse_key(key, &store.key, &store.baselen); if (ret) @@ -1381,7 +1382,6 @@ int git_config_set_multivar(const char *key, const char *value, out_free: if (lock) rollback_lock_file(lock); - free(config_filename); return ret; write_err_out: @@ -1390,6 +1390,24 @@ write_err_out: } +int git_config_set_multivar(const char *key, const char *value, + const char *value_regex, int multi_replace) +{ + const char *config_filename; + char *buf = NULL; + int ret; + + if (config_exclusive_filename) + config_filename = config_exclusive_filename; + else + config_filename = buf = git_pathdup("config"); + + ret = git_config_set_multivar_in_file(config_filename, key, value, + value_regex, multi_replace); + free(buf); + return ret; +} + static int section_name_match (const char *buf, const char *name) { int i = 0, j = 0, dot = 0; diff --git a/connected.c b/connected.c new file mode 100644 index 0000000000..d7624230d4 --- /dev/null +++ b/connected.c @@ -0,0 +1,62 @@ +#include "cache.h" +#include "run-command.h" +#include "sigchain.h" +#include "connected.h" + +/* + * If we feed all the commits we want to verify to this command + * + * $ git rev-list --verify-objects --stdin --not --all + * + * and if it does not error out, that means everything reachable from + * these commits locally exists and is connected to some of our + * existing refs. + * + * Returns 0 if everything is connected, non-zero otherwise. + */ +int check_everything_connected(sha1_iterate_fn fn, int quiet, void *cb_data) +{ + struct child_process rev_list; + const char *argv[] = {"rev-list", "--verify-objects", + "--stdin", "--not", "--all", NULL, NULL}; + char commit[41]; + unsigned char sha1[20]; + int err = 0; + + if (fn(cb_data, sha1)) + return err; + + if (quiet) + argv[5] = "--quiet"; + + memset(&rev_list, 0, sizeof(rev_list)); + rev_list.argv = argv; + rev_list.git_cmd = 1; + rev_list.in = -1; + rev_list.no_stdout = 1; + rev_list.no_stderr = quiet; + if (start_command(&rev_list)) + return error(_("Could not run 'git rev-list'")); + + sigchain_push(SIGPIPE, SIG_IGN); + + commit[40] = '\n'; + do { + memcpy(commit, sha1_to_hex(sha1), 40); + if (write_in_full(rev_list.in, commit, 41) < 0) { + if (errno != EPIPE && errno != EINVAL) + error(_("failed write to rev-list: %s"), + strerror(errno)); + err = -1; + break; + } + } while (!fn(cb_data, sha1)); + + if (close(rev_list.in)) { + error(_("failed to close rev-list's stdin: %s"), strerror(errno)); + err = -1; + } + + sigchain_pop(SIGPIPE); + return finish_command(&rev_list) || err; +} diff --git a/connected.h b/connected.h new file mode 100644 index 0000000000..7e4585a6cb --- /dev/null +++ b/connected.h @@ -0,0 +1,20 @@ +#ifndef CONNECTED_H +#define CONNECTED_H + +/* + * Take callback data, and return next object name in the buffer. + * When called after returning the name for the last object, return -1 + * to signal EOF, otherwise return 0. + */ +typedef int (*sha1_iterate_fn)(void *, unsigned char [20]); + +/* + * Make sure that our object store has all the commits necessary to + * connect the ancestry chain to some of our existing refs, and all + * the trees and blobs that these commits use. + * + * Return 0 if Ok, non zero otherwise (i.e. some missing objects) + */ +extern int check_everything_connected(sha1_iterate_fn, int quiet, void *cb_data); + +#endif /* CONNECTED_H */ diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index fa6d41a1ab..ba077c13f9 100755 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -11,11 +11,11 @@ # will have put this somewhere standard. You should make this script # executable then link to it in the repository you would like to use it in. # For example, on debian the hook is stored in -# /usr/share/doc/git-core/contrib/hooks/post-receive-email: +# /usr/share/git-core/contrib/hooks/post-receive-email: # # chmod a+x post-receive-email # cd /path/to/your/repository.git -# ln -sf /usr/share/doc/git-core/contrib/hooks/post-receive-email hooks/post-receive +# ln -sf /usr/share/git-core/contrib/hooks/post-receive-email hooks/post-receive # # This hook script assumes it is enabled on the central repository of a # project, with all users pushing only to it and not between each other. It @@ -552,23 +552,35 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt static int match_tz(const char *date, int *offp) { char *end; - int offset = strtoul(date+1, &end, 10); - int min, hour; - int n = end - date - 1; + int hour = strtoul(date + 1, &end, 10); + int n = end - (date + 1); + int min = 0; - min = offset % 100; - hour = offset / 100; + if (n == 4) { + /* hhmm */ + min = hour % 100; + hour = hour / 100; + } else if (n != 2) { + min = 99; /* random crap */ + } else if (*end == ':') { + /* hh:mm? */ + min = strtoul(end + 1, &end, 10); + if (end - (date + 1) != 5) + min = 99; /* random crap */ + } /* otherwise we parsed "hh" */ /* - * Don't accept any random crap.. At least 3 digits, and - * a valid minute. We might want to check that the minutes - * are divisible by 30 or something too. + * Don't accept any random crap. Even though some places have + * offset larger than 12 hours (e.g. Pacific/Kiritimati is at + * UTC+14), there is something wrong if hour part is much + * larger than that. We might also want to check that the + * minutes are divisible by 15 or something too. (Offset of + * Kathmandu, Nepal is UTC+5:45) */ - if (min < 60 && n > 2) { - offset = hour*60+min; + if (min < 60 && hour < 24) { + int offset = hour * 60 + min; if (*date == '-') offset = -offset; - *offp = offset; } return end - date; diff --git a/diff-lib.c b/diff-lib.c index f8454dd291..62f4cd94cf 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -289,7 +289,7 @@ static void show_new_file(struct rev_info *revs, /* * New file in the index: it might actually be different in - * the working copy. + * the working tree. */ if (get_stat_data(new, &sha1, &mode, cached, match_missing, &dirty_submodule, &revs->diffopt) < 0) @@ -468,6 +468,7 @@ static int diff_cache(struct rev_info *revs, opts.unpack_data = revs; opts.src_index = &the_index; opts.dst_index = NULL; + opts.pathspec = &revs->diffopt.pathspec; init_tree_desc(&t, tree->buffer, tree->size); return unpack_trees(1, &t, &opts); @@ -311,6 +311,40 @@ split_patches () { this= msgnum= ;; + hg) + this=0 + for hg in "$@" + do + this=$(( $this + 1 )) + msgnum=$(printf "%0${prec}d" $this) + # hg stores changeset metadata in #-commented lines preceding + # the commit message and diff(s). The only metadata we care about + # are the User and Date (Node ID and Parent are hashes which are + # only relevant to the hg repository and thus not useful to us) + # Since we cannot guarantee that the commit message is in + # git-friendly format, we put no Subject: line and just consume + # all of the message as the body + perl -M'POSIX qw(strftime)' -ne 'BEGIN { $subject = 0 } + if ($subject) { print ; } + elsif (/^\# User /) { s/\# User/From:/ ; print ; } + elsif (/^\# Date /) { + my ($hashsign, $str, $time, $tz) = split ; + $tz = sprintf "%+05d", (0-$tz)/36; + print "Date: " . + strftime("%a, %d %b %Y %H:%M:%S ", + localtime($time)) + . "$tz\n"; + } elsif (/^\# /) { next ; } + else { + print "\n", $_ ; + $subject = 1; + } + ' <"$hg" >"$dotest/$msgnum" || clean_abort + done + echo "$this" >"$dotest/last" + this= + msgnum= + ;; *) if test -n "$patch_format" then diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 804a7f4bc9..add2c0247f 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -108,9 +108,7 @@ OPTIONS_SPEC= . git-sh-setup if [ "$(is_bare_repository)" = false ]; then - git diff-files --ignore-submodules --quiet && - git diff-index --cached --quiet HEAD -- || - die "Cannot rewrite branch(es) with a dirty working directory." + require_clean_work_tree 'rewrite branches' fi tempdir=.git-rewrite diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh index 8fc65d0400..ed630b208a 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -21,7 +21,7 @@ check_unchanged () { do echo "$MERGED seems unchanged." printf "Was the merge successful? [y/n] " - read answer + read answer || return 1 case "$answer" in y*|Y*) status=0; break ;; n*|N*) status=1; break ;; diff --git a/git-mergetool.sh b/git-mergetool.sh index 3c157bcd26..b6d463f0d0 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -72,7 +72,7 @@ describe_file () { resolve_symlink_merge () { while true; do printf "Use (l)ocal or (r)emote, or (a)bort? " - read ans + read ans || return 1 case "$ans" in [lL]*) git checkout-index -f --stage=2 -- "$MERGED" diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index c6ba7c1551..94f36c254c 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -472,18 +472,24 @@ do_next () { git rev-parse --verify HEAD > "$state_dir"/stopped-sha ${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution status=$? + # Run in subshell because require_clean_work_tree can die. + dirty=f + (require_clean_work_tree "rebase" 2>/dev/null) || dirty=t if test "$status" -ne 0 then warn "Execution failed: $rest" + test "$dirty" = f || + warn "and made changes to the index and/or the working tree" + warn "You can fix the problem, and then run" warn warn " git rebase --continue" warn exit "$status" - fi - # Run in subshell because require_clean_work_tree can die. - if ! (require_clean_work_tree "rebase") + elif test "$dirty" = t then + warn "Execution succeeded: $rest" + warn "but left changes to the index and/or the working tree" warn "Commit or stash your changes, and then run" warn warn " git rebase --continue" @@ -647,8 +653,24 @@ continue) then : Nothing to commit -- skip this else + if ! test -f "$author_script" + then + die "You have staged changes in your working tree. If these changes are meant to be +squashed into the previous commit, run: + + git commit --amend + +If they are meant to go into a new commit, run: + + git commit + +In both case, once you're done, continue with: + + git rebase --continue +" + fi . "$author_script" || - die "Cannot find the author identity" + die "Error trying to find the author identity to amend commit" current_head= if test -f "$amend" then diff --git a/git-send-email.perl b/git-send-email.perl index 98ab33aae7..734356a6fb 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -275,7 +275,9 @@ $SIG{INT} = \&signal_handler; # Begin by accumulating all the variables (defined above), that we will end up # needing, first, from the command line: -my $rc = GetOptions("sender|from=s" => \$sender, +my $help; +my $rc = GetOptions("h" => \$help, + "sender|from=s" => \$sender, "in-reply-to=s" => \$initial_reply_to, "subject=s" => \$initial_subject, "to=s" => \@initial_to, @@ -313,6 +315,7 @@ my $rc = GetOptions("sender|from=s" => \$sender, "force" => \$force, ); +usage() if $help; unless ($rc) { usage(); } diff --git a/git-stash.sh b/git-stash.sh index 31dec0abf1..c76669284c 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -211,7 +211,7 @@ save_stash () { if test -n "$patch_mode" && test -n "$untracked" then - die "Can't use --patch and ---include-untracked or --all at the same time" + die "Can't use --patch and --include-untracked or --all at the same time" fi stash_msg="$*" @@ -240,7 +240,7 @@ save_stash () { test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION= if test -n "$untracked" then - git clean --force --quiet $CLEAN_X_OPTION + git clean --force --quiet -d $CLEAN_X_OPTION fi if test "$keep_index" = "t" && test -n $i_tree diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 70a576a626..85d64b244d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1517,6 +1517,17 @@ sub esc_path { return $str; } +# Sanitize for use in XHTML + application/xml+xhtm (valid XML 1.0) +sub sanitize { + my $str = shift; + + return undef unless defined $str; + + $str = to_utf8($str); + $str =~ s|([[:cntrl:]])|($1 =~ /[\t\n\r]/ ? $1 : quot_cec($1))|eg; + return $str; +} + # Make control characters "printable", using character escape codes (CEC) sub quot_cec { my $cntrl = shift; @@ -6484,7 +6495,8 @@ sub git_blob { $nr++; $line = untabify($line); printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!, - $nr, esc_attr(href(-replay => 1)), $nr, $nr, $syntax ? to_utf8($line) : esc_html($line, -nbsp=>1); + $nr, esc_attr(href(-replay => 1)), $nr, $nr, + $syntax ? sanitize($line) : esc_html($line, -nbsp=>1); } } close $fd @@ -674,7 +674,7 @@ cwexec (kwset_t kws, char const *text, size_t len, struct kwsmatch *kwsmatch) copy of the preceding main search loops. */ if (lim - mch > kwset->maxd) lim = mch + kwset->maxd; - lmch = 0; + lmch = NULL; d = 1; while (lim - end >= d) { diff --git a/list-objects.c b/list-objects.c index 0fb44e7ed7..39d80c0175 100644 --- a/list-objects.c +++ b/list-objects.c @@ -12,7 +12,8 @@ static void process_blob(struct rev_info *revs, struct blob *blob, show_object_fn show, struct name_path *path, - const char *name) + const char *name, + void *cb_data) { struct object *obj = &blob->object; @@ -23,7 +24,7 @@ static void process_blob(struct rev_info *revs, if (obj->flags & (UNINTERESTING | SEEN)) return; obj->flags |= SEEN; - show(obj, path, name); + show(obj, path, name, cb_data); } /* @@ -52,7 +53,8 @@ static void process_gitlink(struct rev_info *revs, const unsigned char *sha1, show_object_fn show, struct name_path *path, - const char *name) + const char *name, + void *cb_data) { /* Nothing to do */ } @@ -62,7 +64,8 @@ static void process_tree(struct rev_info *revs, show_object_fn show, struct name_path *path, struct strbuf *base, - const char *name) + const char *name, + void *cb_data) { struct object *obj = &tree->object; struct tree_desc desc; @@ -80,7 +83,7 @@ static void process_tree(struct rev_info *revs, if (parse_tree(tree) < 0) die("bad tree object %s", sha1_to_hex(obj->sha1)); obj->flags |= SEEN; - show(obj, path, name); + show(obj, path, name, cb_data); me.up = path; me.elem = name; me.elem_len = strlen(name); @@ -106,14 +109,17 @@ static void process_tree(struct rev_info *revs, if (S_ISDIR(entry.mode)) process_tree(revs, lookup_tree(entry.sha1), - show, &me, base, entry.path); + show, &me, base, entry.path, + cb_data); else if (S_ISGITLINK(entry.mode)) process_gitlink(revs, entry.sha1, - show, &me, entry.path); + show, &me, entry.path, + cb_data); else process_blob(revs, lookup_blob(entry.sha1), - show, &me, entry.path); + show, &me, entry.path, + cb_data); } strbuf_setlen(base, baselen); free(tree->buffer); @@ -185,17 +191,17 @@ void traverse_commit_list(struct rev_info *revs, continue; if (obj->type == OBJ_TAG) { obj->flags |= SEEN; - show_object(obj, NULL, name); + show_object(obj, NULL, name, data); continue; } if (obj->type == OBJ_TREE) { process_tree(revs, (struct tree *)obj, show_object, - NULL, &base, name); + NULL, &base, name, data); continue; } if (obj->type == OBJ_BLOB) { process_blob(revs, (struct blob *)obj, show_object, - NULL, name); + NULL, name, data); continue; } die("unknown pending object %s (%s)", diff --git a/list-objects.h b/list-objects.h index d65dbf03e6..3db7bb6fa3 100644 --- a/list-objects.h +++ b/list-objects.h @@ -2,11 +2,10 @@ #define LIST_OBJECTS_H typedef void (*show_commit_fn)(struct commit *, void *); -typedef void (*show_object_fn)(struct object *, const struct name_path *, const char *); -typedef void (*show_edge_fn)(struct commit *); - +typedef void (*show_object_fn)(struct object *, const struct name_path *, const char *, void *); void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, void *); +typedef void (*show_edge_fn)(struct commit *); void mark_edges_uninteresting(struct commit_list *, struct rev_info *, show_edge_fn); #endif diff --git a/merge-recursive.c b/merge-recursive.c index 6bbc4512a9..c34a4f148b 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -390,7 +390,7 @@ static void record_df_conflict_files(struct merge_options *o, struct string_list *entries) { /* If there is a D/F conflict and the file for such a conflict - * currently exist in the working copy, we want to allow it to be + * currently exist in the working tree, we want to allow it to be * removed to make room for the corresponding directory if needed. * The files underneath the directories of such D/F conflicts will * be processed before the corresponding file involved in the D/F @@ -1627,7 +1627,7 @@ static int merge_content(struct merge_options *o, path_renamed_outside_HEAD = !path2 || !strcmp(path, path2); if (!path_renamed_outside_HEAD) { add_cacheinfo(mfi.mode, mfi.sha, path, - 0 /*stage*/, 1 /*refresh*/, 0 /*options*/); + 0, (!o->call_depth), 0); return mfi.clean; } } else @@ -1,5 +1,6 @@ #include "cache.h" #include "quote.h" +#include "argv-array.h" int quote_path_fully = 1; @@ -120,7 +121,9 @@ char *sq_dequote(char *arg) return sq_dequote_step(arg, NULL); } -int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc) +static int sq_dequote_to_argv_internal(char *arg, + const char ***argv, int *nr, int *alloc, + struct argv_array *array) { char *next = arg; @@ -130,13 +133,27 @@ int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc) char *dequoted = sq_dequote_step(next, &next); if (!dequoted) return -1; - ALLOC_GROW(*argv, *nr + 1, *alloc); - (*argv)[(*nr)++] = dequoted; + if (argv) { + ALLOC_GROW(*argv, *nr + 1, *alloc); + (*argv)[(*nr)++] = dequoted; + } + if (array) + argv_array_push(array, dequoted); } while (next); return 0; } +int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc) +{ + return sq_dequote_to_argv_internal(arg, argv, nr, alloc, NULL); +} + +int sq_dequote_to_argv_array(char *arg, struct argv_array *array) +{ + return sq_dequote_to_argv_internal(arg, NULL, NULL, NULL, array); +} + /* 1 means: quote as octal * 0 means: quote as octal if (quote_path_fully) * -1 means: never quote @@ -40,12 +40,19 @@ extern char *sq_dequote(char *); /* * Same as the above, but can be used to unwrap many arguments in the - * same string separated by space. "next" is changed to point to the - * next argument that should be passed as first parameter. When there - * is no more argument to be dequoted, "next" is updated to point to NULL. + * same string separated by space. Like sq_quote, it works in place, + * modifying arg and appending pointers into it to argv. */ extern int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc); +/* + * Same as above, but store the unquoted strings in an argv_array. We will + * still modify arg in place, but unlike sq_dequote_to_argv, the argv_array + * will duplicate and take ownership of the strings. + */ +struct argv_array; +extern int sq_dequote_to_argv_array(char *arg, struct argv_array *); + extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp); extern size_t quote_c_style(const char *name, struct strbuf *, FILE *, int no_dq); extern void quote_two_c_style(struct strbuf *, const char *, const char *, int); diff --git a/revision.c b/revision.c index c46cfaa3e4..9bae329c15 100644 --- a/revision.c +++ b/revision.c @@ -40,6 +40,47 @@ char *path_name(const struct name_path *path, const char *name) return n; } +static int show_path_component_truncated(FILE *out, const char *name, int len) +{ + int cnt; + for (cnt = 0; cnt < len; cnt++) { + int ch = name[cnt]; + if (!ch || ch == '\n') + return -1; + fputc(ch, out); + } + return len; +} + +static int show_path_truncated(FILE *out, const struct name_path *path) +{ + int emitted, ours; + + if (!path) + return 0; + emitted = show_path_truncated(out, path->up); + if (emitted < 0) + return emitted; + if (emitted) + fputc('/', out); + ours = show_path_component_truncated(out, path->elem, path->elem_len); + if (ours < 0) + return ours; + return ours || emitted; +} + +void show_object_with_name(FILE *out, struct object *obj, const struct name_path *path, const char *component) +{ + struct name_path leaf; + leaf.up = (struct name_path *)path; + leaf.elem = component; + leaf.elem_len = strlen(component); + + fprintf(out, "%s ", sha1_to_hex(obj->sha1)); + show_path_truncated(out, &leaf); + fputc('\n', out); +} + void add_object(struct object *obj, struct object_array *p, struct name_path *path, @@ -729,12 +770,16 @@ static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *li * to filter the result of "A..B" further to the ones that can actually * reach A. */ -static struct commit_list *collect_bottom_commits(struct commit_list *list) +static struct commit_list *collect_bottom_commits(struct rev_info *revs) { - struct commit_list *elem, *bottom = NULL; - for (elem = list; elem; elem = elem->next) - if (elem->item->object.flags & UNINTERESTING) - commit_list_insert(elem->item, &bottom); + struct commit_list *bottom = NULL; + int i; + for (i = 0; i < revs->cmdline.nr; i++) { + struct rev_cmdline_entry *elem = &revs->cmdline.rev[i]; + if ((elem->flags & UNINTERESTING) && + elem->item->type == OBJ_COMMIT) + commit_list_insert((struct commit *)elem->item, &bottom); + } return bottom; } @@ -765,7 +810,7 @@ static int limit_list(struct rev_info *revs) struct commit_list *bottom = NULL; if (revs->ancestry_path) { - bottom = collect_bottom_commits(list); + bottom = collect_bottom_commits(revs); if (!bottom) die("--ancestry-path given but there are no bottom commits"); } @@ -822,6 +867,23 @@ static int limit_list(struct rev_info *revs) return 0; } +static void add_rev_cmdline(struct rev_info *revs, + struct object *item, + const char *name, + int whence, + unsigned flags) +{ + struct rev_cmdline_info *info = &revs->cmdline; + int nr = info->nr; + + ALLOC_GROW(info->rev, nr + 1, info->alloc); + info->rev[nr].item = item; + info->rev[nr].name = name; + info->rev[nr].whence = whence; + info->rev[nr].flags = flags; + info->nr++; +} + struct all_refs_cb { int all_flags; int warned_bad_reflog; @@ -834,6 +896,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, struct all_refs_cb *cb = cb_data; struct object *object = get_reference(cb->all_revs, path, sha1, cb->all_flags); + add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags); add_pending_object(cb->all_revs, object, path); return 0; } @@ -860,6 +923,7 @@ static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data) struct object *o = parse_object(sha1); if (o) { o->flags |= cb->all_flags; + /* ??? CMDLINEFLAGS ??? */ add_pending_object(cb->all_revs, o, ""); } else if (!cb->warned_bad_reflog) { @@ -896,12 +960,13 @@ static void handle_reflog(struct rev_info *revs, unsigned flags) for_each_reflog(handle_one_reflog, &cb); } -static int add_parents_only(struct rev_info *revs, const char *arg, int flags) +static int add_parents_only(struct rev_info *revs, const char *arg_, int flags) { unsigned char sha1[20]; struct object *it; struct commit *commit; struct commit_list *parents; + const char *arg = arg_; if (*arg == '^') { flags ^= UNINTERESTING; @@ -925,6 +990,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags) for (parents = commit->parents; parents; parents = parents->next) { it = &parents->item->object; it->flags |= flags; + add_rev_cmdline(revs, it, arg_, REV_CMD_PARENTS_ONLY, flags); add_pending_object(revs, it, arg); } return 1; @@ -1018,7 +1084,7 @@ static void prepare_show_merge(struct rev_info *revs) revs->limited = 1; } -int handle_revision_arg(const char *arg, struct rev_info *revs, +int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, int cant_be_filename) { @@ -1027,6 +1093,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, struct object *object; unsigned char sha1[20]; int local_flags; + const char *arg = arg_; dotdot = strstr(arg, ".."); if (dotdot) { @@ -1035,6 +1102,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, const char *this = arg; int symmetric = *next == '.'; unsigned int flags_exclude = flags ^ UNINTERESTING; + unsigned int a_flags; *dotdot = 0; next += symmetric; @@ -1069,10 +1137,15 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, add_pending_commit_list(revs, exclude, flags_exclude); free_commit_list(exclude); - a->object.flags |= flags | SYMMETRIC_LEFT; + a_flags = flags | SYMMETRIC_LEFT; } else - a->object.flags |= flags_exclude; + a_flags = flags_exclude; + a->object.flags |= a_flags; b->object.flags |= flags; + add_rev_cmdline(revs, &a->object, this, + REV_CMD_LEFT, a_flags); + add_rev_cmdline(revs, &b->object, next, + REV_CMD_RIGHT, flags); add_pending_object(revs, &a->object, this); add_pending_object(revs, &b->object, next); return 0; @@ -1103,6 +1176,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, if (!cant_be_filename) verify_non_filename(revs->prefix, arg); object = get_reference(revs, arg, sha1, flags ^ local_flags); + add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags); add_pending_object_with_mode(revs, object, arg, mode); return 0; } @@ -1342,6 +1416,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->tree_objects = 1; revs->blob_objects = 1; revs->edge_hint = 1; + } else if (!strcmp(arg, "--verify-objects")) { + revs->tag_objects = 1; + revs->tree_objects = 1; + revs->blob_objects = 1; + revs->verify_objects = 1; } else if (!strcmp(arg, "--unpacked")) { revs->unpacked = 1; } else if (!prefixcmp(arg, "--unpacked=")) { diff --git a/revision.h b/revision.h index 3d64adad18..754f31b1cd 100644 --- a/revision.h +++ b/revision.h @@ -24,6 +24,23 @@ struct rev_info; struct log_info; struct string_list; +struct rev_cmdline_info { + unsigned int nr; + unsigned int alloc; + struct rev_cmdline_entry { + struct object *item; + const char *name; + enum { + REV_CMD_REF, + REV_CMD_PARENTS_ONLY, + REV_CMD_LEFT, + REV_CMD_RIGHT, + REV_CMD_REV + } whence; + unsigned flags; + } *rev; +}; + struct rev_info { /* Starting list */ struct commit_list *commits; @@ -32,6 +49,9 @@ struct rev_info { /* Parents of shown commits */ struct object_array boundary_commits; + /* The end-points specified by the end user */ + struct rev_cmdline_info cmdline; + /* Basic information */ const char *prefix; const char *def; @@ -53,6 +73,7 @@ struct rev_info { tag_objects:1, tree_objects:1, blob_objects:1, + verify_objects:1, edge_hint:1, limited:1, unpacked:1, @@ -185,6 +206,8 @@ struct name_path { char *path_name(const struct name_path *path, const char *name); +extern void show_object_with_name(FILE *, struct object *, const struct name_path *, const char *); + extern void add_object(struct object *obj, struct object_array *p, struct name_path *path, diff --git a/run-command.c b/run-command.c index a2796c4cae..1c51043884 100644 --- a/run-command.c +++ b/run-command.c @@ -1,6 +1,7 @@ #include "cache.h" #include "run-command.h" #include "exec_cmd.h" +#include "argv-array.h" static inline void close_pair(int fd[2]) { @@ -605,26 +606,23 @@ int finish_async(struct async *async) int run_hook(const char *index_file, const char *name, ...) { struct child_process hook; - const char **argv = NULL, *env[2]; + struct argv_array argv = ARGV_ARRAY_INIT; + const char *p, *env[2]; char index[PATH_MAX]; va_list args; int ret; - size_t i = 0, alloc = 0; if (access(git_path("hooks/%s", name), X_OK) < 0) return 0; va_start(args, name); - ALLOC_GROW(argv, i + 1, alloc); - argv[i++] = git_path("hooks/%s", name); - while (argv[i-1]) { - ALLOC_GROW(argv, i + 1, alloc); - argv[i++] = va_arg(args, const char *); - } + argv_array_push(&argv, git_path("hooks/%s", name)); + while ((p = va_arg(args, const char *))) + argv_array_push(&argv, p); va_end(args); memset(&hook, 0, sizeof(hook)); - hook.argv = argv; + hook.argv = argv.argv; hook.no_stdin = 1; hook.stdout_to_stderr = 1; if (index_file) { @@ -635,6 +633,6 @@ int run_hook(const char *index_file, const char *name, ...) } ret = run_command(&hook); - free(argv); + argv_array_clear(&argv); return ret; } diff --git a/sequencer.c b/sequencer.c new file mode 100644 index 0000000000..bc2c046aab --- /dev/null +++ b/sequencer.c @@ -0,0 +1,19 @@ +#include "cache.h" +#include "sequencer.h" +#include "strbuf.h" +#include "dir.h" + +void remove_sequencer_state(int aggressive) +{ + struct strbuf seq_dir = STRBUF_INIT; + struct strbuf seq_old_dir = STRBUF_INIT; + + strbuf_addf(&seq_dir, "%s", git_path(SEQ_DIR)); + strbuf_addf(&seq_old_dir, "%s", git_path(SEQ_OLD_DIR)); + remove_dir_recursively(&seq_old_dir, 0); + rename(git_path(SEQ_DIR), git_path(SEQ_OLD_DIR)); + if (aggressive) + remove_dir_recursively(&seq_old_dir, 0); + strbuf_release(&seq_dir); + strbuf_release(&seq_old_dir); +} diff --git a/sequencer.h b/sequencer.h new file mode 100644 index 0000000000..905d295012 --- /dev/null +++ b/sequencer.h @@ -0,0 +1,20 @@ +#ifndef SEQUENCER_H +#define SEQUENCER_H + +#define SEQ_DIR "sequencer" +#define SEQ_OLD_DIR "sequencer-old" +#define SEQ_HEAD_FILE "sequencer/head" +#define SEQ_TODO_FILE "sequencer/todo" +#define SEQ_OPTS_FILE "sequencer/opts" + +/* + * Removes SEQ_OLD_DIR and renames SEQ_DIR to SEQ_OLD_DIR, ignoring + * any errors. Intended to be used by 'git reset'. + * + * With the aggressive flag, it additionally removes SEQ_OLD_DIR, + * ignoring any errors. Inteded to be used by the sequencer's + * '--reset' subcommand. + */ +void remove_sequencer_state(int aggressive); + +#endif diff --git a/sha1_file.c b/sha1_file.c index e002056b85..3401301444 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -248,27 +248,30 @@ static int link_alt_odb_entry(const char * entry, int len, const char * relative const char *objdir = get_object_directory(); struct alternate_object_database *ent; struct alternate_object_database *alt; - /* 43 = 40-byte + 2 '/' + terminating NUL */ - int pfxlen = len; - int entlen = pfxlen + 43; - int base_len = -1; + int pfxlen, entlen; + struct strbuf pathbuf = STRBUF_INIT; if (!is_absolute_path(entry) && relative_base) { - /* Relative alt-odb */ - if (base_len < 0) - base_len = strlen(relative_base) + 1; - entlen += base_len; - pfxlen += base_len; + strbuf_addstr(&pathbuf, real_path(relative_base)); + strbuf_addch(&pathbuf, '/'); } - ent = xmalloc(sizeof(*ent) + entlen); + strbuf_add(&pathbuf, entry, len); - if (!is_absolute_path(entry) && relative_base) { - memcpy(ent->base, relative_base, base_len - 1); - ent->base[base_len - 1] = '/'; - memcpy(ent->base + base_len, entry, len); - } - else - memcpy(ent->base, entry, pfxlen); + normalize_path_copy(pathbuf.buf, pathbuf.buf); + + pfxlen = strlen(pathbuf.buf); + + /* + * The trailing slash after the directory name is given by + * this function at the end. Remove duplicates. + */ + while (pfxlen && pathbuf.buf[pfxlen-1] == '/') + pfxlen -= 1; + + entlen = pfxlen + 43; /* '/' + 2 hex + '/' + 38 hex + NUL */ + ent = xmalloc(sizeof(*ent) + entlen); + memcpy(ent->base, pathbuf.buf, pfxlen); + strbuf_release(&pathbuf); ent->name = ent->base + pfxlen + 1; ent->base[pfxlen + 3] = '/'; diff --git a/sha1_name.c b/sha1_name.c index ff5992acc9..653b0659be 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -501,12 +501,6 @@ struct object *peel_to_type(const char *name, int namelen, { if (name && !namelen) namelen = strlen(name); - if (!o) { - unsigned char sha1[20]; - if (get_sha1_1(name, namelen, sha1)) - return NULL; - o = parse_object(sha1); - } while (1) { if (!o || (!o->parsed && !parse_object(o->sha1))) return NULL; diff --git a/submodule.c b/submodule.c index ad86534ba1..0b709bc291 100644 --- a/submodule.c +++ b/submodule.c @@ -8,12 +8,18 @@ #include "diffcore.h" #include "refs.h" #include "string-list.h" +#include "sha1-array.h" +#include "argv-array.h" static struct string_list config_name_for_path; static struct string_list config_fetch_recurse_submodules_for_name; static struct string_list config_ignore_for_name; static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; static struct string_list changed_submodule_paths; +static int initialized_fetch_ref_tips; +static struct sha1_array ref_tips_before_fetch; +static struct sha1_array ref_tips_after_fetch; + /* * The following flag is set if the .gitmodules file is unmerged. We then * disable recursion for all submodules where .git/config doesn't have a @@ -474,20 +480,46 @@ static void submodule_collect_changed_cb(struct diff_queue_struct *q, } } +static int add_sha1_to_array(const char *ref, const unsigned char *sha1, + int flags, void *data) +{ + sha1_array_append(data, sha1); + return 0; +} + void check_for_new_submodule_commits(unsigned char new_sha1[20]) { + if (!initialized_fetch_ref_tips) { + for_each_ref(add_sha1_to_array, &ref_tips_before_fetch); + initialized_fetch_ref_tips = 1; + } + + sha1_array_append(&ref_tips_after_fetch, new_sha1); +} + +static void add_sha1_to_argv(const unsigned char sha1[20], void *data) +{ + argv_array_push(data, sha1_to_hex(sha1)); +} + +static void calculate_changed_submodule_paths(void) +{ struct rev_info rev; struct commit *commit; - const char *argv[] = {NULL, NULL, "--not", "--all", NULL}; - int argc = ARRAY_SIZE(argv) - 1; + struct argv_array argv = ARGV_ARRAY_INIT; /* No need to check if there are no submodules configured */ if (!config_name_for_path.nr) return; init_revisions(&rev, NULL); - argv[1] = xstrdup(sha1_to_hex(new_sha1)); - setup_revisions(argc, argv, &rev, NULL); + argv_array_push(&argv, "--"); /* argv[0] program name */ + sha1_array_for_each_unique(&ref_tips_after_fetch, + add_sha1_to_argv, &argv); + argv_array_push(&argv, "--not"); + sha1_array_for_each_unique(&ref_tips_before_fetch, + add_sha1_to_argv, &argv); + setup_revisions(argv.argc, argv.argv, &rev, NULL); if (prepare_revision_walk(&rev)) die("revision walk setup failed"); @@ -511,7 +543,11 @@ void check_for_new_submodule_commits(unsigned char new_sha1[20]) parent = parent->next; } } - free((char *)argv[1]); + + argv_array_clear(&argv); + sha1_array_clear(&ref_tips_before_fetch); + sha1_array_clear(&ref_tips_after_fetch); + initialized_fetch_ref_tips = 0; } int fetch_populated_submodules(int num_options, const char **options, @@ -545,6 +581,8 @@ int fetch_populated_submodules(int num_options, const char **options, cp.git_cmd = 1; cp.no_stdin = 1; + calculate_changed_submodule_paths(); + for (i = 0; i < active_nr; i++) { struct strbuf submodule_path = STRBUF_INIT; struct strbuf submodule_git_dir = STRBUF_INIT; diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh new file mode 100755 index 0000000000..05824fa8e4 --- /dev/null +++ b/t/lib-gpg.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +gpg_version=`gpg --version 2>&1` +if test $? = 127; then + say "You do not seem to have gpg installed" +else + # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19 + # the gpg version 1.0.6 didn't parse trust packets correctly, so for + # that version, creation of signed tags using the generated key fails. + case "$gpg_version" in + 'gpg (GnuPG) 1.0.6'*) + say "Your version of gpg (1.0.6) is too buggy for testing" + ;; + *) + # key generation info: gpg --homedir t/lib-gpg --gen-key + # Type DSA and Elgamal, size 2048 bits, no expiration date. + # Name and email: C O Mitter <committer@example.com> + # No password given, to enable non-interactive operation. + cp -R "$TEST_DIRECTORY"/lib-gpg ./gpghome + chmod 0700 gpghome + GNUPGHOME="$(pwd)/gpghome" + export GNUPGHOME + test_set_prereq GPG + ;; + esac +fi + +sanitize_pgp() { + perl -ne ' + /^-----END PGP/ and $in_pgp = 0; + print unless $in_pgp; + /^-----BEGIN PGP/ and $in_pgp = 1; + ' +} diff --git a/t/t7004/pubring.gpg b/t/lib-gpg/pubring.gpg Binary files differindex 83855fa4e1..83855fa4e1 100644 --- a/t/t7004/pubring.gpg +++ b/t/lib-gpg/pubring.gpg diff --git a/t/t7004/random_seed b/t/lib-gpg/random_seed Binary files differindex 8fed1339ed..8fed1339ed 100644 --- a/t/t7004/random_seed +++ b/t/lib-gpg/random_seed diff --git a/t/t7004/secring.gpg b/t/lib-gpg/secring.gpg Binary files differindex d831cd9eb3..d831cd9eb3 100644 --- a/t/t7004/secring.gpg +++ b/t/lib-gpg/secring.gpg diff --git a/t/t7004/trustdb.gpg b/t/lib-gpg/trustdb.gpg Binary files differindex abace962b8..abace962b8 100644 --- a/t/t7004/trustdb.gpg +++ b/t/lib-gpg/trustdb.gpg diff --git a/t/t0006-date.sh b/t/t0006-date.sh index f87abb5a06..1d29810a7a 100755 --- a/t/t0006-date.sh +++ b/t/t0006-date.sh @@ -40,6 +40,12 @@ check_parse 2008-02 bad check_parse 2008-02-14 bad check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 +0000' check_parse '2008-02-14 20:30:45 -0500' '2008-02-14 20:30:45 -0500' +check_parse '2008-02-14 20:30:45 -0015' '2008-02-14 20:30:45 -0015' +check_parse '2008-02-14 20:30:45 -5' '2008-02-14 20:30:45 +0000' +check_parse '2008-02-14 20:30:45 -5:' '2008-02-14 20:30:45 +0000' +check_parse '2008-02-14 20:30:45 -05' '2008-02-14 20:30:45 -0500' +check_parse '2008-02-14 20:30:45 -:30' '2008-02-14 20:30:45 +0000' +check_parse '2008-02-14 20:30:45 -05:00' '2008-02-14 20:30:45 -0500' check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 -0500' EST5 check_approxidate() { diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 7633930bb4..2f5eada0d2 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -23,7 +23,7 @@ test_expect_success \ test_expect_success \ 'git branch --help should not have created a bogus branch' ' git branch --help </dev/null >/dev/null 2>/dev/null; - ! test -f .git/refs/heads/--help + test_path_is_missing .git/refs/heads/--help ' test_expect_success 'branch -h in broken repository' ' @@ -39,11 +39,11 @@ test_expect_success 'branch -h in broken repository' ' test_expect_success \ 'git branch abc should create a branch' \ - 'git branch abc && test -f .git/refs/heads/abc' + 'git branch abc && test_path_is_file .git/refs/heads/abc' test_expect_success \ 'git branch a/b/c should create a branch' \ - 'git branch a/b/c && test -f .git/refs/heads/a/b/c' + 'git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c' cat >expect <<EOF $_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master @@ -52,15 +52,15 @@ test_expect_success \ 'git branch -l d/e/f should create a branch and a log' \ 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ git branch -l d/e/f && - test -f .git/refs/heads/d/e/f && - test -f .git/logs/refs/heads/d/e/f && + test_path_is_file .git/refs/heads/d/e/f && + test_path_is_file .git/logs/refs/heads/d/e/f && test_cmp expect .git/logs/refs/heads/d/e/f' test_expect_success \ 'git branch -d d/e/f should delete a branch and a log' \ 'git branch -d d/e/f && - test ! -f .git/refs/heads/d/e/f && - test ! -f .git/logs/refs/heads/d/e/f' + test_path_is_missing .git/refs/heads/d/e/f && + test_path_is_missing .git/logs/refs/heads/d/e/f' test_expect_success \ 'git branch j/k should work after branch j has been deleted' \ @@ -78,13 +78,13 @@ test_expect_success \ 'git branch -m m m/m should work' \ 'git branch -l m && git branch -m m m/m && - test -f .git/logs/refs/heads/m/m' + test_path_is_file .git/logs/refs/heads/m/m' test_expect_success \ 'git branch -m n/n n should work' \ 'git branch -l n/n && git branch -m n/n n - test -f .git/logs/refs/heads/n' + test_path_is_file .git/logs/refs/heads/n' test_expect_success 'git branch -m o/o o should fail when o/p exists' ' git branch o/o && @@ -110,6 +110,38 @@ test_expect_success 'git branch -M baz bam should succeed when baz is checked ou git branch -M baz bam ' +test_expect_success 'git branch -v -d t should work' ' + git branch t && + test_path_is_file .git/refs/heads/t && + git branch -v -d t && + test_path_is_missing .git/refs/heads/t +' + +test_expect_success 'git branch -v -m t s should work' ' + git branch t && + test_path_is_file .git/refs/heads/t && + git branch -v -m t s && + test_path_is_missing .git/refs/heads/t && + test_path_is_file .git/refs/heads/s && + git branch -d s +' + +test_expect_success 'git branch -m -d t s should fail' ' + git branch t && + test_path_is_file .git/refs/heads/t && + test_must_fail git branch -m -d t s && + git branch -d t && + test_path_is_missing .git/refs/heads/t +' + +test_expect_success 'git branch --list -d t should fail' ' + git branch t && + test_path_is_file .git/refs/heads/t && + test_must_fail git branch --list -d t && + git branch -d t && + test_path_is_missing .git/refs/heads/t +' + mv .git/config .git/config-saved test_expect_success 'git branch -m q q2 without config should succeed' ' @@ -124,12 +156,12 @@ git config branch.s/s.dummy Hello test_expect_success \ 'git branch -m s/s s should work when s/t is deleted' \ 'git branch -l s/s && - test -f .git/logs/refs/heads/s/s && + test_path_is_file .git/logs/refs/heads/s/s && git branch -l s/t && - test -f .git/logs/refs/heads/s/t && + test_path_is_file .git/logs/refs/heads/s/t && git branch -d s/t && git branch -m s/s s && - test -f .git/logs/refs/heads/s' + test_path_is_file .git/logs/refs/heads/s' test_expect_success 'config information was renamed, too' \ "test $(git config branch.s.dummy) = Hello && @@ -140,8 +172,8 @@ test_expect_success 'renaming a symref is not allowed' \ git symbolic-ref refs/heads/master2 refs/heads/master && test_must_fail git branch -m master2 master3 && git symbolic-ref refs/heads/master2 && - test -f .git/refs/heads/master && - ! test -f .git/refs/heads/master3 + test_path_is_file .git/refs/heads/master && + test_path_is_missing .git/refs/heads/master3 ' test_expect_success SYMLINKS \ @@ -250,8 +282,8 @@ test_expect_success \ 'git checkout -b g/h/i -l should create a branch and a log' \ 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ git checkout -b g/h/i -l master && - test -f .git/refs/heads/g/h/i && - test -f .git/logs/refs/heads/g/h/i && + test_path_is_file .git/refs/heads/g/h/i && + test_path_is_file .git/logs/refs/heads/g/h/i && test_cmp expect .git/logs/refs/heads/g/h/i' test_expect_success 'checkout -b makes reflog by default' ' diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index 6b7c118e4f..76fe7e0060 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -32,6 +32,20 @@ test_expect_success 'git branch shows local branches' ' test_cmp expect actual ' +test_expect_success 'git branch --list shows local branches' ' + git branch --list >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' + branch-one + branch-two +EOF +test_expect_success 'git branch --list pattern shows matching local branches' ' + git branch --list branch* >actual && + test_cmp expect actual +' + cat >expect <<'EOF' origin/HEAD -> origin/branch-one origin/branch-one @@ -67,6 +81,20 @@ test_expect_success 'git branch -v shows branch summaries' ' ' cat >expect <<'EOF' +two +one +EOF +test_expect_success 'git branch --list -v pattern shows branch summaries' ' + git branch --list -v branch* >tmp && + awk "{print \$NF}" <tmp >actual && + test_cmp expect actual +' + +test_expect_success 'git branch -v pattern does not show branch summaries' ' + test_must_fail git branch -v branch* +' + +cat >expect <<'EOF' * (no branch) branch-one branch-two diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 8538813d1d..b981572d73 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -527,6 +527,20 @@ test_expect_success 'auto-amend only edited commits after "edit"' ' git rebase --abort ' +test_expect_success 'clean error after failed "exec"' ' + test_tick && + test_when_finished "git rebase --abort || :" && + ( + FAKE_LINES="1 exec_false" && + export FAKE_LINES && + test_must_fail git rebase -i HEAD^ + ) && + echo "edited again" > file7 && + git add file7 && + test_must_fail git rebase --continue 2>error && + grep "You have staged changes in your working tree." error +' + test_expect_success 'rebase a detached HEAD' ' grandparent=$(git rev-parse HEAD~2) && git checkout $(git rev-parse HEAD) && diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh new file mode 100755 index 0000000000..3bca2b3dd5 --- /dev/null +++ b/t/t3510-cherry-pick-sequence.sh @@ -0,0 +1,214 @@ +#!/bin/sh + +test_description='Test cherry-pick continuation features + + + anotherpick: rewrites foo to d + + picked: rewrites foo to c + + unrelatedpick: rewrites unrelated to reallyunrelated + + base: rewrites foo to b + + initial: writes foo as a, unrelated as unrelated + +' + +. ./test-lib.sh + +pristine_detach () { + git cherry-pick --reset && + git checkout -f "$1^0" && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x +} + +test_expect_success setup ' + echo unrelated >unrelated && + git add unrelated && + test_commit initial foo a && + test_commit base foo b && + test_commit unrelatedpick unrelated reallyunrelated && + test_commit picked foo c && + test_commit anotherpick foo d && + git config advice.detachedhead false + +' + +test_expect_success 'cherry-pick persists data on failure' ' + pristine_detach initial && + test_must_fail git cherry-pick -s base..anotherpick && + test_path_is_dir .git/sequencer && + test_path_is_file .git/sequencer/head && + test_path_is_file .git/sequencer/todo && + test_path_is_file .git/sequencer/opts +' + +test_expect_success 'cherry-pick persists opts correctly' ' + pristine_detach initial && + test_must_fail git cherry-pick -s -m 1 --strategy=recursive -X patience -X ours base..anotherpick && + test_path_is_dir .git/sequencer && + test_path_is_file .git/sequencer/head && + test_path_is_file .git/sequencer/todo && + test_path_is_file .git/sequencer/opts && + echo "true" >expect && + git config --file=.git/sequencer/opts --get-all options.signoff >actual && + test_cmp expect actual && + echo "1" >expect && + git config --file=.git/sequencer/opts --get-all options.mainline >actual && + test_cmp expect actual && + echo "recursive" >expect && + git config --file=.git/sequencer/opts --get-all options.strategy >actual && + test_cmp expect actual && + cat >expect <<-\EOF && + patience + ours + EOF + git config --file=.git/sequencer/opts --get-all options.strategy-option >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick cleans up sequencer state upon success' ' + pristine_detach initial && + git cherry-pick initial..picked && + test_path_is_missing .git/sequencer +' + +test_expect_success '--reset does not complain when no cherry-pick is in progress' ' + pristine_detach initial && + git cherry-pick --reset +' + +test_expect_success '--reset cleans up sequencer state' ' + pristine_detach initial && + test_must_fail git cherry-pick base..picked && + git cherry-pick --reset && + test_path_is_missing .git/sequencer +' + +test_expect_success 'cherry-pick cleans up sequencer state when one commit is left' ' + pristine_detach initial && + test_must_fail git cherry-pick base..picked && + test_path_is_missing .git/sequencer && + echo "resolved" >foo && + git add foo && + git commit && + { + git rev-list HEAD | + git diff-tree --root --stdin | + sed "s/$_x40/OBJID/g" + } >actual && + cat >expect <<-\EOF && + OBJID + :100644 100644 OBJID OBJID M foo + OBJID + :100644 100644 OBJID OBJID M unrelated + OBJID + :000000 100644 OBJID OBJID A foo + :000000 100644 OBJID OBJID A unrelated + EOF + test_cmp expect actual +' + +test_expect_success 'cherry-pick does not implicitly stomp an existing operation' ' + pristine_detach initial && + test_must_fail git cherry-pick base..anotherpick && + test-chmtime -v +0 .git/sequencer >expect && + test_must_fail git cherry-pick unrelatedpick && + test-chmtime -v +0 .git/sequencer >actual && + test_cmp expect actual +' + +test_expect_success '--continue complains when no cherry-pick is in progress' ' + pristine_detach initial && + test_must_fail git cherry-pick --continue +' + +test_expect_success '--continue complains when there are unresolved conflicts' ' + pristine_detach initial && + test_must_fail git cherry-pick base..anotherpick && + test_must_fail git cherry-pick --continue +' + +test_expect_success '--continue continues after conflicts are resolved' ' + pristine_detach initial && + test_must_fail git cherry-pick base..anotherpick && + echo "c" >foo && + git add foo && + git commit && + git cherry-pick --continue && + test_path_is_missing .git/sequencer && + { + git rev-list HEAD | + git diff-tree --root --stdin | + sed "s/$_x40/OBJID/g" + } >actual && + cat >expect <<-\EOF && + OBJID + :100644 100644 OBJID OBJID M foo + OBJID + :100644 100644 OBJID OBJID M foo + OBJID + :100644 100644 OBJID OBJID M unrelated + OBJID + :000000 100644 OBJID OBJID A foo + :000000 100644 OBJID OBJID A unrelated + EOF + test_cmp expect actual +' + +test_expect_success '--continue respects opts' ' + pristine_detach initial && + test_must_fail git cherry-pick -x base..anotherpick && + echo "c" >foo && + git add foo && + git commit && + git cherry-pick --continue && + test_path_is_missing .git/sequencer && + git cat-file commit HEAD >anotherpick_msg && + git cat-file commit HEAD~1 >picked_msg && + git cat-file commit HEAD~2 >unrelatedpick_msg && + git cat-file commit HEAD~3 >initial_msg && + test_must_fail grep "cherry picked from" initial_msg && + grep "cherry picked from" unrelatedpick_msg && + grep "cherry picked from" picked_msg && + grep "cherry picked from" anotherpick_msg +' + +test_expect_success '--signoff is not automatically propagated to resolved conflict' ' + pristine_detach initial && + test_must_fail git cherry-pick --signoff base..anotherpick && + echo "c" >foo && + git add foo && + git commit && + git cherry-pick --continue && + test_path_is_missing .git/sequencer && + git cat-file commit HEAD >anotherpick_msg && + git cat-file commit HEAD~1 >picked_msg && + git cat-file commit HEAD~2 >unrelatedpick_msg && + git cat-file commit HEAD~3 >initial_msg && + test_must_fail grep "Signed-off-by:" initial_msg && + grep "Signed-off-by:" unrelatedpick_msg && + test_must_fail grep "Signed-off-by:" picked_msg && + grep "Signed-off-by:" anotherpick_msg +' + +test_expect_success 'malformed instruction sheet 1' ' + pristine_detach initial && + test_must_fail git cherry-pick base..anotherpick && + echo "resolved" >foo && + git add foo && + git commit && + sed "s/pick /pick/" .git/sequencer/todo >new_sheet && + cp new_sheet .git/sequencer/todo && + test_must_fail git cherry-pick --continue +' + +test_expect_success 'malformed instruction sheet 2' ' + pristine_detach initial && + test_must_fail git cherry-pick base..anotherpick && + echo "resolved" >foo && + git add foo && + git commit && + sed "s/pick/revert/" .git/sequencer/todo >new_sheet && + cp new_sheet .git/sequencer/todo && + test_must_fail git cherry-pick --continue +' + +test_done diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh index 4f2eedfd4f..ef44fb2260 100755 --- a/t/t3905-stash-include-untracked.sh +++ b/t/t3905-stash-include-untracked.sh @@ -17,19 +17,21 @@ test_expect_success 'stash save --include-untracked some dirty working directory echo 3 > file && test_tick && echo 1 > file2 && + mkdir untracked && + echo untracked >untracked/untracked && git stash --include-untracked && git diff-files --quiet && git diff-index --cached --quiet HEAD ' cat > expect <<EOF +?? actual ?? expect -?? output EOF test_expect_success 'stash save --include-untracked cleaned the untracked files' ' - git status --porcelain > output - test_cmp output expect + git status --porcelain >actual && + test_cmp expect actual ' cat > expect.diff <<EOF @@ -40,17 +42,26 @@ index 0000000..d00491f +++ b/file2 @@ -0,0 +1 @@ +1 +diff --git a/untracked/untracked b/untracked/untracked +new file mode 100644 +index 0000000..5a72eb2 +--- /dev/null ++++ b/untracked/untracked +@@ -0,0 +1 @@ ++untracked EOF cat > expect.lstree <<EOF file2 +untracked EOF test_expect_success 'stash save --include-untracked stashed the untracked files' ' test "!" -f file2 && - git diff HEAD..stash^3 -- file2 > output && - test_cmp output expect.diff && - git ls-tree --name-only stash^3: > output && - test_cmp output expect.lstree + test ! -e untracked && + git diff HEAD stash^3 -- file2 untracked >actual && + test_cmp expect.diff actual && + git ls-tree --name-only stash^3: >actual && + test_cmp expect.lstree actual ' test_expect_success 'stash save --patch --include-untracked fails' ' test_must_fail git stash --patch --include-untracked @@ -64,18 +75,21 @@ git clean --force --quiet cat > expect <<EOF M file +?? actual ?? expect ?? file2 -?? output +?? untracked/ EOF test_expect_success 'stash pop after save --include-untracked leaves files untracked again' ' git stash pop && - git status --porcelain > output - test_cmp output expect + git status --porcelain >actual && + test_cmp expect actual && + test "1" = "`cat file2`" && + test untracked = "`cat untracked/untracked`" ' -git clean --force --quiet +git clean --force --quiet -d test_expect_success 'stash save -u dirty index' ' echo 4 > file3 && @@ -96,8 +110,8 @@ EOF test_expect_success 'stash save --include-untracked dirty index got stashed' ' git stash pop --index && - git diff --cached > output && - test_cmp output expect + git diff --cached >actual && + test_cmp expect actual ' git reset > /dev/null @@ -125,30 +139,36 @@ test_expect_success 'stash save --include-untracked removed files got stashed' ' cat > .gitignore <<EOF .gitignore ignored +ignored.d/ EOF test_expect_success 'stash save --include-untracked respects .gitignore' ' echo ignored > ignored && + mkdir ignored.d && + echo ignored >ignored.d/untracked && git stash -u && test -s ignored && + test -s ignored.d/untracked && test -s .gitignore ' test_expect_success 'stash save -u can stash with only untracked files different' ' echo 4 > file4 && - git stash -u + git stash -u && test "!" -f file4 ' test_expect_success 'stash save --all does not respect .gitignore' ' git stash -a && test "!" -f ignored && + test "!" -e ignored.d && test "!" -f .gitignore ' test_expect_success 'stash save --all is stash poppable' ' git stash pop && test -s ignored && + test -s ignored.d/untracked && test -s .gitignore ' diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 7e405d7af6..67975129bc 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -457,22 +457,22 @@ test_expect_success 'thread deep cover-letter in-reply-to' ' ' test_expect_success 'thread via config' ' - git config format.thread true && + test_config format.thread true && check_threading expect.thread master ' test_expect_success 'thread deep via config' ' - git config format.thread deep && + test_config format.thread deep && check_threading expect.deep master ' test_expect_success 'thread config + override' ' - git config format.thread deep && + test_config format.thread deep && check_threading expect.thread --thread master ' test_expect_success 'thread config + --no-thread' ' - git config format.thread deep && + test_config format.thread deep && check_threading expect.no-threading --no-thread master ' diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh new file mode 100755 index 0000000000..8341fc4d15 --- /dev/null +++ b/t/t5504-fetch-receive-strict.sh @@ -0,0 +1,104 @@ +#!/bin/sh + +test_description='fetch/receive strict mode' +. ./test-lib.sh + +test_expect_success setup ' + echo hello >greetings && + git add greetings && + git commit -m greetings && + + S=$(git rev-parse :greetings | sed -e "s|^..|&/|") && + X=$(echo bye | git hash-object -w --stdin | sed -e "s|^..|&/|") && + mv -f .git/objects/$X .git/objects/$S && + + test_must_fail git fsck +' + +test_expect_success 'fetch without strict' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config fetch.fsckobjects false && + git config transfer.fsckobjects false && + test_must_fail git fetch ../.git master + ) +' + +test_expect_success 'fetch with !fetch.fsckobjects' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config fetch.fsckobjects false && + git config transfer.fsckobjects true && + test_must_fail git fetch ../.git master + ) +' + +test_expect_success 'fetch with fetch.fsckobjects' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config fetch.fsckobjects true && + git config transfer.fsckobjects false && + test_must_fail git fetch ../.git master + ) +' + +test_expect_success 'fetch with transfer.fsckobjects' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config transfer.fsckobjects true && + test_must_fail git fetch ../.git master + ) +' + +test_expect_success 'push without strict' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config fetch.fsckobjects false && + git config transfer.fsckobjects false + ) && + git push dst master:refs/heads/test +' + +test_expect_success 'push with !receive.fsckobjects' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config receive.fsckobjects false && + git config transfer.fsckobjects true + ) && + git push dst master:refs/heads/test +' + +test_expect_success 'push with receive.fsckobjects' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config receive.fsckobjects true && + git config transfer.fsckobjects false + ) && + test_must_fail git push dst master:refs/heads/test +' + +test_expect_success 'push with transfer.fsckobjects' ' + rm -rf dst && + git init dst && + ( + cd dst && + git config transfer.fsckobjects true + ) && + test_must_fail git push dst master:refs/heads/test +' + +test_done diff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh index 76410293b3..39b4cb0ecd 100755 --- a/t/t6019-rev-list-ancestry-path.sh +++ b/t/t6019-rev-list-ancestry-path.sh @@ -70,4 +70,42 @@ test_expect_success 'rev-list --ancestry-patch D..M -- M.t' ' test_cmp expect actual ' +# b---bc +# / \ / +# a X +# \ / \ +# c---cb +# +# All refnames prefixed with 'x' to avoid confusion with the tags +# generated by test_commit on case-insensitive systems. +test_expect_success 'setup criss-cross' ' + mkdir criss-cross && + (cd criss-cross && + git init && + test_commit A && + git checkout -b xb master && + test_commit B && + git checkout -b xc master && + test_commit C && + git checkout -b xbc xb -- && + git merge xc && + git checkout -b xcb xc -- && + git merge xb && + git checkout master) +' + +# no commits in bc descend from cb +test_expect_success 'criss-cross: rev-list --ancestry-path cb..bc' ' + (cd criss-cross && + git rev-list --ancestry-path xcb..xbc > actual && + test -z "$(cat actual)") +' + +# no commits in repository descend from cb +test_expect_success 'criss-cross: rev-list --ancestry-path --all ^cb' ' + (cd criss-cross && + git rev-list --ancestry-path --all ^xcb > actual && + test -z "$(cat actual)") +' + test_done diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index 19de5b16eb..19272bc551 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh @@ -51,6 +51,22 @@ test_expect_success 'branch -v' ' test_i18ncmp expect actual ' +cat >expect <<\EOF +b1 origin/master: ahead 1, behind 1 +b2 origin/master: ahead 1, behind 1 +b3 origin/master: behind 1 +b4 origin/master: ahead 2 +EOF + +test_expect_success 'branch -vv' ' + ( + cd test && + git branch -vv + ) | + sed -n -e "$script" >actual && + test_i18ncmp expect actual +' + test_expect_success 'checkout' ' ( cd test && git checkout b1 diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 7dc8a510c7..172178490a 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -6,6 +6,7 @@ test_description='for-each-ref test' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-gpg.sh # Mon Jul 3 15:18:43 2006 +0000 datestamp=1151939923 @@ -37,11 +38,13 @@ test_atom() { case "$1" in head) ref=refs/heads/master ;; tag) ref=refs/tags/testtag ;; + *) ref=$1 ;; esac printf '%s\n' "$3" >expected - test_expect_${4:-success} "basic atom: $1 $2" " + test_expect_${4:-success} $PREREQ "basic atom: $1 $2" " git for-each-ref --format='%($2)' $ref >actual && - test_cmp expected actual + sanitize_pgp <actual >actual.clean && + test_cmp expected actual.clean " } @@ -71,7 +74,10 @@ test_atom head taggerdate '' test_atom head creator 'C O Mitter <committer@example.com> 1151939923 +0200' test_atom head creatordate 'Mon Jul 3 17:18:43 2006 +0200' test_atom head subject 'Initial' +test_atom head contents:subject 'Initial' test_atom head body '' +test_atom head contents:body '' +test_atom head contents:signature '' test_atom head contents 'Initial ' @@ -101,7 +107,10 @@ test_atom tag taggerdate 'Mon Jul 3 17:18:45 2006 +0200' test_atom tag creator 'C O Mitter <committer@example.com> 1151939925 +0200' test_atom tag creatordate 'Mon Jul 3 17:18:45 2006 +0200' test_atom tag subject 'Tagging at 1151939927' +test_atom tag contents:subject 'Tagging at 1151939927' test_atom tag body '' +test_atom tag contents:body '' +test_atom tag contents:signature '' test_atom tag contents 'Tagging at 1151939927 ' @@ -359,4 +368,92 @@ test_expect_success 'an unusual tag with an incomplete line' ' ' +test_expect_success 'create tag with subject and body content' ' + cat >>msg <<-\EOF && + the subject line + + first body line + second body line + EOF + git tag -F msg subject-body +' +test_atom refs/tags/subject-body subject 'the subject line' +test_atom refs/tags/subject-body body 'first body line +second body line +' +test_atom refs/tags/subject-body contents 'the subject line + +first body line +second body line +' + +test_expect_success 'create tag with multiline subject' ' + cat >msg <<-\EOF && + first subject line + second subject line + + first body line + second body line + EOF + git tag -F msg multiline +' +test_atom refs/tags/multiline subject 'first subject line second subject line' +test_atom refs/tags/multiline contents:subject 'first subject line second subject line' +test_atom refs/tags/multiline body 'first body line +second body line +' +test_atom refs/tags/multiline contents:body 'first body line +second body line +' +test_atom refs/tags/multiline contents:signature '' +test_atom refs/tags/multiline contents 'first subject line +second subject line + +first body line +second body line +' + +test_expect_success GPG 'create signed tags' ' + git tag -s -m "" signed-empty && + git tag -s -m "subject line" signed-short && + cat >msg <<-\EOF && + subject line + + body contents + EOF + git tag -s -F msg signed-long +' + +sig='-----BEGIN PGP SIGNATURE----- +-----END PGP SIGNATURE----- +' + +PREREQ=GPG +test_atom refs/tags/signed-empty subject '' +test_atom refs/tags/signed-empty contents:subject '' +test_atom refs/tags/signed-empty body "$sig" +test_atom refs/tags/signed-empty contents:body '' +test_atom refs/tags/signed-empty contents:signature "$sig" +test_atom refs/tags/signed-empty contents "$sig" + +test_atom refs/tags/signed-short subject 'subject line' +test_atom refs/tags/signed-short contents:subject 'subject line' +test_atom refs/tags/signed-short body "$sig" +test_atom refs/tags/signed-short contents:body '' +test_atom refs/tags/signed-short contents:signature "$sig" +test_atom refs/tags/signed-short contents "subject line +$sig" + +test_atom refs/tags/signed-long subject 'subject line' +test_atom refs/tags/signed-long contents:subject 'subject line' +test_atom refs/tags/signed-long body "body contents +$sig" +test_atom refs/tags/signed-long contents:body 'body contents +' +test_atom refs/tags/signed-long contents:signature "$sig" +test_atom refs/tags/signed-long contents "subject line + +body contents +$sig" + test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 097ce2bc83..e93ac73829 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -8,6 +8,7 @@ test_description='git tag Tests for operations with tags.' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-gpg.sh # creating and listing lightweight tags: @@ -585,24 +586,6 @@ test_expect_success \ test_cmp expect actual ' -# subsequent tests require gpg; check if it is available -gpg --version >/dev/null 2>/dev/null -if [ $? -eq 127 ]; then - say "# gpg not found - skipping tag signing and verification tests" -else - # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19 - # the gpg version 1.0.6 didn't parse trust packets correctly, so for - # that version, creation of signed tags using the generated key fails. - case "$(gpg --version)" in - 'gpg (GnuPG) 1.0.6'*) - say "Skipping signed tag tests, because a bug in 1.0.6 version" - ;; - *) - test_set_prereq GPG - ;; - esac -fi - # trying to verify annotated non-signed tags: test_expect_success GPG \ @@ -625,16 +608,6 @@ test_expect_success GPG \ # creating and verifying signed tags: -# key generation info: gpg --homedir t/t7004 --gen-key -# Type DSA and Elgamal, size 2048 bits, no expiration date. -# Name and email: C O Mitter <committer@example.com> -# No password given, to enable non-interactive operation. - -cp -R "$TEST_DIRECTORY"/t7004 ./gpghome -chmod 0700 gpghome -GNUPGHOME="$(pwd)/gpghome" -export GNUPGHOME - get_tag_header signed-tag $commit commit $time >expect echo 'A signed tag message' >>expect echo '-----BEGIN PGP SIGNATURE-----' >>expect diff --git a/t/t7106-reset-sequence.sh b/t/t7106-reset-sequence.sh new file mode 100755 index 0000000000..4956caaf82 --- /dev/null +++ b/t/t7106-reset-sequence.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +test_description='Test interaction of reset --hard with sequencer + + + anotherpick: rewrites foo to d + + picked: rewrites foo to c + + unrelatedpick: rewrites unrelated to reallyunrelated + + base: rewrites foo to b + + initial: writes foo as a, unrelated as unrelated +' + +. ./test-lib.sh + +pristine_detach () { + git cherry-pick --reset && + git checkout -f "$1^0" && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x +} + +test_expect_success setup ' + echo unrelated >unrelated && + git add unrelated && + test_commit initial foo a && + test_commit base foo b && + test_commit unrelatedpick unrelated reallyunrelated && + test_commit picked foo c && + test_commit anotherpick foo d && + git config advice.detachedhead false + +' + +test_expect_success 'reset --hard cleans up sequencer state, providing one-level undo' ' + pristine_detach initial && + test_must_fail git cherry-pick base..anotherpick && + test_path_is_dir .git/sequencer && + git reset --hard && + test_path_is_missing .git/sequencer && + test_path_is_dir .git/sequencer-old && + git reset --hard && + test_path_is_missing .git/sequencer-old +' + +test_done diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh index 72a8731d5e..aa74184c31 100755 --- a/t/t7607-merge-overwrite.sh +++ b/t/t7607-merge-overwrite.sh @@ -107,6 +107,7 @@ error: The following untracked working tree files would be overwritten by merge: sub sub2 Please move or remove them before you can merge. +Aborting EOF test_expect_success 'will not overwrite untracked file in leading path' ' diff --git a/t/t7609-merge-co-error-msgs.sh b/t/t7609-merge-co-error-msgs.sh index c994836c53..0e4a682c64 100755 --- a/t/t7609-merge-co-error-msgs.sh +++ b/t/t7609-merge-co-error-msgs.sh @@ -32,6 +32,7 @@ error: The following untracked working tree files would be overwritten by merge: three two Please move or remove them before you can merge. +Aborting EOF test_expect_success 'untracked files overwritten by merge (fast and non-fast forward)' ' @@ -56,6 +57,7 @@ Please, commit your changes or stash them before you can merge. error: The following untracked working tree files would be overwritten by merge: five Please move or remove them before you can merge. +Aborting EOF test_expect_success 'untracked files or local changes ovewritten by merge' ' @@ -71,6 +73,7 @@ error: Your local changes to the following files would be overwritten by checkou rep/one rep/two Please, commit your changes or stash them before you can switch branches. +Aborting EOF test_expect_success 'cannot switch branches because of local changes' ' @@ -92,6 +95,7 @@ error: Your local changes to the following files would be overwritten by checkou rep/one rep/two Please, commit your changes or stash them before you can switch branches. +Aborting EOF test_expect_success 'not uptodate file porcelain checkout error' ' @@ -105,6 +109,7 @@ error: Updating the following directories would lose untracked files in it: rep rep2 +Aborting EOF test_expect_success 'not_uptodate_dir porcelain checkout error' ' diff --git a/t/t9159-git-svn-no-parent-mergeinfo.sh b/t/t9159-git-svn-no-parent-mergeinfo.sh index 85120b70db..69e4815781 100755 --- a/t/t9159-git-svn-no-parent-mergeinfo.sh +++ b/t/t9159-git-svn-no-parent-mergeinfo.sh @@ -2,6 +2,14 @@ test_description='git svn handling of root commits in merge ranges' . ./lib-git-svn.sh +svn_ver="$(svn --version --quiet)" +case $svn_ver in +0.* | 1.[0-4].*) + skip_all="skipping git-svn test - SVN too old ($svn_ver)" + test_done + ;; +esac + test_expect_success 'test handling of root commits in merge ranges' ' mkdir -p init/trunk init/branches init/tags && echo "r1" > init/trunk/file.txt && diff --git a/t/test-lib.sh b/t/test-lib.sh index d7dfc8b0b1..bdd9513b84 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -950,6 +950,8 @@ then do make_valgrind_symlink $file done + # special-case the mergetools loadables + make_symlink "$GIT_BUILD_DIR"/mergetools "$GIT_VALGRIND/bin/mergetools" OLDIFS=$IFS IFS=: for path in $PATH diff --git a/templates/hooks--post-commit.sample b/templates/hooks--post-commit.sample deleted file mode 100755 index 22668216a3..0000000000 --- a/templates/hooks--post-commit.sample +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -# -# An example hook script that is called after a successful -# commit is made. -# -# To enable this hook, rename this file to "post-commit". - -: Nothing diff --git a/templates/hooks--post-receive.sample b/templates/hooks--post-receive.sample deleted file mode 100755 index 7a83e17ab5..0000000000 --- a/templates/hooks--post-receive.sample +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -# -# An example hook script for the "post-receive" event. -# -# The "post-receive" script is run after receive-pack has accepted a pack -# and the repository has been updated. It is passed arguments in through -# stdin in the form -# <oldrev> <newrev> <refname> -# For example: -# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master -# -# see contrib/hooks/ for a sample, or uncomment the next line and -# rename the file to "post-receive". - -#. /usr/share/doc/git-core/contrib/hooks/post-receive-email diff --git a/transport.c b/transport.c index fa279d531f..e1940615e2 100644 --- a/transport.c +++ b/transport.c @@ -432,7 +432,8 @@ static int fetch_refs_from_bundle(struct transport *transport, int nr_heads, struct ref **to_fetch) { struct bundle_transport_data *data = transport->data; - return unbundle(&data->header, data->fd); + return unbundle(&data->header, data->fd, + transport->progress ? BUNDLE_VERBOSE : 0); } static int close_bundle(struct transport *transport) diff --git a/tree-walk.c b/tree-walk.c index 33f749e1e7..808bb55ba3 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -309,6 +309,18 @@ static void free_extended_entry(struct tree_desc_x *t) } } +static inline int prune_traversal(struct name_entry *e, + struct traverse_info *info, + struct strbuf *base, + int still_interesting) +{ + if (!info->pathspec || still_interesting == 2) + return 2; + if (still_interesting < 0) + return still_interesting; + return tree_entry_interesting(e, base, 0, info->pathspec); +} + int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) { int ret = 0; @@ -316,10 +328,18 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) struct name_entry *entry = xmalloc(n*sizeof(*entry)); int i; struct tree_desc_x *tx = xcalloc(n, sizeof(*tx)); + struct strbuf base = STRBUF_INIT; + int interesting = 1; for (i = 0; i < n; i++) tx[i].d = t[i]; + if (info->prev) { + strbuf_grow(&base, info->pathlen); + make_traverse_path(base.buf, info->prev, &info->name); + base.buf[info->pathlen-1] = '/'; + strbuf_setlen(&base, info->pathlen); + } for (;;) { unsigned long mask, dirmask; const char *first = NULL; @@ -376,16 +396,22 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) mask |= 1ul << i; if (S_ISDIR(entry[i].mode)) dirmask |= 1ul << i; + e = &entry[i]; } if (!mask) break; - ret = info->fn(n, mask, dirmask, entry, info); - if (ret < 0) { - error = ret; - if (!info->show_all_errors) - break; + interesting = prune_traversal(e, info, &base, interesting); + if (interesting < 0) + break; + if (interesting) { + ret = info->fn(n, mask, dirmask, entry, info); + if (ret < 0) { + error = ret; + if (!info->show_all_errors) + break; + } + mask &= ret; } - mask &= ret; ret = 0; for (i = 0; i < n; i++) if (mask & (1ul << i)) @@ -395,6 +421,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) for (i = 0; i < n; i++) free_extended_entry(tx + i); free(tx); + strbuf_release(&base); return error; } diff --git a/tree-walk.h b/tree-walk.h index 39524b7dba..0089581e1d 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -44,6 +44,7 @@ struct traverse_info { struct traverse_info *prev; struct name_entry name; int pathlen; + struct pathspec *pathspec; unsigned long conflicts; traverse_callback_t fn; diff --git a/unpack-trees.c b/unpack-trees.c index cc616c3f99..237aed8c76 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -159,7 +159,7 @@ static void display_error_msgs(struct unpack_trees_options *o) string_list_clear(rejects, 0); } if (something_displayed) - printf("Aborting\n"); + fprintf(stderr, "Aborting\n"); } /* @@ -444,6 +444,7 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, newinfo = *info; newinfo.prev = info; + newinfo.pathspec = info->pathspec; newinfo.name = *p; newinfo.pathlen += tree_entry_len(p->path, p->sha1) + 1; newinfo.conflicts |= df_conflicts; @@ -1040,6 +1041,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options info.fn = unpack_callback; info.data = o; info.show_all_errors = o->show_all_errors; + info.pathspec = o->pathspec; if (o->prefix) { /* diff --git a/unpack-trees.h b/unpack-trees.h index 7998948307..5e432f576e 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -52,6 +52,7 @@ struct unpack_trees_options { const char *prefix; int cache_bottom; struct dir_struct *dir; + struct pathspec *pathspec; merge_fn_t fn; const char *msgs[NB_UNPACK_TREES_ERROR_TYPES]; /* diff --git a/upload-pack.c b/upload-pack.c index 8739bfacdf..470cffd7c1 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -84,22 +84,11 @@ static void show_commit(struct commit *commit, void *data) commit->buffer = NULL; } -static void show_object(struct object *obj, const struct name_path *path, const char *component) +static void show_object(struct object *obj, + const struct name_path *path, const char *component, + void *cb_data) { - /* An object with name "foo\n0000000..." can be used to - * confuse downstream git-pack-objects very badly. - */ - const char *name = path_name(path, component); - const char *ep = strchr(name, '\n'); - if (ep) { - fprintf(pack_pipe, "%s %.*s\n", sha1_to_hex(obj->sha1), - (int) (ep - name), - name); - } - else - fprintf(pack_pipe, "%s %s\n", - sha1_to_hex(obj->sha1), name); - free((char *)name); + show_object_with_name(pack_pipe, obj, path, component); } static void show_edge(struct commit *commit) |