Merge branch 'jc/make-static'
authorJunio C Hamano <gitster@pobox.com>
Tue, 18 Sep 2012 21:37:46 +0000 (14:37 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 18 Sep 2012 21:37:46 +0000 (14:37 -0700)
Turn many file-scope private symbols to static to reduce the
global namespace contamination.

* jc/make-static:
sequencer.c: mark a private file-scope symbol as static
ident.c: mark private file-scope symbols as static
trace.c: mark a private file-scope symbol as static
wt-status.c: mark a private file-scope symbol as static
read-cache.c: mark a private file-scope symbol as static
strbuf.c: mark a private file-scope symbol as static
sha1-array.c: mark a private file-scope symbol as static
symlinks.c: mark private file-scope symbols as static
notes.c: mark a private file-scope symbol as static
rerere.c: mark private file-scope symbols as static
graph.c: mark private file-scope symbols as static
diff.c: mark a private file-scope symbol as static
commit.c: mark a file-scope private symbol as static
builtin/notes.c: mark file-scope private symbols as static

51 files changed:
.gitignore
Documentation/RelNotes/1.7.12.1.txt
Documentation/RelNotes/1.8.0.txt
Documentation/config.txt
Documentation/git-commit.txt
Documentation/git-fast-import.txt
Documentation/git-filter-branch.txt
Documentation/git-for-each-ref.txt
Documentation/git-format-patch.txt
Documentation/git-p4.txt
Documentation/git-shortlog.txt
Documentation/git.txt
Documentation/gitcore-tutorial.txt
Documentation/gittutorial.txt
Documentation/technical/api-string-list.txt
Documentation/technical/pack-protocol.txt
Documentation/user-manual.txt
Makefile
attr.c
builtin/blame.c
builtin/check-attr.c
builtin/commit.c
builtin/fetch-pack.c
builtin/fetch.c
contrib/completion/git-completion.bash
contrib/git-jump/git-jump
diff.c
fetch-pack.h
gettext.c
gettext.h
git-p4.py
http-walker.c
sequencer.c
sequencer.h
string-list.c
string-list.h
t/lib-git-p4.sh
t/perf/.gitignore
t/t0063-string-list.sh [new file with mode: 0755]
t/t3507-cherry-pick-conflict.sh
t/t3510-cherry-pick-sequence.sh
t/t5500-fetch-pack.sh
t/t8004-blame-with-conflicts.sh
t/t9805-git-p4-skip-submit-edit.sh
t/t9807-git-p4-submit.sh
t/t9810-git-p4-rcs.sh
t/t9815-git-p4-submit-fail.sh [new file with mode: 0755]
t/test-lib-functions.sh
test-string-list.c [new file with mode: 0644]
transport.c
transport.h
index 68fe464090606b95b40b8b99e2454edb52862abe..a188a82bb1d6ab080c434a7096bd263545169f3c 100644 (file)
 /test-run-command
 /test-sha1
 /test-sigchain
+/test-string-list
 /test-subprocess
 /test-svn-fe
 /common-cmds.h
index 671e3d2eb52160bbac4be214577750f0bc90d105..b8f04af19f9691d046acc914acff78bbfd81f39f 100644 (file)
@@ -85,6 +85,12 @@ Fixes since v1.7.12
    rely on being able to parse "ls-files -s | while read a b c..."
    started to fail.  Protect them from such a misconfiguration.
 
+ * The attribute system may be asked for a path that itself or its
+   leading directories no longer exists in the working tree, and it is
+   fine if we cannot open .gitattribute file in such a case.  Failure
+   to open per-directory .gitattributes with error status other than
+   ENOENT and ENOTDIR should be diagnosed, but it wasn't.
+
  * After "gitk" showed the contents of a tag, neither "Reread
    references" nor "Reload" did not update what is shown as the
    contents of it, when the user overwrote the tag with "git tag -f".
@@ -92,6 +98,9 @@ Fixes since v1.7.12
  * "ciabot" script (in contrib/) has been updated with extensive
    documentation.
 
+ * "git-jump" script (in contrib/) did not work well when
+   diff.noprefix or diff.mnemonicprefix is in effect.
+
  * Older parts of the documentation described as if having a regular
    file in .git/refs/ hierarchy were the only way to have branches and
    tags, which is not true for quite some time.
@@ -121,3 +130,5 @@ Fixes since v1.7.12
    branch name is a parameter to the option, but the heading for the
    option description was "-B::", not "-B branch::", making the
    documentation misleading.
+
+Also contains numerous documentation updates.
index 90c4f4795fc5e95292d42dde52a7b41b0b76387d..c7bb50316494c384b7401c49dab414e6a7078a57 100644 (file)
@@ -116,6 +116,35 @@ Unless otherwise noted, all the fixes since v1.7.12 in the
 maintenance track are contained in this release (see release notes
 to them for details).
 
+ * "git blame MAKEFILE" run in a history that has "Makefile" but not
+   "MAKEFILE" should say "No such file MAKEFILE in HEAD", but got
+   confused on a case insensitive filesystem and failed to do so.
+   (merge 9aeaab6 jc/maint-blame-no-such-path later to maint).
+
+ * Even during a conflicted merge, "git blame $path" always meant to
+   blame uncommitted changes to the "working tree" version; make it
+   more useful by showing cleanly merged parts as coming from the other
+   branch that is being merged.
+   (merge 9aeaab6 jc/maint-blame-no-such-path later to maint).
+
+ * Documentation talked about "first line of commit log" when it meant
+   the title of the commit.  The description was clarified by defining
+   how the title is decided and rewording the casual mention of "first
+   line" to "title".
+   (merge 52ffe99 jw/doc-commit-title later to maint).
+
+ * The attribute system may be asked for a path that itself or its
+   leading directories no longer exists in the working tree.  Failure
+   to open per-directory .gitattributes with error status other than
+   ENOENT and ENOTDIR are diagnosed.
+   (merge 8e950da jk/config-warn-on-inaccessible-paths later to maint).
+
+ * Earlier we made the diffstat summary line that shows the number of
+   lines added/deleted localizable, but it was found irritating having
+   to see them in various languages on a list whose discussion language
+   is English.
+   (merge 218adaa nd/maint-diffstat-summary later to maint).
+
  * "git fetch --all", when passed "--no-tags", did not honor the
    "--no-tags" option while fetching from individual remotes (the same
    issue existed with "--tags", but combination "--all --tags" makes
index 6416cae51158a1a960f91a0673c89f67e105f0cc..11f320b96267e7bd11f23f13842ca9f92cac6f7f 100644 (file)
@@ -559,8 +559,9 @@ core.whitespace::
 * `space-before-tab` treats a space character that appears immediately
   before a tab character in the initial indent part of the line as an
   error (enabled by default).
-* `indent-with-non-tab` treats a line that is indented with 8 or more
-  space characters as an error (not enabled by default).
+* `indent-with-non-tab` treats a line that is indented with space
+  characters instead of the equivalent tabs as an error (not enabled by
+  default).
 * `tab-in-indent` treats a tab character in the initial indent part of
   the line as an error (not enabled by default).
 * `blank-at-eof` treats blank lines added at the end of file as an error
index 4622297ec98cddc2e1f2e4417b9eeec11a3c91b1..9594ac8e9de9645be71b1528dcb94705f1695283 100644 (file)
@@ -389,8 +389,10 @@ DISCUSSION
 Though not required, it's a good idea to begin the commit message
 with a single short (less than 50 character) line summarizing the
 change, followed by a blank line and then a more thorough description.
-Tools that turn commits into email, for example, use the first line
-on the Subject: line and the rest of the commit in the body.
+The text up to the first blank line in a commit message is treated
+as the commit title, and that title is used throughout git.
+For example, linkgit:git-format-patch[1] turns a commit into email, and it uses
+the title on the Subject line and the rest of the commit in the body.
 
 include::i18n.txt[]
 
index 2620d28b4b38ed3f57ee426e27a2115db8160343..6603a7ab733f4c59dd8725ada536b44bf5e8d12a 100644 (file)
@@ -39,6 +39,10 @@ OPTIONS
        See ``Date Formats'' below for details about which formats
        are supported, and their syntax.
 
+-- done::
+       Terminate with error if there is no 'done' command at the
+       end of the stream.
+
 --force::
        Force updating modified existing branches, even if doing
        so would cause commits to be lost (as the new commit does
@@ -1047,7 +1051,9 @@ done::
        Error out if the stream ends without a 'done' command.
        Without this feature, errors causing the frontend to end
        abruptly at a convenient point in the stream can go
-       undetected.
+       undetected.  This may occur, for example, if an import
+       front end dies in mid-operation without emitting SIGTERM
+       or SIGKILL at its subordinate git fast-import instance.
 
 `option`
 ~~~~~~~~
index 15e7ac80c087eee7d0bf10af5653f421bcbee0dc..e2301f5c0106a35e5fa14d63944abeea2cb09e9b 100644 (file)
@@ -304,6 +304,11 @@ committed a merge between P1 and P2, it will be propagated properly
 and all children of the merge will become merge commits with P1,P2
 as their parents instead of the merge commit.
 
+*NOTE* the changes introduced by the commits, and which are not reverted
+by subsequent commits, will still be in the rewritten branch. If you want
+to throw out _changes_ together with the commits, you should use the
+interactive mode of 'git rebase'.
+
 You can rewrite the commit log messages using `--msg-filter`.  For
 example, 'git svn-id' strings in a repository created by 'git svn' can
 be removed this way:
@@ -314,11 +319,6 @@ git filter-branch --msg-filter '
 '
 -------------------------------------------------------
 
-To restrict rewriting to only part of the history, specify a revision
-range in addition to the new branch name.  The new branch name will
-point to the top-most revision that a 'git rev-list' of this range
-will print.
-
 If you need to add 'Acked-by' lines to, say, the last 10 commits (none
 of which is a merge), use this command:
 
@@ -329,11 +329,10 @@ git filter-branch --msg-filter '
 ' HEAD~10..HEAD
 --------------------------------------------------------
 
-*NOTE* the changes introduced by the commits, and which are not reverted
-by subsequent commits, will still be in the rewritten branch. If you want
-to throw out _changes_ together with the commits, you should use the
-interactive mode of 'git rebase'.
-
+To restrict rewriting to only part of the history, specify a revision
+range in addition to the new branch name.  The new branch name will
+point to the top-most revision that a 'git rev-list' of this range
+will print.
 
 Consider this history:
 
index c872b883ba25144457eccb9967bc68003a3332ea..db55a4e0bbc524c20f08e6862daaa97dd1785b71 100644 (file)
@@ -102,9 +102,10 @@ Fields that have name-email-date tuple as its value (`author`,
 and `date` to extract the named component.
 
 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`.
+Its first line is `contents:subject`, where subject is the concatenation
+of all lines of the commit message up to the first blank line.  The next
+line is 'contents:body', where body is all of the lines after the first
+blank line.  Finally, the optional GPG signature is `contents:signature`.
 
 For sorting purposes, fields with numeric values sort in numeric
 order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
index 04c7346e3e8ba12465e71ad1197ed6a109a473b1..6d43f56279fe7a0d42510035e33424df0bc584f1 100644 (file)
@@ -58,10 +58,13 @@ output, unless the `--stdout` option is specified.
 If `-o` is specified, output files are created in <dir>.  Otherwise
 they are created in the current working directory.
 
-By default, the subject of a single patch is "[PATCH] First Line" and
-the subject when multiple patches are output is "[PATCH n/m] First
-Line". To force 1/1 to be added for a single patch, use `-n`.  To omit
-patch numbers from the subject, use `-N`.
+By default, the subject of a single patch is "[PATCH] " followed by
+the concatenation of lines from the commit message up to the first blank
+line (see the DISCUSSION section of linkgit:git-commit[1]).
+
+When multiple patches are output, the subject prefix will instead be
+"[PATCH n/m] ".  To force 1/1 to be added for a single patch, use `-n`.
+To omit patch numbers from the subject, use `-N`.
 
 If given `--thread`, `git-format-patch` will generate `In-Reply-To` and
 `References` headers to make the second and subsequent patch mails appear
index 8228f33e3fdf0e6ec9c66dd3f55ad8094dfeb6aa..beff6229c8cc4541ca3a8ddd0faf724fceb439e7 100644 (file)
@@ -163,7 +163,7 @@ All commands except clone accept these options.
 --git-dir <dir>::
        Set the 'GIT_DIR' environment variable.  See linkgit:git[1].
 
---verbose::
+--verbose, -v::
        Provide more progress information.
 
 Sync options
@@ -269,6 +269,24 @@ These options can be used to modify 'git p4 submit' behavior.
        Export tags from git as p4 labels. Tags found in git are applied
        to the perforce working directory.
 
+--dry-run, -n::
+       Show just what commits would be submitted to p4; do not change
+       state in git or p4.
+
+--prepare-p4-only::
+       Apply a commit to the p4 workspace, opening, adding and deleting
+       files in p4 as for a normal submit operation.  Do not issue the
+       final "p4 submit", but instead print a message about how to
+       submit manually or revert.  This option always stops after the
+       first (oldest) commit.  Git tags are not exported to p4.
+
+--conflict=(ask|skip|quit)::
+       Conflicts can occur when applying a commit to p4.  When this
+       happens, the default behavior ("ask") is to prompt whether to
+       skip this commit and continue, or quit.  This option can be used
+       to bypass the prompt, causing conflicting commits to be automatically
+       skipped, or to quit trying to apply commits, without prompting.
+
 Rebase options
 ~~~~~~~~~~~~~~
 These options can be used to modify 'git p4 rebase' behavior.
@@ -519,6 +537,10 @@ git-p4.labelExportRegexp::
        Only p4 labels matching this regular expression will be exported. The
        default value is '[a-zA-Z0-9_\-.]+$'.
 
+git-p4.conflict::
+       Specify submit behavior when a conflict with p4 is found, as per
+       --conflict.  The default behavior is 'ask'.
+
 IMPLEMENTATION DETAILS
 ----------------------
 * Changesets from p4 are imported using git fast-import.
index 01d8417316e23bc74f2c86df9004091710437ba7..afeb4cdf16df91f63197d390fdbf1540eff66837 100644 (file)
@@ -14,8 +14,7 @@ git log --pretty=short | 'git shortlog' [-h] [-n] [-s] [-e] [-w]
 DESCRIPTION
 -----------
 Summarizes 'git log' output in a format suitable for inclusion
-in release announcements. Each commit will be grouped by author and
-the first line of the commit message will be shown.
+in release announcements. Each commit will be grouped by author and title.
 
 Additionally, "[PATCH]" will be stripped from the commit description.
 
index 34d8a1bbdfe974f1ba83028f3875bca5b439f418..6710cb0a418fef503f0a8419e9b3cd617a15c485 100644 (file)
@@ -43,9 +43,10 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.7.12/git.html[documentation for release 1.7.12]
+* link:v1.7.12.1/git.html[documentation for release 1.7.12.1]
 
 * release notes for
+  link:RelNotes/1.7.12.1.txt[1.7.12.1],
   link:RelNotes/1.7.12.txt[1.7.12].
 
 * link:v1.7.11.7/git.html[documentation for release 1.7.11.7]
index 9d893369a00b6a924876fb234c1082db2021abf3..5325c5a7d5cab3f74c3866319a60d35e4299da2e 100644 (file)
@@ -956,12 +956,11 @@ $ git show-branch --topo-order --more=1 master mybranch
 ------------------------------------------------
 
 The first two lines indicate that it is showing the two branches
-and the first line of the commit log message from their
-top-of-the-tree commits, you are currently on `master` branch
-(notice the asterisk `*` character), and the first column for
-the later output lines is used to show commits contained in the
+with the titles of their top-of-the-tree commits, you are currently on
+`master` branch (notice the asterisk `*` character), and the first
+column for the later output lines is used to show commits contained in the
 `master` branch, and the second column for the `mybranch`
-branch. Three commits are shown along with their log messages.
+branch. Three commits are shown along with their titles.
 All of them have non blank characters in the first column (`*`
 shows an ordinary commit on the current branch, `-` is a merge commit), which
 means they are now part of the `master` branch. Only the "Some
index dee050567e65301066629c566613b84c6c065169..f1cb6f3be67fbb03583a35601791f0db839fbe1b 100644 (file)
@@ -139,9 +139,11 @@ them to the index, and commit, all in one step.
 A note on commit messages: Though not required, it's a good idea to
 begin the commit message with a single short (less than 50 character)
 line summarizing the change, followed by a blank line and then a more
-thorough description.  Tools that turn commits into email, for
-example, use the first line on the Subject: line and the rest of the
-commit in the body.
+thorough description. The text up to the first blank line in a commit
+message is treated as the commit title, and that title is used
+throughout git.  For example, linkgit:git-format-patch[1] turns a
+commit into email, and it uses the title on the Subject line and the
+rest of the commit in the body.
 
 Git tracks content not files
 ----------------------------
index 5a0c14fcebfcf4d5cbad4900d062703412c501e1..155ac8cb10d53053eb37bf0ea52d42623d6cb3c9 100644 (file)
@@ -20,8 +20,9 @@ If you need something advanced, you can manually malloc() the `items`
 member (you need this if you add things later) and you should set the
 `nr` and `alloc` members in that case, too.
 
-. Adds new items to the list, using `string_list_append` or
-  `string_list_insert`.
+. Adds new items to the list, using `string_list_append`,
+  `string_list_append_nodup`, `string_list_insert`,
+  `string_list_split`, and/or `string_list_split_in_place`.
 
 . Can check if a string is in the list using `string_list_has_string` or
   `unsorted_string_list_has_string` and get it from the list using
@@ -29,18 +30,23 @@ member (you need this if you add things later) and you should set the
 
 . Can sort an unsorted list using `sort_string_list`.
 
+. Can remove duplicate items from a sorted list using
+  `string_list_remove_duplicates`.
+
 . Can remove individual items of an unsorted list using
   `unsorted_string_list_delete_item`.
 
+. Can remove items not matching a criterion from a sorted or unsorted
+  list using `filter_string_list`.
+
 . Finally it should free the list using `string_list_clear`.
 
 Example:
 
 ----
-struct string_list list;
+struct string_list list = STRING_LIST_INIT_NODUP;
 int i;
 
-memset(&list, 0, sizeof(struct string_list));
 string_list_append(&list, "foo");
 string_list_append(&list, "bar");
 for (i = 0; i < list.nr; i++)
@@ -60,6 +66,22 @@ Functions
 
 * General ones (works with sorted and unsorted lists as well)
 
+`filter_string_list`::
+
+       Apply a function to each item in a list, retaining only the
+       items for which the function returns true.  If free_util is
+       true, call free() on the util members of any items that have
+       to be deleted.  Preserve the order of the items that are
+       retained.
+
+`string_list_longest_prefix`::
+
+       Return the longest string within a string_list that is a
+       prefix (in the sense of prefixcmp()) of the specified string,
+       or NULL if no such prefix exists.  This function does not
+       require the string_list to be sorted (it does a linear
+       search).
+
 `print_string_list`::
 
        Dump a string_list to stdout, useful mainly for debugging purposes. It
@@ -96,11 +118,28 @@ write `string_list_insert(...)->util = ...;`.
        Look up a given string in the string_list, returning the containing
        string_list_item. If the string is not found, NULL is returned.
 
+`string_list_remove_duplicates`::
+
+       Remove all but the first of consecutive entries that have the
+       same string value.  If free_util is true, call free() on the
+       util members of any items that have to be deleted.
+
 * Functions for unsorted lists only
 
 `string_list_append`::
 
-       Append a new string to the end of the string_list.
+       Append a new string to the end of the string_list.  If
+       `strdup_string` is set, then the string argument is copied;
+       otherwise the new `string_list_entry` refers to the input
+       string.
+
+`string_list_append_nodup`::
+
+       Append a new string to the end of the string_list.  The new
+       `string_list_entry` always refers to the input string, even if
+       `strdup_string` is set.  This function can be used to hand
+       ownership of a malloc()ed string to a `string_list` that has
+       `strdup_string` set.
 
 `sort_string_list`::
 
@@ -124,6 +163,25 @@ counterpart for sorted lists, which performs a binary search.
        is set. The third parameter controls if the `util` pointer of the
        items should be freed or not.
 
+`string_list_split`::
+`string_list_split_in_place`::
+
+       Split a string into substrings on a delimiter character and
+       append the substrings to a `string_list`.  If `maxsplit` is
+       non-negative, then split at most `maxsplit` times.  Return the
+       number of substrings appended to the list.
++
+`string_list_split` requires a `string_list` that has `strdup_strings`
+set to true; it leaves the input string untouched and makes copies of
+the substrings in newly-allocated memory.
+`string_list_split_in_place` requires a `string_list` that has
+`strdup_strings` set to false; it splits the input string in place,
+overwriting the delimiter characters with NULs and creating new
+string_list_items that point into the original string (the original
+string must therefore not be modified or freed while the `string_list`
+is in use).
+
+
 Data structures
 ---------------
 
index 49cdc571cd7e276df2913b0ccf9d1e2320b31c9d..d51e20f3526e2681b8c05c296b91dd90a4d3e322 100644 (file)
@@ -259,8 +259,10 @@ a positive depth, this step is skipped.
 ----
 
 If the client has requested a positive depth, the server will compute
-the set of commits which are no deeper than the desired depth, starting
-at the client's wants. The server writes 'shallow' lines for each
+the set of commits which are no deeper than the desired depth. The set
+of commits start at the client's wants.
+
+The server writes 'shallow' lines for each
 commit whose parents will not be sent as a result. The server writes
 an 'unshallow' line for each commit which the client has indicated is
 shallow, but is no longer shallow at the currently requested depth
index 03d95dc290cecd2223116be90b591f75d40ced2b..85651b57ae466496e98e9c2507072f9fa8125c4d 100644 (file)
@@ -1136,9 +1136,12 @@ Creating good commit messages
 Though not required, it's a good idea to begin the commit message
 with a single short (less than 50 character) line summarizing the
 change, followed by a blank line and then a more thorough
-description.  Tools that turn commits into email, for example, use
-the first line on the Subject line and the rest of the commit in the
-body.
+description.  The text up to the first blank line in a commit
+message is treated as the commit title, and that title is used
+throughout git.  For example, linkgit:git-format-patch[1] turns a
+commit into email, and it uses the title on the Subject line and the
+rest of the commit in the body.
+
 
 [[ignoring-files]]
 Ignoring files
index 56301dc0a80db5085400436bab3346f2dbdd5321..a49d1db2889e7724f48eb81fc1fb3215e8e28a91 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -509,6 +509,7 @@ TEST_PROGRAMS_NEED_X += test-run-command
 TEST_PROGRAMS_NEED_X += test-scrap-cache-tree
 TEST_PROGRAMS_NEED_X += test-sha1
 TEST_PROGRAMS_NEED_X += test-sigchain
+TEST_PROGRAMS_NEED_X += test-string-list
 TEST_PROGRAMS_NEED_X += test-subprocess
 TEST_PROGRAMS_NEED_X += test-svn-fe
 
diff --git a/attr.c b/attr.c
index 3430faf2ccc5e11a9c04432b7a4f13160130e141..887a9ae46b7044489845abf2072339271caf2d4a 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -353,7 +353,7 @@ static struct attr_stack *read_attr_from_file(const char *path, int macro_ok)
        int lineno = 0;
 
        if (!fp) {
-               if (errno != ENOENT)
+               if (errno != ENOENT && errno != ENOTDIR)
                        warn_on_inaccessible(path);
                return NULL;
        }
index 0e102bf2c2aa26bd0dcbcb1788341eaa7ac2c037..c27ef21c2326afd29a68146d23c734c7ea708556 100644 (file)
@@ -2069,6 +2069,55 @@ static int git_blame_config(const char *var, const char *value, void *cb)
        return git_default_config(var, value, cb);
 }
 
+static void verify_working_tree_path(struct commit *work_tree, const char *path)
+{
+       struct commit_list *parents;
+
+       for (parents = work_tree->parents; parents; parents = parents->next) {
+               const unsigned char *commit_sha1 = parents->item->object.sha1;
+               unsigned char blob_sha1[20];
+               unsigned mode;
+
+               if (!get_tree_entry(commit_sha1, path, blob_sha1, &mode) &&
+                   sha1_object_info(blob_sha1, NULL) == OBJ_BLOB)
+                       return;
+       }
+       die("no such path '%s' in HEAD", path);
+}
+
+static struct commit_list **append_parent(struct commit_list **tail, const unsigned char *sha1)
+{
+       struct commit *parent;
+
+       parent = lookup_commit_reference(sha1);
+       if (!parent)
+               die("no such commit %s", sha1_to_hex(sha1));
+       return &commit_list_insert(parent, tail)->next;
+}
+
+static void append_merge_parents(struct commit_list **tail)
+{
+       int merge_head;
+       const char *merge_head_file = git_path("MERGE_HEAD");
+       struct strbuf line = STRBUF_INIT;
+
+       merge_head = open(merge_head_file, O_RDONLY);
+       if (merge_head < 0) {
+               if (errno == ENOENT)
+                       return;
+               die("cannot open '%s' for reading", merge_head_file);
+       }
+
+       while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) {
+               unsigned char sha1[20];
+               if (line.len < 40 || get_sha1_hex(line.buf, sha1))
+                       die("unknown line in '%s': %s", merge_head_file, line.buf);
+               tail = append_parent(tail, sha1);
+       }
+       close(merge_head);
+       strbuf_release(&line);
+}
+
 /*
  * Prepare a dummy commit that represents the work tree (or staged) item.
  * Note that annotating work tree item never works in the reverse.
@@ -2079,6 +2128,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
 {
        struct commit *commit;
        struct origin *origin;
+       struct commit_list **parent_tail, *parent;
        unsigned char head_sha1[20];
        struct strbuf buf = STRBUF_INIT;
        const char *ident;
@@ -2086,20 +2136,38 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
        int size, len;
        struct cache_entry *ce;
        unsigned mode;
-
-       if (get_sha1("HEAD", head_sha1))
-               die("No such ref: HEAD");
+       struct strbuf msg = STRBUF_INIT;
 
        time(&now);
        commit = xcalloc(1, sizeof(*commit));
-       commit->parents = xcalloc(1, sizeof(*commit->parents));
-       commit->parents->item = lookup_commit_reference(head_sha1);
        commit->object.parsed = 1;
        commit->date = now;
        commit->object.type = OBJ_COMMIT;
+       parent_tail = &commit->parents;
+
+       if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL))
+               die("no such ref: HEAD");
+
+       parent_tail = append_parent(parent_tail, head_sha1);
+       append_merge_parents(parent_tail);
+       verify_working_tree_path(commit, path);
 
        origin = make_origin(commit, path);
 
+       ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
+       strbuf_addstr(&msg, "tree 0000000000000000000000000000000000000000\n");
+       for (parent = commit->parents; parent; parent = parent->next)
+               strbuf_addf(&msg, "parent %s\n",
+                           sha1_to_hex(parent->item->object.sha1));
+       strbuf_addf(&msg,
+                   "author %s\n"
+                   "committer %s\n\n"
+                   "Version of %s from %s\n",
+                   ident, ident, path,
+                   (!contents_from ? path :
+                    (!strcmp(contents_from, "-") ? "standard input" : contents_from)));
+       commit->buffer = strbuf_detach(&msg, NULL);
+
        if (!contents_from || strcmp("-", contents_from)) {
                struct stat st;
                const char *read_from;
@@ -2136,7 +2204,6 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
        }
        else {
                /* Reading from stdin */
-               contents_from = "standard input";
                mode = 0;
                if (strbuf_read(&buf, 0, 0) < 0)
                        die_errno("failed to read from stdin");
@@ -2181,16 +2248,6 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
         */
        cache_tree_invalidate_path(active_cache_tree, path);
 
-       commit->buffer = xmalloc(400);
-       ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
-       snprintf(commit->buffer, 400,
-               "tree 0000000000000000000000000000000000000000\n"
-               "parent %s\n"
-               "author %s\n"
-               "committer %s\n\n"
-               "Version of %s from %s\n",
-               sha1_to_hex(head_sha1),
-               ident, ident, path, contents_from ? contents_from : path);
        return commit;
 }
 
index e1ff575daac8e1c12706c9555efcea0238cb7e64..075d01d30c58d40abb48ff820ba6f8d310f785d0 100644 (file)
@@ -9,7 +9,7 @@ static int cached_attrs;
 static int stdin_paths;
 static const char * const check_attr_usage[] = {
 N_("git check-attr [-a | --all | attr...] [--] pathname..."),
-N_("git check-attr --stdin [-a | --all | attr...] < <list-of-paths>"),
+N_("git check-attr --stdin [-z] [-a | --all | attr...] < <list-of-paths>"),
 NULL
 };
 
index 778cf16fde64ab81bbe75ec0949a77e6fd9ee702..4d50484837ece50276ca7a09239815dcca5b5f7d 100644 (file)
@@ -28,6 +28,7 @@
 #include "submodule.h"
 #include "gpg-interface.h"
 #include "column.h"
+#include "sequencer.h"
 
 static const char * const builtin_commit_usage[] = {
        N_("git commit [options] [--] <filepattern>..."),
@@ -466,8 +467,6 @@ static int is_a_merge(const struct commit *current_head)
        return !!(current_head->parents && current_head->parents->next);
 }
 
-static const char sign_off_header[] = "Signed-off-by: ";
-
 static void export_one(const char *var, const char *s, const char *e, int hack)
 {
        struct strbuf buf = STRBUF_INIT;
@@ -552,47 +551,6 @@ static void determine_author_info(struct strbuf *author_ident)
        }
 }
 
-static int ends_rfc2822_footer(struct strbuf *sb)
-{
-       int ch;
-       int hit = 0;
-       int i, j, k;
-       int len = sb->len;
-       int first = 1;
-       const char *buf = sb->buf;
-
-       for (i = len - 1; i > 0; i--) {
-               if (hit && buf[i] == '\n')
-                       break;
-               hit = (buf[i] == '\n');
-       }
-
-       while (i < len - 1 && buf[i] == '\n')
-               i++;
-
-       for (; i < len; i = k) {
-               for (k = i; k < len && buf[k] != '\n'; k++)
-                       ; /* do nothing */
-               k++;
-
-               if ((buf[k] == ' ' || buf[k] == '\t') && !first)
-                       continue;
-
-               first = 0;
-
-               for (j = 0; i + j < len; j++) {
-                       ch = buf[i + j];
-                       if (ch == ':')
-                               break;
-                       if (isalnum(ch) ||
-                           (ch == '-'))
-                               continue;
-                       return 0;
-               }
-       }
-       return 1;
-}
-
 static char *cut_ident_timestamp_part(char *string)
 {
        char *ket = strrchr(string, '>');
@@ -717,21 +675,30 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                stripspace(&sb, 0);
 
        if (signoff) {
-               struct strbuf sob = STRBUF_INIT;
-               int i;
-
-               strbuf_addstr(&sob, sign_off_header);
-               strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
-                                            getenv("GIT_COMMITTER_EMAIL")));
-               strbuf_addch(&sob, '\n');
-               for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--)
-                       ; /* do nothing */
-               if (prefixcmp(sb.buf + i, sob.buf)) {
-                       if (!i || !ends_rfc2822_footer(&sb))
-                               strbuf_addch(&sb, '\n');
-                       strbuf_addbuf(&sb, &sob);
+               /*
+                * See if we have a Conflicts: block at the end. If yes, count
+                * its size, so we can ignore it.
+                */
+               int ignore_footer = 0;
+               int i, eol, previous = 0;
+               const char *nl;
+
+               for (i = 0; i < sb.len; i++) {
+                       nl = memchr(sb.buf + i, '\n', sb.len - i);
+                       if (nl)
+                               eol = nl - sb.buf;
+                       else
+                               eol = sb.len;
+                       if (!prefixcmp(sb.buf + previous, "\nConflicts:\n")) {
+                               ignore_footer = sb.len - previous;
+                               break;
+                       }
+                       while (i < eol)
+                               i++;
+                       previous = eol;
                }
-               strbuf_release(&sob);
+
+               append_signoff(&sb, ignore_footer);
        }
 
        if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
index fdda36f1497eaa971e81cc1df0fb21ada6d21120..e6443986b81050b01ebd7c57a7afd7abb5c65a45 100644 (file)
@@ -525,72 +525,59 @@ static void mark_recent_complete_commits(unsigned long cutoff)
        }
 }
 
-static void filter_refs(struct ref **refs, int nr_match, char **match)
+static int non_matching_ref(struct string_list_item *item, void *unused)
+{
+       if (item->util) {
+               item->util = NULL;
+               return 0;
+       }
+       else
+               return 1;
+}
+
+static void filter_refs(struct ref **refs, struct string_list *sought)
 {
-       struct ref **return_refs;
        struct ref *newlist = NULL;
        struct ref **newtail = &newlist;
        struct ref *ref, *next;
-       struct ref *fastarray[32];
-       int match_pos;
-
-       if (nr_match && !args.fetch_all) {
-               if (ARRAY_SIZE(fastarray) < nr_match)
-                       return_refs = xcalloc(nr_match, sizeof(struct ref *));
-               else {
-                       return_refs = fastarray;
-                       memset(return_refs, 0, sizeof(struct ref *) * nr_match);
-               }
-       }
-       else
-               return_refs = NULL;
+       int sought_pos;
 
-       match_pos = 0;
+       sought_pos = 0;
        for (ref = *refs; ref; ref = next) {
+               int keep = 0;
                next = ref->next;
                if (!memcmp(ref->name, "refs/", 5) &&
                    check_refname_format(ref->name + 5, 0))
                        ; /* trash */
-               else if (args.fetch_all &&
-                        (!args.depth || prefixcmp(ref->name, "refs/tags/") )) {
-                       *newtail = ref;
-                       ref->next = NULL;
-                       newtail = &ref->next;
-                       continue;
-               }
                else {
-                       int cmp = -1;
-                       while (match_pos < nr_match) {
-                               cmp = strcmp(ref->name, match[match_pos]);
-                               if (cmp < 0) /* definitely do not have it */
-                                       break;
-                               else if (cmp == 0) { /* definitely have it */
-                                       match[match_pos][0] = '\0';
-                                       return_refs[match_pos] = ref;
+                       while (sought_pos < sought->nr) {
+                               int cmp = strcmp(ref->name, sought->items[sought_pos].string);
+                               if (cmp < 0)
+                                       break; /* definitely do not have it */
+                               else if (cmp == 0) {
+                                       keep = 1; /* definitely have it */
+                                       sought->items[sought_pos++].util = "matched";
                                        break;
                                }
-                               else /* might have it; keep looking */
-                                       match_pos++;
+                               else
+                                       sought_pos++; /* might have it; keep looking */
                        }
-                       if (!cmp)
-                               continue; /* we will link it later */
                }
-               free(ref);
-       }
 
-       if (!args.fetch_all) {
-               int i;
-               for (i = 0; i < nr_match; i++) {
-                       ref = return_refs[i];
-                       if (ref) {
-                               *newtail = ref;
-                               ref->next = NULL;
-                               newtail = &ref->next;
-                       }
+               if (! keep && args.fetch_all &&
+                   (!args.depth || prefixcmp(ref->name, "refs/tags/")))
+                       keep = 1;
+
+               if (keep) {
+                       *newtail = ref;
+                       ref->next = NULL;
+                       newtail = &ref->next;
+               } else {
+                       free(ref);
                }
-               if (return_refs != fastarray)
-                       free(return_refs);
        }
+
+       filter_string_list(sought, 0, non_matching_ref, NULL);
        *refs = newlist;
 }
 
@@ -599,7 +586,7 @@ static void mark_alternate_complete(const struct ref *ref, void *unused)
        mark_complete(NULL, ref->old_sha1, 0, NULL);
 }
 
-static int everything_local(struct ref **refs, int nr_match, char **match)
+static int everything_local(struct ref **refs, struct string_list *sought)
 {
        struct ref *ref;
        int retval;
@@ -650,7 +637,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
                }
        }
 
-       filter_refs(refs, nr_match, match);
+       filter_refs(refs, sought);
 
        for (retval = 1, ref = *refs; ref ; ref = ref->next) {
                const unsigned char *remote = ref->old_sha1;
@@ -781,8 +768,7 @@ static int get_pack(int xd[2], char **pack_lockfile)
 
 static struct ref *do_fetch_pack(int fd[2],
                const struct ref *orig_ref,
-               int nr_match,
-               char **match,
+               struct string_list *sought,
                char **pack_lockfile)
 {
        struct ref *ref = copy_ref_list(orig_ref);
@@ -839,7 +825,7 @@ static struct ref *do_fetch_pack(int fd[2],
                                agent_len, agent_feature);
        }
 
-       if (everything_local(&ref, nr_match, match)) {
+       if (everything_local(&ref, sought)) {
                packet_flush(fd[1]);
                goto all_done;
        }
@@ -859,19 +845,6 @@ static struct ref *do_fetch_pack(int fd[2],
        return ref;
 }
 
-static int remove_duplicates(int nr_heads, char **heads)
-{
-       int src, dst;
-
-       if (!nr_heads)
-               return 0;
-
-       for (src = dst = 1; src < nr_heads; src++)
-               if (strcmp(heads[src], heads[dst-1]))
-                       heads[dst++] = heads[src];
-       return dst;
-}
-
 static int fetch_pack_config(const char *var, const char *value, void *cb)
 {
        if (strcmp(var, "fetch.unpacklimit") == 0) {
@@ -922,8 +895,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
        int i, ret;
        struct ref *ref = NULL;
        const char *dest = NULL;
-       int alloc_heads = 0, nr_heads = 0;
-       char **heads = NULL;
+       struct string_list sought = STRING_LIST_INIT_DUP;
        int fd[2];
        char *pack_lockfile = NULL;
        char **pack_lockfile_ptr = NULL;
@@ -1000,9 +972,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
         * Copy refs from cmdline to growable list, then append any
         * refs from the standard input:
         */
-       ALLOC_GROW(heads, argc - i, alloc_heads);
        for (; i < argc; i++)
-               heads[nr_heads++] = xstrdup(argv[i]);
+               string_list_append(&sought, xstrdup(argv[i]));
        if (args.stdin_refs) {
                if (args.stateless_rpc) {
                        /* in stateless RPC mode we use pkt-line to read
@@ -1015,17 +986,14 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
                                        break;
                                if (line[n-1] == '\n')
                                        n--;
-                               ALLOC_GROW(heads, nr_heads + 1, alloc_heads);
-                               heads[nr_heads++] = xmemdupz(line, n);
+                               string_list_append(&sought, xmemdupz(line, n));
                        }
                }
                else {
                        /* read from stdin one ref per line, until EOF */
                        struct strbuf line = STRBUF_INIT;
-                       while (strbuf_getline(&line, stdin, '\n') != EOF) {
-                               ALLOC_GROW(heads, nr_heads + 1, alloc_heads);
-                               heads[nr_heads++] = strbuf_detach(&line, NULL);
-                       }
+                       while (strbuf_getline(&line, stdin, '\n') != EOF)
+                               string_list_append(&sought, strbuf_detach(&line, NULL));
                        strbuf_release(&line);
                }
        }
@@ -1042,7 +1010,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
        get_remote_heads(fd[0], &ref, 0, NULL);
 
        ref = fetch_pack(&args, fd, conn, ref, dest,
-               nr_heads, heads, pack_lockfile_ptr);
+                        &sought, pack_lockfile_ptr);
        if (pack_lockfile) {
                printf("lock %s\n", pack_lockfile);
                fflush(stdout);
@@ -1050,21 +1018,18 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
        close(fd[0]);
        close(fd[1]);
        if (finish_connect(conn))
-               ref = NULL;
-       ret = !ref;
-
-       if (!ret && nr_heads) {
-               /* If the heads to pull were given, we should have
-                * consumed all of them by matching the remote.
-                * Otherwise, 'git fetch remote no-such-ref' would
-                * silently succeed without issuing an error.
-                */
-               for (i = 0; i < nr_heads; i++)
-                       if (heads[i] && heads[i][0]) {
-                               error("no such remote ref %s", heads[i]);
-                               ret = 1;
-                       }
-       }
+               return 1;
+
+       ret = !ref || sought.nr;
+
+       /*
+        * If the heads to pull were given, we should have consumed
+        * all of them by matching the remote.  Otherwise, 'git fetch
+        * remote no-such-ref' would silently succeed without issuing
+        * an error.
+        */
+       for (i = 0; i < sought.nr; i++)
+               error("no such remote ref %s", sought.items[i].string);
        while (ref) {
                printf("%s %s\n",
                       sha1_to_hex(ref->old_sha1), ref->name);
@@ -1074,18 +1039,12 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
        return ret;
 }
 
-static int compare_heads(const void *a, const void *b)
-{
-       return strcmp(*(const char **)a, *(const char **)b);
-}
-
 struct ref *fetch_pack(struct fetch_pack_args *my_args,
                       int fd[], struct child_process *conn,
                       const struct ref *ref,
-               const char *dest,
-               int nr_heads,
-               char **heads,
-               char **pack_lockfile)
+                      const char *dest,
+                      struct string_list *sought,
+                      char **pack_lockfile)
 {
        struct stat st;
        struct ref *ref_cpy;
@@ -1098,16 +1057,16 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
                        st.st_mtime = 0;
        }
 
-       if (heads && nr_heads) {
-               qsort(heads, nr_heads, sizeof(*heads), compare_heads);
-               nr_heads = remove_duplicates(nr_heads, heads);
+       if (sought->nr) {
+               sort_string_list(sought);
+               string_list_remove_duplicates(sought, 0);
        }
 
        if (!ref) {
                packet_flush(fd[1]);
                die("no matching remote head");
        }
-       ref_cpy = do_fetch_pack(fd, ref, nr_heads, heads, pack_lockfile);
+       ref_cpy = do_fetch_pack(fd, ref, sought, pack_lockfile);
 
        if (args.depth > 0) {
                struct cache_time mtime;
index d0dcc8888f14e8305250afcab7eb4ca7a3f6648c..4b5a89839b66f201bda42f06cabdf781d44f0192 100644 (file)
@@ -256,9 +256,8 @@ static int update_local_ref(struct ref *ref,
        if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
                if (verbosity > 0)
                        strbuf_addf(display, "= %-*s %-*s -> %s",
-                                   TRANSPORT_SUMMARY_WIDTH,
-                                   _("[up to date]"), REFCOL_WIDTH,
-                                   remote, pretty_ref);
+                                   TRANSPORT_SUMMARY(_("[up to date]")),
+                                   REFCOL_WIDTH, remote, pretty_ref);
                return 0;
        }
 
@@ -272,7 +271,7 @@ static int update_local_ref(struct ref *ref,
                 */
                strbuf_addf(display,
                            _("! %-*s %-*s -> %s  (can't fetch in current branch)"),
-                           TRANSPORT_SUMMARY_WIDTH, _("[rejected]"),
+                           TRANSPORT_SUMMARY(_("[rejected]")),
                            REFCOL_WIDTH, remote, pretty_ref);
                return 1;
        }
@@ -283,7 +282,7 @@ static int update_local_ref(struct ref *ref,
                r = s_update_ref("updating tag", ref, 0);
                strbuf_addf(display, "%c %-*s %-*s -> %s%s",
                            r ? '!' : '-',
-                           TRANSPORT_SUMMARY_WIDTH, _("[tag update]"),
+                           TRANSPORT_SUMMARY(_("[tag update]")),
                            REFCOL_WIDTH, remote, pretty_ref,
                            r ? _("  (unable to update local ref)") : "");
                return r;
@@ -318,7 +317,7 @@ static int update_local_ref(struct ref *ref,
                r = s_update_ref(msg, ref, 0);
                strbuf_addf(display, "%c %-*s %-*s -> %s%s",
                            r ? '!' : '*',
-                           TRANSPORT_SUMMARY_WIDTH, what,
+                           TRANSPORT_SUMMARY(what),
                            REFCOL_WIDTH, remote, pretty_ref,
                            r ? _("  (unable to update local ref)") : "");
                return r;
@@ -358,7 +357,7 @@ static int update_local_ref(struct ref *ref,
                return r;
        } else {
                strbuf_addf(display, "! %-*s %-*s -> %s  %s",
-                           TRANSPORT_SUMMARY_WIDTH, _("[rejected]"),
+                           TRANSPORT_SUMMARY(_("[rejected]")),
                            REFCOL_WIDTH, remote, pretty_ref,
                            _("(non-fast-forward)"));
                return 1;
@@ -555,7 +554,7 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map)
                        result |= delete_ref(ref->name, NULL, 0);
                if (verbosity >= 0) {
                        fprintf(stderr, " x %-*s %-*s -> %s\n",
-                               TRANSPORT_SUMMARY_WIDTH, _("[deleted]"),
+                               TRANSPORT_SUMMARY(_("[deleted]")),
                                REFCOL_WIDTH, _("(none)"), prettify_refname(ref->name));
                        warn_dangling_symref(stderr, dangling_msg, ref->name);
                }
index 1b43329ed79bfb09eac7a017c5ab7466fedc497b..be800e09bdf0656951c415105e0d32ce0b0edf9e 100644 (file)
@@ -1018,7 +1018,8 @@ _git_commit ()
        --*)
                __gitcomp "
                        --all --author= --signoff --verify --no-verify
-                       --edit --amend --include --only --interactive
+                       --edit --no-edit
+                       --amend --include --only --interactive
                        --dry-run --reuse-message= --reedit-message=
                        --reset-author --file= --message= --template=
                        --cleanup= --untracked-files --untracked-files=
index a33674e47a669e45f257a92aaffad8002d190c5d..dc90cd6379de4e9bf1eadc69560867601227b55d 100755 (executable)
@@ -21,9 +21,9 @@ open_editor() {
 }
 
 mode_diff() {
-       git diff --relative "$@" |
+       git diff --no-prefix --relative "$@" |
        perl -ne '
-       if (m{^\+\+\+ b/(.*)}) { $file = $1; next }
+       if (m{^\+\+\+ (.*)}) { $file = $1; next }
        defined($file) or next;
        if (m/^@@ .*\+(\d+)/) { $line = $1; next }
        defined($line) or next;
diff --git a/diff.c b/diff.c
index ddcdfed7879d6cca568544e3256c1e2df5973089..35d3f073859acf66dbd38c6096c0cc47bd26b6e2 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -1398,11 +1398,11 @@ int print_stat_summary(FILE *fp, int files, int insertions, int deletions)
 
        if (!files) {
                assert(insertions == 0 && deletions == 0);
-               return fprintf(fp, "%s\n", _(" 0 files changed"));
+               return fprintf(fp, "%s\n", " 0 files changed");
        }
 
        strbuf_addf(&sb,
-                   Q_(" %d file changed", " %d files changed", files),
+                   (files == 1) ? " %d file changed" : " %d files changed",
                    files);
 
        /*
@@ -1419,8 +1419,7 @@ int print_stat_summary(FILE *fp, int files, int insertions, int deletions)
                 * do not translate it.
                 */
                strbuf_addf(&sb,
-                           Q_(", %d insertion(+)", ", %d insertions(+)",
-                              insertions),
+                           (insertions == 1) ? ", %d insertion(+)" : ", %d insertions(+)",
                            insertions);
        }
 
@@ -1430,8 +1429,7 @@ int print_stat_summary(FILE *fp, int files, int insertions, int deletions)
                 * do not translate it.
                 */
                strbuf_addf(&sb,
-                           Q_(", %d deletion(-)", ", %d deletions(-)",
-                              deletions),
+                           (deletions == 1) ? ", %d deletion(-)" : ", %d deletions(-)",
                            deletions);
        }
        strbuf_addch(&sb, '\n');
index 7c2069c97234453cb6a0a774c1859b7055b791d8..cb148719bfd3bace27a0ca9611f1c38066fb6ed4 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef FETCH_PACK_H
 #define FETCH_PACK_H
 
+#include "string-list.h"
+
 struct fetch_pack_args {
        const char *uploadpack;
        int unpacklimit;
@@ -17,12 +19,18 @@ struct fetch_pack_args {
                stateless_rpc:1;
 };
 
+/*
+ * sought contains the full names of remote references that should be
+ * updated from.  On return, the names that were found on the remote
+ * will have been removed from the list.  The util members of the
+ * string_list_items are used internally; they must be NULL on entry
+ * (and will be NULL on exit).
+ */
 struct ref *fetch_pack(struct fetch_pack_args *args,
-               int fd[], struct child_process *conn,
-               const struct ref *ref,
-               const char *dest,
-               int nr_heads,
-               char **heads,
-               char **pack_lockfile);
+                      int fd[], struct child_process *conn,
+                      const struct ref *ref,
+                      const char *dest,
+                      struct string_list *sought,
+                      char **pack_lockfile);
 
 #endif
index f75bca7f56b7b27c135d92acba816c40211fdece..71e954563d7068500bd41269dd57f01b097f7c3d 100644 (file)
--- a/gettext.c
+++ b/gettext.c
@@ -4,6 +4,8 @@
 
 #include "git-compat-util.h"
 #include "gettext.h"
+#include "strbuf.h"
+#include "utf8.h"
 
 #ifndef NO_GETTEXT
 #      include <locale.h>
@@ -27,10 +29,9 @@ int use_gettext_poison(void)
 #endif
 
 #ifndef NO_GETTEXT
+static const char *charset;
 static void init_gettext_charset(const char *domain)
 {
-       const char *charset;
-
        /*
           This trick arranges for messages to be emitted in the user's
           requested encoding, but avoids setting LC_CTYPE from the
@@ -128,4 +129,14 @@ void git_setup_gettext(void)
        init_gettext_charset("git");
        textdomain("git");
 }
+
+/* return the number of columns of string 's' in current locale */
+int gettext_width(const char *s)
+{
+       static int is_utf8 = -1;
+       if (is_utf8 == -1)
+               is_utf8 = !strcmp(charset, "UTF-8");
+
+       return is_utf8 ? utf8_strwidth(s) : strlen(s);
+}
 #endif
index 376297bf730d421fa13237350d2beeb525be5ec4..7671d09d04bb87541b11209b5cdbddbb9e4e59b8 100644 (file)
--- a/gettext.h
+++ b/gettext.h
 
 #ifndef NO_GETTEXT
 extern void git_setup_gettext(void);
+extern int gettext_width(const char *s);
 #else
 static inline void git_setup_gettext(void)
 {
 }
+static inline int gettext_width(const char *s)
+{
+       return strlen(s);
+}
 #endif
 
 #ifdef GETTEXT_POISON
index aed1a2de3295bcd57a8c8c602cbdd28a6a6f2f7f..882b1bbab53c6e184c7eadb5250c128b59c93482 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -844,6 +844,9 @@ def run(self, args):
         return True
 
 class P4Submit(Command, P4UserMap):
+
+    conflict_behavior_choices = ("ask", "skip", "quit")
+
     def __init__(self):
         Command.__init__(self)
         P4UserMap.__init__(self)
@@ -853,12 +856,19 @@ def __init__(self):
                 # preserve the user, requires relevant p4 permissions
                 optparse.make_option("--preserve-user", dest="preserveUser", action="store_true"),
                 optparse.make_option("--export-labels", dest="exportLabels", action="store_true"),
+                optparse.make_option("--dry-run", "-n", dest="dry_run", action="store_true"),
+                optparse.make_option("--prepare-p4-only", dest="prepare_p4_only", action="store_true"),
+                optparse.make_option("--conflict", dest="conflict_behavior",
+                                     choices=self.conflict_behavior_choices)
         ]
         self.description = "Submit changes from git to the perforce depot."
         self.usage += " [name of git branch to submit into perforce depot]"
         self.origin = ""
         self.detectRenames = False
         self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true"
+        self.dry_run = False
+        self.prepare_p4_only = False
+        self.conflict_behavior = None
         self.isWindows = (platform.system() == "Windows")
         self.exportLabels = False
         self.p4HasMoveCommand = p4_has_command("move")
@@ -1088,7 +1098,10 @@ def edit_template(self, template_file):
                 return False
 
     def applyCommit(self, id):
-        print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
+        """Apply one commit, return True if it succeeded."""
+
+        print "Applying", read_pipe(["git", "show", "-s",
+                                     "--format=format:%h %s", id])
 
         (p4User, gitEmail) = self.p4UserForCommit(id)
 
@@ -1195,34 +1208,13 @@ def applyCommit(self, id):
                     patch_succeeded = True
 
         if not patch_succeeded:
-            print "What do you want to do?"
-            response = "x"
-            while response != "s" and response != "a" and response != "w":
-                response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
-                                     "and with .rej files / [w]rite the patch to a file (patch.txt) ")
-            if response == "s":
-                print "Skipping! Good luck with the next patches..."
-                for f in editedFiles:
-                    p4_revert(f)
-                for f in filesToAdd:
-                    os.remove(f)
-                return
-            elif response == "a":
-                os.system(applyPatchCmd)
-                if len(filesToAdd) > 0:
-                    print "You may also want to call p4 add on the following files:"
-                    print " ".join(filesToAdd)
-                if len(filesToDelete):
-                    print "The following files should be scheduled for deletion with p4 delete:"
-                    print " ".join(filesToDelete)
-                die("Please resolve and submit the conflict manually and "
-                    + "continue afterwards with git p4 submit --continue")
-            elif response == "w":
-                system(diffcmd + " > patch.txt")
-                print "Patch saved to patch.txt in %s !" % self.clientPath
-                die("Please resolve and submit the conflict manually and "
-                    "continue afterwards with git p4 submit --continue")
+            for f in editedFiles:
+                p4_revert(f)
+            return False
 
+        #
+        # Apply the patch for real, and do add/delete/+x handling.
+        #
         system(applyPatchCmd)
 
         for f in filesToAdd:
@@ -1236,6 +1228,10 @@ def applyCommit(self, id):
             mode = filesToChangeExecBit[f]
             setP4ExecBit(f, mode)
 
+        #
+        # Build p4 change description, starting with the contents
+        # of the git commit message.
+        #
         logMessage = extractLogMessageFromGitCommit(id)
         logMessage = logMessage.strip()
         (logMessage, jobs) = self.separate_jobs_from_description(logMessage)
@@ -1244,8 +1240,16 @@ def applyCommit(self, id):
         submitTemplate = self.prepareLogMessage(template, logMessage, jobs)
 
         if self.preserveUser:
-           submitTemplate = submitTemplate + ("\n######## Actual user %s, modified after commit\n" % p4User)
+           submitTemplate += "\n######## Actual user %s, modified after commit\n" % p4User
+
+        if self.checkAuthorship and not self.p4UserIsMe(p4User):
+            submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail
+            submitTemplate += "######## Use option --preserve-user to modify authorship.\n"
+            submitTemplate += "######## Variable git-p4.skipUserNameCheck hides this message.\n"
 
+        separatorLine = "######## everything below this line is just the diff #######\n"
+
+        # diff
         if os.environ.has_key("P4DIFF"):
             del(os.environ["P4DIFF"])
         diff = ""
@@ -1253,6 +1257,7 @@ def applyCommit(self, id):
             diff += p4_read_pipe(['diff', '-du',
                                   wildcard_encode(editedFile)])
 
+        # new file diff
         newdiff = ""
         for newFile in filesToAdd:
             newdiff += "==== new file ====\n"
@@ -1263,13 +1268,7 @@ def applyCommit(self, id):
                 newdiff += "+" + line
             f.close()
 
-        if self.checkAuthorship and not self.p4UserIsMe(p4User):
-            submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail
-            submitTemplate += "######## Use option --preserve-user to modify authorship.\n"
-            submitTemplate += "######## Variable git-p4.skipUserNameCheck hides this message.\n"
-
-        separatorLine = "######## everything below this line is just the diff #######\n"
-
+        # change description file: submitTemplate, separatorLine, diff, newdiff
         (handle, fileName) = tempfile.mkstemp()
         tmpFile = os.fdopen(handle, "w+")
         if self.isWindows:
@@ -1279,8 +1278,47 @@ def applyCommit(self, id):
         tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
         tmpFile.close()
 
+        if self.prepare_p4_only:
+            #
+            # Leave the p4 tree prepared, and the submit template around
+            # and let the user decide what to do next
+            #
+            print
+            print "P4 workspace prepared for submission."
+            print "To submit or revert, go to client workspace"
+            print "  " + self.clientPath
+            print
+            print "To submit, use \"p4 submit\" to write a new description,"
+            print "or \"p4 submit -i %s\" to use the one prepared by" \
+                  " \"git p4\"." % fileName
+            print "You can delete the file \"%s\" when finished." % fileName
+
+            if self.preserveUser and p4User and not self.p4UserIsMe(p4User):
+                print "To preserve change ownership by user %s, you must\n" \
+                      "do \"p4 change -f <change>\" after submitting and\n" \
+                      "edit the User field."
+            if pureRenameCopy:
+                print "After submitting, renamed files must be re-synced."
+                print "Invoke \"p4 sync -f\" on each of these files:"
+                for f in pureRenameCopy:
+                    print "  " + f
+
+            print
+            print "To revert the changes, use \"p4 revert ...\", and delete"
+            print "the submit template file \"%s\"" % fileName
+            if filesToAdd:
+                print "Since the commit adds new files, they must be deleted:"
+                for f in filesToAdd:
+                    print "  " + f
+            print
+            return True
+
+        #
+        # Let the user edit the change description, then submit it.
+        #
         if self.edit_template(fileName):
             # read the edited message and submit
+            ret = True
             tmpFile = open(fileName, "rb")
             message = tmpFile.read()
             tmpFile.close()
@@ -1304,14 +1342,18 @@ def applyCommit(self, id):
 
         else:
             # skip this patch
+            ret = False
             print "Submission cancelled, undoing p4 changes."
             for f in editedFiles:
                 p4_revert(f)
             for f in filesToAdd:
                 p4_revert(f)
                 os.remove(f)
+            for f in filesToDelete:
+                p4_revert(f)
 
         os.remove(fileName)
+        return ret
 
     # Export git tags as p4 labels. Create a p4 label and then tag
     # with that.
@@ -1369,14 +1411,20 @@ def exportGitTags(self, gitTags):
             for mapping in clientSpec.mappings:
                 labelTemplate += "\t%s\n" % mapping.depot_side.path
 
-            p4_write_pipe(["label", "-i"], labelTemplate)
+            if self.dry_run:
+                print "Would create p4 label %s for tag" % name
+            elif self.prepare_p4_only:
+                print "Not creating p4 label %s for tag due to option" \
+                      " --prepare-p4-only" % name
+            else:
+                p4_write_pipe(["label", "-i"], labelTemplate)
 
-            # Use the label
-            p4_system(["tag", "-l", name] +
-                      ["%s@%s" % (mapping.depot_side.path, changelist) for mapping in clientSpec.mappings])
+                # Use the label
+                p4_system(["tag", "-l", name] +
+                          ["%s@%s" % (mapping.depot_side.path, changelist) for mapping in clientSpec.mappings])
 
-            if verbose:
-                print "created p4 label for tag %s" % name
+                if verbose:
+                    print "created p4 label for tag %s" % name
 
     def run(self, args):
         if len(args) == 0:
@@ -1403,6 +1451,16 @@ def run(self, args):
             if not self.canChangeChangelists():
                 die("Cannot preserve user names without p4 super-user or admin permissions")
 
+        # if not set from the command line, try the config file
+        if self.conflict_behavior is None:
+            val = gitConfig("git-p4.conflict")
+            if val:
+                if val not in self.conflict_behavior_choices:
+                    die("Invalid value '%s' for config git-p4.conflict" % val)
+            else:
+                val = "ask"
+            self.conflict_behavior = val
+
         if self.verbose:
             print "Origin branch is " + self.origin
 
@@ -1435,12 +1493,15 @@ def run(self, args):
             os.makedirs(self.clientPath)
 
         chdir(self.clientPath)
-        print "Synchronizing p4 checkout..."
-        if new_client_dir:
-            # old one was destroyed, and maybe nobody told p4
-            p4_sync("...", "-f")
+        if self.dry_run:
+            print "Would synchronize p4 checkout in %s" % self.clientPath
         else:
-            p4_sync("...")
+            print "Synchronizing p4 checkout..."
+            if new_client_dir:
+                # old one was destroyed, and maybe nobody told p4
+                p4_sync("...", "-f")
+            else:
+                p4_sync("...")
         self.check()
 
         commits = []
@@ -1487,14 +1548,64 @@ def run(self, args):
         if gitConfig("git-p4.detectCopiesHarder", "--bool") == "true":
             self.diffOpts += " --find-copies-harder"
 
-        while len(commits) > 0:
-            commit = commits[0]
-            commits = commits[1:]
-            self.applyCommit(commit)
+        #
+        # Apply the commits, one at a time.  On failure, ask if should
+        # continue to try the rest of the patches, or quit.
+        #
+        if self.dry_run:
+            print "Would apply"
+        applied = []
+        last = len(commits) - 1
+        for i, commit in enumerate(commits):
+            if self.dry_run:
+                print " ", read_pipe(["git", "show", "-s",
+                                      "--format=format:%h %s", commit])
+                ok = True
+            else:
+                ok = self.applyCommit(commit)
+            if ok:
+                applied.append(commit)
+            else:
+                if self.prepare_p4_only and i < last:
+                    print "Processing only the first commit due to option" \
+                          " --prepare-p4-only"
+                    break
+                if i < last:
+                    quit = False
+                    while True:
+                        # prompt for what to do, or use the option/variable
+                        if self.conflict_behavior == "ask":
+                            print "What do you want to do?"
+                            response = raw_input("[s]kip this commit but apply"
+                                                 " the rest, or [q]uit? ")
+                            if not response:
+                                continue
+                        elif self.conflict_behavior == "skip":
+                            response = "s"
+                        elif self.conflict_behavior == "quit":
+                            response = "q"
+                        else:
+                            die("Unknown conflict_behavior '%s'" %
+                                self.conflict_behavior)
+
+                        if response[0] == "s":
+                            print "Skipping this commit, but applying the rest"
+                            break
+                        if response[0] == "q":
+                            print "Quitting"
+                            quit = True
+                            break
+                    if quit:
+                        break
+
+        chdir(self.oldWorkingDirectory)
 
-        if len(commits) == 0:
-            print "All changes applied!"
-            chdir(self.oldWorkingDirectory)
+        if self.dry_run:
+            pass
+        elif self.prepare_p4_only:
+            pass
+        elif len(commits) == len(applied):
+            print "All commits applied!"
 
             sync = P4Sync()
             sync.run([])
@@ -1502,6 +1613,20 @@ def run(self, args):
             rebase = P4Rebase()
             rebase.rebase()
 
+        else:
+            if len(applied) == 0:
+                print "No commits applied."
+            else:
+                print "Applied only the commits marked with '*':"
+                for c in commits:
+                    if c in applied:
+                        star = "*"
+                    else:
+                        star = " "
+                    print star, read_pipe(["git", "show", "-s",
+                                           "--format=format:%h %s",  c])
+                print "You will have to do 'git p4 sync' and rebase."
+
         if gitConfig("git-p4.exportLabels", "--bool") == "true":
             self.exportLabels = True
 
@@ -1512,6 +1637,10 @@ def run(self, args):
             missingGitTags = gitTags - p4Labels
             self.exportGitTags(missingGitTags)
 
+        # exit with error unless everything applied perfecly
+        if len(commits) != len(applied):
+                sys.exit(1)
+
         return True
 
 class View(object):
@@ -3015,7 +3144,7 @@ def main():
 
     args = sys.argv[2:]
 
-    options.append(optparse.make_option("--verbose", dest="verbose", action="store_true"))
+    options.append(optparse.make_option("--verbose", "-v", dest="verbose", action="store_true"))
     if cmd.needsGit:
         options.append(optparse.make_option("--git-dir", dest="gitdir"))
 
index 51a906e9e3316582561304995e1a1bbbdcd6ca7c..1516c5eb290746bad4551515fda7930d2f55d32b 100644 (file)
@@ -396,7 +396,7 @@ static int fetch_indices(struct walker *walker, struct alt_base *repo)
        return ret;
 }
 
-static int fetch_pack(struct walker *walker, struct alt_base *repo, unsigned char *sha1)
+static int http_fetch_pack(struct walker *walker, struct alt_base *repo, unsigned char *sha1)
 {
        struct packed_git *target;
        int ret;
@@ -524,7 +524,7 @@ static int fetch(struct walker *walker, unsigned char *sha1)
        if (!fetch_object(walker, altbase, sha1))
                return 0;
        while (altbase) {
-               if (!fetch_pack(walker, altbase, sha1))
+               if (!http_fetch_pack(walker, altbase, sha1))
                        return 0;
                fetch_alternates(walker, data->alt->base);
                altbase = altbase->next;
index ce8a59f576ac4950cd3abacd2f7a60086751de2a..e3723d20956719d2cba13ee6c6890ff1a8cfd21a 100644 (file)
@@ -17,6 +17,8 @@
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
+const char sign_off_header[] = "Signed-off-by: ";
+
 static void remove_sequencer_state(void)
 {
        struct strbuf seq_dir = STRBUF_INIT;
@@ -233,6 +235,9 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
                die(_("%s: Unable to write new index file"), action_name(opts));
        rollback_lock_file(&index_lock);
 
+       if (opts->signoff)
+               append_signoff(msgbuf, 0);
+
        if (!clean) {
                int i;
                strbuf_addstr(msgbuf, "\nConflicts:\n");
@@ -1011,3 +1016,63 @@ int sequencer_pick_revisions(struct replay_opts *opts)
        save_opts(opts);
        return pick_commits(todo_list, opts);
 }
+
+static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer)
+{
+       int ch;
+       int hit = 0;
+       int i, j, k;
+       int len = sb->len - ignore_footer;
+       int first = 1;
+       const char *buf = sb->buf;
+
+       for (i = len - 1; i > 0; i--) {
+               if (hit && buf[i] == '\n')
+                       break;
+               hit = (buf[i] == '\n');
+       }
+
+       while (i < len - 1 && buf[i] == '\n')
+               i++;
+
+       for (; i < len; i = k) {
+               for (k = i; k < len && buf[k] != '\n'; k++)
+                       ; /* do nothing */
+               k++;
+
+               if ((buf[k] == ' ' || buf[k] == '\t') && !first)
+                       continue;
+
+               first = 0;
+
+               for (j = 0; i + j < len; j++) {
+                       ch = buf[i + j];
+                       if (ch == ':')
+                               break;
+                       if (isalnum(ch) ||
+                           (ch == '-'))
+                               continue;
+                       return 0;
+               }
+       }
+       return 1;
+}
+
+void append_signoff(struct strbuf *msgbuf, int ignore_footer)
+{
+       struct strbuf sob = STRBUF_INIT;
+       int i;
+
+       strbuf_addstr(&sob, sign_off_header);
+       strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
+                               getenv("GIT_COMMITTER_EMAIL")));
+       strbuf_addch(&sob, '\n');
+       for (i = msgbuf->len - 1 - ignore_footer; i > 0 && msgbuf->buf[i - 1] != '\n'; i--)
+               ; /* do nothing */
+       if (prefixcmp(msgbuf->buf + i, sob.buf)) {
+               if (!i || !ends_rfc2822_footer(msgbuf, ignore_footer))
+                       strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, "\n", 1);
+               strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, sob.buf, sob.len);
+       }
+       strbuf_release(&sob);
+}
index 99eb7fa09043094bdb6f1599c19716b9f578586c..9d57d57524d411c54d55346a5ecff67bc256595c 100644 (file)
@@ -46,4 +46,8 @@ struct replay_opts {
 
 int sequencer_pick_revisions(struct replay_opts *opts);
 
+extern const char sign_off_header[];
+
+void append_signoff(struct strbuf *msgbuf, int ignore_footer);
+
 #endif
index d9810aba421cbbc844b45ea5ca713a480a699eb2..c54b816244f69614c67c532d20719081a4b34bd4 100644 (file)
@@ -92,6 +92,23 @@ struct string_list_item *string_list_lookup(struct string_list *list, const char
        return list->items + i;
 }
 
+void string_list_remove_duplicates(struct string_list *list, int free_util)
+{
+       if (list->nr > 1) {
+               int src, dst;
+               for (src = dst = 1; src < list->nr; src++) {
+                       if (!strcmp(list->items[dst - 1].string, list->items[src].string)) {
+                               if (list->strdup_strings)
+                                       free(list->items[src].string);
+                               if (free_util)
+                                       free(list->items[src].util);
+                       } else
+                               list->items[dst++] = list->items[src];
+               }
+               list->nr = dst;
+       }
+}
+
 int for_each_string_list(struct string_list *list,
                         string_list_each_func_t fn, void *cb_data)
 {
@@ -102,6 +119,43 @@ int for_each_string_list(struct string_list *list,
        return ret;
 }
 
+void filter_string_list(struct string_list *list, int free_util,
+                       string_list_each_func_t want, void *cb_data)
+{
+       int src, dst = 0;
+       for (src = 0; src < list->nr; src++) {
+               if (want(&list->items[src], cb_data)) {
+                       list->items[dst++] = list->items[src];
+               } else {
+                       if (list->strdup_strings)
+                               free(list->items[src].string);
+                       if (free_util)
+                               free(list->items[src].util);
+               }
+       }
+       list->nr = dst;
+}
+
+char *string_list_longest_prefix(const struct string_list *prefixes,
+                                const char *string)
+{
+       int i, max_len = -1;
+       char *retval = NULL;
+
+       for (i = 0; i < prefixes->nr; i++) {
+               char *prefix = prefixes->items[i].string;
+               if (!prefixcmp(string, prefix)) {
+                       int len = strlen(prefix);
+                       if (len > max_len) {
+                               retval = prefix;
+                               max_len = len;
+                       }
+               }
+       }
+
+       return retval;
+}
+
 void string_list_clear(struct string_list *list, int free_util)
 {
        if (list->items) {
@@ -148,13 +202,23 @@ void print_string_list(const struct string_list *p, const char *text)
                printf("%s:%p\n", p->items[i].string, p->items[i].util);
 }
 
-struct string_list_item *string_list_append(struct string_list *list, const char *string)
+struct string_list_item *string_list_append_nodup(struct string_list *list,
+                                                 char *string)
 {
+       struct string_list_item *retval;
        ALLOC_GROW(list->items, list->nr + 1, list->alloc);
-       list->items[list->nr].string =
-               list->strdup_strings ? xstrdup(string) : (char *)string;
-       list->items[list->nr].util = NULL;
-       return list->items + list->nr++;
+       retval = &list->items[list->nr++];
+       retval->string = string;
+       retval->util = NULL;
+       return retval;
+}
+
+struct string_list_item *string_list_append(struct string_list *list,
+                                           const char *string)
+{
+       return string_list_append_nodup(
+                       list,
+                       list->strdup_strings ? xstrdup(string) : (char *)string);
 }
 
 static int cmp_items(const void *a, const void *b)
@@ -194,3 +258,56 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_
        list->items[i] = list->items[list->nr-1];
        list->nr--;
 }
+
+int string_list_split(struct string_list *list, const char *string,
+                     int delim, int maxsplit)
+{
+       int count = 0;
+       const char *p = string, *end;
+
+       if (!list->strdup_strings)
+               die("internal error in string_list_split(): "
+                   "list->strdup_strings must be set");
+       for (;;) {
+               count++;
+               if (maxsplit >= 0 && count > maxsplit) {
+                       string_list_append(list, p);
+                       return count;
+               }
+               end = strchr(p, delim);
+               if (end) {
+                       string_list_append_nodup(list, xmemdupz(p, end - p));
+                       p = end + 1;
+               } else {
+                       string_list_append(list, p);
+                       return count;
+               }
+       }
+}
+
+int string_list_split_in_place(struct string_list *list, char *string,
+                              int delim, int maxsplit)
+{
+       int count = 0;
+       char *p = string, *end;
+
+       if (list->strdup_strings)
+               die("internal error in string_list_split_in_place(): "
+                   "list->strdup_strings must not be set");
+       for (;;) {
+               count++;
+               if (maxsplit >= 0 && count > maxsplit) {
+                       string_list_append(list, p);
+                       return count;
+               }
+               end = strchr(p, delim);
+               if (end) {
+                       *end = '\0';
+                       string_list_append(list, p);
+                       p = end + 1;
+               } else {
+                       string_list_append(list, p);
+                       return count;
+               }
+       }
+}
index 0684cb73bfd27846182479a4e192d682a44f201a..5efd07b44e020428326ff32ff258449ec347861f 100644 (file)
@@ -29,6 +29,24 @@ int for_each_string_list(struct string_list *list,
 #define for_each_string_list_item(item,list) \
        for (item = (list)->items; item < (list)->items + (list)->nr; ++item)
 
+/*
+ * Apply want to each item in list, retaining only the ones for which
+ * the function returns true.  If free_util is true, call free() on
+ * the util members of any items that have to be deleted.  Preserve
+ * the order of the items that are retained.
+ */
+void filter_string_list(struct string_list *list, int free_util,
+                       string_list_each_func_t want, void *cb_data);
+
+/*
+ * Return the longest string in prefixes that is a prefix (in the
+ * sense of prefixcmp()) of string, or NULL if no such prefix exists.
+ * This function does not require the string_list to be sorted (it
+ * does a linear search).
+ */
+char *string_list_longest_prefix(const struct string_list *prefixes, const char *string);
+
+
 /* Use these functions only on sorted lists: */
 int string_list_has_string(const struct string_list *list, const char *string);
 int string_list_find_insert_index(const struct string_list *list, const char *string,
@@ -38,11 +56,64 @@ struct string_list_item *string_list_insert_at_index(struct string_list *list,
                                                     int insert_at, const char *string);
 struct string_list_item *string_list_lookup(struct string_list *list, const char *string);
 
+/*
+ * Remove all but the first of consecutive entries with the same
+ * string value.  If free_util is true, call free() on the util
+ * members of any items that have to be deleted.
+ */
+void string_list_remove_duplicates(struct string_list *sorted_list, int free_util);
+
+
 /* Use these functions only on unsorted lists: */
+
+/*
+ * Add string to the end of list.  If list->strdup_string is set, then
+ * string is copied; otherwise the new string_list_entry refers to the
+ * input string.
+ */
 struct string_list_item *string_list_append(struct string_list *list, const char *string);
+
+/*
+ * Like string_list_append(), except string is never copied.  When
+ * list->strdup_strings is set, this function can be used to hand
+ * ownership of a malloc()ed string to list without making an extra
+ * copy.
+ */
+struct string_list_item *string_list_append_nodup(struct string_list *list, char *string);
+
 void sort_string_list(struct string_list *list);
 int unsorted_string_list_has_string(struct string_list *list, const char *string);
 struct string_list_item *unsorted_string_list_lookup(struct string_list *list,
                                                     const char *string);
+
 void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util);
+
+/*
+ * Split string into substrings on character delim and append the
+ * substrings to list.  The input string is not modified.
+ * list->strdup_strings must be set, as new memory needs to be
+ * allocated to hold the substrings.  If maxsplit is non-negative,
+ * then split at most maxsplit times.  Return the number of substrings
+ * appended to list.
+ *
+ * Examples:
+ *   string_list_split(l, "foo:bar:baz", ':', -1) -> ["foo", "bar", "baz"]
+ *   string_list_split(l, "foo:bar:baz", ':', 0) -> ["foo:bar:baz"]
+ *   string_list_split(l, "foo:bar:baz", ':', 1) -> ["foo", "bar:baz"]
+ *   string_list_split(l, "foo:bar:", ':', -1) -> ["foo", "bar", ""]
+ *   string_list_split(l, "", ':', -1) -> [""]
+ *   string_list_split(l, ":", ':', -1) -> ["", ""]
+ */
+int string_list_split(struct string_list *list, const char *string,
+                     int delim, int maxsplit);
+
+/*
+ * Like string_list_split(), except that string is split in-place: the
+ * delimiter characters in string are overwritten with NULs, and the
+ * new string_list_items point into string (which therefore must not
+ * be modified or freed while the string_list is in use).
+ * list->strdup_strings must *not* be set.
+ */
+int string_list_split_in_place(struct string_list *list, char *string,
+                              int delim, int maxsplit);
 #endif /* STRING_LIST_H */
index d748c36df596b640d403626fce3200d85d68253b..7061dce7e5de2f67e60fe6c42c40519f29cad4a1 100644 (file)
@@ -26,9 +26,10 @@ testid=${this_test#t}
 git_p4_test_start=9800
 P4DPORT=$((10669 + ($testid - $git_p4_test_start)))
 
-export P4PORT=localhost:$P4DPORT
-export P4CLIENT=client
-export P4EDITOR=:
+P4PORT=localhost:$P4DPORT
+P4CLIENT=client
+P4EDITOR=:
+export P4PORT P4CLIENT P4EDITOR
 
 db="$TRASH_DIRECTORY/db"
 cli=$(test-path-utils real_path "$TRASH_DIRECTORY/cli")
index 50f5cc1ed901e5abd7839dcb58ff94aa96a9c641..982eb8e3a949a74e5c26a952a33364c25f6f5b74 100644 (file)
@@ -1,2 +1,3 @@
-build/
-test-results/
+/build/
+/test-results/
+/trash directory*/
diff --git a/t/t0063-string-list.sh b/t/t0063-string-list.sh
new file mode 100755 (executable)
index 0000000..41c8826
--- /dev/null
@@ -0,0 +1,121 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Michael Haggerty
+#
+
+test_description='Test string list functionality'
+
+. ./test-lib.sh
+
+test_split () {
+       cat >expected &&
+       test_expect_success "split $1 at $2, max $3" "
+               test-string-list split '$1' '$2' '$3' >actual &&
+               test_cmp expected actual &&
+               test-string-list split_in_place '$1' '$2' '$3' >actual &&
+               test_cmp expected actual
+       "
+}
+
+test_longest_prefix () {
+       test "$(test-string-list longest_prefix "$1" "$2")" = "$3"
+}
+
+test_no_longest_prefix () {
+       test_must_fail test-string-list longest_prefix "$1" "$2"
+}
+
+test_split "foo:bar:baz" ":" "-1" <<EOF
+3
+[0]: "foo"
+[1]: "bar"
+[2]: "baz"
+EOF
+
+test_split "foo:bar:baz" ":" "0" <<EOF
+1
+[0]: "foo:bar:baz"
+EOF
+
+test_split "foo:bar:baz" ":" "1" <<EOF
+2
+[0]: "foo"
+[1]: "bar:baz"
+EOF
+
+test_split "foo:bar:baz" ":" "2" <<EOF
+3
+[0]: "foo"
+[1]: "bar"
+[2]: "baz"
+EOF
+
+test_split "foo:bar:" ":" "-1" <<EOF
+3
+[0]: "foo"
+[1]: "bar"
+[2]: ""
+EOF
+
+test_split "" ":" "-1" <<EOF
+1
+[0]: ""
+EOF
+
+test_split ":" ":" "-1" <<EOF
+2
+[0]: ""
+[1]: ""
+EOF
+
+test_expect_success "test filter_string_list" '
+       test "x-" = "x$(test-string-list filter - y)" &&
+       test "x-" = "x$(test-string-list filter no y)" &&
+       test yes = "$(test-string-list filter yes y)" &&
+       test yes = "$(test-string-list filter no:yes y)" &&
+       test yes = "$(test-string-list filter yes:no y)" &&
+       test y1:y2 = "$(test-string-list filter y1:y2 y)" &&
+       test y2:y1 = "$(test-string-list filter y2:y1 y)" &&
+       test "x-" = "x$(test-string-list filter x1:x2 y)"
+'
+
+test_expect_success "test remove_duplicates" '
+       test "x-" = "x$(test-string-list remove_duplicates -)" &&
+       test "x" = "x$(test-string-list remove_duplicates "")" &&
+       test a = "$(test-string-list remove_duplicates a)" &&
+       test a = "$(test-string-list remove_duplicates a:a)" &&
+       test a = "$(test-string-list remove_duplicates a:a:a:a:a)" &&
+       test a:b = "$(test-string-list remove_duplicates a:b)" &&
+       test a:b = "$(test-string-list remove_duplicates a:a:b)" &&
+       test a:b = "$(test-string-list remove_duplicates a:b:b)" &&
+       test a:b:c = "$(test-string-list remove_duplicates a:b:c)" &&
+       test a:b:c = "$(test-string-list remove_duplicates a:a:b:c)" &&
+       test a:b:c = "$(test-string-list remove_duplicates a:b:b:c)" &&
+       test a:b:c = "$(test-string-list remove_duplicates a:b:c:c)" &&
+       test a:b:c = "$(test-string-list remove_duplicates a:a:b:b:c:c)" &&
+       test a:b:c = "$(test-string-list remove_duplicates a:a:a:b:b:b:c:c:c)"
+'
+
+test_expect_success "test longest_prefix" '
+       test_no_longest_prefix - '' &&
+       test_no_longest_prefix - x &&
+       test_longest_prefix "" x "" &&
+       test_longest_prefix x x x &&
+       test_longest_prefix "" foo "" &&
+       test_longest_prefix : foo "" &&
+       test_longest_prefix f foo f &&
+       test_longest_prefix foo foobar foo &&
+       test_longest_prefix foo foo foo &&
+       test_no_longest_prefix bar foo &&
+       test_no_longest_prefix bar:bar foo &&
+       test_no_longest_prefix foobar foo &&
+       test_longest_prefix foo:bar foo foo &&
+       test_longest_prefix foo:bar bar bar &&
+       test_longest_prefix foo::bar foo foo &&
+       test_longest_prefix foo:foobar foo foo &&
+       test_longest_prefix foobar:foo foo foo &&
+       test_longest_prefix foo: bar "" &&
+       test_longest_prefix :foo bar ""
+'
+
+test_done
index 0c81b3c427af47f0b0ef73170372ef9df70b4ade..c82f7210c4ca10e35d9d2000aa8da2d10194a6c6 100755 (executable)
@@ -30,6 +30,7 @@ test_expect_success setup '
        test_commit initial foo a &&
        test_commit base foo b &&
        test_commit picked foo c &&
+       test_commit --signoff picked-signed foo d &&
        git config advice.detachedhead false
 
 '
@@ -340,4 +341,35 @@ test_expect_success 'revert conflict, diff3 -m style' '
        test_cmp expected actual
 '
 
+test_expect_success 'failed cherry-pick does not forget -s' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -s picked &&
+       test_i18ngrep -e "Signed-off-by" .git/MERGE_MSG
+'
+
+test_expect_success 'commit after failed cherry-pick does not add duplicated -s' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -s picked-signed &&
+       git commit -a -s &&
+       test $(git show -s |grep -c "Signed-off-by") = 1
+'
+
+test_expect_success 'commit after failed cherry-pick adds -s at the right place' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick picked &&
+       git commit -a -s &&
+       pwd &&
+       cat <<EOF > expected &&
+picked
+
+Signed-off-by: C O Mitter <committer@example.com>
+
+Conflicts:
+       foo
+EOF
+
+       git show -s --pretty=format:%B > actual &&
+       test_cmp expected actual
+'
+
 test_done
index f4e6450d6a88ae0abd158e52634c47a23872ad2c..b5fb527b2e686d158a6e7d53d6246b9e66f80a7b 100755 (executable)
@@ -410,7 +410,7 @@ test_expect_success '--continue respects -x in first commit in multi-pick' '
        grep "cherry picked from.*$picked" msg
 '
 
-test_expect_success '--signoff is not automatically propagated to resolved conflict' '
+test_expect_failure '--signoff is automatically propagated to resolved conflict' '
        pristine_detach initial &&
        test_expect_code 1 git cherry-pick --signoff base..anotherpick &&
        echo "c" >foo &&
@@ -428,7 +428,7 @@ test_expect_success '--signoff is not automatically propagated to resolved confl
        grep "Signed-off-by:" anotherpick_msg
 '
 
-test_expect_success '--signoff dropped for implicit commit of resolution, multi-pick case' '
+test_expect_failure '--signoff dropped for implicit commit of resolution, multi-pick case' '
        pristine_detach initial &&
        test_must_fail git cherry-pick -s picked anotherpick &&
        echo c >foo &&
@@ -441,7 +441,7 @@ test_expect_success '--signoff dropped for implicit commit of resolution, multi-
        ! grep Signed-off-by: msg
 '
 
-test_expect_success 'sign-off needs to be reaffirmed after conflict resolution, single-pick case' '
+test_expect_failure 'sign-off needs to be reaffirmed after conflict resolution, single-pick case' '
        pristine_detach initial &&
        test_must_fail git cherry-pick -s picked &&
        echo c >foo &&
index e80a2af348565a0a3d001ef28cb76427acfee495..6322e8ade8436dc4e66b9874638e7ca6b7d222b3 100755 (executable)
@@ -391,10 +391,55 @@ test_expect_success 'fetch mixed refs from cmdline and stdin' '
 test_expect_success 'test duplicate refs from stdin' '
        (
        cd client &&
-       test_must_fail git fetch-pack --stdin --no-progress .. <../input.dup
+       git fetch-pack --stdin --no-progress .. <../input.dup
        ) >output &&
        cut -d " " -f 2 <output | sort >actual &&
        test_cmp expect actual
 '
 
+test_expect_success 'set up tests of missing reference' '
+       cat >expect-error <<-\EOF
+       error: no such remote ref refs/heads/xyzzy
+       EOF
+'
+
+test_expect_success 'test lonely missing ref' '
+       (
+               cd client &&
+               test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy
+       ) >/dev/null 2>error-m &&
+       test_cmp expect-error error-m
+'
+
+test_expect_success 'test missing ref after existing' '
+       (
+               cd client &&
+               test_must_fail git fetch-pack --no-progress .. refs/heads/A refs/heads/xyzzy
+       ) >/dev/null 2>error-em &&
+       test_cmp expect-error error-em
+'
+
+test_expect_success 'test missing ref before existing' '
+       (
+               cd client &&
+               test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy refs/heads/A
+       ) >/dev/null 2>error-me &&
+       test_cmp expect-error error-me
+'
+
+test_expect_success 'test --all, --depth, and explicit head' '
+       (
+               cd client &&
+               git fetch-pack --no-progress --all --depth=1 .. refs/heads/A
+       ) >out-adh 2>error-adh
+'
+
+test_expect_success 'test --all, --depth, and explicit tag' '
+       git tag OLDTAG refs/heads/B~5 &&
+       (
+               cd client &&
+               git fetch-pack --no-progress --all --depth=1 .. refs/tags/OLDTAG
+       ) >out-adt 2>error-adt
+'
+
 test_done
index ba19ac127e630c01e009fa6eda1fac0086d7184d..9c353ab22276c052de054281dd57808d1826a355 100755 (executable)
@@ -66,7 +66,7 @@ test_expect_success \
        git blame file2
 '
 
-test_expect_success 'blame runs on conflicted file in stages 1,3' '
+test_expect_success 'blame does not crash with conflicted file in stages 1,3' '
        git blame file1
 '
 
index fb3c8ec12c7a3724c3b4f719c91357cc4afed078..ff2cc79701cce5ff570ff3c11d1ad0b60459d9ef 100755 (executable)
@@ -38,7 +38,7 @@ test_expect_success 'no config, unedited, say no' '
                cd "$git" &&
                echo line >>file1 &&
                git commit -a -m "change 3 (not really)" &&
-               printf "bad response\nn\n" | git p4 submit &&
+               printf "bad response\nn\n" | test_expect_code 1 git p4 submit &&
                p4 changes //depot/... >wc &&
                test_line_count = 2 wc
        )
index 9394fd4e9b5bd7eedc1f3d0552aa71d429b64308..0ae048f29f548eac6cfa37fbabcd9b7f80440515 100755 (executable)
@@ -54,6 +54,47 @@ test_expect_success 'submit --origin' '
        )
 '
 
+test_expect_success 'submit --dry-run' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               test_commit "dry-run1" &&
+               test_commit "dry-run2" &&
+               git p4 submit --dry-run >out &&
+               test_i18ngrep "Would apply" out
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_missing "dry-run1.t" &&
+               test_path_is_missing "dry-run2.t"
+       )
+'
+
+test_expect_success 'submit --dry-run --export-labels' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo dry-run1 >dry-run1 &&
+               git add dry-run1 &&
+               git commit -m "dry-run1" dry-run1 &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit &&
+               echo dry-run2 >dry-run2 &&
+               git add dry-run2 &&
+               git commit -m "dry-run2" dry-run2 &&
+               git tag -m "dry-run-tag1" dry-run-tag1 HEAD^ &&
+               git p4 submit --dry-run --export-labels >out &&
+               test_i18ngrep "Would create p4 label" out
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file "dry-run1" &&
+               test_path_is_missing "dry-run2"
+       )
+'
+
 test_expect_success 'submit with allowSubmit' '
        test_when_finished cleanup_git &&
        git p4 clone --dest="$git" //depot &&
@@ -334,6 +375,30 @@ test_expect_success 'description with Jobs section and bogus following text' '
                make_job $(cat jobname) &&
                test_must_fail git p4 submit 2>err &&
                test_i18ngrep "Unknown field name" err
+       ) &&
+       (
+               cd "$cli" &&
+               p4 revert desc6 &&
+               rm desc6
+       )
+'
+
+test_expect_success 'submit --prepare-p4-only' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo prep-only-add >prep-only-add &&
+               git add prep-only-add &&
+               git commit -m "prep only add" &&
+               git p4 submit --prepare-p4-only >out &&
+               test_i18ngrep "prepared for submission" out &&
+               test_i18ngrep "must be deleted" out
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file prep-only-add &&
+               p4 fstat -T action prep-only-add | grep -w add
        )
 '
 
index e9daa9c4f6bab493ad5351d4ec9c7c00c6152e68..fe30ad881f67c9f1fdcb2ee40922880126ec13cc 100755 (executable)
@@ -160,9 +160,6 @@ test_expect_success 'cleanup after failure' '
 # the cli file so that submit will get a conflict.  Make sure that
 # scrubbing doesn't make a mess of things.
 #
-# Assumes that git-p4 exits leaving the p4 file open, with the
-# conflict-generating patch unapplied.
-#
 # This might happen only if the git repo is behind the p4 repo at
 # submit time, and there is a conflict.
 #
@@ -181,14 +178,11 @@ test_expect_success 'do not scrub plain text' '
                        sed -i "s/^line5/line5 p4 edit/" file_text &&
                        p4 submit -d "file5 p4 edit"
                ) &&
-               ! git p4 submit &&
+               echo s | test_expect_code 1 git p4 submit &&
                (
-                       # exepct something like:
-                       #    file_text - file(s) not opened on this client
-                       # but not copious diff output
+                       # make sure the file is not left open
                        cd "$cli" &&
-                       p4 diff file_text >wc &&
-                       test_line_count = 1 wc
+                       ! p4 fstat -T action file_text
                )
        )
 '
@@ -343,44 +337,6 @@ test_expect_failure 'Add keywords in git which do not match the default p4 value
        )
 '
 
-# Check that the existing merge conflict handling still works.
-# Modify kwfile1.c in git, and delete in p4. We should be able
-# to skip the git commit.
-#
-test_expect_success 'merge conflict handling still works' '
-       test_when_finished cleanup_git &&
-       (
-               cd "$cli" &&
-               echo "Hello:\$Id\$" >merge2.c &&
-               echo "World" >>merge2.c &&
-               p4 add -t ktext merge2.c &&
-               p4 submit -d "add merge test file"
-       ) &&
-       git p4 clone --dest="$git" //depot &&
-       (
-               cd "$git" &&
-               sed -e "/Hello/d" merge2.c >merge2.c.tmp &&
-               mv merge2.c.tmp merge2.c &&
-               git add merge2.c &&
-               git commit -m "Modifying merge2.c"
-       ) &&
-       (
-               cd "$cli" &&
-               p4 delete merge2.c &&
-               p4 submit -d "remove merge test file"
-       ) &&
-       (
-               cd "$git" &&
-               test -f merge2.c &&
-               git config git-p4.skipSubmitEdit true &&
-               git config git-p4.attemptRCSCleanup true &&
-               !(echo "s" | git p4 submit) &&
-               git rebase --skip &&
-               ! test -f merge2.c
-       )
-'
-
-
 test_expect_success 'kill p4d' '
        kill_p4d
 '
diff --git a/t/t9815-git-p4-submit-fail.sh b/t/t9815-git-p4-submit-fail.sh
new file mode 100755 (executable)
index 0000000..d2b7b3d
--- /dev/null
@@ -0,0 +1,429 @@
+#!/bin/sh
+
+test_description='git p4 submit failure handling'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               p4 client -o | sed "/LineEnd/s/:.*/:unix/" | p4 client -i &&
+               echo line1 >file1 &&
+               p4 add file1 &&
+               p4 submit -d "line1 in file1"
+       )
+'
+
+test_expect_success 'conflict on one commit' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$cli" &&
+               p4 open file1 &&
+               echo line2 >>file1 &&
+               p4 submit -d "line2 in file1"
+       ) &&
+       (
+               # now this commit should cause a conflict
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               echo line3 >>file1 &&
+               git add file1 &&
+               git commit -m "line3 in file1 will conflict" &&
+               test_expect_code 1 git p4 submit >out &&
+               test_i18ngrep "No commits applied" out
+       )
+'
+
+test_expect_success 'conflict on second of two commits' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$cli" &&
+               p4 open file1 &&
+               echo line3 >>file1 &&
+               p4 submit -d "line3 in file1"
+       ) &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               # this commit is okay
+               test_commit "first_commit_okay" &&
+               # now this submit should cause a conflict
+               echo line4 >>file1 &&
+               git add file1 &&
+               git commit -m "line4 in file1 will conflict" &&
+               test_expect_code 1 git p4 submit >out &&
+               test_i18ngrep "Applied only the commits" out
+       )
+'
+
+test_expect_success 'conflict on first of two commits, skip' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$cli" &&
+               p4 open file1 &&
+               echo line4 >>file1 &&
+               p4 submit -d "line4 in file1"
+       ) &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               # this submit should cause a conflict
+               echo line5 >>file1 &&
+               git add file1 &&
+               git commit -m "line5 in file1 will conflict" &&
+               # but this commit is okay
+               test_commit "okay_commit_after_skip" &&
+               echo s | test_expect_code 1 git p4 submit >out &&
+               test_i18ngrep "Applied only the commits" out
+       )
+'
+
+test_expect_success 'conflict on first of two commits, quit' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$cli" &&
+               p4 open file1 &&
+               echo line7 >>file1 &&
+               p4 submit -d "line7 in file1"
+       ) &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               # this submit should cause a conflict
+               echo line8 >>file1 &&
+               git add file1 &&
+               git commit -m "line8 in file1 will conflict" &&
+               # but this commit is okay
+               test_commit "okay_commit_after_quit" &&
+               echo q | test_expect_code 1 git p4 submit >out &&
+               test_i18ngrep "No commits applied" out
+       )
+'
+
+test_expect_success 'conflict cli and config options' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git p4 submit --conflict=ask &&
+               git p4 submit --conflict=skip &&
+               git p4 submit --conflict=quit &&
+               test_expect_code 2 git p4 submit --conflict=foo &&
+               test_expect_code 2 git p4 submit --conflict &&
+               git config git-p4.conflict foo &&
+               test_expect_code 1 git p4 submit &&
+               git config --unset git-p4.conflict &&
+               git p4 submit
+       )
+'
+
+test_expect_success 'conflict on first of two commits, --conflict=skip' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$cli" &&
+               p4 open file1 &&
+               echo line9 >>file1 &&
+               p4 submit -d "line9 in file1"
+       ) &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               # this submit should cause a conflict
+               echo line10 >>file1 &&
+               git add file1 &&
+               git commit -m "line10 in file1 will conflict" &&
+               # but this commit is okay
+               test_commit "okay_commit_after_auto_skip" &&
+               test_expect_code 1 git p4 submit --conflict=skip >out &&
+               test_i18ngrep "Applied only the commits" out
+       )
+'
+
+test_expect_success 'conflict on first of two commits, --conflict=quit' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$cli" &&
+               p4 open file1 &&
+               echo line11 >>file1 &&
+               p4 submit -d "line11 in file1"
+       ) &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               # this submit should cause a conflict
+               echo line12 >>file1 &&
+               git add file1 &&
+               git commit -m "line12 in file1 will conflict" &&
+               # but this commit is okay
+               test_commit "okay_commit_after_auto_quit" &&
+               test_expect_code 1 git p4 submit --conflict=quit >out &&
+               test_i18ngrep "No commits applied" out
+       )
+'
+
+#
+# Cleanup after submit fail, all cases.  Some modifications happen
+# before trying to apply the patch.  Make sure these are unwound
+# properly.  Put each one in a diff along with something that will
+# obviously conflict.  Make sure it is back to normal after.
+#
+
+test_expect_success 'cleanup edit p4 populate' '
+       (
+               cd "$cli" &&
+               echo text file >text &&
+               p4 add text &&
+               echo text+x file >text+x &&
+               chmod 755 text+x &&
+               p4 add text+x &&
+               p4 submit -d "populate p4"
+       )
+'
+
+setup_conflict() {
+       # clone before modifying file1 to force it to conflict
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       # ticks outside subshells
+       test_tick &&
+       (
+               cd "$cli" &&
+               p4 open file1 &&
+               echo $test_tick >>file1 &&
+               p4 submit -d "$test_tick in file1"
+       ) &&
+       test_tick &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               # easy conflict
+               echo $test_tick >>file1 &&
+               git add file1
+               # caller will add more and submit
+       )
+}
+
+test_expect_success 'cleanup edit after submit fail' '
+       setup_conflict &&
+       (
+               cd "$git" &&
+               echo another line >>text &&
+               git add text &&
+               git commit -m "conflict" &&
+               test_expect_code 1 git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               # make sure it is not open
+               ! p4 fstat -T action text
+       )
+'
+
+test_expect_success 'cleanup add after submit fail' '
+       setup_conflict &&
+       (
+               cd "$git" &&
+               echo new file >textnew &&
+               git add textnew &&
+               git commit -m "conflict" &&
+               test_expect_code 1 git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               # make sure it is not there
+               # and that p4 thinks it is not added
+               #   P4 returns 0 both for "not there but added" and
+               #   "not there", so grep.
+               test_path_is_missing textnew &&
+               p4 fstat -T action textnew 2>&1 | grep "no such file"
+       )
+'
+
+test_expect_success 'cleanup delete after submit fail' '
+       setup_conflict &&
+       (
+               cd "$git" &&
+               git rm text+x &&
+               git commit -m "conflict" &&
+               test_expect_code 1 git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               # make sure it is there
+               test_path_is_file text+x &&
+               ! p4 fstat -T action text+x
+       )
+'
+
+test_expect_success 'cleanup copy after submit fail' '
+       setup_conflict &&
+       (
+               cd "$git" &&
+               cp text text2 &&
+               git add text2 &&
+               git commit -m "conflict" &&
+               git config git-p4.detectCopies true &&
+               git config git-p4.detectCopiesHarder true &&
+               # make sure setup is okay
+               git diff-tree -r -C --find-copies-harder HEAD | grep text2 | grep C100 &&
+               test_expect_code 1 git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_missing text2 &&
+               p4 fstat -T action text2 2>&1 | grep "no such file"
+       )
+'
+
+test_expect_success 'cleanup rename after submit fail' '
+       setup_conflict &&
+       (
+               cd "$git" &&
+               git mv text text2 &&
+               git commit -m "conflict" &&
+               git config git-p4.detectRenames true &&
+               # make sure setup is okay
+               git diff-tree -r -M HEAD | grep text2 | grep R100 &&
+               test_expect_code 1 git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_missing text2 &&
+               p4 fstat -T action text2 2>&1 | grep "no such file"
+       )
+'
+
+#
+# Cleanup after deciding not to submit during editTemplate.  This
+# involves unwinding more work, because files have been added, deleted
+# and chmod-ed now.  Same approach as above.
+#
+
+test_expect_success 'cleanup edit after submit cancel' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo line >>text &&
+               git add text &&
+               git commit -m text &&
+               echo n | test_expect_code 1 git p4 submit &&
+               git reset --hard HEAD^
+       ) &&
+       (
+               cd "$cli" &&
+               ! p4 fstat -T action text &&
+               test_cmp "$git"/text text
+       )
+'
+
+test_expect_success 'cleanup add after submit cancel' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo line >textnew &&
+               git add textnew &&
+               git commit -m textnew &&
+               echo n | test_expect_code 1 git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_missing textnew &&
+               p4 fstat -T action textnew 2>&1 | grep "no such file"
+       )
+'
+
+test_expect_success 'cleanup delete after submit cancel' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git rm text &&
+               git commit -m "rm text" &&
+               echo n | test_expect_code 1 git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file text &&
+               ! p4 fstat -T action text
+       )
+'
+
+test_expect_success 'cleanup copy after submit cancel' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               cp text text2 &&
+               git add text2 &&
+               git commit -m text2 &&
+               git config git-p4.detectCopies true &&
+               git config git-p4.detectCopiesHarder true &&
+               git diff-tree -r -C --find-copies-harder HEAD | grep text2 | grep C100 &&
+               echo n | test_expect_code 1 git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_missing text2 &&
+               p4 fstat -T action text2 2>&1 | grep "no such file"
+       )
+'
+
+test_expect_success 'cleanup rename after submit cancel' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git mv text text2 &&
+               git commit -m text2 &&
+               git config git-p4.detectRenames true &&
+               git diff-tree -r -M HEAD | grep text2 | grep R100 &&
+               echo n | test_expect_code 1 git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_missing text2 &&
+               p4 fstat -T action text2 2>&1 | grep "no such file"
+               test_path_is_file text &&
+               ! p4 fstat -T action text
+       )
+'
+
+test_expect_success 'cleanup chmod after submit cancel' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               chmod u+x text &&
+               chmod u-x text+x &&
+               git add text text+x &&
+               git commit -m "chmod texts" &&
+               echo n | test_expect_code 1 git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file text &&
+               ! p4 fstat -T action text &&
+               stat --format=%A text | egrep ^-r-- &&
+               test_path_is_file text+x &&
+               ! p4 fstat -T action text+x &&
+               stat --format=%A text+x | egrep ^-r-x
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
index 9bc57d27e961720c98f75f95ab3186723e77f88b..8889ba5104cd3f7c783a179d45816d3a82685ef0 100644 (file)
@@ -144,11 +144,22 @@ test_pause () {
 
 test_commit () {
        notick= &&
-       if test "z$1" = "z--notick"
-       then
-               notick=yes
+       signoff= &&
+       while test $# != 0
+       do
+               case "$1" in
+               --notick)
+                       notick=yes
+                       ;;
+               --signoff)
+                       signoff="$1"
+                       ;;
+               *)
+                       break
+                       ;;
+               esac
                shift
-       fi &&
+       done &&
        file=${2:-"$1.t"} &&
        echo "${3-$1}" > "$file" &&
        git add "$file" &&
@@ -156,7 +167,7 @@ test_commit () {
        then
                test_tick
        fi &&
-       git commit -m "$1" &&
+       git commit $signoff -m "$1" &&
        git tag "$1"
 }
 
diff --git a/test-string-list.c b/test-string-list.c
new file mode 100644 (file)
index 0000000..4693295
--- /dev/null
@@ -0,0 +1,123 @@
+#include "cache.h"
+#include "string-list.h"
+
+/*
+ * Parse an argument into a string list.  arg should either be a
+ * ':'-separated list of strings, or "-" to indicate an empty string
+ * list (as opposed to "", which indicates a string list containing a
+ * single empty string).  list->strdup_strings must be set.
+ */
+static void parse_string_list(struct string_list *list, const char *arg)
+{
+       if (!strcmp(arg, "-"))
+               return;
+
+       (void)string_list_split(list, arg, ':', -1);
+}
+
+static void write_list(const struct string_list *list)
+{
+       int i;
+       for (i = 0; i < list->nr; i++)
+               printf("[%d]: \"%s\"\n", i, list->items[i].string);
+}
+
+static void write_list_compact(const struct string_list *list)
+{
+       int i;
+       if (!list->nr)
+               printf("-\n");
+       else {
+               printf("%s", list->items[0].string);
+               for (i = 1; i < list->nr; i++)
+                       printf(":%s", list->items[i].string);
+               printf("\n");
+       }
+}
+
+static int prefix_cb(struct string_list_item *item, void *cb_data)
+{
+       const char *prefix = (const char *)cb_data;
+       return !prefixcmp(item->string, prefix);
+}
+
+int main(int argc, char **argv)
+{
+       if (argc == 5 && !strcmp(argv[1], "split")) {
+               struct string_list list = STRING_LIST_INIT_DUP;
+               int i;
+               const char *s = argv[2];
+               int delim = *argv[3];
+               int maxsplit = atoi(argv[4]);
+
+               i = string_list_split(&list, s, delim, maxsplit);
+               printf("%d\n", i);
+               write_list(&list);
+               string_list_clear(&list, 0);
+               return 0;
+       }
+
+       if (argc == 5 && !strcmp(argv[1], "split_in_place")) {
+               struct string_list list = STRING_LIST_INIT_NODUP;
+               int i;
+               char *s = xstrdup(argv[2]);
+               int delim = *argv[3];
+               int maxsplit = atoi(argv[4]);
+
+               i = string_list_split_in_place(&list, s, delim, maxsplit);
+               printf("%d\n", i);
+               write_list(&list);
+               string_list_clear(&list, 0);
+               free(s);
+               return 0;
+       }
+
+       if (argc == 4 && !strcmp(argv[1], "filter")) {
+               /*
+                * Retain only the items that have the specified prefix.
+                * Arguments: list|- prefix
+                */
+               struct string_list list = STRING_LIST_INIT_DUP;
+               const char *prefix = argv[3];
+
+               parse_string_list(&list, argv[2]);
+               filter_string_list(&list, 0, prefix_cb, (void *)prefix);
+               write_list_compact(&list);
+               string_list_clear(&list, 0);
+               return 0;
+       }
+
+       if (argc == 3 && !strcmp(argv[1], "remove_duplicates")) {
+               struct string_list list = STRING_LIST_INIT_DUP;
+
+               parse_string_list(&list, argv[2]);
+               string_list_remove_duplicates(&list, 0);
+               write_list_compact(&list);
+               string_list_clear(&list, 0);
+               return 0;
+       }
+
+       if (argc == 4 && !strcmp(argv[1], "longest_prefix")) {
+               /* arguments: <colon-separated-prefixes>|- <string> */
+               struct string_list prefixes = STRING_LIST_INIT_DUP;
+               int retval;
+               const char *prefix_string = argv[2];
+               const char *string = argv[3];
+               const char *match;
+
+               parse_string_list(&prefixes, prefix_string);
+               match = string_list_longest_prefix(&prefixes, string);
+               if (match) {
+                       printf("%s\n", match);
+                       retval = 0;
+               }
+               else
+                       retval = 1;
+               string_list_clear(&prefixes, 0);
+               return retval;
+       }
+
+       fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
+               argv[1] ? argv[1] : "(there was none)");
+       return 1;
+}
index 1811b500d92b1120a01d0ac09f86c0218f3d163b..9932f402dfee2605dbb498b120813aebaa3961f8 100644 (file)
@@ -518,8 +518,7 @@ static int fetch_refs_via_pack(struct transport *transport,
                               int nr_heads, struct ref **to_fetch)
 {
        struct git_transport_data *data = transport->data;
-       char **heads = xmalloc(nr_heads * sizeof(*heads));
-       char **origh = xmalloc(nr_heads * sizeof(*origh));
+       struct string_list sought = STRING_LIST_INIT_DUP;
        const struct ref *refs;
        char *dest = xstrdup(transport->url);
        struct fetch_pack_args args;
@@ -538,7 +537,7 @@ static int fetch_refs_via_pack(struct transport *transport,
        args.depth = data->options.depth;
 
        for (i = 0; i < nr_heads; i++)
-               origh[i] = heads[i] = xstrdup(to_fetch[i]->name);
+               string_list_append(&sought, to_fetch[i]->name);
 
        if (!data->got_remote_heads) {
                connect_setup(transport, 0, 0);
@@ -548,7 +547,7 @@ static int fetch_refs_via_pack(struct transport *transport,
 
        refs = fetch_pack(&args, data->fd, data->conn,
                          refs_tmp ? refs_tmp : transport->remote_refs,
-                         dest, nr_heads, heads, &transport->pack_lockfile);
+                         dest, &sought, &transport->pack_lockfile);
        close(data->fd[0]);
        close(data->fd[1]);
        if (finish_connect(data->conn))
@@ -558,10 +557,7 @@ static int fetch_refs_via_pack(struct transport *transport,
 
        free_refs(refs_tmp);
 
-       for (i = 0; i < nr_heads; i++)
-               free(origh[i]);
-       free(origh);
-       free(heads);
+       string_list_clear(&sought, 0);
        free(dest);
        return (refs ? 0 : -1);
 }
index b866c126e695810131cdab537b8b994c0c32e14e..3b21c4abe6d8890f99fe0169792605c98644fafd 100644 (file)
@@ -106,6 +106,7 @@ struct transport {
 #define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
 
 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
+#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
 
 /* Returns a transport suitable for the url */
 struct transport *transport_get(struct remote *, const char *);