diff options
51 files changed, 1694 insertions, 382 deletions
diff --git a/.gitignore b/.gitignore index 68fe464090..a188a82bb1 100644 --- a/.gitignore +++ b/.gitignore @@ -194,6 +194,7 @@ /test-run-command /test-sha1 /test-sigchain +/test-string-list /test-subprocess /test-svn-fe /common-cmds.h diff --git a/Documentation/RelNotes/1.7.12.1.txt b/Documentation/RelNotes/1.7.12.1.txt index 671e3d2eb5..b8f04af19f 100644 --- a/Documentation/RelNotes/1.7.12.1.txt +++ b/Documentation/RelNotes/1.7.12.1.txt @@ -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. diff --git a/Documentation/RelNotes/1.8.0.txt b/Documentation/RelNotes/1.8.0.txt index 90c4f4795f..c7bb503164 100644 --- a/Documentation/RelNotes/1.8.0.txt +++ b/Documentation/RelNotes/1.8.0.txt @@ -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 diff --git a/Documentation/config.txt b/Documentation/config.txt index 6416cae511..11f320b962 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -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 diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 4622297ec9..9594ac8e9d 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -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[] diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index 2620d28b4b..6603a7ab73 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -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` ~~~~~~~~ diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index 15e7ac80c0..e2301f5c01 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -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: diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index c872b883ba..db55a4e0bb 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -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`). diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 04c7346e3e..6d43f56279 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -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 diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt index 8228f33e3f..beff6229c8 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -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. diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index 01d8417316..afeb4cdf16 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -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. diff --git a/Documentation/git.txt b/Documentation/git.txt index 34d8a1bbdf..6710cb0a41 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -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] diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index 9d893369a0..5325c5a7d5 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -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 diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt index dee050567e..f1cb6f3be6 100644 --- a/Documentation/gittutorial.txt +++ b/Documentation/gittutorial.txt @@ -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 ---------------------------- diff --git a/Documentation/technical/api-string-list.txt b/Documentation/technical/api-string-list.txt index 5a0c14fceb..155ac8cb10 100644 --- a/Documentation/technical/api-string-list.txt +++ b/Documentation/technical/api-string-list.txt @@ -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 --------------- diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt index 49cdc571cd..d51e20f352 100644 --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/technical/pack-protocol.txt @@ -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 diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 03d95dc290..85651b57ae 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -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 @@ -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 @@ -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; } diff --git a/builtin/blame.c b/builtin/blame.c index 0e102bf2c2..c27ef21c23 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -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; } diff --git a/builtin/check-attr.c b/builtin/check-attr.c index e1ff575daa..075d01d30c 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -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 }; diff --git a/builtin/commit.c b/builtin/commit.c index 778cf16fde..4d50484837 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -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) diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index fdda36f149..e6443986b8 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -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; diff --git a/builtin/fetch.c b/builtin/fetch.c index d0dcc8888f..4b5a89839b 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -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); } diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 1b43329ed7..be800e09bd 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -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= diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump index a33674e47a..dc90cd6379 100755 --- a/contrib/git-jump/git-jump +++ b/contrib/git-jump/git-jump @@ -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; @@ -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'); diff --git a/fetch-pack.h b/fetch-pack.h index 7c2069c972..cb148719bf 100644 --- a/fetch-pack.h +++ b/fetch-pack.h @@ -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 @@ -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 @@ -30,10 +30,15 @@ #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 @@ -844,6 +844,9 @@ class P4RollBack(Command): 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 @@ class P4Submit(Command, P4UserMap): # 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 @@ class P4Submit(Command, P4UserMap): 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 @@ class P4Submit(Command, P4UserMap): 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 @@ class P4Submit(Command, P4UserMap): 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 @@ class P4Submit(Command, P4UserMap): 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 @@ class P4Submit(Command, P4UserMap): 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 @@ class P4Submit(Command, P4UserMap): 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 @@ class P4Submit(Command, P4UserMap): 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 @@ class P4Submit(Command, P4UserMap): 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 @@ class P4Submit(Command, P4UserMap): 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 @@ class P4Submit(Command, P4UserMap): 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 @@ class P4Submit(Command, P4UserMap): 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 @@ class P4Submit(Command, P4UserMap): 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 @@ class P4Submit(Command, P4UserMap): 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 @@ class P4Submit(Command, P4UserMap): 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")) diff --git a/http-walker.c b/http-walker.c index 51a906e9e3..1516c5eb29 100644 --- a/http-walker.c +++ b/http-walker.c @@ -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; diff --git a/sequencer.c b/sequencer.c index ce8a59f576..e3723d2095 100644 --- a/sequencer.c +++ b/sequencer.c @@ -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); +} diff --git a/sequencer.h b/sequencer.h index 99eb7fa090..9d57d57524 100644 --- a/sequencer.h +++ b/sequencer.h @@ -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 diff --git a/string-list.c b/string-list.c index d9810aba42..c54b816244 100644 --- a/string-list.c +++ b/string-list.c @@ -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; + } + } +} diff --git a/string-list.h b/string-list.h index 0684cb73bf..5efd07b44e 100644 --- a/string-list.h +++ b/string-list.h @@ -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 */ diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh index d748c36df5..7061dce7e5 100644 --- a/t/lib-git-p4.sh +++ b/t/lib-git-p4.sh @@ -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") diff --git a/t/perf/.gitignore b/t/perf/.gitignore index 50f5cc1ed9..982eb8e3a9 100644 --- a/t/perf/.gitignore +++ b/t/perf/.gitignore @@ -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 index 0000000000..41c8826a74 --- /dev/null +++ b/t/t0063-string-list.sh @@ -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 diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh index 0c81b3c427..c82f7210c4 100755 --- a/t/t3507-cherry-pick-conflict.sh +++ b/t/t3507-cherry-pick-conflict.sh @@ -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 diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index f4e6450d6a..b5fb527b2e 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -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 && diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index e80a2af348..6322e8ade8 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -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 diff --git a/t/t8004-blame-with-conflicts.sh b/t/t8004-blame-with-conflicts.sh index ba19ac127e..9c353ab222 100755 --- a/t/t8004-blame-with-conflicts.sh +++ b/t/t8004-blame-with-conflicts.sh @@ -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 ' diff --git a/t/t9805-git-p4-skip-submit-edit.sh b/t/t9805-git-p4-skip-submit-edit.sh index fb3c8ec12c..ff2cc79701 100755 --- a/t/t9805-git-p4-skip-submit-edit.sh +++ b/t/t9805-git-p4-skip-submit-edit.sh @@ -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 ) diff --git a/t/t9807-git-p4-submit.sh b/t/t9807-git-p4-submit.sh index 9394fd4e9b..0ae048f29f 100755 --- a/t/t9807-git-p4-submit.sh +++ b/t/t9807-git-p4-submit.sh @@ -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 ) ' diff --git a/t/t9810-git-p4-rcs.sh b/t/t9810-git-p4-rcs.sh index e9daa9c4f6..fe30ad881f 100755 --- a/t/t9810-git-p4-rcs.sh +++ b/t/t9810-git-p4-rcs.sh @@ -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 index 0000000000..d2b7b3d98d --- /dev/null +++ b/t/t9815-git-p4-submit-fail.sh @@ -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 diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 9bc57d27e9..8889ba5104 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -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 index 0000000000..4693295a98 --- /dev/null +++ b/test-string-list.c @@ -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; +} diff --git a/transport.c b/transport.c index 1811b500d9..9932f402df 100644 --- a/transport.c +++ b/transport.c @@ -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); } diff --git a/transport.h b/transport.h index b866c126e6..3b21c4abe6 100644 --- a/transport.h +++ b/transport.h @@ -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 *); |