diff options
101 files changed, 2034 insertions, 692 deletions
diff --git a/Documentation/RelNotes/2.10.5.txt b/Documentation/RelNotes/2.10.5.txt new file mode 100644 index 0000000000..a498fd6fdc --- /dev/null +++ b/Documentation/RelNotes/2.10.5.txt @@ -0,0 +1,17 @@ +Git v2.10.5 Release Notes +========================= + +Fixes since v2.10.4 +------------------- + + * "git cvsserver" no longer is invoked by "git daemon" by default, + as it is old and largely unmaintained. + + * Various Perl scripts did not use safe_pipe_capture() instead of + backticks, leaving them susceptible to end-user input. They have + been corrected. + +Credits go to joernchen <joernchen@phenoelit.de> for finding the +unsafe constructs in "git cvsserver", and to Jeff King at GitHub for +finding and fixing instances of the same issue in other scripts. + diff --git a/Documentation/RelNotes/2.11.4.txt b/Documentation/RelNotes/2.11.4.txt new file mode 100644 index 0000000000..ad4da8eb09 --- /dev/null +++ b/Documentation/RelNotes/2.11.4.txt @@ -0,0 +1,17 @@ +Git v2.11.4 Release Notes +========================= + +Fixes since v2.11.3 +------------------- + + * "git cvsserver" no longer is invoked by "git daemon" by default, + as it is old and largely unmaintained. + + * Various Perl scripts did not use safe_pipe_capture() instead of + backticks, leaving them susceptible to end-user input. They have + been corrected. + +Credits go to joernchen <joernchen@phenoelit.de> for finding the +unsafe constructs in "git cvsserver", and to Jeff King at GitHub for +finding and fixing instances of the same issue in other scripts. + diff --git a/Documentation/RelNotes/2.12.5.txt b/Documentation/RelNotes/2.12.5.txt new file mode 100644 index 0000000000..8fa73cfce7 --- /dev/null +++ b/Documentation/RelNotes/2.12.5.txt @@ -0,0 +1,17 @@ +Git v2.12.5 Release Notes +========================= + +Fixes since v2.12.4 +------------------- + + * "git cvsserver" no longer is invoked by "git daemon" by default, + as it is old and largely unmaintained. + + * Various Perl scripts did not use safe_pipe_capture() instead of + backticks, leaving them susceptible to end-user input. They have + been corrected. + +Credits go to joernchen <joernchen@phenoelit.de> for finding the +unsafe constructs in "git cvsserver", and to Jeff King at GitHub for +finding and fixing instances of the same issue in other scripts. + diff --git a/Documentation/RelNotes/2.13.6.txt b/Documentation/RelNotes/2.13.6.txt new file mode 100644 index 0000000000..afcae9c808 --- /dev/null +++ b/Documentation/RelNotes/2.13.6.txt @@ -0,0 +1,17 @@ +Git v2.13.6 Release Notes +========================= + +Fixes since v2.13.5 +------------------- + + * "git cvsserver" no longer is invoked by "git daemon" by default, + as it is old and largely unmaintained. + + * Various Perl scripts did not use safe_pipe_capture() instead of + backticks, leaving them susceptible to end-user input. They have + been corrected. + +Credits go to joernchen <joernchen@phenoelit.de> for finding the +unsafe constructs in "git cvsserver", and to Jeff King at GitHub for +finding and fixing instances of the same issue in other scripts. + diff --git a/Documentation/RelNotes/2.14.2.txt b/Documentation/RelNotes/2.14.2.txt index bcfe78f59d..bec9186ade 100644 --- a/Documentation/RelNotes/2.14.2.txt +++ b/Documentation/RelNotes/2.14.2.txt @@ -91,4 +91,15 @@ Fixes since v2.14.1 * "git archive" did not work well with pathspecs and the export-ignore attribute. + * "git cvsserver" no longer is invoked by "git daemon" by default, + as it is old and largely unmaintained. + + * Various Perl scripts did not use safe_pipe_capture() instead of + backticks, leaving them susceptible to end-user input. They have + been corrected. + Also contains various documentation updates and code clean-ups. + +Credits go to joernchen <joernchen@phenoelit.de> for finding the +unsafe constructs in "git cvsserver", and to Jeff King at GitHub for +finding and fixing instances of the same issue in other scripts. diff --git a/Documentation/RelNotes/2.15.0.txt b/Documentation/RelNotes/2.15.0.txt index 290cad5287..8a869e4ef1 100644 --- a/Documentation/RelNotes/2.15.0.txt +++ b/Documentation/RelNotes/2.15.0.txt @@ -84,6 +84,9 @@ UI, Workflows & Features used in a way similar to existing "--is-bare-repository" and friends. + * "git describe --match <pattern>" has been taught to play well with + the "--all" option. + Performance, Internal Implementation, Development Support etc. @@ -189,6 +192,16 @@ Performance, Internal Implementation, Development Support etc. expect failures under a limited stack situation. This has been fixed. + * Many codepaths have been updated to squelch -Wimplicit-fallthrough + warnings from Gcc 7 (which is a good code hygiene). + + * Add a helper for DLL loading in anticipation for its need in a + future topic RSN. + + * "git status --ignored", when noticing that a directory without any + tracked path is ignored, still enumerated all the ignored paths in + the directory, which is unnecessary. The codepath has been + optimized to avoid this overhead. Also contains various documentation updates and code clean-ups. @@ -359,6 +372,55 @@ Fixes since v2.14 alternate object stores overrun the end of the string. (merge f0f7bebef7 jk/info-alternates-fix later to maint). + * "git describe --match" learned to take multiple patterns in v2.13 + series, but the feature ignored the patterns after the first one + and did not work at all. This has been fixed. + (merge da769d2986 jk/describe-omit-some-refs later to maint). + + * "git filter-branch" cannot reproduce a history with a tag without + the tagger field, which only ancient versions of Git allowed to be + created. This has been corrected. + (merge b2c1ca6b4b ic/fix-filter-branch-to-handle-tag-without-tagger later to maint). + + * "git cat-file --textconv" started segfaulting recently, which + has been corrected. + (merge cc0ea7c9e5 jk/diff-blob later to maint). + + * The built-in pattern to detect the "function header" for HTML did + not match <H1>..<H6> elements without any attributes, which has + been fixed. + (merge 9c03caca2c ik/userdiff-html-h-element-fix later to maint). + + * "git mailinfo" was loose in decoding quoted printable and produced + garbage when the two letters after the equal sign are not + hexadecimal. This has been fixed. + (merge c8cf423eab rs/mailinfo-qp-decode-fix later to maint). + + * The machinery to create xdelta used in pack files received the + sizes of the data in size_t, but lost the higher bits of them by + storing them in "unsigned int" during the computation, which is + fixed. + + * The delta format used in the packfile cannot reference data at + offset larger than what can be expressed in 4-byte, but the + generator for the data failed to make sure the offset does not + overflow. This has been corrected. + + * The documentation for '-X<option>' for merges was misleadingly + written to suggest that "-s theirs" exists, which is not the case. + (merge c25d98b2a7 jc/merge-x-theirs-docfix later to maint). + + * "git fast-export" with -M/-C option issued "copy" instruction on a + path that is simultaneously modified, which was incorrect. + (merge b3e8ca89cf jt/fast-export-copy-modify-fix later to maint). + + * Many codepaths have been updated to squelch -Wsign-compare + warnings. + (merge 071bcaab64 rj/no-sign-compare later to maint). + + * Memory leaks in various codepaths have been plugged. + (merge 4d01a7fa65 ma/leakplugs later to maint). + * Other minor doc, test and build updates and code cleanups. (merge f094b89a4d ma/parse-maybe-bool later to maint). (merge 39b00fa4d4 jk/drop-sha1-entry-pos later to maint). @@ -376,3 +438,6 @@ Fixes since v2.14 (merge f7a32dd97f kd/doc-for-each-ref later to maint). (merge be94568bc7 ez/doc-duplicated-words-fix later to maint). (merge 01e4be6c3d ks/test-readme-phrasofix later to maint). + (merge 217bb56d4f hn/typofix later to maint). + (merge c08fd6388c jk/doc-read-tree-table-asciidoctor-fix later to maint). + (merge c3342b362e ks/doc-use-camelcase-for-config-name later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index dc4e3f58a2..1ac0ae6adb 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -3085,10 +3085,14 @@ submodule.<name>.url:: See linkgit:git-submodule[1] and linkgit:gitmodules[5] for details. submodule.<name>.update:: - The default update procedure for a submodule. This variable - is populated by `git submodule init` from the - linkgit:gitmodules[5] file. See description of 'update' - command in linkgit:git-submodule[1]. + The method by which a submodule is updated by 'git submodule update', + which is the only affected command, others such as + 'git checkout --recurse-submodules' are unaffected. It exists for + historical reasons, when 'git submodule' was the only command to + interact with submodules; settings like `submodule.active` + and `pull.rebase` are more specific. It is populated by + `git submodule init` from the linkgit:gitmodules[5] file. + See description of 'update' command in linkgit:git-submodule[1]. submodule.<name>.branch:: The remote branch name for a submodule, used by `git submodule diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index e292737b9c..fe029ac6fc 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -18,6 +18,7 @@ SYNOPSIS 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>] 'git branch' --unset-upstream [<branchname>] 'git branch' (-m | -M) [<oldbranch>] <newbranch> +'git branch' (-c | -C) [<oldbranch>] <newbranch> 'git branch' (-d | -D) [-r] <branchname>... 'git branch' --edit-description [<branchname>] @@ -64,6 +65,10 @@ If <oldbranch> had a corresponding reflog, it is renamed to match renaming. If <newbranch> exists, -M must be used to force the rename to happen. +The `-c` and `-C` options have the exact same semantics as `-m` and +`-M`, except instead of the branch being renamed it along with its +config and reflog will be copied to a new name. + With a `-d` or `-D` option, `<branchname>` will be deleted. You may specify more than one branch for deletion. If the branch currently has a reflog then the reflog will also be deleted. @@ -92,10 +97,10 @@ OPTIONS all changes made to the branch ref, enabling use of date based sha1 expressions such as "<branchname>@\{yesterday}". Note that in non-bare repositories, reflogs are usually - enabled by default by the `core.logallrefupdates` config option. + enabled by default by the `core.logAllRefUpdates` config option. The negated form `--no-create-reflog` only overrides an earlier `--create-reflog`, but currently does not negate the setting of - `core.logallrefupdates`. + `core.logAllRefUpdates`. -f:: --force:: @@ -104,7 +109,7 @@ OPTIONS In combination with `-d` (or `--delete`), allow deleting the branch irrespective of its merged status. In combination with `-m` (or `--move`), allow renaming the branch even if the new - branch name already exists. + branch name already exists, the same applies for `-c` (or `--copy`). -m:: --move:: @@ -113,6 +118,13 @@ OPTIONS -M:: Shortcut for `--move --force`. +-c:: +--copy:: + Copy a branch and the corresponding reflog. + +-C:: + Shortcut for `--copy --force`. + --color[=<when>]:: Color branches to highlight current, local, and remote-tracking branches. diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index 26f19d3b07..c924c945ba 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -87,19 +87,23 @@ OPTIONS --match <pattern>:: Only consider tags matching the given `glob(7)` pattern, - excluding the "refs/tags/" prefix. This can be used to avoid - leaking private tags from the repository. If given multiple times, a - list of patterns will be accumulated, and tags matching any of the - patterns will be considered. Use `--no-match` to clear and reset the - list of patterns. + excluding the "refs/tags/" prefix. If used with `--all`, it also + considers local branches and remote-tracking references matching the + pattern, excluding respectively "refs/heads/" and "refs/remotes/" + prefix; references of other types are never considered. If given + multiple times, a list of patterns will be accumulated, and tags + matching any of the patterns will be considered. Use `--no-match` to + clear and reset the list of patterns. --exclude <pattern>:: Do not consider tags matching the given `glob(7)` pattern, excluding - the "refs/tags/" prefix. This can be used to narrow the tag space and - find only tags matching some meaningful criteria. If given multiple - times, a list of patterns will be accumulated and tags matching any - of the patterns will be excluded. When combined with --match a tag will - be considered when it matches at least one --match pattern and does not + the "refs/tags/" prefix. If used with `--all`, it also does not consider + local branches and remote-tracking references matching the pattern, + excluding respectively "refs/heads/" and "refs/remotes/" prefix; + references of other types are never considered. If given multiple times, + a list of patterns will be accumulated and tags matching any of the + patterns will be excluded. When combined with --match a tag will be + considered when it matches at least one --match pattern and does not match any of the --exclude patterns. Use `--no-exclude` to clear and reset the list of patterns. diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index 9e5169aa64..bebdcdec5a 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -14,7 +14,7 @@ SYNOPSIS [--commit-filter <command>] [--tag-name-filter <command>] [--subdirectory-filter <directory>] [--prune-empty] [--original <namespace>] [-d <directory>] [-f | --force] - [--] [<rev-list options>...] + [--state-branch <branch>] [--] [<rev-list options>...] DESCRIPTION ----------- @@ -198,6 +198,12 @@ to other tags will be rewritten to point to the underlying commit. directory or when there are already refs starting with 'refs/original/', unless forced. +--state-branch <branch>:: + This option will cause the mapping from old to new objects to + be loaded from named branch upon startup and saved as a new + commit to that branch upon exit, enabling incremental of large + trees. If '<branch>' does not exist it will be created. + <rev-list options>...:: Arguments for 'git rev-list'. All positive refs included by these options are rewritten. You may also specify options diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 02576d8c0a..72bd809fb8 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -179,6 +179,7 @@ Here are the "carry forward" rules, where "I" denotes the index, "clean" means that index and work tree coincide, and "exists"/"nothing" refer to the presence of a path in the specified commit: +.... I H M Result ------------------------------------------------------- 0 nothing nothing nothing (does not happen) @@ -217,6 +218,7 @@ refer to the presence of a path in the specified commit: 19 no no yes exists exists keep index 20 yes yes no exists exists use M 21 no yes no exists exists fail +.... In all "keep index" cases, the index entry stays as in the original index file. If the entry is not up to date, diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 6805a74aec..3cedfb0fd2 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -430,13 +430,15 @@ without an explicit `--interactive`. --autosquash:: --no-autosquash:: When the commit log message begins with "squash! ..." (or - "fixup! ..."), and there is a commit whose title begins with - the same ..., automatically modify the todo list of rebase -i - so that the commit marked for squashing comes right after the - commit to be modified, and change the action of the moved - commit from `pick` to `squash` (or `fixup`). Ignores subsequent - "fixup! " or "squash! " after the first, in case you referred to an - earlier fixup/squash with `git commit --fixup/--squash`. + "fixup! ..."), and there is already a commit in the todo list that + matches the same `...`, automatically modify the todo list of rebase + -i so that the commit marked for squashing comes right after the + commit to be modified, and change the action of the moved commit + from `pick` to `squash` (or `fixup`). A commit matches the `...` if + the commit subject matches, or if the `...` refers to the commit's + hash. As a fall-back, partial matches of the commit subject work, + too. The recommended way to create fixup/squash commits is by using + the `--fixup`/`--squash` options of linkgit:git-commit[1]. + This option is only valid when the `--interactive` option is used. + diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt index 2e30a3e42d..54cf2560be 100644 --- a/Documentation/git-shell.txt +++ b/Documentation/git-shell.txt @@ -79,6 +79,22 @@ EOF $ chmod +x $HOME/git-shell-commands/no-interactive-login ---------------- +To enable git-cvsserver access (which should generally have the +`no-interactive-login` example above as a prerequisite, as creating +the git-shell-commands directory allows interactive logins): + +---------------- +$ cat >$HOME/git-shell-commands/cvs <<\EOF +if ! test $# = 1 && test "$1" = "server" +then + echo >&2 "git-cvsserver only handles \"server\"" + exit 1 +fi +exec git cvsserver server +EOF +$ chmod +x $HOME/git-shell-commands/cvs +---------------- + SEE ALSO -------- ssh(1), diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 543fb425ee..95e9f391d8 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -174,7 +174,7 @@ This option is only applicable when listing tags without annotation lines. `core.logAllRefUpdates` in linkgit:git-config[1]. The negated form `--no-create-reflog` only overrides an earlier `--create-reflog`, but currently does not negate the setting of - `core.logallrefupdates`. + `core.logAllRefUpdates`. <tagname>:: The name of the tag to create, delete, or describe. diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 1bb4f92d4d..5d3f45560e 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -127,11 +127,10 @@ help message found in the commented portion of the commit template. commit-msg ~~~~~~~~~~ -This hook is invoked by 'git commit', and can be bypassed -with the `--no-verify` option. It takes a single parameter, the -name of the file that holds the proposed commit log message. -Exiting with a non-zero status causes the 'git commit' to -abort. +This hook is invoked by 'git commit' and 'git merge', and can be +bypassed with the `--no-verify` option. It takes a single parameter, +the name of the file that holds the proposed commit log message. +Exiting with a non-zero status causes the command to abort. The hook is allowed to edit the message file in place, and can be used to normalize the message into some project standard format. It diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt index 2eb92b9327..a09d597463 100644 --- a/Documentation/merge-strategies.txt +++ b/Documentation/merge-strategies.txt @@ -39,7 +39,8 @@ even look at what the other tree contains at all. It discards everything the other tree did, declaring 'our' history contains all that happened in it. theirs;; - This is the opposite of 'ours'. + This is the opposite of 'ours'; note that, unlike 'ours', there is + no 'theirs' merge stragegy to confuse this merge option with. patience;; With this option, 'merge-recursive' spends a little extra time @@ -2920,6 +2920,7 @@ static int apply_one_fragment(struct apply_state *state, if (plen && (ws_rule & WS_BLANK_AT_EOF) && ws_blank_line(patch + 1, plen, ws_rule)) is_blank_context = 1; + /* fallthrough */ case '-': memcpy(old, patch + 1, plen); add_line_info(&preimage, old, plen, @@ -2927,7 +2928,7 @@ static int apply_one_fragment(struct apply_state *state, old += plen; if (first == '-') break; - /* Fall-through for ' ' */ + /* fallthrough */ case '+': /* --no-add does not add new lines */ if (first == '+' && state->no_add) @@ -826,7 +826,8 @@ static int check_ancestors(const char *prefix) /* Clean up objects used, as they will be reused. */ clear_commit_marks_for_object_array(&pending_copy, ALL_REV_FLAGS); - free(pending_copy.objects); + + object_array_clear(&pending_copy); return res; } @@ -191,9 +191,8 @@ int validate_new_branchname(const char *name, struct strbuf *ref, if (!attr_only) { const char *head; - struct object_id oid; - head = resolve_ref_unsafe("HEAD", 0, oid.hash, NULL); + head = resolve_ref_unsafe("HEAD", 0, NULL, NULL); if (!is_bare_repository() && head && !strcmp(head, ref->buf)) die(_("Cannot force update the current branch.")); } diff --git a/builtin/branch.c b/builtin/branch.c index 355f9ef5da..b998e16d0c 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -28,6 +28,7 @@ static const char * const builtin_branch_usage[] = { N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"), N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."), N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"), + N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"), N_("git branch [<options>] [-r | -a] [--points-at]"), N_("git branch [<options>] [-r | -a] [--format]"), NULL @@ -456,15 +457,19 @@ static void reject_rebase_or_bisect_branch(const char *target) free_worktrees(worktrees); } -static void rename_branch(const char *oldname, const char *newname, int force) +static void copy_or_rename_branch(const char *oldname, const char *newname, int copy, int force) { struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT; struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT; int recovery = 0; int clobber_head_ok; - if (!oldname) - die(_("cannot rename the current branch while not on any.")); + if (!oldname) { + if (copy) + die(_("cannot copy the current branch while not on any.")); + else + die(_("cannot rename the current branch while not on any.")); + } if (strbuf_check_branch_ref(&oldref, oldname)) { /* @@ -487,16 +492,29 @@ static void rename_branch(const char *oldname, const char *newname, int force) reject_rebase_or_bisect_branch(oldref.buf); - strbuf_addf(&logmsg, "Branch: renamed %s to %s", - oldref.buf, newref.buf); + if (copy) + strbuf_addf(&logmsg, "Branch: copied %s to %s", + oldref.buf, newref.buf); + else + strbuf_addf(&logmsg, "Branch: renamed %s to %s", + oldref.buf, newref.buf); - if (rename_ref(oldref.buf, newref.buf, logmsg.buf)) + if (!copy && rename_ref(oldref.buf, newref.buf, logmsg.buf)) die(_("Branch rename failed")); + if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf)) + die(_("Branch copy failed")); - if (recovery) - warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11); + if (recovery) { + if (copy) + warning(_("Copied a misnamed branch '%s' away"), + oldref.buf + 11); + else + warning(_("Renamed a misnamed branch '%s' away"), + oldref.buf + 11); + } - if (replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf)) + if (!copy && + replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf)) die(_("Branch renamed to %s, but HEAD is not updated!"), newname); strbuf_release(&logmsg); @@ -505,8 +523,10 @@ static void rename_branch(const char *oldname, const char *newname, int force) strbuf_release(&oldref); strbuf_addf(&newsection, "branch.%s", newref.buf + 11); strbuf_release(&newref); - if (git_config_rename_section(oldsection.buf, newsection.buf) < 0) + if (!copy && git_config_rename_section(oldsection.buf, newsection.buf) < 0) die(_("Branch is renamed, but update of config-file failed")); + if (copy && strcmp(oldname, newname) && git_config_copy_section(oldsection.buf, newsection.buf) < 0) + die(_("Branch is copied, but update of config-file failed")); strbuf_release(&oldsection); strbuf_release(&newsection); } @@ -544,7 +564,7 @@ static int edit_branch_description(const char *branch_name) int cmd_branch(int argc, const char **argv, const char *prefix) { - int delete = 0, rename = 0, force = 0, list = 0; + int delete = 0, rename = 0, copy = 0, force = 0, list = 0; int reflog = 0, edit_description = 0; int quiet = 0, unset_upstream = 0; const char *new_upstream = NULL; @@ -581,6 +601,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2), OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1), OPT_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2), + OPT_BIT('c', "copy", ©, N_("copy a branch and its reflog"), 1), + OPT_BIT('C', NULL, ©, N_("copy a branch, even if target exists"), 2), OPT_BOOL(0, "list", &list, N_("list branch names")), OPT_BOOL('l', "create-reflog", &reflog, N_("create the branch's reflog")), OPT_BOOL(0, "edit-description", &edit_description, @@ -624,14 +646,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, 0); - if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0) + if (!delete && !rename && !copy && !edit_description && !new_upstream && !unset_upstream && argc == 0) list = 1; if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr || filter.no_commit) list = 1; - if (!!delete + !!rename + !!new_upstream + + if (!!delete + !!rename + !!copy + !!new_upstream + list + unset_upstream > 1) usage_with_options(builtin_branch_usage, options); @@ -649,6 +671,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (force) { delete *= 2; rename *= 2; + copy *= 2; } if (delete) { @@ -703,13 +726,22 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (edit_branch_description(branch_name)) return 1; + } else if (copy) { + if (!argc) + die(_("branch name required")); + else if (argc == 1) + copy_or_rename_branch(head, argv[0], 1, copy > 1); + else if (argc == 2) + copy_or_rename_branch(argv[0], argv[1], 1, copy > 1); + else + die(_("too many branches for a copy operation")); } else if (rename) { if (!argc) die(_("branch name required")); else if (argc == 1) - rename_branch(head, argv[0], rename > 1); + copy_or_rename_branch(head, argv[0], 0, rename > 1); else if (argc == 2) - rename_branch(argv[0], argv[1], rename > 1); + copy_or_rename_branch(argv[0], argv[1], 0, rename > 1); else die(_("too many branches for a rename operation")); } else if (new_upstream) { diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 4ccbfaac31..f5fa4fd75a 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -97,7 +97,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, return !has_object_file(&oid); case 'w': - if (!path[0]) + if (!path) die("git cat-file --filters %s: <object> must be " "<sha1:path>", obj_name); @@ -107,12 +107,13 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, break; case 'c': - if (!path[0]) + if (!path) die("git cat-file --textconv %s: <object> must be <sha1:path>", obj_name); if (textconv_object(path, obj_context.mode, &oid, 1, &buf, &size)) break; + /* else fallthrough */ case 'p': type = sha1_object_info(oid.hash, NULL); diff --git a/builtin/checkout.c b/builtin/checkout.c index 5c202b7af5..3345a0d16f 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -436,6 +436,7 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, * update paths in the work tree, and we cannot revert * them. */ + /* fallthrough */ case 0: return 0; default: @@ -796,9 +797,14 @@ static void orphaned_commit_warning(struct commit *old, struct commit *new) for_each_ref(add_pending_uninteresting_ref, &revs); add_pending_oid(&revs, "HEAD", &new->object.oid, UNINTERESTING); + /* Save pending objects, so they can be cleaned up later. */ refs = revs.pending; revs.leak_pending = 1; + /* + * prepare_revision_walk (together with .leak_pending = 1) makes us + * the sole owner of the list of pending objects. + */ if (prepare_revision_walk(&revs)) die(_("internal error in revision walk")); if (!(old->object.flags & UNINTERESTING)) @@ -806,8 +812,10 @@ static void orphaned_commit_warning(struct commit *old, struct commit *new) else describe_detached_head(_("Previous HEAD position was"), old); + /* Clean up objects used, as they will be reused. */ clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS); - free(refs.objects); + + object_array_clear(&refs); } static int switch_branches(const struct checkout_opts *opts, diff --git a/builtin/commit.c b/builtin/commit.c index 58f9747c2f..0f8ddb6866 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -335,7 +335,7 @@ static void refresh_cache_or_die(int refresh_flags) static const char *prepare_index(int argc, const char **argv, const char *prefix, const struct commit *current_head, int is_status) { - struct string_list partial; + struct string_list partial = STRING_LIST_INIT_DUP; struct pathspec pathspec; int refresh_flags = REFRESH_QUIET; const char *ret; @@ -380,7 +380,8 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix warning(_("Failed to update main cache tree")); commit_style = COMMIT_NORMAL; - return get_lock_file_path(&index_lock); + ret = get_lock_file_path(&index_lock); + goto out; } /* @@ -403,7 +404,8 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) die(_("unable to write new_index file")); commit_style = COMMIT_NORMAL; - return get_lock_file_path(&index_lock); + ret = get_lock_file_path(&index_lock); + goto out; } /* @@ -429,7 +431,8 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix rollback_lock_file(&index_lock); } commit_style = COMMIT_AS_IS; - return get_index_file(); + ret = get_index_file(); + goto out; } /* @@ -460,7 +463,6 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix die(_("cannot do a partial commit during a cherry-pick.")); } - string_list_init(&partial, 1); if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec)) exit(1); @@ -490,6 +492,9 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix discard_cache(); ret = get_lock_file_path(&false_lock); read_cache_from(ret); +out: + string_list_clear(&partial, 0); + clear_pathspec(&pathspec); return ret; } @@ -1431,7 +1436,6 @@ static void print_summary(const char *prefix, const struct object_id *oid, struct rev_info rev; struct commit *commit; struct strbuf format = STRBUF_INIT; - struct object_id junk_oid; const char *head; struct pretty_print_context pctx = {0}; struct strbuf author_ident = STRBUF_INIT; @@ -1484,7 +1488,7 @@ static void print_summary(const char *prefix, const struct object_id *oid, rev.diffopt.break_opt = 0; diff_setup_done(&rev.diffopt); - head = resolve_ref_unsafe("HEAD", 0, junk_oid.hash, NULL); + head = resolve_ref_unsafe("HEAD", 0, NULL, NULL); if (!strcmp(head, "HEAD")) head = _("detached HEAD"); else diff --git a/builtin/describe.c b/builtin/describe.c index e77163e909..29075dbd0f 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -129,13 +129,24 @@ static void add_to_known_names(const char *path, static int get_name(const char *path, const struct object_id *oid, int flag, void *cb_data) { - int is_tag = starts_with(path, "refs/tags/"); + int is_tag = 0; struct object_id peeled; int is_annotated, prio; - - /* Reject anything outside refs/tags/ unless --all */ - if (!all && !is_tag) + const char *path_to_match = NULL; + + if (skip_prefix(path, "refs/tags/", &path_to_match)) { + is_tag = 1; + } else if (all) { + if ((exclude_patterns.nr || patterns.nr) && + !skip_prefix(path, "refs/heads/", &path_to_match) && + !skip_prefix(path, "refs/remotes/", &path_to_match)) { + /* Only accept reference of known type if there are match/exclude patterns */ + return 0; + } + } else { + /* Reject anything outside refs/tags/ unless --all */ return 0; + } /* * If we're given exclude patterns, first exclude any tag which match @@ -144,11 +155,8 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi if (exclude_patterns.nr) { struct string_list_item *item; - if (!is_tag) - return 0; - for_each_string_list_item(item, &exclude_patterns) { - if (!wildmatch(item->string, path + 10, 0)) + if (!wildmatch(item->string, path_to_match, 0)) return 0; } } @@ -158,18 +166,18 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi * pattern. */ if (patterns.nr) { + int found = 0; struct string_list_item *item; - if (!is_tag) - return 0; - for_each_string_list_item(item, &patterns) { - if (!wildmatch(item->string, path + 10, 0)) + if (!wildmatch(item->string, path_to_match, 0)) { + found = 1; break; + } + } - /* If we get here, no pattern matched. */ + if (!found) return 0; - } } /* Is it annotated? */ diff --git a/builtin/fast-export.c b/builtin/fast-export.c index d412c0a8f3..2fb60d6d48 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -344,6 +344,7 @@ static void show_filemodify(struct diff_queue_struct *q, struct diff_options *options, void *data) { int i; + struct string_list *changed = data; /* * Handle files below a directory first, in case they are all deleted @@ -359,20 +360,31 @@ static void show_filemodify(struct diff_queue_struct *q, case DIFF_STATUS_DELETED: printf("D "); print_path(spec->path); + string_list_insert(changed, spec->path); putchar('\n'); break; case DIFF_STATUS_COPIED: case DIFF_STATUS_RENAMED: - printf("%c ", q->queue[i]->status); - print_path(ospec->path); - putchar(' '); - print_path(spec->path); - putchar('\n'); - - if (!oidcmp(&ospec->oid, &spec->oid) && - ospec->mode == spec->mode) - break; + /* + * If a change in the file corresponding to ospec->path + * has been observed, we cannot trust its contents + * because the diff is calculated based on the prior + * contents, not the current contents. So, declare a + * copy or rename only if there was no change observed. + */ + if (!string_list_has_string(changed, ospec->path)) { + printf("%c ", q->queue[i]->status); + print_path(ospec->path); + putchar(' '); + print_path(spec->path); + string_list_insert(changed, spec->path); + putchar('\n'); + + if (!oidcmp(&ospec->oid, &spec->oid) && + ospec->mode == spec->mode) + break; + } /* fallthrough */ case DIFF_STATUS_TYPE_CHANGED: @@ -393,6 +405,7 @@ static void show_filemodify(struct diff_queue_struct *q, get_object_mark(object)); } print_path(spec->path); + string_list_insert(changed, spec->path); putchar('\n'); break; @@ -528,7 +541,8 @@ static void anonymize_ident_line(const char **beg, const char **end) *end = out->buf + out->len; } -static void handle_commit(struct commit *commit, struct rev_info *rev) +static void handle_commit(struct commit *commit, struct rev_info *rev, + struct string_list *paths_of_changed_objects) { int saved_output_format = rev->diffopt.output_format; const char *commit_buffer; @@ -615,6 +629,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) if (full_tree) printf("deleteall\n"); log_tree_diff_flush(rev); + string_list_clear(paths_of_changed_objects, 0); rev->diffopt.output_format = saved_output_format; printf("\n"); @@ -630,15 +645,15 @@ static void *anonymize_tag(const void *old, size_t *len) return strbuf_detach(&out, len); } -static void handle_tail(struct object_array *commits, struct rev_info *revs) +static void handle_tail(struct object_array *commits, struct rev_info *revs, + struct string_list *paths_of_changed_objects) { struct commit *commit; while (commits->nr) { - commit = (struct commit *)commits->objects[commits->nr - 1].item; + commit = (struct commit *)object_array_pop(commits); if (has_unshown_parent(commit)) return; - handle_commit(commit, revs); - commits->nr--; + handle_commit(commit, revs, paths_of_changed_objects); } } @@ -977,6 +992,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) char *export_filename = NULL, *import_filename = NULL; uint32_t lastimportid; struct string_list refspecs_list = STRING_LIST_INIT_NODUP; + struct string_list paths_of_changed_objects = STRING_LIST_INIT_DUP; struct option options[] = { OPT_INTEGER(0, "progress", &progress, N_("show progress after <n> objects")), @@ -1049,14 +1065,15 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); revs.diffopt.format_callback = show_filemodify; + revs.diffopt.format_callback_data = &paths_of_changed_objects; DIFF_OPT_SET(&revs.diffopt, RECURSIVE); while ((commit = get_revision(&revs))) { if (has_unshown_parent(commit)) { add_object_array(&commit->object, NULL, &commits); } else { - handle_commit(commit, &revs); - handle_tail(&commits, &revs); + handle_commit(commit, &revs, &paths_of_changed_objects); + handle_tail(&commits, &revs, &paths_of_changed_objects); } } diff --git a/builtin/fsck.c b/builtin/fsck.c index 1e4c471b41..56afe405b8 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -182,12 +182,7 @@ static int traverse_reachable(void) if (show_progress) progress = start_delayed_progress(_("Checking connectivity"), 0); while (pending.nr) { - struct object_array_entry *entry; - struct object *obj; - - entry = pending.objects + --pending.nr; - obj = entry->item; - result |= traverse_one_object(obj); + result |= traverse_one_object(object_array_pop(&pending)); display_progress(progress, ++nr); } stop_progress(&progress); diff --git a/builtin/log.c b/builtin/log.c index f8cccbc964..d81a09051e 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1660,10 +1660,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) check_head = 1; if (check_head) { - struct object_id oid; const char *ref, *v; ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, - oid.hash, NULL); + NULL, NULL); if (ref && skip_prefix(ref, "refs/heads/", &v)) branch_name = xstrdup(v); else diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index f721137eaf..5ee2c48ffb 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2563,8 +2563,8 @@ struct in_pack_object { }; struct in_pack { - int alloc; - int nr; + unsigned int alloc; + unsigned int nr; struct in_pack_object *array; }; diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c index c82b4dce68..f8519363a3 100644 --- a/builtin/rebase--helper.c +++ b/builtin/rebase--helper.c @@ -12,15 +12,30 @@ static const char * const builtin_rebase_helper_usage[] = { int cmd_rebase__helper(int argc, const char **argv, const char *prefix) { struct replay_opts opts = REPLAY_OPTS_INIT; + int keep_empty = 0; enum { - CONTINUE = 1, ABORT + CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_SHA1S, EXPAND_SHA1S, + CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH } command = 0; struct option options[] = { OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")), + OPT_BOOL(0, "keep-empty", &keep_empty, N_("keep empty commits")), OPT_CMDMODE(0, "continue", &command, N_("continue rebase"), CONTINUE), OPT_CMDMODE(0, "abort", &command, N_("abort rebase"), ABORT), + OPT_CMDMODE(0, "make-script", &command, + N_("make rebase script"), MAKE_SCRIPT), + OPT_CMDMODE(0, "shorten-ids", &command, + N_("shorten SHA-1s in the todo list"), SHORTEN_SHA1S), + OPT_CMDMODE(0, "expand-ids", &command, + N_("expand SHA-1s in the todo list"), EXPAND_SHA1S), + OPT_CMDMODE(0, "check-todo-list", &command, + N_("check the todo list"), CHECK_TODO_LIST), + OPT_CMDMODE(0, "skip-unnecessary-picks", &command, + N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS), + OPT_CMDMODE(0, "rearrange-squash", &command, + N_("rearrange fixup/squash lines"), REARRANGE_SQUASH), OPT_END() }; @@ -37,5 +52,17 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) return !!sequencer_continue(&opts); if (command == ABORT && argc == 1) return !!sequencer_remove_state(&opts); + if (command == MAKE_SCRIPT && argc > 1) + return !!sequencer_make_script(keep_empty, stdout, argc, argv); + if (command == SHORTEN_SHA1S && argc == 1) + return !!transform_todo_ids(1); + if (command == EXPAND_SHA1S && argc == 1) + return !!transform_todo_ids(0); + if (command == CHECK_TODO_LIST && argc == 1) + return !!check_todo_list(); + if (command == SKIP_UNNECESSARY_PICKS && argc == 1) + return !!skip_unnecessary_picks(); + if (command == REARRANGE_SQUASH && argc == 1) + return !!rearrange_squash(); usage_with_options(builtin_rebase_helper_usage, options); } diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index dd06b3fb4f..29a0f3b75f 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1207,11 +1207,10 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) const char *dst_name; struct string_list_item *item; struct command *dst_cmd; - unsigned char sha1[GIT_MAX_RAWSZ]; int flag; strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); - dst_name = resolve_ref_unsafe(buf.buf, 0, sha1, &flag); + dst_name = resolve_ref_unsafe(buf.buf, 0, NULL, &flag); strbuf_release(&buf); if (!(flag & REF_ISSYMREF)) diff --git a/builtin/reflog.c b/builtin/reflog.c index e237d927a0..2067cca5b1 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -126,7 +126,7 @@ static int commit_is_complete(struct commit *commit) struct commit *c; struct commit_list *parent; - c = (struct commit *)study.objects[--study.nr].item; + c = (struct commit *)object_array_pop(&study); if (!c->object.parsed && !parse_object(&c->object.oid)) c->object.flags |= INCOMPLETE; @@ -182,8 +182,8 @@ static int commit_is_complete(struct commit *commit) found.objects[i].item->flags |= SEEN; } /* free object arrays */ - free(study.objects); - free(found.objects); + object_array_clear(&study); + object_array_clear(&found); return !is_incomplete; } diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c index bfb21ba7d2..6a9127a33c 100644 --- a/builtin/remote-ext.c +++ b/builtin/remote-ext.c @@ -57,7 +57,7 @@ static char *strip_escapes(const char *str, const char *service, special = str[rpos]; if (rpos == 1) break; - /* Fall-through to error. */ + /* fallthrough */ default: die("Bad remote-ext placeholder '%%%c'.", str[rpos]); diff --git a/builtin/remote.c b/builtin/remote.c index 33ba739332..4f5cac96b0 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -558,14 +558,13 @@ static int read_remote_branches(const char *refname, struct strbuf buf = STRBUF_INIT; struct string_list_item *item; int flag; - struct object_id orig_oid; const char *symref; strbuf_addf(&buf, "refs/remotes/%s/", rename->old); if (starts_with(refname, buf.buf)) { item = string_list_append(rename->remote_branches, xstrdup(refname)); symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING, - orig_oid.hash, &flag); + NULL, &flag); if (flag & REF_ISSYMREF) item->util = xstrdup(symref); else diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index b9c13d3d9d..a8d7e6f7ae 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -387,6 +387,14 @@ static const char *skipspaces(const char *s) return s; } +static char *findspace(const char *s) +{ + for (; *s; s++) + if (isspace(*s)) + return (char*)s; + return NULL; +} + static int cmd_parseopt(int argc, const char **argv, const char *prefix) { static int keep_dashdash = 0, stop_at_non_option = 0; @@ -434,7 +442,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) /* parse: (<short>|<short>,<long>|<long>)[*=?!]*<arghint>? SP+ <help> */ while (strbuf_getline(&sb, stdin) != EOF) { const char *s; - const char *help; + char *help; struct option *o; if (!sb.len) @@ -444,15 +452,17 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) memset(opts + onb, 0, sizeof(opts[onb])); o = &opts[onb++]; - help = strchr(sb.buf, ' '); - if (!help || *sb.buf == ' ') { + help = findspace(sb.buf); + if (!help || sb.buf == help) { o->type = OPTION_GROUP; o->help = xstrdup(skipspaces(sb.buf)); continue; } + *help = '\0'; + o->type = OPTION_CALLBACK; - o->help = xstrdup(skipspaces(help)); + o->help = xstrdup(skipspaces(help+1)); o->value = &parsed; o->flags = PARSE_OPT_NOARG; o->callback = &parseopt_dump; diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 818fe74f0a..06ed02f994 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -17,9 +17,8 @@ static char *get_default_remote(void) { char *dest = NULL, *ret; - unsigned char sha1[20]; struct strbuf sb = STRBUF_INIT; - const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL); + const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL); if (!refname) die(_("No such ref: %s"), "HEAD"); @@ -1089,8 +1088,7 @@ static const char *remote_submodule_branch(const char *path) return "master"; if (!strcmp(branch, ".")) { - unsigned char sha1[20]; - const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL); + const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL); if (!refname) die(_("No such ref: %s"), "HEAD"); @@ -1189,6 +1187,7 @@ static int push_check(int argc, const char **argv, const char *prefix) break; die("HEAD does not match the named branch in the superproject"); } + /* fallthrough */ default: die("src refspec '%s' must name a ref", rs->src); diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index df75cb9d4a..17aabaa679 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -12,9 +12,8 @@ static const char * const git_symbolic_ref_usage[] = { static int check_symref(const char *HEAD, int quiet, int shorten, int print) { - unsigned char sha1[20]; int flag; - const char *refname = resolve_ref_unsafe(HEAD, 0, sha1, &flag); + const char *refname = resolve_ref_unsafe(HEAD, 0, NULL, &flag); if (!refname) die("No such ref: %s", HEAD); @@ -157,9 +157,14 @@ int verify_bundle(struct bundle_header *header, int verbose) req_nr = revs.pending.nr; setup_revisions(2, argv, &revs, NULL); + /* Save pending objects, so they can be cleaned up later. */ refs = revs.pending; revs.leak_pending = 1; + /* + * prepare_revision_walk (together with .leak_pending = 1) makes us + * the sole owner of the list of pending objects. + */ if (prepare_revision_walk(&revs)) die(_("revision walk setup failed")); @@ -176,8 +181,10 @@ int verify_bundle(struct bundle_header *header, int verbose) refs.objects[i].name); } + /* Clean up objects used, as they will be reused. */ clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS); - free(refs.objects); + + object_array_clear(&refs); if (verbose) { struct ref_list *r; @@ -1244,8 +1244,8 @@ static inline unsigned int hexval(unsigned char c) */ static inline int hex2chr(const char *s) { - int val = hexval(s[0]); - return (val < 0) ? val : (val << 4) | hexval(s[1]); + unsigned int val = hexval(s[0]); + return (val & ~0xf) ? val : (val << 4) | hexval(s[1]); } /* Convert to/from hex/sha1 representation */ diff --git a/commit-slab.h b/commit-slab.h index 333d81e370..dcaab8ca04 100644 --- a/commit-slab.h +++ b/commit-slab.h @@ -78,7 +78,7 @@ static MAYBE_UNUSED void init_ ##slabname(struct slabname *s) \ \ static MAYBE_UNUSED void clear_ ##slabname(struct slabname *s) \ { \ - int i; \ + unsigned int i; \ for (i = 0; i < s->slab_count; i++) \ free(s->slab[i]); \ s->slab_count = 0; \ @@ -89,13 +89,13 @@ static MAYBE_UNUSED elemtype *slabname## _at_peek(struct slabname *s, \ const struct commit *c, \ int add_if_missing) \ { \ - int nth_slab, nth_slot; \ + unsigned int nth_slab, nth_slot; \ \ nth_slab = c->index / s->slab_size; \ nth_slot = c->index % s->slab_size; \ \ if (s->slab_count <= nth_slab) { \ - int i; \ + unsigned int i; \ if (!add_if_missing) \ return NULL; \ REALLOC_ARRAY(s->slab, nth_slab + 1); \ @@ -1086,6 +1086,7 @@ struct commit_list *reduce_heads(struct commit_list *heads) num_head = remove_redundant(array, num_head); for (i = 0; i < num_head; i++) tail = &commit_list_insert(array[i], tail)->next; + free(array); return result; } diff --git a/compat/win32/lazyload.h b/compat/win32/lazyload.h new file mode 100644 index 0000000000..9e631c8593 --- /dev/null +++ b/compat/win32/lazyload.h @@ -0,0 +1,57 @@ +#ifndef LAZYLOAD_H +#define LAZYLOAD_H + +/* + * A pair of macros to simplify loading of DLL functions. Example: + * + * DECLARE_PROC_ADDR(kernel32.dll, BOOL, CreateHardLinkW, + * LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); + * + * if (!INIT_PROC_ADDR(CreateHardLinkW)) + * return error("Could not find CreateHardLinkW() function"; + * + * if (!CreateHardLinkW(source, target, NULL)) + * return error("could not create hardlink from %S to %S", + * source, target); + */ + +struct proc_addr { + const char *const dll; + const char *const function; + FARPROC pfunction; + unsigned initialized : 1; +}; + +/* Declares a function to be loaded dynamically from a DLL. */ +#define DECLARE_PROC_ADDR(dll, rettype, function, ...) \ + static struct proc_addr proc_addr_##function = \ + { #dll, #function, NULL, 0 }; \ + static rettype (WINAPI *function)(__VA_ARGS__) + +/* + * Loads a function from a DLL (once-only). + * Returns non-NULL function pointer on success. + * Returns NULL + errno == ENOSYS on failure. + * This function is not thread-safe. + */ +#define INIT_PROC_ADDR(function) \ + (function = get_proc_addr(&proc_addr_##function)) + +static inline void *get_proc_addr(struct proc_addr *proc) +{ + /* only do this once */ + if (!proc->initialized) { + HANDLE hnd; + proc->initialized = 1; + hnd = LoadLibraryExA(proc->dll, NULL, + LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hnd) + proc->pfunction = GetProcAddress(hnd, proc->function); + } + /* set ENOSYS if DLL or function was not found */ + if (!proc->pfunction) + errno = ENOSYS; + return proc->pfunction; +} + +#endif @@ -2200,7 +2200,7 @@ static struct { size_t *offset; unsigned int offset_alloc; enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state; - int seen; + unsigned int seen; } store; static int matches(const char *key, const char *value) @@ -2292,11 +2292,10 @@ static int write_error(const char *filename) return 4; } -static ssize_t write_section(int fd, const char *key) +static struct strbuf store_create_section(const char *key) { const char *dot; int i; - ssize_t ret; struct strbuf sb = STRBUF_INIT; dot = memchr(key, '.', store.baselen); @@ -2312,7 +2311,15 @@ static ssize_t write_section(int fd, const char *key) strbuf_addf(&sb, "[%.*s]\n", store.baselen, key); } - ret = write_in_full(fd, sb.buf, sb.len); + return sb; +} + +static ssize_t write_section(int fd, const char *key) +{ + struct strbuf sb = store_create_section(key); + ssize_t ret; + + ret = write_in_full(fd, sb.buf, sb.len) == sb.len; strbuf_release(&sb); return ret; @@ -2355,6 +2362,7 @@ static ssize_t write_pair(int fd, const char *key, const char *value) case '"': case '\\': strbuf_addch(&sb, '\\'); + /* fallthrough */ default: strbuf_addch(&sb, value[i]); break; @@ -2742,8 +2750,8 @@ static int section_name_is_ok(const char *name) } /* if new_name == NULL, the section is removed instead */ -int git_config_rename_section_in_file(const char *config_filename, - const char *old_name, const char *new_name) +static int git_config_copy_or_rename_section_in_file(const char *config_filename, + const char *old_name, const char *new_name, int copy) { int ret = 0, remove = 0; char *filename_buf = NULL; @@ -2752,6 +2760,7 @@ int git_config_rename_section_in_file(const char *config_filename, char buf[1024]; FILE *config_file = NULL; struct stat st; + struct strbuf copystr = STRBUF_INIT; if (new_name && !section_name_is_ok(new_name)) { ret = error("invalid section name: %s", new_name); @@ -2790,12 +2799,30 @@ int git_config_rename_section_in_file(const char *config_filename, while (fgets(buf, sizeof(buf), config_file)) { int i; int length; + int is_section = 0; char *output = buf; for (i = 0; buf[i] && isspace(buf[i]); i++) ; /* do nothing */ if (buf[i] == '[') { /* it's a section */ - int offset = section_name_match(&buf[i], old_name); + int offset; + is_section = 1; + + /* + * When encountering a new section under -c we + * need to flush out any section we're already + * coping and begin anew. There might be + * multiple [branch "$name"] sections. + */ + if (copystr.len > 0) { + if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) { + ret = write_error(get_lock_file_path(lock)); + goto out; + } + strbuf_reset(©str); + } + + offset = section_name_match(&buf[i], old_name); if (offset > 0) { ret++; if (new_name == NULL) { @@ -2803,25 +2830,29 @@ int git_config_rename_section_in_file(const char *config_filename, continue; } store.baselen = strlen(new_name); - if (write_section(out_fd, new_name) < 0) { - ret = write_error(get_lock_file_path(lock)); - goto out; - } - /* - * We wrote out the new section, with - * a newline, now skip the old - * section's length - */ - output += offset + i; - if (strlen(output) > 0) { + if (!copy) { + if (write_section(out_fd, new_name) < 0) { + ret = write_error(get_lock_file_path(lock)); + goto out; + } /* - * More content means there's - * a declaration to put on the - * next line; indent with a - * tab + * We wrote out the new section, with + * a newline, now skip the old + * section's length */ - output -= 1; - output[0] = '\t'; + output += offset + i; + if (strlen(output) > 0) { + /* + * More content means there's + * a declaration to put on the + * next line; indent with a + * tab + */ + output -= 1; + output[0] = '\t'; + } + } else { + copystr = store_create_section(new_name); } } remove = 0; @@ -2829,11 +2860,30 @@ int git_config_rename_section_in_file(const char *config_filename, if (remove) continue; length = strlen(output); + + if (!is_section && copystr.len > 0) { + strbuf_add(©str, output, length); + } + if (write_in_full(out_fd, output, length) < 0) { ret = write_error(get_lock_file_path(lock)); goto out; } } + + /* + * Copy a trailing section at the end of the config, won't be + * flushed by the usual "flush because we have a new section + * logic in the loop above. + */ + if (copystr.len > 0) { + if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) { + ret = write_error(get_lock_file_path(lock)); + goto out; + } + strbuf_reset(©str); + } + fclose(config_file); config_file = NULL; commit_and_out: @@ -2849,11 +2899,30 @@ out_no_rollback: return ret; } +int git_config_rename_section_in_file(const char *config_filename, + const char *old_name, const char *new_name) +{ + return git_config_copy_or_rename_section_in_file(config_filename, + old_name, new_name, 0); +} + int git_config_rename_section(const char *old_name, const char *new_name) { return git_config_rename_section_in_file(NULL, old_name, new_name); } +int git_config_copy_section_in_file(const char *config_filename, + const char *old_name, const char *new_name) +{ + return git_config_copy_or_rename_section_in_file(config_filename, + old_name, new_name, 1); +} + +int git_config_copy_section(const char *old_name, const char *new_name) +{ + return git_config_copy_section_in_file(NULL, old_name, new_name); +} + /* * Call this to report error for your variable that should not * get a boolean value (i.e. "[my] var" means "true"). @@ -70,6 +70,8 @@ extern int git_config_set_multivar_in_file_gently(const char *, const char *, co extern void git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int); extern int git_config_rename_section(const char *, const char *); extern int git_config_rename_section_in_file(const char *, const char *, const char *); +extern int git_config_copy_section(const char *, const char *); +extern int git_config_copy_section_in_file(const char *, const char *, const char *); extern const char *git_etc_gitconfig(void); extern int git_env_bool(const char *, int); extern unsigned long git_env_ulong(const char *, unsigned long); @@ -1545,8 +1545,9 @@ static int ident_filter_fn(struct stream_filter *filter, switch (ident->state) { default: strbuf_add(&ident->left, head, ident->state); + /* fallthrough */ case IDENT_SKIPPING: - /* fallthru */ + /* fallthrough */ case IDENT_DRAINING: ident_drain(ident, &output, osize_p); } diff --git a/diff-delta.c b/diff-delta.c index 3797ce6041..e49643353b 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -319,7 +319,9 @@ create_delta(const struct delta_index *index, const void *trg_buf, unsigned long trg_size, unsigned long *delta_size, unsigned long max_size) { - unsigned int i, outpos, outsize, moff, msize, val; + unsigned int i, val; + off_t outpos, moff; + size_t l, outsize, msize; int inscnt; const unsigned char *ref_data, *ref_top, *data, *top; unsigned char *out; @@ -336,20 +338,20 @@ create_delta(const struct delta_index *index, return NULL; /* store reference buffer size */ - i = index->src_size; - while (i >= 0x80) { - out[outpos++] = i | 0x80; - i >>= 7; + l = index->src_size; + while (l >= 0x80) { + out[outpos++] = l | 0x80; + l >>= 7; } - out[outpos++] = i; + out[outpos++] = l; /* store target buffer size */ - i = trg_size; - while (i >= 0x80) { - out[outpos++] = i | 0x80; - i >>= 7; + l = trg_size; + while (l >= 0x80) { + out[outpos++] = l | 0x80; + l >>= 7; } - out[outpos++] = i; + out[outpos++] = l; ref_data = index->src_buf; ref_top = ref_data + index->src_size; @@ -452,6 +454,9 @@ create_delta(const struct delta_index *index, moff += msize; msize = left; + if (moff > 0xffffffff) + msize = 0; + if (msize < 4096) { int j; val = 0; diff --git a/diff-lib.c b/diff-lib.c index 2a52b07954..4e0980caa8 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -549,7 +549,6 @@ int index_differs_from(const char *def, int diff_flags, rev.diffopt.flags |= diff_flags; rev.diffopt.ita_invisible_in_index = ita_invisible_in_index; run_diff_index(&rev, 1); - if (rev.pending.alloc) - free(rev.pending.objects); + object_array_clear(&rev.pending); return (DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES) != 0); } @@ -1541,7 +1541,7 @@ static void emit_rewrite_diff(const char *name_a, struct diff_words_buffer { mmfile_t text; - long alloc; + unsigned long alloc; struct diff_words_orig { const char *begin, *end; } *orig; @@ -49,7 +49,7 @@ struct cached_dir { static enum path_treatment read_directory_recursive(struct dir_struct *dir, struct index_state *istate, const char *path, int len, struct untracked_cache_dir *untracked, - int check_only, const struct pathspec *pathspec); + int check_only, int stop_at_first_file, const struct pathspec *pathspec); static int get_dtype(struct dirent *de, struct index_state *istate, const char *path, int len); @@ -1404,8 +1404,13 @@ static enum path_treatment treat_directory(struct dir_struct *dir, untracked = lookup_untracked(dir->untracked, untracked, dirname + baselen, len - baselen); + + /* + * If this is an excluded directory, then we only need to check if + * the directory contains any files. + */ return read_directory_recursive(dir, istate, dirname, len, - untracked, 1, pathspec); + untracked, 1, exclude, pathspec); } /* @@ -1633,7 +1638,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir, * with check_only set. */ return read_directory_recursive(dir, istate, path->buf, path->len, - cdir->ucd, 1, pathspec); + cdir->ucd, 1, 0, pathspec); /* * We get path_recurse in the first run when * directory_exists_in_index() returns index_nonexistent. We @@ -1793,12 +1798,20 @@ static void close_cached_dir(struct cached_dir *cdir) * Also, we ignore the name ".git" (even if it is not a directory). * That likely will not change. * + * If 'stop_at_first_file' is specified, 'path_excluded' is returned + * to signal that a file was found. This is the least significant value that + * indicates that a file was encountered that does not depend on the order of + * whether an untracked or exluded path was encountered first. + * * Returns the most significant path_treatment value encountered in the scan. + * If 'stop_at_first_file' is specified, `path_excluded` is the most + * significant path_treatment value that will be returned. */ + static enum path_treatment read_directory_recursive(struct dir_struct *dir, struct index_state *istate, const char *base, int baselen, struct untracked_cache_dir *untracked, int check_only, - const struct pathspec *pathspec) + int stop_at_first_file, const struct pathspec *pathspec) { struct cached_dir cdir; enum path_treatment state, subdir_state, dir_state = path_none; @@ -1832,12 +1845,34 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, subdir_state = read_directory_recursive(dir, istate, path.buf, path.len, ud, - check_only, pathspec); + check_only, stop_at_first_file, pathspec); if (subdir_state > dir_state) dir_state = subdir_state; } if (check_only) { + if (stop_at_first_file) { + /* + * If stopping at first file, then + * signal that a file was found by + * returning `path_excluded`. This is + * to return a consistent value + * regardless of whether an ignored or + * excluded file happened to be + * encountered 1st. + * + * In current usage, the + * `stop_at_first_file` is passed when + * an ancestor directory has matched + * an exclude pattern, so any found + * files will be excluded. + */ + if (dir_state >= path_excluded) { + dir_state = path_excluded; + break; + } + } + /* abort early if maximum state has been reached */ if (dir_state == path_untracked) { if (cdir.fdir) @@ -2108,7 +2143,7 @@ int read_directory(struct dir_struct *dir, struct index_state *istate, */ dir->untracked = NULL; if (!len || treat_leading_path(dir, istate, path, len, pathspec)) - read_directory_recursive(dir, istate, path, len, untracked, 0, pathspec); + read_directory_recursive(dir, istate, path, len, untracked, 0, 0, pathspec); QSORT(dir->entries, dir->nr, cmp_dir_entry); QSORT(dir->ignored, dir->ignored_nr, cmp_dir_entry); @@ -588,6 +588,7 @@ static int fsck_tree(struct tree *item, struct fsck_options *options) case S_IFREG | 0664: if (!options->strict) break; + /* fallthrough */ default: has_bad_modes = 1; } diff --git a/git-archimport.perl b/git-archimport.perl index 9cb123a07d..b7c173c345 100755 --- a/git-archimport.perl +++ b/git-archimport.perl @@ -983,7 +983,7 @@ sub find_parents { # check that we actually know about the branch next unless -e "$git_dir/refs/heads/$branch"; - my $mergebase = `git-merge-base $branch $ps->{branch}`; + my $mergebase = safe_pipe_capture(qw(git-merge-base), $branch, $ps->{branch}); if ($?) { # Don't die here, Arch supports one-way cherry-picking # between branches with no common base (or any relationship @@ -1074,7 +1074,7 @@ sub find_parents { sub git_rev_parse { my $name = shift; - my $val = `git-rev-parse $name`; + my $val = safe_pipe_capture(qw(git-rev-parse), $name); die "Error: git-rev-parse $name" if $?; chomp $val; return $val; diff --git a/git-compat-util.h b/git-compat-util.h index 9bc15b0363..cedad4d581 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -898,9 +898,11 @@ static inline char *xstrdup_or_null(const char *str) static inline size_t xsize_t(off_t len) { - if (len > (size_t) len) + size_t size = (size_t) len; + + if (len != (off_t) size) die("Cannot handle files this big"); - return (size_t)len; + return size; } __attribute__((format (printf, 3, 4))) diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 1e4e65a45d..36929921ea 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -642,6 +642,7 @@ sub is_sha1 { sub get_headref ($) { my $name = shift; + $name =~ s/'/'\\''/; my $r = `git rev-parse --verify '$name' 2>/dev/null`; return undef unless $? == 0; chomp $r; diff --git a/git-cvsserver.perl b/git-cvsserver.perl index d50c85ed7b..ae1044273d 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -356,7 +356,7 @@ sub req_Root return 0; } - my @gitvars = `git config -l`; + my @gitvars = safe_pipe_capture(qw(git config -l)); if ($?) { print "E problems executing git-config on the server -- this is not a git repository or the PATH is not set correctly.\n"; print "E \n"; @@ -841,7 +841,7 @@ sub req_Modified # Save the file data in $state $state->{entries}{$state->{directory}.$data}{modified_filename} = $filename; $state->{entries}{$state->{directory}.$data}{modified_mode} = $mode; - $state->{entries}{$state->{directory}.$data}{modified_hash} = `git hash-object $filename`; + $state->{entries}{$state->{directory}.$data}{modified_hash} = safe_pipe_capture('git','hash-object',$filename); $state->{entries}{$state->{directory}.$data}{modified_hash} =~ s/\s.*$//s; #$log->debug("req_Modified : file=$data mode=$mode size=$size"); @@ -943,7 +943,7 @@ sub req_co # Provide list of modules, if -c was used. if (exists $state->{opt}{c}) { - my $showref = `git show-ref --heads`; + my $showref = safe_pipe_capture(qw(git show-ref --heads)); for my $line (split '\n', $showref) { if ( $line =~ m% refs/heads/(.*)$% ) { print "M $1\t$1\n"; @@ -1181,7 +1181,7 @@ sub req_update # projects (heads in this case) to checkout. # if ($state->{module} eq '') { - my $showref = `git show-ref --heads`; + my $showref = safe_pipe_capture(qw(git show-ref --heads)); print "E cvs update: Updating .\n"; for my $line (split '\n', $showref) { if ( $line =~ m% refs/heads/(.*)$% ) { @@ -1463,7 +1463,7 @@ sub req_update # transmit file, format is single integer on a line by itself (file # size) followed by the file contents # TODO : we should copy files in blocks - my $data = `cat $mergedFile`; + my $data = safe_pipe_capture('cat', $mergedFile); $log->debug("File size : " . length($data)); print length($data) . "\n"; print $data; @@ -1579,7 +1579,7 @@ sub req_ci $branchRef = "refs/heads/$stickyInfo->{tag}"; } - $parenthash = `git show-ref -s $branchRef`; + $parenthash = safe_pipe_capture('git', 'show-ref', '-s', $branchRef); chomp $parenthash; if ($parenthash !~ /^[0-9a-f]{40}$/) { @@ -1687,7 +1687,7 @@ sub req_ci return; } - my $treehash = `git write-tree`; + my $treehash = safe_pipe_capture(qw(git write-tree)); chomp $treehash; $log->debug("Treehash : $treehash, Parenthash : $parenthash"); @@ -1704,7 +1704,7 @@ sub req_ci } close $msg_fh; - my $commithash = `git commit-tree $treehash -p $parenthash < $msg_filename`; + my $commithash = safe_pipe_capture('git', 'commit-tree', $treehash, '-p', $parenthash, '-F', $msg_filename); chomp($commithash); $log->info("Commit hash : $commithash"); @@ -2854,12 +2854,12 @@ sub transmitfile die "Need filehash" unless ( defined ( $filehash ) and $filehash =~ /^[a-zA-Z0-9]{40}$/ ); - my $type = `git cat-file -t $filehash`; + my $type = safe_pipe_capture('git', 'cat-file', '-t', $filehash); chomp $type; die ( "Invalid type '$type' (expected 'blob')" ) unless ( defined ( $type ) and $type eq "blob" ); - my $size = `git cat-file -s $filehash`; + my $size = safe_pipe_capture('git', 'cat-file', '-s', $filehash); chomp $size; $log->debug("transmitfile($filehash) size=$size, type=$type"); @@ -3040,7 +3040,7 @@ sub ensureWorkTree chdir $work->{emptyDir} or die "Unable to chdir to $work->{emptyDir}\n"; - my $ver = `git show-ref -s refs/heads/$state->{module}`; + my $ver = safe_pipe_capture('git', 'show-ref', '-s', "refs/heads/$state->{module}"); chomp $ver; if ($ver !~ /^[0-9a-f]{40}$/) { @@ -3287,7 +3287,7 @@ sub open_blob_or_die die "Need filehash\n"; } - my $type = `git cat-file -t $name`; + my $type = safe_pipe_capture('git', 'cat-file', '-t', $name); chomp $type; unless ( defined ( $type ) and $type eq "blob" ) @@ -3296,7 +3296,7 @@ sub open_blob_or_die die ( "Invalid type '$type' (expected 'blob')" ) } - my $size = `git cat-file -s $name`; + my $size = safe_pipe_capture('git', 'cat-file', '-s', $name); chomp $size; $log->debug("open_blob_or_die($name) size=$size, type=$type"); @@ -3406,6 +3406,22 @@ sub refHashEqual return $out; } +# an alternative to `command` that allows input to be passed as an array +# to work around shell problems with weird characters in arguments + +sub safe_pipe_capture { + + my @output; + + if (my $pid = open my $child, '-|') { + @output = (<$child>); + close $child or die join(' ',@_).": $! $?"; + } else { + exec(@_) or die "$! $?"; # exec() can fail the executable can't be found + } + return wantarray ? @output : join('',@output); +} + package GITCVS::log; @@ -3797,10 +3813,10 @@ sub update # first lets get the commit list $ENV{GIT_DIR} = $self->{git_path}; - my $commitsha1 = `git rev-parse $self->{module}`; + my $commitsha1 = ::safe_pipe_capture('git', 'rev-parse', $self->{module}); chomp $commitsha1; - my $commitinfo = `git cat-file commit $self->{module} 2>&1`; + my $commitinfo = ::safe_pipe_capture('git', 'cat-file', 'commit', $self->{module}); unless ( $commitinfo =~ /tree\s+[a-zA-Z0-9]{40}/ ) { die("Invalid module '$self->{module}'"); @@ -3882,7 +3898,7 @@ sub update # several candidate merge bases. let's assume # that the first one is the best one. my $base = eval { - safe_pipe_capture('git', 'merge-base', + ::safe_pipe_capture('git', 'merge-base', $lastpicked, $parent); }; # The two branches may not be related at all, @@ -4749,7 +4765,7 @@ sub getMetaFromCommithash return $retVal; } - my($fileHash)=safe_pipe_capture("git","rev-parse","$revCommit:$filename"); + my($fileHash) = ::safe_pipe_capture("git","rev-parse","$revCommit:$filename"); chomp $fileHash; if(!($fileHash=~/^[0-9a-f]{40}$/)) { @@ -4844,8 +4860,8 @@ sub lookupCommitRef return $commitHash; } - $commitHash=safe_pipe_capture("git","rev-parse","--verify","--quiet", - $self->unescapeRefName($ref)); + $commitHash = ::safe_pipe_capture("git","rev-parse","--verify","--quiet", + $self->unescapeRefName($ref)); $commitHash=~s/\s*$//; if(!($commitHash=~/^[0-9a-f]{40}$/)) { @@ -4854,7 +4870,7 @@ sub lookupCommitRef if( defined($commitHash) ) { - my $type=safe_pipe_capture("git","cat-file","-t",$commitHash); + my $type = ::safe_pipe_capture("git","cat-file","-t",$commitHash); if( ! ($type=~/^commit\s*$/ ) ) { $commitHash=undef; @@ -4907,7 +4923,7 @@ sub commitmessage return $message; } - my @lines = safe_pipe_capture("git", "cat-file", "commit", $commithash); + my @lines = ::safe_pipe_capture("git", "cat-file", "commit", $commithash); shift @lines while ( $lines[0] =~ /\S/ ); $message = join("",@lines); $message .= " " if ( $message =~ /\n$/ ); @@ -5056,25 +5072,6 @@ sub in_array return $retval; } -=head2 safe_pipe_capture - -an alternative to `command` that allows input to be passed as an array -to work around shell problems with weird characters in arguments - -=cut -sub safe_pipe_capture { - - my @output; - - if (my $pid = open my $child, '-|') { - @output = (<$child>); - close $child or die join(' ',@_).": $! $?"; - } else { - exec(@_) or die "$! $?"; # exec() can fail the executable can't be found - } - return wantarray ? @output : join('',@output); -} - =head2 mangle_dirname create a string from a directory name that is suitable to use as diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 3a74602ef3..3365a3b866 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -86,7 +86,7 @@ USAGE="[--setup <command>] [--env-filter <command>] [--parent-filter <command>] [--msg-filter <command>] [--commit-filter <command>] [--tag-name-filter <command>] [--subdirectory-filter <directory>] [--original <namespace>] - [-d <directory>] [-f | --force] + [-d <directory>] [-f | --force] [--state-branch <branch>] [--] [<rev-list options>...]" OPTIONS_SPEC= @@ -106,6 +106,7 @@ filter_msg=cat filter_commit= filter_tag_name= filter_subdir= +state_branch= orig_namespace=refs/original/ force= prune_empty= @@ -181,6 +182,9 @@ do --original) orig_namespace=$(expr "$OPTARG/" : '\(.*[^/]\)/*$')/ ;; + --state-branch) + state_branch="$OPTARG" + ;; *) usage ;; @@ -219,6 +223,13 @@ trap 'cd "$orig_dir"; rm -rf "$tempdir"' 0 ORIG_GIT_DIR="$GIT_DIR" ORIG_GIT_WORK_TREE="$GIT_WORK_TREE" ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE" +ORIG_GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" +ORIG_GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" +ORIG_GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" +ORIG_GIT_COMMITTER_NAME="$GIT_COMMITTER_NAME" +ORIG_GIT_COMMITTER_EMAIL="$GIT_COMMITTER_EMAIL" +ORIG_GIT_COMMITTER_DATE="$GIT_COMMITTER_DATE" + GIT_WORK_TREE=. export GIT_DIR GIT_WORK_TREE @@ -252,6 +263,26 @@ export GIT_INDEX_FILE # map old->new commit ids for rewriting parents mkdir ../map || die "Could not create map/ directory" +if test -n "$state_branch" +then + state_commit=$(git rev-parse --no-flags --revs-only "$state_branch") + if test -n "$state_commit" + then + echo "Populating map from $state_branch ($state_commit)" 1>&2 + perl -e'open(MAP, "-|", "git show $ARGV[0]:filter.map") or die; + while (<MAP>) { + m/(.*):(.*)/ or die; + open F, ">../map/$1" or die; + print F "$2" or die; + close(F) or die; + } + close(MAP) or die;' "$state_commit" \ + || die "Unable to load state from $state_branch:filter.map" + else + echo "Branch $state_branch does not exist. Will create" 1>&2 + fi +fi + # we need "--" only if there are no path arguments in $@ nonrevs=$(git rev-parse --no-revs "$@") || exit if test -z "$nonrevs" @@ -530,7 +561,7 @@ if [ "$filter_tag_name" ]; then }' \ -e '/^-----BEGIN PGP SIGNATURE-----/q' \ -e 'p' ) | - git mktag) || + git hash-object -t tag -w --stdin) || die "Could not create new tag object for $ref" if git cat-file tag "$ref" | \ sane_grep '^-----BEGIN PGP SIGNATURE-----' >/dev/null 2>&1 @@ -544,12 +575,9 @@ if [ "$filter_tag_name" ]; then done fi -cd "$orig_dir" -rm -rf "$tempdir" - -trap - 0 - unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE +unset GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE +unset GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_COMMITTER_DATE test -z "$ORIG_GIT_DIR" || { GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR } @@ -561,6 +589,58 @@ test -z "$ORIG_GIT_INDEX_FILE" || { GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" && export GIT_INDEX_FILE } +test -z "$ORIG_GIT_AUTHOR_NAME" || { + GIT_AUTHOR_NAME="$ORIG_GIT_AUTHOR_NAME" && + export GIT_AUTHOR_NAME +} +test -z "$ORIG_GIT_AUTHOR_EMAIL" || { + GIT_AUTHOR_EMAIL="$ORIG_GIT_AUTHOR_EMAIL" && + export GIT_AUTHOR_EMAIL +} +test -z "$ORIG_GIT_AUTHOR_DATE" || { + GIT_AUTHOR_DATE="$ORIG_GIT_AUTHOR_DATE" && + export GIT_AUTHOR_DATE +} +test -z "$ORIG_GIT_COMMITTER_NAME" || { + GIT_COMMITTER_NAME="$ORIG_GIT_COMMITTER_NAME" && + export GIT_COMMITTER_NAME +} +test -z "$ORIG_GIT_COMMITTER_EMAIL" || { + GIT_COMMITTER_EMAIL="$ORIG_GIT_COMMITTER_EMAIL" && + export GIT_COMMITTER_EMAIL +} +test -z "$ORIG_GIT_COMMITTER_DATE" || { + GIT_COMMITTER_DATE="$ORIG_GIT_COMMITTER_DATE" && + export GIT_COMMITTER_DATE +} + +if test -n "$state_branch" +then + echo "Saving rewrite state to $state_branch" 1>&2 + state_blob=$( + perl -e'opendir D, "../map" or die; + open H, "|-", "git hash-object -w --stdin" or die; + foreach (sort readdir(D)) { + next if m/^\.\.?$/; + open F, "<../map/$_" or die; + chomp($f = <F>); + print H "$_:$f\n" or die; + } + close(H) or die;' || die "Unable to save state") + state_tree=$(/bin/echo -e "100644 blob $state_blob\tfilter.map" | git mktree) + if test -n "$state_commit" + then + state_commit=$(/bin/echo "Sync" | git commit-tree "$state_tree" -p "$state_commit") + else + state_commit=$(/bin/echo "Sync" | git commit-tree "$state_tree" ) + fi + git update-ref "$state_branch" "$state_commit" +fi + +cd "$orig_dir" +rm -rf "$tempdir" + +trap - 0 if [ "$(is_bare_repository)" = false ]; then git read-tree -u -m HEAD || exit diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 29b7e8824b..2563dc52da 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -155,13 +155,13 @@ reschedule_last_action () { append_todo_help () { gettext " Commands: - p, pick = use commit - r, reword = use commit, but edit the commit message - e, edit = use commit, but stop for amending - s, squash = use commit, but meld into previous commit - f, fixup = like \"squash\", but discard this commit's log message - x, exec = run command (the rest of the line) using shell - d, drop = remove commit +p, pick = use commit +r, reword = use commit, but edit the commit message +e, edit = use commit, but stop for amending +s, squash = use commit, but meld into previous commit +f, fixup = like \"squash\", but discard this commit's log message +x, exec = run command (the rest of the line) using shell +d, drop = remove commit These lines can be re-ordered; they are executed from top to bottom. " | git stripspace --comment-lines >>"$todo" @@ -714,154 +714,12 @@ do_rest () { done } -# skip picking commits whose parents are unchanged -skip_unnecessary_picks () { - fd=3 - while read -r command rest - do - # fd=3 means we skip the command - case "$fd,$command" in - 3,pick|3,p) - # pick a commit whose parent is current $onto -> skip - sha1=${rest%% *} - case "$(git rev-parse --verify --quiet "$sha1"^)" in - "$onto"*) - onto=$sha1 - ;; - *) - fd=1 - ;; - esac - ;; - 3,"$comment_char"*|3,) - # copy comments - ;; - *) - fd=1 - ;; - esac - printf '%s\n' "$command${rest:+ }$rest" >&$fd - done <"$todo" >"$todo.new" 3>>"$done" && - mv -f "$todo".new "$todo" && - case "$(peek_next_command)" in - squash|s|fixup|f) - record_in_rewritten "$onto" - ;; - esac || - die "$(gettext "Could not skip unnecessary pick commands")" -} - -transform_todo_ids () { - while read -r command rest - do - case "$command" in - "$comment_char"* | exec) - # Be careful for oddball commands like 'exec' - # that do not have a SHA-1 at the beginning of $rest. - ;; - *) - sha1=$(git rev-parse --verify --quiet "$@" ${rest%%[ ]*}) && - rest="$sha1 ${rest#*[ ]}" - ;; - esac - printf '%s\n' "$command${rest:+ }$rest" - done <"$todo" >"$todo.new" && - mv -f "$todo.new" "$todo" -} - expand_todo_ids() { - transform_todo_ids + git rebase--helper --expand-ids } collapse_todo_ids() { - transform_todo_ids --short -} - -# Rearrange the todo list that has both "pick sha1 msg" and -# "pick sha1 fixup!/squash! msg" appears in it so that the latter -# comes immediately after the former, and change "pick" to -# "fixup"/"squash". -# -# Note that if the config has specified a custom instruction format -# each log message will be re-retrieved in order to normalize the -# autosquash arrangement -rearrange_squash () { - # extract fixup!/squash! lines and resolve any referenced sha1's - while read -r pick sha1 message - do - test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1}) - case "$message" in - "squash! "*|"fixup! "*) - action="${message%%!*}" - rest=$message - prefix= - # skip all squash! or fixup! (but save for later) - while : - do - case "$rest" in - "squash! "*|"fixup! "*) - prefix="$prefix${rest%%!*}," - rest="${rest#*! }" - ;; - *) - break - ;; - esac - done - printf '%s %s %s %s\n' "$sha1" "$action" "$prefix" "$rest" - # if it's a single word, try to resolve to a full sha1 and - # emit a second copy. This allows us to match on both message - # and on sha1 prefix - if test "${rest#* }" = "$rest"; then - fullsha="$(git rev-parse -q --verify "$rest" 2>/dev/null)" - if test -n "$fullsha"; then - # prefix the action to uniquely identify this line as - # intended for full sha1 match - echo "$sha1 +$action $prefix $fullsha" - fi - fi - esac - done >"$1.sq" <"$1" - test -s "$1.sq" || return - - used= - while read -r pick sha1 message - do - case " $used" in - *" $sha1 "*) continue ;; - esac - printf '%s\n' "$pick $sha1 $message" - test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1}) - used="$used$sha1 " - while read -r squash action msg_prefix msg_content - do - case " $used" in - *" $squash "*) continue ;; - esac - emit=0 - case "$action" in - +*) - action="${action#+}" - # full sha1 prefix test - case "$msg_content" in "$sha1"*) emit=1;; esac ;; - *) - # message prefix test - case "$message" in "$msg_content"*) emit=1;; esac ;; - esac - if test $emit = 1; then - if test -n "${format}" - then - msg_content=$(git log -n 1 --format="${format}" ${squash}) - else - msg_content="$(echo "$msg_prefix" | sed "s/,/! /g")$msg_content" - fi - printf '%s\n' "$action $squash $msg_content" - used="$used$squash " - fi - done <"$1.sq" - done >"$1.rearranged" <"$1" - cat "$1.rearranged" >"$1" - rm -f "$1.sq" "$1.rearranged" + git rebase--helper --shorten-ids } # Add commands after a pick or after a squash/fixup serie @@ -885,96 +743,6 @@ add_exec_commands () { mv "$1.new" "$1" } -# Check if the SHA-1 passed as an argument is a -# correct one, if not then print $2 in "$todo".badsha -# $1: the SHA-1 to test -# $2: the line number of the input -# $3: the input filename -check_commit_sha () { - badsha=0 - if test -z "$1" - then - badsha=1 - else - sha1_verif="$(git rev-parse --verify --quiet $1^{commit})" - if test -z "$sha1_verif" - then - badsha=1 - fi - fi - - if test $badsha -ne 0 - then - line="$(sed -n -e "${2}p" "$3")" - warn "$(eval_gettext "\ -Warning: the SHA-1 is missing or isn't a commit in the following line: - - \$line")" - warn - fi - - return $badsha -} - -# prints the bad commits and bad commands -# from the todolist in stdin -check_bad_cmd_and_sha () { - retval=0 - lineno=0 - while read -r command rest - do - lineno=$(( $lineno + 1 )) - case $command in - "$comment_char"*|''|noop|x|exec) - # Doesn't expect a SHA-1 - ;; - "$cr") - # Work around CR left by "read" (e.g. with Git for - # Windows' Bash). - ;; - pick|p|drop|d|reword|r|edit|e|squash|s|fixup|f) - if ! check_commit_sha "${rest%%[ ]*}" "$lineno" "$1" - then - retval=1 - fi - ;; - *) - line="$(sed -n -e "${lineno}p" "$1")" - warn "$(eval_gettext "\ -Warning: the command isn't recognized in the following line: - - \$line")" - warn - retval=1 - ;; - esac - done <"$1" - return $retval -} - -# Print the list of the SHA-1 of the commits -# from stdin to stdout -todo_list_to_sha_list () { - git stripspace --strip-comments | - while read -r command sha1 rest - do - case $command in - "$comment_char"*|''|noop|x|"exec") - ;; - *) - long_sha=$(git rev-list --no-walk "$sha1" 2>/dev/null) - printf "%s\n" "$long_sha" - ;; - esac - done -} - -# Use warn for each line in stdin -warn_lines () { - while read -r line - do - warn " - $line" - done -} - # Switch to the branch in $into and notify it in the reflog checkout_onto () { GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" @@ -989,74 +757,6 @@ get_missing_commit_check_level () { printf '%s' "$check_level" | tr 'A-Z' 'a-z' } -# Check if the user dropped some commits by mistake -# Behaviour determined by rebase.missingCommitsCheck. -# Check if there is an unrecognized command or a -# bad SHA-1 in a command. -check_todo_list () { - raise_error=f - - check_level=$(get_missing_commit_check_level) - - case "$check_level" in - warn|error) - # Get the SHA-1 of the commits - todo_list_to_sha_list <"$todo".backup >"$todo".oldsha1 - todo_list_to_sha_list <"$todo" >"$todo".newsha1 - - # Sort the SHA-1 and compare them - sort -u "$todo".oldsha1 >"$todo".oldsha1+ - mv "$todo".oldsha1+ "$todo".oldsha1 - sort -u "$todo".newsha1 >"$todo".newsha1+ - mv "$todo".newsha1+ "$todo".newsha1 - comm -2 -3 "$todo".oldsha1 "$todo".newsha1 >"$todo".miss - - # Warn about missing commits - if test -s "$todo".miss - then - test "$check_level" = error && raise_error=t - - warn "$(gettext "\ -Warning: some commits may have been dropped accidentally. -Dropped commits (newer to older):")" - - # Make the list user-friendly and display - opt="--no-walk=sorted --format=oneline --abbrev-commit --stdin" - git rev-list $opt <"$todo".miss | warn_lines - - warn "$(gettext "\ -To avoid this message, use \"drop\" to explicitly remove a commit. - -Use 'git config rebase.missingCommitsCheck' to change the level of warnings. -The possible behaviours are: ignore, warn, error.")" - warn - fi - ;; - ignore) - ;; - *) - warn "$(eval_gettext "Unrecognized setting \$check_level for option rebase.missingCommitsCheck. Ignoring.")" - ;; - esac - - if ! check_bad_cmd_and_sha "$todo" - then - raise_error=t - fi - - if test $raise_error = t - then - # Checkout before the first commit of the - # rebase: this way git rebase --continue - # will work correctly as it expects HEAD to be - # placed before the commit of the next action - checkout_onto - - warn "$(gettext "You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.")" - die "$(gettext "Or you can abort the rebase with 'git rebase --abort'.")" - fi -} - # The whole contents of this file is run by dot-sourcing it from # inside a shell function. It used to be that "return"s we see # below were not inside any function, and expected to return @@ -1211,26 +911,27 @@ else revisions=$onto...$orig_head shortrevisions=$shorthead fi -format=$(git config --get rebase.instructionFormat) -# the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse -git rev-list $merges_option --format="%m%H ${format:-%s}" \ - --reverse --left-right --topo-order \ - $revisions ${restrict_revision+^$restrict_revision} | \ - sed -n "s/^>//p" | -while read -r sha1 rest -do - - if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1 - then - comment_out="$comment_char " - else - comment_out= - fi +if test t != "$preserve_merges" +then + git rebase--helper --make-script ${keep_empty:+--keep-empty} \ + $revisions ${restrict_revision+^$restrict_revision} >"$todo" +else + format=$(git config --get rebase.instructionFormat) + # the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse + git rev-list $merges_option --format="%m%H ${format:-%s}" \ + --reverse --left-right --topo-order \ + $revisions ${restrict_revision+^$restrict_revision} | \ + sed -n "s/^>//p" | + while read -r sha1 rest + do + + if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1 + then + comment_out="$comment_char " + else + comment_out= + fi - if test t != "$preserve_merges" - then - printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo" - else if test -z "$rebase_root" then preserve=t @@ -1249,8 +950,8 @@ do touch "$rewritten"/$sha1 printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo" fi - fi -done + done +fi # Watch for commits that been dropped by --cherry-pick if test t = "$preserve_merges" @@ -1280,7 +981,7 @@ then fi test -s "$todo" || echo noop >> "$todo" -test -n "$autosquash" && rearrange_squash "$todo" +test -z "$autosquash" || git rebase--helper --rearrange-squash || exit test -n "$cmd" && add_exec_commands "$todo" todocount=$(git stripspace --strip-comments <"$todo" | wc -l) @@ -1316,11 +1017,17 @@ git_sequence_editor "$todo" || has_action "$todo" || return 2 -check_todo_list +git rebase--helper --check-todo-list || { + ret=$? + checkout_onto + exit $ret +} expand_todo_ids -test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks +test -d "$rewritten" || test -n "$force_rebase" || +onto="$(git rebase--helper --skip-unnecessary-picks)" || +die "Could not skip unnecessary pick commands" checkout_onto if test -z "$rebase_root" && test ! -d "$rewritten" diff --git a/git-rebase.sh b/git-rebase.sh index ad8415e3cf..6344e8d5e3 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -350,6 +350,9 @@ do shift break ;; + *) + usage + ;; esac shift done diff --git a/http-backend.c b/http-backend.c index e51c7805c8..f3dc218b2a 100644 --- a/http-backend.c +++ b/http-backend.c @@ -486,10 +486,9 @@ static int show_head_ref(const char *refname, const struct object_id *oid, struct strbuf *buf = cb_data; if (flag & REF_ISSYMREF) { - struct object_id unused; const char *target = resolve_ref_unsafe(refname, RESOLVE_REF_READING, - unused.hash, NULL); + NULL, NULL); if (target) strbuf_addf(buf, "ref: %s\n", strip_namespace(target)); diff --git a/http-push.c b/http-push.c index e4c9b065ce..d860c477c6 100644 --- a/http-push.c +++ b/http-push.c @@ -1523,6 +1523,7 @@ static int remote_exists(const char *path) break; case HTTP_ERROR: error("unable to access '%s': %s", url, curl_errorstr); + /* fallthrough */ default: ret = -1; } @@ -638,9 +638,7 @@ static int curl_trace(CURL *handle, curl_infotype type, char *data, size_t size, switch (type) { case CURLINFO_TEXT: trace_printf_key(&trace_curl, "== Info: %s", data); - default: /* we ignore unknown types by default */ - return 0; - + break; case CURLINFO_HEADER_OUT: text = "=> Send header"; curl_dump_header(text, (unsigned char *)data, size, DO_FILTER); @@ -665,6 +663,9 @@ static int curl_trace(CURL *handle, curl_infotype type, char *data, size_t size, text = "<= Recv SSL data"; curl_dump_data(text, (unsigned char *)data, size); break; + + default: /* we ignore unknown types by default */ + return 0; } return 0; } diff --git a/line-log.c b/line-log.c index ab0709f9ae..545ad0f28b 100644 --- a/line-log.c +++ b/line-log.c @@ -90,7 +90,7 @@ static int range_cmp(const void *_r, const void *_s) */ static void range_set_check_invariants(struct range_set *rs) { - int i; + unsigned int i; if (!rs) return; @@ -110,8 +110,8 @@ static void range_set_check_invariants(struct range_set *rs) */ void sort_and_merge_range_set(struct range_set *rs) { - int i; - int o = 0; /* output cursor */ + unsigned int i; + unsigned int o = 0; /* output cursor */ QSORT(rs->ranges, rs->nr, range_cmp); @@ -144,7 +144,7 @@ void sort_and_merge_range_set(struct range_set *rs) static void range_set_union(struct range_set *out, struct range_set *a, struct range_set *b) { - int i = 0, j = 0; + unsigned int i = 0, j = 0; struct range *ra = a->ranges; struct range *rb = b->ranges; /* cannot make an alias of out->ranges: it may change during grow */ @@ -186,7 +186,7 @@ static void range_set_union(struct range_set *out, static void range_set_difference(struct range_set *out, struct range_set *a, struct range_set *b) { - int i, j = 0; + unsigned int i, j = 0; for (i = 0; i < a->nr; i++) { long start = a->ranges[i].start; long end = a->ranges[i].end; @@ -397,7 +397,7 @@ static void diff_ranges_filter_touched(struct diff_ranges *out, struct diff_ranges *diff, struct range_set *rs) { - int i, j = 0; + unsigned int i, j = 0; assert(out->target.nr == 0); @@ -426,7 +426,7 @@ static void range_set_shift_diff(struct range_set *out, struct range_set *rs, struct diff_ranges *diff) { - int i, j = 0; + unsigned int i, j = 0; long offset = 0; struct range *src = rs->ranges; struct range *target = diff->target.ranges; @@ -873,7 +873,7 @@ static char *output_prefix(struct diff_options *opt) static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range) { - int i, j = 0; + unsigned int i, j = 0; long p_lines, t_lines; unsigned long *p_ends = NULL, *t_ends = NULL; struct diff_filepair *pair = range->pair; @@ -906,7 +906,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang long t_start = range->ranges.ranges[i].start; long t_end = range->ranges.ranges[i].end; long t_cur = t_start; - int j_last; + unsigned int j_last; while (j < diff->target.nr && diff->target.ranges[j].end < t_start) j++; diff --git a/line-log.h b/line-log.h index 7a5c24e2df..e2a5ee7c6d 100644 --- a/line-log.h +++ b/line-log.h @@ -14,7 +14,7 @@ struct range { /* A set of ranges. The ranges must always be disjoint and sorted. */ struct range_set { - int alloc, nr; + unsigned int alloc, nr; struct range *ranges; }; diff --git a/log-tree.c b/log-tree.c index 410ab4f02d..cea056234d 100644 --- a/log-tree.c +++ b/log-tree.c @@ -185,7 +185,6 @@ static const struct name_decoration *current_pointed_by_HEAD(const struct name_d { const struct name_decoration *list, *head = NULL; const char *branch_name = NULL; - struct object_id unused; int rru_flags; /* First find HEAD */ @@ -198,7 +197,7 @@ static const struct name_decoration *current_pointed_by_HEAD(const struct name_d return NULL; /* Now resolve and find the matching current branch */ - branch_name = resolve_ref_unsafe("HEAD", 0, unused.hash, &rru_flags); + branch_name = resolve_ref_unsafe("HEAD", 0, NULL, &rru_flags); if (!(rru_flags & REF_ISSYMREF)) return NULL; diff --git a/mailinfo.c b/mailinfo.c index f2387a3267..a89db22ab0 100644 --- a/mailinfo.c +++ b/mailinfo.c @@ -367,11 +367,16 @@ static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047) while ((c = *in++) != 0) { if (c == '=') { - int d = *in++; + int ch, d = *in; if (d == '\n' || !d) break; /* drop trailing newline */ - strbuf_addch(out, (hexval(d) << 4) | hexval(*in++)); - continue; + ch = hex2chr(in); + if (ch >= 0) { + strbuf_addch(out, ch); + in += 2; + continue; + } + /* garbage -- fall through */ } if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */ c = 0x20; @@ -822,6 +827,7 @@ static void handle_filter(struct mailinfo *mi, struct strbuf *line) if (!handle_commit_msg(mi, line)) break; mi->filter_stage++; + /* fallthrough */ case 1: handle_patch(mi, line); break; @@ -353,6 +353,19 @@ static void object_array_release_entry(struct object_array_entry *ent) free(ent->path); } +struct object *object_array_pop(struct object_array *array) +{ + struct object *ret; + + if (!array->nr) + return NULL; + + ret = array->objects[array->nr - 1].item; + object_array_release_entry(&array->objects[array->nr - 1]); + array->nr--; + return ret; +} + void object_array_filter(struct object_array *array, object_array_each_func_t want, void *cb_data) { @@ -116,6 +116,14 @@ int object_list_contains(struct object_list *list, struct object *obj); void add_object_array(struct object *obj, const char *name, struct object_array *array); void add_object_array_with_path(struct object *obj, const char *name, struct object_array *array, unsigned mode, const char *path); +/* + * Returns NULL if the array is empty. Otherwise, returns the last object + * after removing its entry from the array. Other resources associated + * with that object are left in an unspecified state and should not be + * examined. + */ +struct object *object_array_pop(struct object_array *array); + typedef int (*object_array_each_func_t)(struct object_array_entry *, void *); /* diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c index 8e47a96b3b..a8df5ce2ab 100644 --- a/pack-bitmap-write.c +++ b/pack-bitmap-write.c @@ -297,9 +297,7 @@ void bitmap_writer_build(struct packing_data *to_pack) traverse_commit_list(&revs, show_commit, show_object, base); - revs.pending.nr = 0; - revs.pending.alloc = 0; - revs.pending.objects = NULL; + object_array_clear(&revs.pending); stored->bitmap = bitmap_to_ewah(base); need_reset = 0; diff --git a/pack-bitmap.c b/pack-bitmap.c index cb3d14ba45..42e3d5f4f2 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -654,8 +654,6 @@ static int in_bitmapped_pack(struct object_list *roots) int prepare_bitmap_walk(struct rev_info *revs) { unsigned int i; - unsigned int pending_nr = revs->pending.nr; - struct object_array_entry *pending_e = revs->pending.objects; struct object_list *wants = NULL; struct object_list *haves = NULL; @@ -670,8 +668,8 @@ int prepare_bitmap_walk(struct rev_info *revs) return -1; } - for (i = 0; i < pending_nr; ++i) { - struct object *object = pending_e[i].item; + for (i = 0; i < revs->pending.nr; ++i) { + struct object *object = revs->pending.objects[i].item; if (object->type == OBJ_NONE) parse_object_or_die(&object->oid, NULL); @@ -715,9 +713,7 @@ int prepare_bitmap_walk(struct rev_info *revs) if (!bitmap_git.loaded && load_pack_bitmap() < 0) return -1; - revs->pending.nr = 0; - revs->pending.alloc = 0; - revs->pending.objects = NULL; + object_array_clear(&revs->pending); if (haves) { revs->ignore_missing_links = 1; diff --git a/parse-options.c b/parse-options.c index 0dd9fc6a0d..fca7159646 100644 --- a/parse-options.c +++ b/parse-options.c @@ -581,6 +581,7 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx, const struct option *opts, int full, int err) { FILE *outfile = err ? stderr : stdout; + int need_newline; if (!usagestr) return PARSE_OPT_HELP; @@ -599,12 +600,11 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx, if (**usagestr) fprintf_ln(outfile, _(" %s"), _(*usagestr)); else - putchar('\n'); + fputc('\n', outfile); usagestr++; } - if (opts->type != OPTION_GROUP) - fputc('\n', outfile); + need_newline = 1; for (; opts->type != OPTION_END; opts++) { size_t pos; @@ -612,6 +612,7 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx, if (opts->type == OPTION_GROUP) { fputc('\n', outfile); + need_newline = 0; if (*opts->help) fprintf(outfile, "%s\n", _(opts->help)); continue; @@ -619,6 +620,11 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx, if (!full && (opts->flags & PARSE_OPT_HIDDEN)) continue; + if (need_newline) { + fputc('\n', outfile); + need_newline = 0; + } + pos = fprintf(outfile, " "); if (opts->short_name) { if (opts->flags & PARSE_OPT_NODASH) @@ -431,6 +431,7 @@ void tcl_quote_buf(struct strbuf *sb, const char *src) case '{': case '}': case '$': case '\\': case '"': strbuf_addch(sb, '\\'); + /* fallthrough */ default: strbuf_addch(sb, c); break; diff --git a/read-cache.c b/read-cache.c index cdcd11c71e..65f4fe8375 100644 --- a/read-cache.c +++ b/read-cache.c @@ -220,6 +220,7 @@ static int ce_modified_check_fs(const struct cache_entry *ce, struct stat *st) case S_IFDIR: if (S_ISGITLINK(ce->ce_mode)) return ce_compare_gitlink(ce) ? DATA_CHANGED : 0; + /* else fallthrough */ default: return TYPE_CHANGED; } @@ -239,8 +239,7 @@ int read_ref(const char *refname, unsigned char *sha1) int ref_exists(const char *refname) { - unsigned char sha1[20]; - return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL); + return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, NULL, NULL); } static int filter_refs(const char *refname, const struct object_id *oid, @@ -286,12 +285,11 @@ static int warn_if_dangling_symref(const char *refname, const struct object_id * { struct warn_if_dangling_data *d = cb_data; const char *resolves_to; - struct object_id junk; if (!(flags & REF_ISSYMREF)) return 0; - resolves_to = resolve_ref_unsafe(refname, 0, junk.hash, NULL); + resolves_to = resolve_ref_unsafe(refname, 0, NULL, NULL); if (!resolves_to || (d->refname ? strcmp(resolves_to, d->refname) @@ -1398,9 +1396,12 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, unsigned char *sha1, int *flags) { static struct strbuf sb_refname = STRBUF_INIT; + struct object_id unused_oid; int unused_flags; int symref_count; + if (!sha1) + sha1 = unused_oid.hash; if (!flags) flags = &unused_flags; @@ -2035,3 +2036,14 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg) { return refs_rename_ref(get_main_ref_store(), oldref, newref, logmsg); } + +int refs_copy_existing_ref(struct ref_store *refs, const char *oldref, + const char *newref, const char *logmsg) +{ + return refs->be->copy_ref(refs, oldref, newref, logmsg); +} + +int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg) +{ + return refs_copy_existing_ref(get_main_ref_store(), oldref, newref, logmsg); +} @@ -10,10 +10,11 @@ struct worktree; /* * Resolve a reference, recursively following symbolic refererences. * - * Store the referred-to object's name in sha1 and return the name of - * the non-symbolic reference that ultimately pointed at it. The - * return value, if not NULL, is a pointer into either a static buffer - * or the input ref. + * Return the name of the non-symbolic reference that ultimately pointed + * at the resolved object name. The return value, if not NULL, is a + * pointer into either a static buffer or the input ref. + * + * If sha1 is non-NULL, store the referred-to object's name in it. * * If the reference cannot be resolved to an object, the behavior * depends on the RESOLVE_REF_READING flag: @@ -441,7 +442,14 @@ char *shorten_unambiguous_ref(const char *refname, int strict); /** rename ref, return 0 on success **/ int refs_rename_ref(struct ref_store *refs, const char *oldref, const char *newref, const char *logmsg); -int rename_ref(const char *oldref, const char *newref, const char *logmsg); +int rename_ref(const char *oldref, const char *newref, + const char *logmsg); + +/** copy ref, return 0 on success **/ +int refs_copy_existing_ref(struct ref_store *refs, const char *oldref, + const char *newref, const char *logmsg); +int copy_existing_ref(const char *oldref, const char *newref, + const char *logmsg); int refs_create_symref(struct ref_store *refs, const char *refname, const char *target, const char *logmsg); diff --git a/refs/files-backend.c b/refs/files-backend.c index dac33628b3..38d16a13a8 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1258,9 +1258,9 @@ static int commit_ref_update(struct files_ref_store *refs, const struct object_id *oid, const char *logmsg, struct strbuf *err); -static int files_rename_ref(struct ref_store *ref_store, +static int files_copy_or_rename_ref(struct ref_store *ref_store, const char *oldrefname, const char *newrefname, - const char *logmsg) + const char *logmsg, int copy) { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_WRITE, "rename_ref"); @@ -1292,8 +1292,12 @@ static int files_rename_ref(struct ref_store *ref_store, } if (flag & REF_ISSYMREF) { - ret = error("refname %s is a symbolic ref, renaming it is not supported", - oldrefname); + if (copy) + ret = error("refname %s is a symbolic ref, copying it is not supported", + oldrefname); + else + ret = error("refname %s is a symbolic ref, renaming it is not supported", + oldrefname); goto out; } if (!refs_rename_ref_available(&refs->base, oldrefname, newrefname)) { @@ -1301,13 +1305,19 @@ static int files_rename_ref(struct ref_store *ref_store, goto out; } - if (log && rename(sb_oldref.buf, tmp_renamed_log.buf)) { + if (!copy && log && rename(sb_oldref.buf, tmp_renamed_log.buf)) { ret = error("unable to move logfile logs/%s to logs/"TMP_RENAMED_LOG": %s", oldrefname, strerror(errno)); goto out; } - if (refs_delete_ref(&refs->base, logmsg, oldrefname, + if (copy && log && copy_file(tmp_renamed_log.buf, sb_oldref.buf, 0644)) { + ret = error("unable to copy logfile logs/%s to logs/"TMP_RENAMED_LOG": %s", + oldrefname, strerror(errno)); + goto out; + } + + if (!copy && refs_delete_ref(&refs->base, logmsg, oldrefname, orig_oid.hash, REF_NODEREF)) { error("unable to delete old %s", oldrefname); goto rollback; @@ -1320,7 +1330,7 @@ static int files_rename_ref(struct ref_store *ref_store, * the safety anyway; we want to delete the reference whatever * its current value. */ - if (!refs_read_ref_full(&refs->base, newrefname, + if (!copy && !refs_read_ref_full(&refs->base, newrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, oid.hash, NULL) && refs_delete_ref(&refs->base, NULL, newrefname, @@ -1351,7 +1361,10 @@ static int files_rename_ref(struct ref_store *ref_store, lock = lock_ref_sha1_basic(refs, newrefname, NULL, NULL, NULL, REF_NODEREF, NULL, &err); if (!lock) { - error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf); + if (copy) + error("unable to copy '%s' to '%s': %s", oldrefname, newrefname, err.buf); + else + error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf); strbuf_release(&err); goto rollback; } @@ -1402,6 +1415,22 @@ static int files_rename_ref(struct ref_store *ref_store, return ret; } +static int files_rename_ref(struct ref_store *ref_store, + const char *oldrefname, const char *newrefname, + const char *logmsg) +{ + return files_copy_or_rename_ref(ref_store, oldrefname, + newrefname, logmsg, 0); +} + +static int files_copy_ref(struct ref_store *ref_store, + const char *oldrefname, const char *newrefname, + const char *logmsg) +{ + return files_copy_or_rename_ref(ref_store, oldrefname, + newrefname, logmsg, 1); +} + static int close_ref_gently(struct ref_lock *lock) { if (close_lock_file_gently(&lock->lk)) @@ -1676,13 +1705,12 @@ static int commit_ref_update(struct files_ref_store *refs, * check with HEAD only which should cover 99% of all usage * scenarios (even 100% of the default ones). */ - struct object_id head_oid; int head_flag; const char *head_ref; head_ref = refs_resolve_ref_unsafe(&refs->base, "HEAD", RESOLVE_REF_READING, - head_oid.hash, &head_flag); + NULL, &head_flag); if (head_ref && (head_flag & REF_ISSYMREF) && !strcmp(head_ref, lock->ref_name)) { struct strbuf log_err = STRBUF_INIT; @@ -3065,6 +3093,7 @@ struct ref_storage_be refs_be_files = { files_create_symref, files_delete_refs, files_rename_ref, + files_copy_ref, files_ref_iterator_begin, files_read_raw_ref, diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 3bc47ffd5e..9c0d685c7f 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -966,6 +966,13 @@ static int packed_rename_ref(struct ref_store *ref_store, die("BUG: packed reference store does not support renaming references"); } +static int packed_copy_ref(struct ref_store *ref_store, + const char *oldrefname, const char *newrefname, + const char *logmsg) +{ + die("BUG: packed reference store does not support copying references"); +} + static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_store) { return empty_ref_iterator_begin(); @@ -1031,6 +1038,7 @@ struct ref_storage_be refs_be_packed = { packed_create_symref, packed_delete_refs, packed_rename_ref, + packed_copy_ref, packed_ref_iterator_begin, packed_read_raw_ref, diff --git a/refs/refs-internal.h b/refs/refs-internal.h index d7d344de73..8821e27ed9 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -559,6 +559,9 @@ typedef int delete_refs_fn(struct ref_store *ref_store, const char *msg, typedef int rename_ref_fn(struct ref_store *ref_store, const char *oldref, const char *newref, const char *logmsg); +typedef int copy_ref_fn(struct ref_store *ref_store, + const char *oldref, const char *newref, + const char *logmsg); /* * Iterate over the references in `ref_store` whose names start with @@ -657,6 +660,7 @@ struct ref_storage_be { create_symref_fn *create_symref; delete_refs_fn *delete_refs; rename_ref_fn *rename_ref; + copy_ref_fn *copy_ref; ref_iterator_begin_fn *iterator_begin; read_raw_ref_fn *read_raw_ref; @@ -466,7 +466,6 @@ static void alias_all_urls(void) static void read_config(void) { static int loaded; - struct object_id oid; int flag; if (loaded) @@ -475,7 +474,7 @@ static void read_config(void) current_branch = NULL; if (startup_info->have_repository) { - const char *head_ref = resolve_ref_unsafe("HEAD", 0, oid.hash, &flag); + const char *head_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flag); if (head_ref && (flag & REF_ISSYMREF) && skip_prefix(head_ref, "refs/heads/", &head_ref)) { current_branch = make_branch(head_ref, 0); @@ -1105,10 +1104,9 @@ static struct ref *make_linked_ref(const char *name, struct ref ***tail) static char *guess_ref(const char *name, struct ref *peer) { struct strbuf buf = STRBUF_INIT; - struct object_id oid; const char *r = resolve_ref_unsafe(peer->name, RESOLVE_REF_READING, - oid.hash, NULL); + NULL, NULL); if (!r) return NULL; @@ -1166,12 +1164,11 @@ static int match_explicit(struct ref *src, struct ref *dst, return -1; if (!dst_value) { - struct object_id oid; int flag; dst_value = resolve_ref_unsafe(matched_src->name, RESOLVE_REF_READING, - oid.hash, &flag); + NULL, &flag); if (!dst_value || ((flag & REF_ISSYMREF) && !starts_with(dst_value, "refs/heads/"))) @@ -1792,10 +1789,9 @@ const char *branch_get_push(struct branch *branch, struct strbuf *err) static int ignore_symref_update(const char *refname) { - struct object_id oid; int flag; - if (!resolve_ref_unsafe(refname, 0, oid.hash, &flag)) + if (!resolve_ref_unsafe(refname, 0, NULL, &flag)) return 0; /* non-existing refs are OK */ return (flag & REF_ISSYMREF); } diff --git a/revision.c b/revision.c index 1520f69d93..d167223e69 100644 --- a/revision.c +++ b/revision.c @@ -1106,7 +1106,7 @@ static void add_rev_cmdline(struct rev_info *revs, unsigned flags) { struct rev_cmdline_info *info = &revs->cmdline; - int nr = info->nr; + unsigned int nr = info->nr; ALLOC_GROW(info->rev, nr + 1, info->alloc); info->rev[nr].item = item; @@ -2248,11 +2248,10 @@ static int handle_revision_pseudo_opt(const char *submodule, static void NORETURN diagnose_missing_default(const char *def) { - unsigned char sha1[20]; int flags; const char *refname; - refname = resolve_ref_unsafe(def, 0, sha1, &flags); + refname = resolve_ref_unsafe(def, 0, NULL, &flags); if (!refname || !(flags & REF_ISSYMREF) || (flags & REF_ISBROKEN)) die(_("your current branch appears to be broken")); diff --git a/revision.h b/revision.h index 3a3d3e2cf8..54761200ad 100644 --- a/revision.h +++ b/revision.h @@ -150,6 +150,17 @@ struct rev_info { date_mode_explicit:1, preserve_subject:1; unsigned int disable_stdin:1; + /* + * Set `leak_pending` to prevent `prepare_revision_walk()` from clearing + * the array of pending objects (`pending`). It will still forget about + * the array and its entries, so they really are leaked. This can be + * useful if the `struct object_array` `pending` is copied before + * calling `prepare_revision_walk()`. By setting `leak_pending`, you + * effectively claim ownership of the old array, so you should most + * likely call `object_array_clear(&pending_copy)` once you are done. + * Observe that this is about ownership of the array and its entries, + * not the commits referenced by those entries. + */ unsigned int leak_pending:1; /* --show-linear-break */ unsigned int track_linear:1, diff --git a/send-pack.c b/send-pack.c index b865f662e4..a8cc6b266e 100644 --- a/send-pack.c +++ b/send-pack.c @@ -497,7 +497,7 @@ int send_pack(struct send_pack_args *args, strbuf_release(&cap_buf); return atomic_push_failure(args, remote_refs, ref); } - /* Fallthrough for non atomic case. */ + /* else fallthrough */ default: continue; } diff --git a/sequencer.c b/sequencer.c index 60636ce54b..b8c1e876fa 100644 --- a/sequencer.c +++ b/sequencer.c @@ -20,6 +20,7 @@ #include "trailer.h" #include "log-tree.h" #include "wt-status.h" +#include "hashmap.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -2435,3 +2436,533 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) strbuf_release(&sob); } + +int sequencer_make_script(int keep_empty, FILE *out, + int argc, const char **argv) +{ + char *format = NULL; + struct pretty_print_context pp = {0}; + struct strbuf buf = STRBUF_INIT; + struct rev_info revs; + struct commit *commit; + + init_revisions(&revs, NULL); + revs.verbose_header = 1; + revs.max_parents = 1; + revs.cherry_pick = 1; + revs.limited = 1; + revs.reverse = 1; + revs.right_only = 1; + revs.sort_order = REV_SORT_IN_GRAPH_ORDER; + revs.topo_order = 1; + + revs.pretty_given = 1; + git_config_get_string("rebase.instructionFormat", &format); + if (!format || !*format) { + free(format); + format = xstrdup("%s"); + } + get_commit_format(format, &revs); + free(format); + pp.fmt = revs.commit_format; + pp.output_encoding = get_log_output_encoding(); + + if (setup_revisions(argc, argv, &revs, NULL) > 1) + return error(_("make_script: unhandled options")); + + if (prepare_revision_walk(&revs) < 0) + return error(_("make_script: error preparing revisions")); + + while ((commit = get_revision(&revs))) { + strbuf_reset(&buf); + if (!keep_empty && is_original_commit_empty(commit)) + strbuf_addf(&buf, "%c ", comment_line_char); + strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid)); + pretty_print_commit(&pp, commit, &buf); + strbuf_addch(&buf, '\n'); + fputs(buf.buf, out); + } + strbuf_release(&buf); + return 0; +} + + +int transform_todo_ids(int shorten_ids) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int fd, res, i; + FILE *out; + + strbuf_reset(&todo_list.buf); + fd = open(todo_file, O_RDONLY); + if (fd < 0) + return error_errno(_("could not open '%s'"), todo_file); + if (strbuf_read(&todo_list.buf, fd, 0) < 0) { + close(fd); + return error(_("could not read '%s'."), todo_file); + } + close(fd); + + res = parse_insn_buffer(todo_list.buf.buf, &todo_list); + if (res) { + todo_list_release(&todo_list); + return error(_("unusable todo list: '%s'"), todo_file); + } + + out = fopen(todo_file, "w"); + if (!out) { + todo_list_release(&todo_list); + return error(_("unable to open '%s' for writing"), todo_file); + } + for (i = 0; i < todo_list.nr; i++) { + struct todo_item *item = todo_list.items + i; + int bol = item->offset_in_buf; + const char *p = todo_list.buf.buf + bol; + int eol = i + 1 < todo_list.nr ? + todo_list.items[i + 1].offset_in_buf : + todo_list.buf.len; + + if (item->command >= TODO_EXEC && item->command != TODO_DROP) + fwrite(p, eol - bol, 1, out); + else { + const char *id = shorten_ids ? + short_commit_name(item->commit) : + oid_to_hex(&item->commit->object.oid); + int len; + + p += strspn(p, " \t"); /* left-trim command */ + len = strcspn(p, " \t"); /* length of command */ + + fprintf(out, "%.*s %s %.*s\n", + len, p, id, item->arg_len, item->arg); + } + } + fclose(out); + todo_list_release(&todo_list); + return 0; +} + +enum check_level { + CHECK_IGNORE = 0, CHECK_WARN, CHECK_ERROR +}; + +static enum check_level get_missing_commit_check_level(void) +{ + const char *value; + + if (git_config_get_value("rebase.missingcommitscheck", &value) || + !strcasecmp("ignore", value)) + return CHECK_IGNORE; + if (!strcasecmp("warn", value)) + return CHECK_WARN; + if (!strcasecmp("error", value)) + return CHECK_ERROR; + warning(_("unrecognized setting %s for option" + "rebase.missingCommitsCheck. Ignoring."), value); + return CHECK_IGNORE; +} + +/* + * Check if the user dropped some commits by mistake + * Behaviour determined by rebase.missingCommitsCheck. + * Check if there is an unrecognized command or a + * bad SHA-1 in a command. + */ +int check_todo_list(void) +{ + enum check_level check_level = get_missing_commit_check_level(); + struct strbuf todo_file = STRBUF_INIT; + struct todo_list todo_list = TODO_LIST_INIT; + struct strbuf missing = STRBUF_INIT; + int advise_to_edit_todo = 0, res = 0, fd, i; + + strbuf_addstr(&todo_file, rebase_path_todo()); + fd = open(todo_file.buf, O_RDONLY); + if (fd < 0) { + res = error_errno(_("could not open '%s'"), todo_file.buf); + goto leave_check; + } + if (strbuf_read(&todo_list.buf, fd, 0) < 0) { + close(fd); + res = error(_("could not read '%s'."), todo_file.buf); + goto leave_check; + } + close(fd); + advise_to_edit_todo = res = + parse_insn_buffer(todo_list.buf.buf, &todo_list); + + if (res || check_level == CHECK_IGNORE) + goto leave_check; + + /* Mark the commits in git-rebase-todo as seen */ + for (i = 0; i < todo_list.nr; i++) { + struct commit *commit = todo_list.items[i].commit; + if (commit) + commit->util = (void *)1; + } + + todo_list_release(&todo_list); + strbuf_addstr(&todo_file, ".backup"); + fd = open(todo_file.buf, O_RDONLY); + if (fd < 0) { + res = error_errno(_("could not open '%s'"), todo_file.buf); + goto leave_check; + } + if (strbuf_read(&todo_list.buf, fd, 0) < 0) { + close(fd); + res = error(_("could not read '%s'."), todo_file.buf); + goto leave_check; + } + close(fd); + strbuf_release(&todo_file); + res = !!parse_insn_buffer(todo_list.buf.buf, &todo_list); + + /* Find commits in git-rebase-todo.backup yet unseen */ + for (i = todo_list.nr - 1; i >= 0; i--) { + struct todo_item *item = todo_list.items + i; + struct commit *commit = item->commit; + if (commit && !commit->util) { + strbuf_addf(&missing, " - %s %.*s\n", + short_commit_name(commit), + item->arg_len, item->arg); + commit->util = (void *)1; + } + } + + /* Warn about missing commits */ + if (!missing.len) + goto leave_check; + + if (check_level == CHECK_ERROR) + advise_to_edit_todo = res = 1; + + fprintf(stderr, + _("Warning: some commits may have been dropped accidentally.\n" + "Dropped commits (newer to older):\n")); + + /* Make the list user-friendly and display */ + fputs(missing.buf, stderr); + strbuf_release(&missing); + + fprintf(stderr, _("To avoid this message, use \"drop\" to " + "explicitly remove a commit.\n\n" + "Use 'git config rebase.missingCommitsCheck' to change " + "the level of warnings.\n" + "The possible behaviours are: ignore, warn, error.\n\n")); + +leave_check: + strbuf_release(&todo_file); + todo_list_release(&todo_list); + + if (advise_to_edit_todo) + fprintf(stderr, + _("You can fix this with 'git rebase --edit-todo' " + "and then run 'git rebase --continue'.\n" + "Or you can abort the rebase with 'git rebase" + " --abort'.\n")); + + return res; +} + +/* skip picking commits whose parents are unchanged */ +int skip_unnecessary_picks(void) +{ + const char *todo_file = rebase_path_todo(); + struct strbuf buf = STRBUF_INIT; + struct todo_list todo_list = TODO_LIST_INIT; + struct object_id onto_oid, *oid = &onto_oid, *parent_oid; + int fd, i; + + if (!read_oneliner(&buf, rebase_path_onto(), 0)) + return error(_("could not read 'onto'")); + if (get_oid(buf.buf, &onto_oid)) { + strbuf_release(&buf); + return error(_("need a HEAD to fixup")); + } + strbuf_release(&buf); + + fd = open(todo_file, O_RDONLY); + if (fd < 0) { + return error_errno(_("could not open '%s'"), todo_file); + } + if (strbuf_read(&todo_list.buf, fd, 0) < 0) { + close(fd); + return error(_("could not read '%s'."), todo_file); + } + close(fd); + if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) { + todo_list_release(&todo_list); + return -1; + } + + for (i = 0; i < todo_list.nr; i++) { + struct todo_item *item = todo_list.items + i; + + if (item->command >= TODO_NOOP) + continue; + if (item->command != TODO_PICK) + break; + if (parse_commit(item->commit)) { + todo_list_release(&todo_list); + return error(_("could not parse commit '%s'"), + oid_to_hex(&item->commit->object.oid)); + } + if (!item->commit->parents) + break; /* root commit */ + if (item->commit->parents->next) + break; /* merge commit */ + parent_oid = &item->commit->parents->item->object.oid; + if (hashcmp(parent_oid->hash, oid->hash)) + break; + oid = &item->commit->object.oid; + } + if (i > 0) { + int offset = i < todo_list.nr ? + todo_list.items[i].offset_in_buf : todo_list.buf.len; + const char *done_path = rebase_path_done(); + + fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); + if (fd < 0) { + error_errno(_("could not open '%s' for writing"), + done_path); + todo_list_release(&todo_list); + return -1; + } + if (write_in_full(fd, todo_list.buf.buf, offset) < 0) { + error_errno(_("could not write to '%s'"), done_path); + todo_list_release(&todo_list); + close(fd); + return -1; + } + close(fd); + + fd = open(rebase_path_todo(), O_WRONLY, 0666); + if (fd < 0) { + error_errno(_("could not open '%s' for writing"), + rebase_path_todo()); + todo_list_release(&todo_list); + return -1; + } + if (write_in_full(fd, todo_list.buf.buf + offset, + todo_list.buf.len - offset) < 0) { + error_errno(_("could not write to '%s'"), + rebase_path_todo()); + close(fd); + todo_list_release(&todo_list); + return -1; + } + if (ftruncate(fd, todo_list.buf.len - offset) < 0) { + error_errno(_("could not truncate '%s'"), + rebase_path_todo()); + todo_list_release(&todo_list); + close(fd); + return -1; + } + close(fd); + + todo_list.current = i; + if (is_fixup(peek_command(&todo_list, 0))) + record_in_rewritten(oid, peek_command(&todo_list, 0)); + } + + todo_list_release(&todo_list); + printf("%s\n", oid_to_hex(oid)); + + return 0; +} + +struct subject2item_entry { + struct hashmap_entry entry; + int i; + char subject[FLEX_ARRAY]; +}; + +static int subject2item_cmp(const void *fndata, + const struct subject2item_entry *a, + const struct subject2item_entry *b, const void *key) +{ + return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject); +} + +/* + * Rearrange the todo list that has both "pick commit-id msg" and "pick + * commit-id fixup!/squash! msg" in it so that the latter is put immediately + * after the former, and change "pick" to "fixup"/"squash". + * + * Note that if the config has specified a custom instruction format, each log + * message will have to be retrieved from the commit (as the oneline in the + * script cannot be trusted) in order to normalize the autosquash arrangement. + */ +int rearrange_squash(void) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + struct hashmap subject2item; + int res = 0, rearranged = 0, *next, *tail, fd, i; + char **subjects; + + fd = open(todo_file, O_RDONLY); + if (fd < 0) + return error_errno(_("could not open '%s'"), todo_file); + if (strbuf_read(&todo_list.buf, fd, 0) < 0) { + close(fd); + return error(_("could not read '%s'."), todo_file); + } + close(fd); + if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) { + todo_list_release(&todo_list); + return -1; + } + + /* + * The hashmap maps onelines to the respective todo list index. + * + * If any items need to be rearranged, the next[i] value will indicate + * which item was moved directly after the i'th. + * + * In that case, last[i] will indicate the index of the latest item to + * be moved to appear after the i'th. + */ + hashmap_init(&subject2item, (hashmap_cmp_fn) subject2item_cmp, + NULL, todo_list.nr); + ALLOC_ARRAY(next, todo_list.nr); + ALLOC_ARRAY(tail, todo_list.nr); + ALLOC_ARRAY(subjects, todo_list.nr); + for (i = 0; i < todo_list.nr; i++) { + struct strbuf buf = STRBUF_INIT; + struct todo_item *item = todo_list.items + i; + const char *commit_buffer, *subject, *p; + size_t subject_len; + int i2 = -1; + struct subject2item_entry *entry; + + next[i] = tail[i] = -1; + if (item->command >= TODO_EXEC) { + subjects[i] = NULL; + continue; + } + + if (is_fixup(item->command)) { + todo_list_release(&todo_list); + return error(_("the script was already rearranged.")); + } + + item->commit->util = item; + + parse_commit(item->commit); + commit_buffer = get_commit_buffer(item->commit, NULL); + find_commit_subject(commit_buffer, &subject); + format_subject(&buf, subject, " "); + subject = subjects[i] = strbuf_detach(&buf, &subject_len); + unuse_commit_buffer(item->commit, commit_buffer); + if ((skip_prefix(subject, "fixup! ", &p) || + skip_prefix(subject, "squash! ", &p))) { + struct commit *commit2; + + for (;;) { + while (isspace(*p)) + p++; + if (!skip_prefix(p, "fixup! ", &p) && + !skip_prefix(p, "squash! ", &p)) + break; + } + + if ((entry = hashmap_get_from_hash(&subject2item, + strhash(p), p))) + /* found by title */ + i2 = entry->i; + else if (!strchr(p, ' ') && + (commit2 = + lookup_commit_reference_by_name(p)) && + commit2->util) + /* found by commit name */ + i2 = (struct todo_item *)commit2->util + - todo_list.items; + else { + /* copy can be a prefix of the commit subject */ + for (i2 = 0; i2 < i; i2++) + if (subjects[i2] && + starts_with(subjects[i2], p)) + break; + if (i2 == i) + i2 = -1; + } + } + if (i2 >= 0) { + rearranged = 1; + todo_list.items[i].command = + starts_with(subject, "fixup!") ? + TODO_FIXUP : TODO_SQUASH; + if (next[i2] < 0) + next[i2] = i; + else + next[tail[i2]] = i; + tail[i2] = i; + } else if (!hashmap_get_from_hash(&subject2item, + strhash(subject), subject)) { + FLEX_ALLOC_MEM(entry, subject, subject, subject_len); + entry->i = i; + hashmap_entry_init(entry, strhash(entry->subject)); + hashmap_put(&subject2item, entry); + } + } + + if (rearranged) { + struct strbuf buf = STRBUF_INIT; + + for (i = 0; i < todo_list.nr; i++) { + enum todo_command command = todo_list.items[i].command; + int cur = i; + + /* + * Initially, all commands are 'pick's. If it is a + * fixup or a squash now, we have rearranged it. + */ + if (is_fixup(command)) + continue; + + while (cur >= 0) { + int offset = todo_list.items[cur].offset_in_buf; + int end_offset = cur + 1 < todo_list.nr ? + todo_list.items[cur + 1].offset_in_buf : + todo_list.buf.len; + char *bol = todo_list.buf.buf + offset; + char *eol = todo_list.buf.buf + end_offset; + + /* replace 'pick', by 'fixup' or 'squash' */ + command = todo_list.items[cur].command; + if (is_fixup(command)) { + strbuf_addstr(&buf, + todo_command_info[command].str); + bol += strcspn(bol, " \t"); + } + + strbuf_add(&buf, bol, eol - bol); + + cur = next[cur]; + } + } + + fd = open(todo_file, O_WRONLY); + if (fd < 0) + res = error_errno(_("could not open '%s'"), todo_file); + else if (write(fd, buf.buf, buf.len) < 0) + res = error_errno(_("could not read '%s'."), todo_file); + else if (ftruncate(fd, buf.len) < 0) + res = error_errno(_("could not finish '%s'"), + todo_file); + close(fd); + strbuf_release(&buf); + } + + free(next); + free(tail); + for (i = 0; i < todo_list.nr; i++) + free(subjects[i]); + free(subjects); + hashmap_free(&subject2item, 1); + todo_list_release(&todo_list); + + return res; +} diff --git a/sequencer.h b/sequencer.h index f885b68395..6f3d3df82c 100644 --- a/sequencer.h +++ b/sequencer.h @@ -45,6 +45,14 @@ int sequencer_continue(struct replay_opts *opts); int sequencer_rollback(struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts); +int sequencer_make_script(int keep_empty, FILE *out, + int argc, const char **argv); + +int transform_todo_ids(int shorten_ids); +int check_todo_list(void); +int skip_unnecessary_picks(void); +int rearrange_squash(void); + extern const char sign_off_header[]; void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag); @@ -99,7 +99,7 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth, cur_depth = 0; } else { commit = (struct commit *) - stack.objects[--stack.nr].item; + object_array_pop(&stack); cur_depth = *(int *)commit->util; } } @@ -25,19 +25,6 @@ static int do_generic_cmd(const char *me, char *arg) return execv_git_cmd(my_argv); } -static int do_cvs_cmd(const char *me, char *arg) -{ - const char *cvsserver_argv[3] = { - "cvsserver", "server", NULL - }; - - if (!arg || strcmp(arg, "server")) - die("git-cvsserver only handles server: %s", arg); - - setup_path(); - return execv_git_cmd(cvsserver_argv); -} - static int is_valid_cmd_name(const char *cmd) { /* Test command contains no . or / characters */ @@ -134,7 +121,6 @@ static struct commands { { "git-receive-pack", do_generic_cmd }, { "git-upload-pack", do_generic_cmd }, { "git-upload-archive", do_generic_cmd }, - { "cvs", do_cvs_cmd }, { NULL }, }; diff --git a/submodule.c b/submodule.c index 50bf3fb09c..3e3ea11761 100644 --- a/submodule.c +++ b/submodule.c @@ -1685,7 +1685,7 @@ static int find_first_merges(struct object_array *result, const char *path, add_object_array(merges.objects[i].item, NULL, result); } - free(merges.objects); + object_array_clear(&merges); return result->nr; } @@ -1790,7 +1790,7 @@ int merge_submodule(struct object_id *result, const char *path, print_commit((struct commit *) merges.objects[i].item); } - free(merges.objects); + object_array_clear(&merges); return 0; } diff --git a/submodule.h b/submodule.h index 6b52133c88..f0da0277a4 100644 --- a/submodule.h +++ b/submodule.h @@ -120,7 +120,7 @@ extern int submodule_move_head(const char *path, /* * Prepare the "env_array" parameter of a "struct child_process" for executing - * a submodule by clearing any repo-specific envirionment variables, but + * a submodule by clearing any repo-specific environment variables, but * retaining any config in the environment. */ extern void prepare_submodule_repo_env(struct argv_array *out); diff --git a/t/helper/test-line-buffer.c b/t/helper/test-line-buffer.c index 81575fe2ab..078dd7e29d 100644 --- a/t/helper/test-line-buffer.c +++ b/t/helper/test-line-buffer.c @@ -17,27 +17,17 @@ static uint32_t strtouint32(const char *s) static void handle_command(const char *command, const char *arg, struct line_buffer *buf) { - switch (*command) { - case 'b': - if (starts_with(command, "binary ")) { - struct strbuf sb = STRBUF_INIT; - strbuf_addch(&sb, '>'); - buffer_read_binary(buf, &sb, strtouint32(arg)); - fwrite(sb.buf, 1, sb.len, stdout); - strbuf_release(&sb); - return; - } - case 'c': - if (starts_with(command, "copy ")) { - buffer_copy_bytes(buf, strtouint32(arg)); - return; - } - case 's': - if (starts_with(command, "skip ")) { - buffer_skip_bytes(buf, strtouint32(arg)); - return; - } - default: + if (starts_with(command, "binary ")) { + struct strbuf sb = STRBUF_INIT; + strbuf_addch(&sb, '>'); + buffer_read_binary(buf, &sb, strtouint32(arg)); + fwrite(sb.buf, 1, sb.len, stdout); + strbuf_release(&sb); + } else if (starts_with(command, "copy ")) { + buffer_copy_bytes(buf, strtouint32(arg)); + } else if (starts_with(command, "skip ")) { + buffer_skip_bytes(buf, strtouint32(arg)); + } else { die("unrecognized command: %s", command); } } diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c index 75fe883aac..630c76d127 100644 --- a/t/helper/test-parse-options.c +++ b/t/helper/test-parse-options.c @@ -99,6 +99,8 @@ int cmd_main(int argc, const char **argv) const char *prefix = "prefix/"; const char *usage[] = { "test-parse-options <options>", + "", + "A helper function for the parse-options API.", NULL }; struct string_list expect = STRING_LIST_INIT_NODUP; diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 74d2cd76fe..0c2fc81d7b 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -10,6 +10,8 @@ test_description='our own option parser' cat >expect <<\EOF usage: test-parse-options <options> + A helper function for the parse-options API. + --yes get a boolean -D, --no-doubt begins with 'no-' -B, --no-fear be brave diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh index 310f93fd30..a859abedf5 100755 --- a/t/t1502-rev-parse-parseopt.sh +++ b/t/t1502-rev-parse-parseopt.sh @@ -28,6 +28,9 @@ test_expect_success 'setup optionspec' ' |g,fluf?path short and long option optional argument |longest=very-long-argument-hint a very long argument hint |pair=key=value with an equals sign in the hint +|aswitch help te=t contains? fl*g characters!` +|bswitch=hint hint has trailing tab character +|cswitch switch has trailing tab character |short-hint=a with a one symbol hint | |Extras @@ -35,6 +38,25 @@ test_expect_success 'setup optionspec' ' EOF ' +test_expect_success 'setup optionspec-no-switches' ' + sed -e "s/^|//" >optionspec_no_switches <<\EOF +|some-command [options] <args>... +| +|some-command does foo and bar! +|-- +EOF +' + +test_expect_success 'setup optionspec-only-hidden-switches' ' + sed -e "s/^|//" >optionspec_only_hidden_switches <<\EOF +|some-command [options] <args>... +| +|some-command does foo and bar! +|-- +|hidden1* A hidden switch +EOF +' + test_expect_success 'test --parseopt help output' ' sed -e "s/^|//" >expect <<\END_EXPECT && |cat <<\EOF @@ -62,6 +84,9 @@ test_expect_success 'test --parseopt help output' ' | --longest <very-long-argument-hint> | a very long argument hint | --pair <key=value> with an equals sign in the hint +| --aswitch help te=t contains? fl*g characters!` +| --bswitch <hint> hint has trailing tab character +| --cswitch switch has trailing tab character | --short-hint <a> with a one symbol hint | |Extras @@ -73,19 +98,100 @@ END_EXPECT test_i18ncmp expect output ' +test_expect_success 'test --parseopt help output no switches' ' + sed -e "s/^|//" >expect <<\END_EXPECT && +|cat <<\EOF +|usage: some-command [options] <args>... +| +| some-command does foo and bar! +| +|EOF +END_EXPECT + test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec_no_switches && + test_i18ncmp expect output +' + +test_expect_success 'test --parseopt help output hidden switches' ' + sed -e "s/^|//" >expect <<\END_EXPECT && +|cat <<\EOF +|usage: some-command [options] <args>... +| +| some-command does foo and bar! +| +|EOF +END_EXPECT + test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec_only_hidden_switches && + test_i18ncmp expect output +' + +test_expect_success 'test --parseopt help-all output hidden switches' ' + sed -e "s/^|//" >expect <<\END_EXPECT && +|cat <<\EOF +|usage: some-command [options] <args>... +| +| some-command does foo and bar! +| +| --hidden1 A hidden switch +| +|EOF +END_EXPECT + test_expect_code 129 git rev-parse --parseopt -- --help-all > output < optionspec_only_hidden_switches && + test_i18ncmp expect output +' + +test_expect_success 'test --parseopt invalid switch help output' ' + sed -e "s/^|//" >expect <<\END_EXPECT && +|error: unknown option `does-not-exist'\'' +|usage: some-command [options] <args>... +| +| some-command does foo and bar! +| +| -h, --help show the help +| --foo some nifty option --foo +| --bar ... some cool option --bar with an argument +| -b, --baz a short and long option +| +|An option group Header +| -C[...] option C with an optional argument +| -d, --data[=...] short and long option with an optional argument +| +|Argument hints +| -B <arg> short option required argument +| --bar2 <arg> long option required argument +| -e, --fuz <with-space> +| short and long option required argument +| -s[<some>] short option optional argument +| --long[=<data>] long option optional argument +| -g, --fluf[=<path>] short and long option optional argument +| --longest <very-long-argument-hint> +| a very long argument hint +| --pair <key=value> with an equals sign in the hint +| --aswitch help te=t contains? fl*g characters!` +| --bswitch <hint> hint has trailing tab character +| --cswitch switch has trailing tab character +| --short-hint <a> with a one symbol hint +| +|Extras +| --extra1 line above used to cause a segfault but no longer does +| +END_EXPECT + test_expect_code 129 git rev-parse --parseopt -- --does-not-exist 1>/dev/null 2>output < optionspec && + test_i18ncmp expect output +' + test_expect_success 'setup expect.1' " cat > expect <<EOF -set -- --foo --bar 'ham' -b -- 'arg' +set -- --foo --bar 'ham' -b --aswitch -- 'arg' EOF " test_expect_success 'test --parseopt' ' - git rev-parse --parseopt -- --foo --bar=ham --baz arg < optionspec > output && + git rev-parse --parseopt -- --foo --bar=ham --baz --aswitch arg < optionspec > output && test_cmp expect output ' test_expect_success 'test --parseopt with mixed options and arguments' ' - git rev-parse --parseopt -- --foo arg --bar=ham --baz < optionspec > output && + git rev-parse --parseopt -- --foo arg --bar=ham --baz --aswitch < optionspec > output && test_cmp expect output ' diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index d971649979..3ac7ebf85f 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -381,6 +381,262 @@ test_expect_success 'config information was renamed, too' ' test_must_fail git config branch.s/s.dummy ' +test_expect_success 'git branch -m correctly renames multiple config sections' ' + test_when_finished "git checkout master" && + git checkout -b source master && + + # Assert that a config file with multiple config sections has + # those sections preserved... + cat >expect <<-\EOF && + branch.dest.key1=value1 + some.gar.b=age + branch.dest.key2=value2 + EOF + cat >config.branch <<\EOF && +;; Note the lack of -\EOF above & mixed indenting here. This is +;; intentional, we are also testing that the formatting of copied +;; sections is preserved. + +;; Comment for source. Tabs +[branch "source"] + ;; Comment for the source value + key1 = value1 +;; Comment for some.gar. Spaces +[some "gar"] + ;; Comment for the some.gar value + b = age +;; Comment for source, again. Mixed tabs/spaces. +[branch "source"] + ;; Comment for the source value, again + key2 = value2 +EOF + cat config.branch >>.git/config && + git branch -m source dest && + git config -f .git/config -l | grep -F -e source -e dest -e some.gar >actual && + test_cmp expect actual && + + # ...and that the comments for those sections are also + # preserved. + cat config.branch | sed "s/\"source\"/\"dest\"/" >expect && + sed -n -e "/Note the lack/,\$p" .git/config >actual && + test_cmp expect actual +' + +test_expect_success 'git branch -c dumps usage' ' + test_expect_code 128 git branch -c 2>err && + test_i18ngrep "branch name required" err +' + +test_expect_success 'git branch --copy dumps usage' ' + test_expect_code 128 git branch --copy 2>err && + test_i18ngrep "branch name required" err +' + +test_expect_success 'git branch -c d e should work' ' + git branch -l d && + git reflog exists refs/heads/d && + git config branch.d.dummy Hello && + git branch -c d e && + git reflog exists refs/heads/d && + git reflog exists refs/heads/e && + echo Hello >expect && + git config branch.e.dummy >actual && + test_cmp expect actual && + echo Hello >expect && + git config branch.d.dummy >actual && + test_cmp expect actual +' + +test_expect_success 'git branch --copy is a synonym for -c' ' + git branch -l copy && + git reflog exists refs/heads/copy && + git config branch.copy.dummy Hello && + git branch --copy copy copy-to && + git reflog exists refs/heads/copy && + git reflog exists refs/heads/copy-to && + echo Hello >expect && + git config branch.copy.dummy >actual && + test_cmp expect actual && + echo Hello >expect && + git config branch.copy-to.dummy >actual && + test_cmp expect actual +' + +test_expect_success 'git branch -c ee ef should copy ee to create branch ef' ' + git checkout -b ee && + git reflog exists refs/heads/ee && + git config branch.ee.dummy Hello && + git branch -c ee ef && + git reflog exists refs/heads/ee && + git reflog exists refs/heads/ef && + test $(git config branch.ee.dummy) = Hello && + test $(git config branch.ef.dummy) = Hello && + test $(git rev-parse --abbrev-ref HEAD) = ee +' + +test_expect_success 'git branch -c f/f g/g should work' ' + git branch -l f/f && + git reflog exists refs/heads/f/f && + git config branch.f/f.dummy Hello && + git branch -c f/f g/g && + git reflog exists refs/heads/f/f && + git reflog exists refs/heads/g/g && + test $(git config branch.f/f.dummy) = Hello && + test $(git config branch.g/g.dummy) = Hello +' + +test_expect_success 'git branch -c m2 m2 should work' ' + git branch -l m2 && + git reflog exists refs/heads/m2 && + git config branch.m2.dummy Hello && + git branch -c m2 m2 && + git reflog exists refs/heads/m2 && + test $(git config branch.m2.dummy) = Hello +' + +test_expect_success 'git branch -c zz zz/zz should fail' ' + git branch -l zz && + git reflog exists refs/heads/zz && + test_must_fail git branch -c zz zz/zz +' + +test_expect_success 'git branch -c b/b b should fail' ' + git branch -l b/b && + test_must_fail git branch -c b/b b +' + +test_expect_success 'git branch -C o/q o/p should work when o/p exists' ' + git branch -l o/q && + git reflog exists refs/heads/o/q && + git reflog exists refs/heads/o/p && + git branch -C o/q o/p +' + +test_expect_success 'git branch -c -f o/q o/p should work when o/p exists' ' + git reflog exists refs/heads/o/q && + git reflog exists refs/heads/o/p && + git branch -c -f o/q o/p +' + +test_expect_success 'git branch -c qq rr/qq should fail when r exists' ' + git branch qq && + git branch rr && + test_must_fail git branch -c qq rr/qq +' + +test_expect_success 'git branch -C b1 b2 should fail when b2 is checked out' ' + git branch b1 && + git checkout -b b2 && + test_must_fail git branch -C b1 b2 +' + +test_expect_success 'git branch -C c1 c2 should succeed when c1 is checked out' ' + git checkout -b c1 && + git branch c2 && + git branch -C c1 c2 && + test $(git rev-parse --abbrev-ref HEAD) = c1 +' + +test_expect_success 'git branch -C c1 c2 should never touch HEAD' ' + msg="Branch: copied refs/heads/c1 to refs/heads/c2" && + ! grep "$msg$" .git/logs/HEAD +' + +test_expect_success 'git branch -C master should work when master is checked out' ' + git checkout master && + git branch -C master +' + +test_expect_success 'git branch -C master master should work when master is checked out' ' + git checkout master && + git branch -C master master +' + +test_expect_success 'git branch -C master5 master5 should work when master is checked out' ' + git checkout master && + git branch master5 && + git branch -C master5 master5 +' + +test_expect_success 'git branch -C ab cd should overwrite existing config for cd' ' + git branch -l cd && + git reflog exists refs/heads/cd && + git config branch.cd.dummy CD && + git branch -l ab && + git reflog exists refs/heads/ab && + git config branch.ab.dummy AB && + git branch -C ab cd && + git reflog exists refs/heads/ab && + git reflog exists refs/heads/cd && + test $(git config branch.ab.dummy) = AB && + test $(git config branch.cd.dummy) = AB +' + +test_expect_success 'git branch -c correctly copies multiple config sections' ' + FOO=1 && + export FOO && + test_when_finished "git checkout master" && + git checkout -b source2 master && + + # Assert that a config file with multiple config sections has + # those sections preserved... + cat >expect <<-\EOF && + branch.source2.key1=value1 + branch.dest2.key1=value1 + more.gar.b=age + branch.source2.key2=value2 + branch.dest2.key2=value2 + EOF + cat >config.branch <<\EOF && +;; Note the lack of -\EOF above & mixed indenting here. This is +;; intentional, we are also testing that the formatting of copied +;; sections is preserved. + +;; Comment for source2. Tabs +[branch "source2"] + ;; Comment for the source2 value + key1 = value1 +;; Comment for more.gar. Spaces +[more "gar"] + ;; Comment for the more.gar value + b = age +;; Comment for source2, again. Mixed tabs/spaces. +[branch "source2"] + ;; Comment for the source2 value, again + key2 = value2 +EOF + cat config.branch >>.git/config && + git branch -c source2 dest2 && + git config -f .git/config -l | grep -F -e source2 -e dest2 -e more.gar >actual && + test_cmp expect actual && + + # ...and that the comments and formatting for those sections + # is also preserved. + cat >expect <<\EOF && +;; Comment for source2. Tabs +[branch "source2"] + ;; Comment for the source2 value + key1 = value1 +;; Comment for more.gar. Spaces +[branch "dest2"] + ;; Comment for the source2 value + key1 = value1 +;; Comment for more.gar. Spaces +[more "gar"] + ;; Comment for the more.gar value + b = age +;; Comment for source2, again. Mixed tabs/spaces. +[branch "source2"] + ;; Comment for the source2 value, again + key2 = value2 +[branch "dest2"] + ;; Comment for the source2 value, again + key2 = value2 +EOF + sed -n -e "/Comment for source2/,\$p" .git/config >actual && + test_cmp expect actual +' + test_expect_success 'deleting a symref' ' git branch target && git symbolic-ref refs/heads/symref refs/heads/target && diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 37821d2454..3704dbb2ec 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1249,20 +1249,13 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' ' test B = $(git cat-file commit HEAD^ | sed -ne \$p) ' -cat >expect <<EOF -Warning: the command isn't recognized in the following line: - - badcmd $(git rev-list --oneline -1 master~1) - -You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'. -Or you can abort the rebase with 'git rebase --abort'. -EOF - test_expect_success 'static check of bad command' ' rebase_setup_and_clean bad-cmd && set_fake_editor && test_must_fail env FAKE_LINES="1 2 3 bad 4 5" \ git rebase -i --root 2>actual && - test_i18ncmp expect actual && + test_i18ngrep "badcmd $(git rev-list --oneline -1 master~1)" actual && + test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual && FAKE_LINES="1 2 3 drop 4 5" git rebase --edit-todo && git rebase --continue && test E = $(git cat-file commit HEAD | sed -ne \$p) && @@ -1284,20 +1277,13 @@ test_expect_success 'tabs and spaces are accepted in the todolist' ' test E = $(git cat-file commit HEAD | sed -ne \$p) ' -cat >expect <<EOF -Warning: the SHA-1 is missing or isn't a commit in the following line: - - edit XXXXXXX False commit - -You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'. -Or you can abort the rebase with 'git rebase --abort'. -EOF - test_expect_success 'static check of bad SHA-1' ' rebase_setup_and_clean bad-sha && set_fake_editor && test_must_fail env FAKE_LINES="1 2 edit fakesha 3 4 5 #" \ git rebase -i --root 2>actual && - test_i18ncmp expect actual && + test_i18ngrep "edit XXXXXXX False commit" actual && + test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual && FAKE_LINES="1 2 4 5 6" git rebase --edit-todo && git rebase --continue && test E = $(git cat-file commit HEAD | sed -ne \$p) diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index 5848949ec3..e364c12622 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -271,6 +271,18 @@ test_expect_success C_LOCALE_OUTPUT 'autosquash with custom inst format' ' test 2 = $(git cat-file commit HEAD^ | grep squash | wc -l) ' +test_expect_success 'autosquash with empty custom instructionFormat' ' + git reset --hard base && + test_commit empty-instructionFormat-test && + ( + set_cat_todo_editor && + test_must_fail git -c rebase.instructionFormat= \ + rebase --autosquash --force -i HEAD^ >actual && + git log -1 --format="pick %h %s" >expect && + test_cmp expect actual + ) +' + set_backup_editor () { write_script backup-editor.sh <<-\EOF cp "$1" .git/backup-"$(basename "$1")" @@ -278,7 +290,7 @@ set_backup_editor () { test_set_editor "$PWD/backup-editor.sh" } -test_expect_failure 'autosquash with multiple empty patches' ' +test_expect_success 'autosquash with multiple empty patches' ' test_tick && git commit --allow-empty -m "empty" && test_tick && @@ -304,4 +316,18 @@ test_expect_success 'extra spaces after fixup!' ' test $base = $parent ' +test_expect_success 'wrapped original subject' ' + if test -d .git/rebase-merge; then git rebase --abort; fi && + base=$(git rev-parse HEAD) && + echo "wrapped subject" >wrapped && + git add wrapped && + test_tick && + git commit --allow-empty -m "$(printf "To\nfixup")" && + test_tick && + git commit --allow-empty -m "fixup! To fixup" && + git rebase -i --autosquash --keep-empty HEAD~2 && + parent=$(git rev-parse HEAD^) && + test $base = $parent +' + test_done diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 3d45dc2955..1c0e8659d9 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -182,10 +182,41 @@ check_describe "test2-lightweight-*" --tags --match="test2-*" check_describe "test2-lightweight-*" --long --tags --match="test2-*" HEAD^ -check_describe "test1-lightweight-*" --long --tags --match="test1-*" --match="test2-*" HEAD^ +check_describe "test2-lightweight-*" --long --tags --match="test1-*" --match="test2-*" HEAD^ check_describe "test2-lightweight-*" --long --tags --match="test1-*" --no-match --match="test2-*" HEAD^ +check_describe "test1-lightweight-*" --long --tags --match="test1-*" --match="test3-*" HEAD + +check_describe "test1-lightweight-*" --long --tags --match="test3-*" --match="test1-*" HEAD + +test_expect_success 'set-up branches' ' + git branch branch_A A && + git branch branch_C c && + git update-ref refs/remotes/origin/remote_branch_A "A^{commit}" && + git update-ref refs/remotes/origin/remote_branch_C "c^{commit}" && + git update-ref refs/original/original_branch_A test-annotated~2 +' + +check_describe "heads/branch_A*" --all --match="branch_*" --exclude="branch_C" HEAD + +check_describe "remotes/origin/remote_branch_A*" --all --match="origin/remote_branch_*" --exclude="origin/remote_branch_C" HEAD + +check_describe "original/original_branch_A*" --all test-annotated~1 + +test_expect_success '--match does not work for other types' ' + test_must_fail git describe --all --match="*original_branch_*" test-annotated~1 +' + +test_expect_success '--exclude does not work for other types' ' + R=$(git describe --all --exclude="any_pattern_even_not_matching" test-annotated~1) && + case "$R" in + *original_branch_A*) echo "fail: Found unknown reference $R with --exclude" + false;; + *) echo ok: Found some known type;; + esac +' + test_expect_success 'name-rev with exact tags' ' echo A >expect && tag_object=$(git rev-parse refs/tags/A) && diff --git a/t/t8010-cat-file-filters.sh b/t/t8010-cat-file-filters.sh index d8242e467e..0f86c19174 100755 --- a/t/t8010-cat-file-filters.sh +++ b/t/t8010-cat-file-filters.sh @@ -51,6 +51,11 @@ test_expect_success '--path=<path> complains without --textconv/--filters' ' grep "path.*needs.*filters" err ' +test_expect_success '--textconv/--filters complain without path' ' + test_must_fail git cat-file --textconv HEAD && + test_must_fail git cat-file --filters HEAD +' + test_expect_success 'cat-file --textconv --batch works' ' sha1=$(git rev-parse -q --verify HEAD:world.txt) && test_config diff.txt.textconv "tr A-Za-z N-ZA-Mn-za-m <" && diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 8dcb05c4a5..866ddf6058 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -234,7 +234,7 @@ test_expect_success 'fast-export -C -C | fast-import' ' mkdir new && git --git-dir=new/.git init && git fast-export -C -C --signed-tags=strip --all > output && - grep "^C file6 file7\$" output && + grep "^C file2 file4\$" output && cat output | (cd new && git fast-import && @@ -522,4 +522,22 @@ test_expect_success 'delete refspec' ' test_cmp expected actual ' +test_expect_success 'when using -C, do not declare copy when source of copy is also modified' ' + test_create_repo src && + echo a_line >src/file.txt && + git -C src add file.txt && + git -C src commit -m 1st_commit && + + cp src/file.txt src/file2.txt && + echo another_line >>src/file.txt && + git -C src add file.txt file2.txt && + git -C src commit -m 2nd_commit && + + test_create_repo dst && + git -C src fast-export --all -C | git -C dst fast-import && + git -C src show >expected && + git -C dst show >actual && + test_cmp expected actual +' + test_done diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index 432c61d246..c30660d606 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -588,4 +588,52 @@ test_expect_success 'cvs annotate' ' test_cmp ../expect ../actual ' +#------------ +# running via git-shell +#------------ + +cd "$WORKDIR" + +test_expect_success 'create remote-cvs helper' ' + write_script remote-cvs <<-\EOF + exec git shell -c "cvs server" + EOF +' + +test_expect_success 'cvs server does not run with vanilla git-shell' ' + ( + cd cvswork && + CVS_SERVER=$WORKDIR/remote-cvs && + export CVS_SERVER && + test_must_fail cvs log merge + ) +' + +test_expect_success 'configure git shell to run cvs server' ' + mkdir "$HOME"/git-shell-commands && + + write_script "$HOME"/git-shell-commands/cvs <<-\EOF && + if ! test $# = 1 && test "$1" = "server" + then + echo >&2 "git-cvsserver only handles \"server\"" + exit 1 + fi + exec git cvsserver server + EOF + + # Should not be used, but part of the recommended setup + write_script "$HOME"/git-shell-commands/no-interactive-login <<-\EOF + echo Interactive login forbidden + EOF +' + +test_expect_success 'cvs server can run with recommended config' ' + ( + cd cvswork && + CVS_SERVER=$WORKDIR/remote-cvs && + export CVS_SERVER && + cvs log merge + ) +' + test_done diff --git a/transport.c b/transport.c index d75ff0514d..fb8c01e57a 100644 --- a/transport.c +++ b/transport.c @@ -26,7 +26,6 @@ static void set_upstreams(struct transport *transport, struct ref *refs, const char *localname; const char *tmp; const char *remotename; - unsigned char sha[20]; int flag = 0; /* * Check suitability for tracking. Must be successful / @@ -44,7 +43,7 @@ static void set_upstreams(struct transport *transport, struct ref *refs, localname = ref->peer_ref->name; remotename = ref->name; tmp = resolve_ref_unsafe(localname, RESOLVE_REF_READING, - sha, &flag); + NULL, &flag); if (tmp && flag & REF_ISSYMREF && starts_with(tmp, "refs/heads/")) localname = tmp; diff --git a/tree-walk.c b/tree-walk.c index c99309069a..684f0e3373 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -582,12 +582,11 @@ enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_s int retval = MISSING_OBJECT; struct dir_state *parents = NULL; size_t parents_alloc = 0; - ssize_t parents_nr = 0; + size_t i, parents_nr = 0; unsigned char current_tree_sha1[20]; struct strbuf namebuf = STRBUF_INIT; struct tree_desc t; int follows_remaining = GET_TREE_ENTRY_FOLLOW_SYMLINKS_MAX_LINKS; - int i; init_tree_desc(&t, NULL, 0UL); strbuf_addstr(&namebuf, name); diff --git a/upload-pack.c b/upload-pack.c index 7efff2fbfd..e25f725c0f 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -888,7 +888,7 @@ static void receive_needs(void) } shallow_nr += shallows.nr; - free(shallows.objects); + object_array_clear(&shallows); } /* return non-zero if the ref is hidden, otherwise 0 */ @@ -965,11 +965,10 @@ static int find_symref(const char *refname, const struct object_id *oid, { const char *symref_target; struct string_list_item *item; - struct object_id unused; if ((flag & REF_ISSYMREF) == 0) return 0; - symref_target = resolve_ref_unsafe(refname, 0, unused.hash, &flag); + symref_target = resolve_ref_unsafe(refname, 0, NULL, &flag); if (!symref_target || (flag & REF_ISSYMREF) == 0) die("'%s' is a symref but it is not?", refname); item = string_list_append(cb_data, refname); diff --git a/userdiff.c b/userdiff.c index 6321103ce2..dbfb4e13cd 100644 --- a/userdiff.c +++ b/userdiff.c @@ -38,7 +38,7 @@ IPATTERN("fortran", "|//|\\*\\*|::|[/<>=]="), IPATTERN("fountain", "^((\\.[^.]|(int|ext|est|int\\.?/ext|i/e)[. ]).*)$", "[^ \t-]+"), -PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$", +PATTERNS("html", "^[ \t]*(<[Hh][1-6]([ \t].*)?>.*)$", "[^<>= \t]+"), PATTERNS("java", "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n" diff --git a/worktree.c b/worktree.c index 8aaeea0377..70015629dc 100644 --- a/worktree.c +++ b/worktree.c @@ -307,7 +307,6 @@ const struct worktree *find_shared_symref(const char *symref, for (i = 0; worktrees[i]; i++) { struct worktree *wt = worktrees[i]; const char *symref_target; - unsigned char sha1[20]; struct ref_store *refs; int flags; @@ -327,7 +326,7 @@ const struct worktree *find_shared_symref(const char *symref, refs = get_worktree_ref_store(wt); symref_target = refs_resolve_ref_unsafe(refs, symref, 0, - sha1, &flags); + NULL, &flags); if ((flags & REF_ISSYMREF) && !strcmp(symref_target, target)) { existing = wt; break; |