diff options
253 files changed, 7201 insertions, 4145 deletions
diff --git a/.gitignore b/.gitignore index 388cc4beee..3284a1e9b1 100644 --- a/.gitignore +++ b/.gitignore @@ -119,6 +119,7 @@ /git-rebase--helper /git-rebase--interactive /git-rebase--merge +/git-rebase--preserve-merges /git-receive-pack /git-reflog /git-remote @@ -35,11 +35,13 @@ Chris Wright <chrisw@sous-sol.org> <chrisw@osdl.org> Cord Seele <cowose@gmail.com> <cowose@googlemail.com> Christian Couder <chriscool@tuxfamily.org> <christian.couder@gmail.com> Christian Stimming <stimming@tuhh.de> <chs@ckiste.goetheallee> +Christopher DÃaz Riveros <chrisadr@gentoo.org> Christopher Diaz Riveros Csaba Henk <csaba@gluster.com> <csaba@lowlife.hu> Dan Johnson <computerdruid@gmail.com> Dana L. How <danahow@gmail.com> <how@deathvalley.cswitch.com> Dana L. How <danahow@gmail.com> Dana How Daniel Barkalow <barkalow@iabervon.org> +Daniel Knittl-Frank <knittl89@googlemail.com> knittl Daniel Trstenjak <daniel.trstenjak@gmail.com> <daniel.trstenjak@online.de> Daniel Trstenjak <daniel.trstenjak@gmail.com> <trsten@science-computing.de> David Brown <git@davidb.org> <davidb@quicinc.com> @@ -57,6 +59,7 @@ Eric S. Raymond <esr@thyrsus.com> Eric Wong <e@80x24.org> <normalperson@yhbt.net> Erik Faye-Lund <kusmabite@gmail.com> <kusmabite@googlemail.com> Eyvind Bernhardsen <eyvind.bernhardsen@gmail.com> <eyvind-git@orakel.ntnu.no> +Fangyi Zhou <fangyi.zhou@yuriko.moe> Zhou Fangyi Florian Achleitner <florian.achleitner.2.6.31@gmail.com> <florian.achleitner2.6.31@gmail.com> Franck Bui-Huu <vagabon.xyz@gmail.com> <fbuihuu@gmail.com> Frank Lichtenheld <frank@lichtenheld.de> <djpig@debian.org> @@ -86,6 +89,8 @@ Jason McMullan <mcmullan@netapp.com> Jason Riedy <ejr@eecs.berkeley.edu> <ejr@EECS.Berkeley.EDU> Jason Riedy <ejr@eecs.berkeley.edu> <ejr@cs.berkeley.edu> Jay Soffian <jaysoffian@gmail.com> <jaysoffian+git@gmail.com> +Jean-Noël Avila <jn.avila@free.fr> Jean-Noel Avila +Jean-Noël Avila <jn.avila@free.fr> Jean-Noël AVILA Jeff King <peff@peff.net> <peff@github.com> Jeff Muizelaar <jmuizelaar@mozilla.com> <jeff@infidigm.net> Jens Axboe <axboe@kernel.dk> <axboe@suse.de> @@ -149,6 +154,7 @@ Matt Draisey <matt@draisey.ca> <mattdraisey@sympatico.ca> Matt Kraai <kraai@ftbfs.org> <matt.kraai@amo.abbott.com> Matt McCutchen <matt@mattmccutchen.net> <hashproduct@gmail.com> Matthias Kestenholz <matthias@spinlock.ch> <mk@spinlock.ch> +Matthias Rüster <matthias.ruester@gmail.com> Matthias Ruester Matthias Urlichs <matthias@urlichs.de> <smurf@kiste.(none)> Matthias Urlichs <matthias@urlichs.de> <smurf@smurf.noris.de> Michael Coleman <tutufan@gmail.com> @@ -213,6 +219,8 @@ Sean Estabrooks <seanlkml@sympatico.ca> Sebastian Schuberth <sschuberth@gmail.com> <sschuberth@visageimaging.com> Seth Falcon <seth@userprimary.net> <sfalcon@fhcrc.org> Shawn O. Pearce <spearce@spearce.org> +Wei Shuyu <wsy@dogben.com> Shuyu Wei +Sidhant Sharma <tigerkid001@gmail.com> Sidhant Sharma [:tk] Simon Hausmann <hausmann@kde.org> <simon@lst.de> Simon Hausmann <hausmann@kde.org> <shausman@trolltech.com> Stefan Beller <stefanbeller@gmail.com> <stefanbeller@googlemail.com> @@ -253,7 +261,8 @@ Uwe Kleine-König <u.kleine-koenig@pengutronix.de> <ukleinek@informatik.uni-frei Uwe Kleine-König <u.kleine-koenig@pengutronix.de> <uzeisberger@io.fsforth.de> Uwe Kleine-König <u.kleine-koenig@pengutronix.de> <zeisberg@informatik.uni-freiburg.de> Ville Skyttä <ville.skytta@iki.fi> <scop@xemacs.org> -Vitaly "_Vi" Shukela <public_vi@tut.by> +Vitaly "_Vi" Shukela <vi0oss@gmail.com> <public_vi@tut.by> +Vitaly "_Vi" Shukela <vi0oss@gmail.com> Vitaly _Vi Shukela W. Trevor King <wking@tremily.us> <wking@drexel.edu> William Pursell <bill.pursell@gmail.com> YONETANI Tomokazu <y0n3t4n1@gmail.com> <qhwt+git@les.ath.cx> diff --git a/Documentation/RelNotes/2.19.0.txt b/Documentation/RelNotes/2.19.0.txt new file mode 100644 index 0000000000..f2e261abf3 --- /dev/null +++ b/Documentation/RelNotes/2.19.0.txt @@ -0,0 +1,159 @@ +Git 2.19 Release Notes +====================== + +Updates since v2.18 +------------------- + +UI, Workflows & Features + + * "git diff" compares the index and the working tree. For paths + added with intent-to-add bit, the command shows the full contents + of them as added, but the paths themselves were not marked as new + files. They are now shown as new by default. + + "git apply" learned the "--intent-to-add" option so that an + otherwise working-tree-only application of a patch will add new + paths to the index marked with the "intent-to-add" bit. + + * "git grep" learned the "--column" option that gives not just the + line number but the column number of the hit. + + * The "-l" option in "git branch -l" is an unfortunate short-hand for + "--create-reflog", but many users, both old and new, somehow expect + it to be something else, perhaps "--list". This step warns when "-l" + is used as a short-hand for "--create-reflog" and warns about the + future repurposing of the it when it is used. + + +Performance, Internal Implementation, Development Support etc. + + * The bulk of "git submodule foreach" has been rewritten in C. + + * The in-core "commit" object had an all-purpose "void *util" field, + which was tricky to use especially in library-ish part of the + code. All of the existing uses of the field has been migrated to a + more dedicated "commit-slab" mechanism and the field is eliminated. + + * A less often used command "git show-index" has been modernized. + (merge fb3010c31f jk/show-index later to maint). + + * The conversion to pass "the_repository" and then "a_repository" + throughout the object access API continues. + + * Continuing with the idea to programatically enumerate various + pieces of data required for command line completion, teach the + codebase to report the list of configuration variables + subcommands care about to help complete them. + + * Separate "rebase -p" codepath out of "rebase -i" implementation to + slim down the latter and make it easier to manage. + + * Make refspec parsing codepath more robust. + + * Some flaky tests have been fixed. + + * Continuing with the idea to programmatically enumerate various + pieces of data required for command line completion, the codebase + has been taught to enumerate options prefixed with "--no-" to + negate them. + + * Build and test procedure for netrc credential helper (in contrib/) + has been updated. + + * The conversion to pass "the_repository" and then "a_repository" + throughout the object access API continues. + + * Remove unused function definitions and declarations from ewah + bitmap subsystem. + + * Code preparation to make "git p4" closer to be usable with Python 3. + + * Tighten the API to make it harder to misuse in-tree .gitmodules + file, even though it shares the same syntax with configuration + files, to read random configuration items from it. + + +Fixes since v2.18 +----------------- + + * "git remote update" can take both a single remote nickname and a + nickname for remote groups, and the completion script (in contrib/) + has been taught about it. + (merge 9cd4382ad5 ls/complete-remote-update-names later to maint). + + * "git fetch --shallow-since=<cutoff>" that specifies the cut-off + point that is newer than the existing history used to end up + grabbing the entire history. Such a request now errors out. + (merge e34de73c56 nd/reject-empty-shallow-request later to maint). + + * Fix for 2.17-era regression around `core.safecrlf`. + (merge 6cb09125be as/safecrlf-quiet-fix later to maint). + + * The recent addition of "partial clone" experimental feature kicked + in when it shouldn't, namely, when there is no partial-clone filter + defined even if extensions.partialclone is set. + (merge cac1137dc4 jh/partial-clone later to maint). + + * "git send-pack --signed" (hence "git push --signed" over the http + transport) did not read user ident from the config mechanism to + determine whom to sign the push certificate as, which has been + corrected. + (merge d067d98887 ms/send-pack-honor-config later to maint). + + * "git fetch-pack --all" used to unnecessarily fail upon seeing an + annotated tag that points at an object other than a commit. + (merge c12c9df527 jk/fetch-all-peeled-fix later to maint). + + * When user edits the patch in "git add -p" and the user's editor is + set to strip trailing whitespaces indiscriminately, an empty line + that is unchanged in the patch would become completely empty + (instead of a line with a sole SP on it). The code introduced in + Git 2.17 timeframe failed to parse such a patch, but now it learned + to notice the situation and cope with it. + (merge f4d35a6b49 pw/add-p-recount later to maint). + + * The code to try seeing if a fetch is necessary in a submodule + during a fetch with --recurse-submodules got confused when the path + to the submodule was changed in the range of commits in the + superproject, sometimes showing "(null)". This has been corrected. + + * "git submodule" did not correctly adjust core.worktree setting that + indicates whether/where a submodule repository has its associated + working tree across various state transitions, which has been + corrected. + (merge 984cd77ddb sb/submodule-core-worktree later to maint). + + * Bugfix for "rebase -i" corner case regression. + (merge a9279c6785 pw/rebase-i-keep-reword-after-conflict later to maint). + + * Recently added "--base" option to "git format-patch" command did + not correctly generate prereq patch ids. + (merge 15b76c1fb3 xy/format-patch-prereq-patch-id-fix later to maint). + + * POSIX portability fix in Makefile to fix a glitch introduced a few + releases ago. + (merge 6600054e9b dj/runtime-prefix later to maint). + + * "git filter-branch" when used with the "--state-branch" option + still attempted to rewrite the commits whose filtered result is + known from the previous attempt (which is recorded on the state + branch); the command has been corrected not to waste cycles doing + so. + (merge 709cfe848a mb/filter-branch-optim later to maint). + + * Clarify that setting core.ignoreCase to deviate from reality would + not turn a case-incapable filesystem into a case-capable one. + (merge 48294b512a ms/core-icase-doc later to maint). + + * Code cleanup, docfix, build fix, etc. + (merge aee9be2ebe sg/update-ref-stdin-cleanup later to maint). + (merge 037714252f jc/clean-after-sanity-tests later to maint). + (merge 5b26c3c941 en/merge-recursive-cleanup later to maint). + (merge 0dcbc0392e bw/config-refer-to-gitsubmodules-doc later to maint). + (merge bb4d000e87 bw/protocol-v2 later to maint). + (merge 928f0ab4ba vs/typofixes later to maint). + (merge d7f590be84 en/rebase-i-microfixes later to maint). + (merge 81d395cc85 js/rebase-recreate-merge later to maint). + (merge 51d1863168 tz/exclude-doc-smallfixes later to maint). + (merge a9aa3c0927 ds/commit-graph later to maint). + (merge 5cf8e06474 js/enhanced-version-info later to maint). diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 2488544407..b44fd51f27 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -298,7 +298,7 @@ smaller project it is a good discipline to follow it. The sign-off is a simple line at the end of the explanation for the patch, which certifies that you wrote it or otherwise have -the right to pass it on as a open-source patch. The rules are +the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify the below D-C-O: [[dco]] @@ -403,7 +403,7 @@ don't demand). +git log -p {litdd} _$area_you_are_modifying_+ would help you find out who they are. . You get comments and suggestions for improvements. You may - even get them in a "on top of your change" patch form. + even get them in an "on top of your change" patch form. . Polish, refine, and re-send to the list and the people who spend their time to improve your patch. Go back to step (2). diff --git a/Documentation/config.txt b/Documentation/config.txt index ab641bf5a9..43b2de7b5f 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -354,7 +354,7 @@ advice.*:: Advice on what to do when you've accidentally added one git repo inside of another. ignoredHook:: - Advice shown if an hook is ignored because the hook is not + Advice shown if a hook is ignored because the hook is not set as executable. waitingForEditor:: Print a message to the terminal whenever Git is waiting for @@ -390,16 +390,19 @@ core.hideDotFiles:: default mode is 'dotGitOnly'. core.ignoreCase:: - If true, this option enables various workarounds to enable + Internal variable which enables various workarounds to enable Git to work better on filesystems that are not case sensitive, - like FAT. For example, if a directory listing finds - "makefile" when Git expects "Makefile", Git will assume + like APFS, HFS+, FAT, NTFS, etc. For example, if a directory listing + finds "makefile" when Git expects "Makefile", Git will assume it is really the same file, and continue to remember it as "Makefile". + The default is false, except linkgit:git-clone[1] or linkgit:git-init[1] will probe and set core.ignoreCase true if appropriate when the repository is created. ++ +Git relies on the proper configuration of this variable for your operating +and file system. Modifying this value may result in unexpected behavior. core.precomposeUnicode:: This option is only used by Mac OS implementation of Git. @@ -1162,7 +1165,8 @@ color.diff.<slot>:: color.decorate.<slot>:: Use customized color for 'git log --decorate' output. `<slot>` is one of `branch`, `remoteBranch`, `tag`, `stash` or `HEAD` for local - branches, remote-tracking branches, tags, stash and HEAD, respectively. + branches, remote-tracking branches, tags, stash and HEAD, respectively + and `grafted` for grafted commits. color.grep:: When set to `always`, always highlight matches. When `false` (or @@ -1181,8 +1185,10 @@ color.grep.<slot>:: filename prefix (when not using `-h`) `function`;; function name lines (when using `-p`) -`linenumber`;; +`lineNumber`;; line number prefix (when using `-n`) +`column`;; + column number prefix (when using `--column`) `match`;; matching text (same as setting `matchContext` and `matchSelected`) `matchContext`;; @@ -1797,6 +1803,9 @@ gitweb.snapshot:: grep.lineNumber:: If set to true, enable `-n` option by default. +grep.column:: + If set to true, enable the `--column` option by default. + grep.patternType:: Set the default matching behavior. Using a value of 'basic', 'extended', 'fixed', or 'perl' will enable the `--basic-regexp`, `--extended-regexp`, @@ -3327,12 +3336,13 @@ submodule.<name>.ignore:: submodule.<name>.active:: Boolean value indicating if the submodule is of interest to git commands. This config option takes precedence over the - submodule.active config option. + submodule.active config option. See linkgit:gitsubmodules[7] for + details. submodule.active:: A repeated field which contains a pathspec used to match against a submodule's path to determine if the submodule is of interest to git - commands. + commands. See linkgit:gitsubmodules[7] for details. submodule.recurse:: Specifies if commands recurse into submodules by default. This @@ -3479,6 +3489,13 @@ Note that this configuration variable is ignored if it is seen in the repository-level config (this is a safety measure against fetching from untrusted repositories). +uploadpack.allowRefInWant:: + If this option is set, `upload-pack` will support the `ref-in-want` + feature of the protocol version 2 `fetch` command. This feature + is intended for the benefit of load-balanced servers which may + not have the same view of what OIDs their refs point to due to + replication delay. + url.<base>.insteadOf:: Any URL that starts with this value will be rewritten to start, instead, with <base>. In cases where some site serves a diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 67228494c0..b9aa39000f 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -9,7 +9,7 @@ git-apply - Apply a patch to files and/or to the index SYNOPSIS -------- [verse] -'git apply' [--stat] [--numstat] [--summary] [--check] [--index] [--3way] +'git apply' [--stat] [--numstat] [--summary] [--check] [--index | --intent-to-add] [--3way] [--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse] [--allow-binary-replacement | --binary] [--reject] [-z] [-p<n>] [-C<n>] [--inaccurate-eof] [--recount] [--cached] @@ -74,6 +74,14 @@ OPTIONS cached data, apply the patch, and store the result in the index without using the working tree. This implies `--index`. +--intent-to-add:: + When applying the patch only to the working tree, mark new + files to be added to the index later (see `--intent-to-add` + option in linkgit:git-add[1]). This option is ignored unless + running in a Git repository and `--index` is not specified. + Note that `--index` could be implied by other options such + as `--cached` or `--3way`. + -3:: --3way:: When the patch does not apply cleanly, fall back on 3-way merge if diff --git a/Documentation/git-bisect-lk2009.txt b/Documentation/git-bisect-lk2009.txt index 78479b003e..0f9ef2f25e 100644 --- a/Documentation/git-bisect-lk2009.txt +++ b/Documentation/git-bisect-lk2009.txt @@ -1103,7 +1103,7 @@ _____________ Combining test suites, git bisect and other systems together ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We have seen that test suites an git bisect are very powerful when +We have seen that test suites and git bisect are very powerful when used together. It can be even more powerful if you can combine them with other systems. diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 02eccbb931..1072ca0eb6 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -91,7 +91,6 @@ OPTIONS -D:: Shortcut for `--delete --force`. --l:: --create-reflog:: Create the branch's reflog. This activates recording of all changes made to the branch ref, enabling use of date @@ -101,6 +100,8 @@ OPTIONS The negated form `--no-create-reflog` only overrides an earlier `--create-reflog`, but currently does not negate the setting of `core.logAllRefUpdates`. ++ +The `-l` option is a deprecated synonym for `--create-reflog`. -f:: --force:: diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 312409a607..0de3493b80 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -13,7 +13,7 @@ SYNOPSIS [-v | --invert-match] [-h|-H] [--full-name] [-E | --extended-regexp] [-G | --basic-regexp] [-P | --perl-regexp] - [-F | --fixed-strings] [-n | --line-number] + [-F | --fixed-strings] [-n | --line-number] [--column] [-l | --files-with-matches] [-L | --files-without-match] [(-O | --open-files-in-pager) [<pager>]] [-z | --null] @@ -44,6 +44,9 @@ CONFIGURATION grep.lineNumber:: If set to true, enable `-n` option by default. +grep.column:: + If set to true, enable the `--column` option by default. + grep.patternType:: Set the default matching behavior. Using a value of 'basic', 'extended', 'fixed', or 'perl' will enable the `--basic-regexp`, `--extended-regexp`, @@ -169,6 +172,10 @@ providing this option will cause it to die. --line-number:: Prefix the line number to matching lines. +--column:: + Prefix the 1-indexed byte-offset of the first match from the start of the + matching line. + -l:: --files-with-matches:: --name-only:: diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt index a40fc38d8b..83d25d825a 100644 --- a/Documentation/git-help.txt +++ b/Documentation/git-help.txt @@ -45,6 +45,11 @@ OPTIONS When used with `--verbose` print description for all recognized commands. +-c:: +--config:: + List all available configuration variables. This is a short + summary of the list in linkgit:git-config[1]. + -g:: --guides:: Prints a list of useful guides on the standard output. This diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt index 032613c420..7b157441eb 100644 --- a/Documentation/git-imap-send.txt +++ b/Documentation/git-imap-send.txt @@ -68,8 +68,8 @@ imap.tunnel:: to the server. Required when imap.host is not set. imap.host:: - A URL identifying the server. Use a `imap://` prefix for non-secure - connections and a `imaps://` prefix for secure connections. + A URL identifying the server. Use an `imap://` prefix for non-secure + connections and an `imaps://` prefix for secure connections. Ignored when imap.tunnel is set, but required otherwise. imap.user:: diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index e8dec1b3c8..df2b64dbb6 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -199,7 +199,7 @@ OPTIONS .git/NOTES_MERGE_REF symref is updated to the resulting commit. --abort:: - Abort/reset a in-progress 'git notes merge', i.e. a notes merge + Abort/reset an in-progress 'git notes merge', i.e. a notes merge with conflicts. This simply removes all files related to the notes merge. diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 0e20a66e73..a7850415b1 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -243,11 +243,15 @@ leave out at most one of A and B, in which case it defaults to HEAD. --keep-empty:: Keep the commits that do not change anything from its parents in the result. ++ +See also INCOMPATIBLE OPTIONS below. --allow-empty-message:: By default, rebasing commits with an empty message will fail. This option overrides that behavior, allowing commits with empty messages to be rebased. ++ +See also INCOMPATIBLE OPTIONS below. --skip:: Restart the rebasing process by skipping the current patch. @@ -271,6 +275,8 @@ branch on top of the <upstream> branch. Because of this, when a merge conflict happens, the side reported as 'ours' is the so-far rebased series, starting with <upstream>, and 'theirs' is the working branch. In other words, the sides are swapped. ++ +See also INCOMPATIBLE OPTIONS below. -s <strategy>:: --strategy=<strategy>:: @@ -280,8 +286,10 @@ other words, the sides are swapped. + Because 'git rebase' replays each commit from the working branch on top of the <upstream> branch using the given strategy, using -the 'ours' strategy simply discards all patches from the <branch>, +the 'ours' strategy simply empties all patches from the <branch>, which makes little sense. ++ +See also INCOMPATIBLE OPTIONS below. -X <strategy-option>:: --strategy-option=<strategy-option>:: @@ -289,6 +297,8 @@ which makes little sense. This implies `--merge` and, if no strategy has been specified, `-s recursive`. Note the reversal of 'ours' and 'theirs' as noted above for the `-m` option. ++ +See also INCOMPATIBLE OPTIONS below. -S[<keyid>]:: --gpg-sign[=<keyid>]:: @@ -324,17 +334,21 @@ which makes little sense. and after each change. When fewer lines of surrounding context exist they all must match. By default no context is ever ignored. ++ +See also INCOMPATIBLE OPTIONS below. --f:: +--no-ff:: --force-rebase:: - Force a rebase even if the current branch is up to date and - the command without `--force` would return without doing anything. +-f:: + Individually replay all rebased commits instead of fast-forwarding + over the unchanged ones. This ensures that the entire history of + the rebased branch is composed of new commits. + -You may find this (or --no-ff with an interactive rebase) helpful after -reverting a topic branch merge, as this option recreates the topic branch with -fresh commits so it can be remerged successfully without needing to "revert -the reversion" (see the -link:howto/revert-a-faulty-merge.html[revert-a-faulty-merge How-To] for details). +You may find this helpful after reverting a topic branch merge, as this option +recreates the topic branch with fresh commits so it can be remerged +successfully without needing to "revert the reversion" (see the +link:howto/revert-a-faulty-merge.html[revert-a-faulty-merge How-To] for +details). --fork-point:: --no-fork-point:: @@ -355,19 +369,22 @@ default is `--no-fork-point`, otherwise the default is `--fork-point`. --whitespace=<option>:: These flag are passed to the 'git apply' program (see linkgit:git-apply[1]) that applies the patch. - Incompatible with the --interactive option. ++ +See also INCOMPATIBLE OPTIONS below. --committer-date-is-author-date:: --ignore-date:: These flags are passed to 'git am' to easily change the dates of the rebased commits (see linkgit:git-am[1]). - Incompatible with the --interactive option. ++ +See also INCOMPATIBLE OPTIONS below. --signoff:: Add a Signed-off-by: trailer to all the rebased commits. Note that if `--interactive` is given then only commits marked to be - picked, edited or reworded will have the trailer added. Incompatible - with the `--preserve-merges` option. + picked, edited or reworded will have the trailer added. ++ +See also INCOMPATIBLE OPTIONS below. -i:: --interactive:: @@ -378,6 +395,8 @@ default is `--no-fork-point`, otherwise the default is `--fork-point`. The commit list format can be changed by setting the configuration option rebase.instructionFormat. A customized instruction format will automatically have the long commit hash prepended to the format. ++ +See also INCOMPATIBLE OPTIONS below. -r:: --rebase-merges[=(rebase-cousins|no-rebase-cousins)]:: @@ -404,7 +423,7 @@ It is currently only possible to recreate the merge commits using the `recursive` merge strategy; Different merge strategies can be used only via explicit `exec git merge -s <strategy> [...]` commands. + -See also REBASING MERGES below. +See also REBASING MERGES and INCOMPATIBLE OPTIONS below. -p:: --preserve-merges:: @@ -415,6 +434,8 @@ See also REBASING MERGES below. This uses the `--interactive` machinery internally, but combining it with the `--interactive` option explicitly is generally not a good idea unless you know what you are doing (see BUGS below). ++ +See also INCOMPATIBLE OPTIONS below. -x <cmd>:: --exec <cmd>:: @@ -437,6 +458,8 @@ squash/fixup series. + This uses the `--interactive` machinery internally, but it can be run without an explicit `--interactive`. ++ +See also INCOMPATIBLE OPTIONS below. --root:: Rebase all commits reachable from <branch>, instead of @@ -447,6 +470,8 @@ without an explicit `--interactive`. When used together with both --onto and --preserve-merges, 'all' root commits will be rewritten to have <newbase> as parent instead. ++ +See also INCOMPATIBLE OPTIONS below. --autosquash:: --no-autosquash:: @@ -461,11 +486,11 @@ without an explicit `--interactive`. 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. -+ If the `--autosquash` option is enabled by default using the configuration variable `rebase.autoSquash`, this option can be used to override and disable this setting. ++ +See also INCOMPATIBLE OPTIONS below. --autostash:: --no-autostash:: @@ -475,17 +500,73 @@ used to override and disable this setting. with care: the final stash application after a successful rebase might result in non-trivial conflicts. ---no-ff:: - With --interactive, cherry-pick all rebased commits instead of - fast-forwarding over the unchanged ones. This ensures that the - entire history of the rebased branch is composed of new commits. -+ -Without --interactive, this is a synonym for --force-rebase. -+ -You may find this helpful after reverting a topic branch merge, as this option -recreates the topic branch with fresh commits so it can be remerged -successfully without needing to "revert the reversion" (see the -link:howto/revert-a-faulty-merge.html[revert-a-faulty-merge How-To] for details). +INCOMPATIBLE OPTIONS +-------------------- + +git-rebase has many flags that are incompatible with each other, +predominantly due to the fact that it has three different underlying +implementations: + + * one based on linkgit:git-am[1] (the default) + * one based on git-merge-recursive (merge backend) + * one based on linkgit:git-cherry-pick[1] (interactive backend) + +Flags only understood by the am backend: + + * --committer-date-is-author-date + * --ignore-date + * --whitespace + * --ignore-whitespace + * -C + +Flags understood by both merge and interactive backends: + + * --merge + * --strategy + * --strategy-option + * --allow-empty-message + +Flags only understood by the interactive backend: + + * --[no-]autosquash + * --rebase-merges + * --preserve-merges + * --interactive + * --exec + * --keep-empty + * --autosquash + * --edit-todo + * --root when used in combination with --onto + +Other incompatible flag pairs: + + * --preserve-merges and --interactive + * --preserve-merges and --signoff + * --preserve-merges and --rebase-merges + * --rebase-merges and --strategy + * --rebase-merges and --strategy-option + +BEHAVIORAL DIFFERENCES +----------------------- + + * empty commits: + + am-based rebase will drop any "empty" commits, whether the + commit started empty (had no changes relative to its parent to + start with) or ended empty (all changes were already applied + upstream in other commits). + + merge-based rebase does the same. + + interactive-based rebase will by default drop commits that + started empty and halt if it hits a commit that ended up empty. + The `--keep-empty` option exists for interactive rebases to allow + it to keep commits that started empty. + + * directory rename detection: + + merge-based and interactive-based rebases work fine with + directory rename detection. am-based rebases sometimes do not. include::merge-strategies.txt[] @@ -804,7 +885,7 @@ The ripple effect of a "hard case" recovery is especially bad: case" recovery too! REBASING MERGES ------------------ +--------------- The interactive rebase command was originally designed to handle individual patch series. As such, it makes sense to exclude merge diff --git a/Documentation/git-show-index.txt b/Documentation/git-show-index.txt index a8a9509e0e..424e4ba84c 100644 --- a/Documentation/git-show-index.txt +++ b/Documentation/git-show-index.txt @@ -14,13 +14,27 @@ SYNOPSIS DESCRIPTION ----------- -Read the idx file for a Git packfile created with -'git pack-objects' command from the standard input, and -dump its contents. +Read the `.idx` file for a Git packfile (created with +linkgit:git-pack-objects[1] or linkgit:git-index-pack[1]) from the +standard input, and dump its contents. The output consists of one object +per line, with each line containing two or three space-separated +columns: -The information it outputs is subset of what you can get from -'git verify-pack -v'; this command only shows the packfile -offset and SHA-1 of each object. + - the first column is the offset in bytes of the object within the + corresponding packfile + + - the second column is the object id of the object + + - if the index version is 2 or higher, the third column contains the + CRC32 of the object data + +The objects are output in the order in which they are found in the index +file, which should be (in a correctly constructed file) sorted by object +id. + +Note that you can get more information on a packfile by calling +linkgit:git-verify-pack[1]. However, as this command considers only the +index file itself, it's both faster and more flexible. GIT --- diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index c4467ffb98..d9f422d560 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -106,7 +106,7 @@ It is optional: it defaults to 'traditional'. The possible options are: + - 'traditional' - Shows ignored files and directories, unless - --untracked-files=all is specifed, in which case + --untracked-files=all is specified, in which case individual files in ignored directories are displayed. - 'no' - Show no ignored files. diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index ef9d9d28a9..ba3c4df550 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -183,12 +183,17 @@ information too. foreach [--recursive] <command>:: Evaluates an arbitrary shell command in each checked out submodule. - The command has access to the variables $name, $path, $sha1 and - $toplevel: + The command has access to the variables $name, $sm_path, $displaypath, + $sha1 and $toplevel: $name is the name of the relevant submodule section in `.gitmodules`, - $path is the name of the submodule directory relative to the - superproject, $sha1 is the commit as recorded in the superproject, - and $toplevel is the absolute path to the top-level of the superproject. + $sm_path is the path of the submodule as recorded in the immediate + superproject, $displaypath contains the relative path from the + current working directory to the submodules root directory, + $sha1 is the commit as recorded in the immediate + superproject, and $toplevel is the absolute path to the top-level + of the immediate superproject. + Note that to avoid conflicts with '$PATH' on Windows, the '$path' + variable is now a deprecated synonym of '$sm_path' variable. Any submodules defined in the superproject but not checked out are ignored by this command. Unless given `--quiet`, foreach prints the name of each submodule before evaluating the command. diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 7ea24fc942..b99029520d 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -452,7 +452,7 @@ Any other arguments are passed directly to 'git log' 'commit-diff':: Commits the diff of two tree-ish arguments from the - command-line. This command does not rely on being inside an `git svn + command-line. This command does not rely on being inside a `git svn init`-ed repository. This command takes three arguments, (a) the original tree to diff against, (b) the new tree result, (c) the URL of the target Subversion repository. The final argument diff --git a/Documentation/giteveryday.txt b/Documentation/giteveryday.txt index 10c8ff93c0..9f2528fc8c 100644 --- a/Documentation/giteveryday.txt +++ b/Documentation/giteveryday.txt @@ -244,7 +244,7 @@ commands in addition to the ones needed by participants. This section can also be used by those who respond to `git request-pull` or pull-request on GitHub (www.github.com) to -integrate the work of others into their history. An sub-area +integrate the work of others into their history. A sub-area lieutenant for a repository will act both as a participant and as an integrator. diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index ff5d7f9ed6..d107daaffd 100644 --- a/Documentation/gitignore.txt +++ b/Documentation/gitignore.txt @@ -7,7 +7,7 @@ gitignore - Specifies intentionally untracked files to ignore SYNOPSIS -------- -$HOME/.config/git/ignore, $GIT_DIR/info/exclude, .gitignore +$XDG_CONFIG_HOME/git/ignore, $GIT_DIR/info/exclude, .gitignore DESCRIPTION ----------- diff --git a/Documentation/gitsubmodules.txt b/Documentation/gitsubmodules.txt index 3b9faabdbb..504c5f1a88 100644 --- a/Documentation/gitsubmodules.txt +++ b/Documentation/gitsubmodules.txt @@ -194,7 +194,7 @@ In the above config only the submodule 'bar' and 'baz' are active, Note that (c) is a historical artefact and will be ignored if the (a) and (b) specify that the submodule is not active. In other words, -if we have an `submodule.<name>.active` set to `false` or if the +if we have a `submodule.<name>.active` set to `false` or if the submodule's path is excluded in the pathspec in `submodule.active`, the url doesn't matter whether it is present or not. This is illustrated in the example that follows. diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 6c2d23dc48..0d2aa48c63 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -334,7 +334,7 @@ The optional colon that terminates the "magic signature" can be omitted if the pattern begins with a character that does not belong to "magic signature" symbol set and is not a colon. + -In the long form, the leading colon `:` is followed by a open +In the long form, the leading colon `:` is followed by an open parenthesis `(`, a comma-separated list of zero or more "magic words", and a close parentheses `)`, and the remainder is the pattern to match against the path. diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt index 4f44ca24f6..5abb8e8b1f 100644 --- a/Documentation/technical/api-directory-listing.txt +++ b/Documentation/technical/api-directory-listing.txt @@ -54,7 +54,7 @@ The notable options are: this case, the contents are returned as individual entries. + If this is set, files and directories that explicitly match an ignore -pattern are reported. Implicity ignored directories (directories that +pattern are reported. Implicitly ignored directories (directories that do not match an ignore pattern, but whose contents are all ignored) are not reported, instead all of the contents are reported. diff --git a/Documentation/technical/api-gitattributes.txt b/Documentation/technical/api-gitattributes.txt index e7cbb7c13a..45f0df600f 100644 --- a/Documentation/technical/api-gitattributes.txt +++ b/Documentation/technical/api-gitattributes.txt @@ -146,7 +146,7 @@ To get the values of all attributes associated with a file: * Iterate over the `attr_check.items[]` array to examine the attribute names and values. The name of the attribute - described by a `attr_check.items[]` object can be retrieved via + described by an `attr_check.items[]` object can be retrieved via `git_attr_name(check->items[i].attr)`. (Please note that no items will be returned for unset attributes, so `ATTR_UNSET()` will return false for all returned `attr_check.items[]` objects.) diff --git a/Documentation/technical/commit-graph-format.txt b/Documentation/technical/commit-graph-format.txt index ad6af8105c..cc0474ba3e 100644 --- a/Documentation/technical/commit-graph-format.txt +++ b/Documentation/technical/commit-graph-format.txt @@ -18,9 +18,9 @@ metadata, including: the graph file. These positional references are stored as unsigned 32-bit integers -corresponding to the array position withing the list of commit OIDs. We -use the most-significant bit for special purposes, so we can store at most -(1 << 31) - 1 (around 2 billion) commits. +corresponding to the array position within the list of commit OIDs. Due +to some special constants we use to track parents, we can store at most +(1 << 30) + (1 << 29) + (1 << 28) - 1 (around 1.8 billion) commits. == Commit graph files have the following format: @@ -70,10 +70,10 @@ CHUNK DATA: OID Lookup (ID: {'O', 'I', 'D', 'L'}) (N * H bytes) The OIDs for all commits in the graph, sorted in ascending order. - Commit Data (ID: {'C', 'G', 'E', 'T' }) (N * (H + 16) bytes) + Commit Data (ID: {'C', 'D', 'A', 'T' }) (N * (H + 16) bytes) * The first H bytes are for the OID of the root tree. * The next 8 bytes are for the positions of the first two parents - of the ith commit. Stores value 0xffffffff if no parent in that + of the ith commit. Stores value 0x7000000 if no parent in that position. If there are more than two parents, the second value has its most-significant bit on and the other bits store an array position into the Large Edge List chunk. diff --git a/Documentation/technical/commit-graph.txt b/Documentation/technical/commit-graph.txt index 0550c6d0dc..e1a883eb46 100644 --- a/Documentation/technical/commit-graph.txt +++ b/Documentation/technical/commit-graph.txt @@ -77,6 +77,29 @@ in the commit graph. We can treat these commits as having "infinite" generation number and walk until reaching commits with known generation number. +We use the macro GENERATION_NUMBER_INFINITY = 0xFFFFFFFF to mark commits not +in the commit-graph file. If a commit-graph file was written by a version +of Git that did not compute generation numbers, then those commits will +have generation number represented by the macro GENERATION_NUMBER_ZERO = 0. + +Since the commit-graph file is closed under reachability, we can guarantee +the following weaker condition on all commits: + + If A and B are commits with generation numbers N amd M, respectively, + and N < M, then A cannot reach B. + +Note how the strict inequality differs from the inequality when we have +fully-computed generation numbers. Using strict inequality may result in +walking a few extra commits, but the simplicity in dealing with commits +with generation number *_INFINITY or *_ZERO is valuable. + +We use the macro GENERATION_NUMBER_MAX = 0x3FFFFFFF to for commits whose +generation numbers are computed to be at least this value. We limit at +this value since it is the largest value that can be stored in the +commit-graph file using the 30 bits available to generation numbers. This +presents another case where a commit can have generation number equal to +that of a parent. + Design Details -------------- @@ -98,18 +121,14 @@ Future Work - The 'commit-graph' subcommand does not have a "verify" mode that is necessary for integration with fsck. -- The file format includes room for precomputed generation numbers. These - are not currently computed, so all generation numbers will be marked as - 0 (or "uncomputed"). A later patch will include this calculation. - - After computing and storing generation numbers, we must make graph walks aware of generation numbers to gain the performance benefits they enable. This will mostly be accomplished by swapping a commit-date-ordered priority queue with one ordered by generation number. The following operations are important candidates: - - paint_down_to_common() - 'log --topo-order' + - 'tag --merged' - Currently, parse_commit_gently() requires filling in the root tree object for a commit. This passes through lookup_tree() and consequently diff --git a/Documentation/technical/directory-rename-detection.txt b/Documentation/technical/directory-rename-detection.txt new file mode 100644 index 0000000000..1c0086e287 --- /dev/null +++ b/Documentation/technical/directory-rename-detection.txt @@ -0,0 +1,115 @@ +Directory rename detection +========================== + +Rename detection logic in diffcore-rename that checks for renames of +individual files is aggregated and analyzed in merge-recursive for cases +where combinations of renames indicate that a full directory has been +renamed. + +Scope of abilities +------------------ + +It is perhaps easiest to start with an example: + + * When all of x/a, x/b and x/c have moved to z/a, z/b and z/c, it is + likely that x/d added in the meantime would also want to move to z/d by + taking the hint that the entire directory 'x' moved to 'z'. + +More interesting possibilities exist, though, such as: + + * one side of history renames x -> z, and the other renames some file to + x/e, causing the need for the merge to do a transitive rename. + + * one side of history renames x -> z, but also renames all files within + x. For example, x/a -> z/alpha, x/b -> z/bravo, etc. + + * both 'x' and 'y' being merged into a single directory 'z', with a + directory rename being detected for both x->z and y->z. + + * not all files in a directory being renamed to the same location; + i.e. perhaps most the files in 'x' are now found under 'z', but a few + are found under 'w'. + + * a directory being renamed, which also contained a subdirectory that was + renamed to some entirely different location. (And perhaps the inner + directory itself contained inner directories that were renamed to yet + other locations). + + * combinations of the above; see t/t6043-merge-rename-directories.sh for + various interesting cases. + +Limitations -- applicability of directory renames +------------------------------------------------- + +In order to prevent edge and corner cases resulting in either conflicts +that cannot be represented in the index or which might be too complex for +users to try to understand and resolve, a couple basic rules limit when +directory rename detection applies: + + 1) If a given directory still exists on both sides of a merge, we do + not consider it to have been renamed. + + 2) If a subset of to-be-renamed files have a file or directory in the + way (or would be in the way of each other), "turn off" the directory + rename for those specific sub-paths and report the conflict to the + user. + + 3) If the other side of history did a directory rename to a path that + your side of history renamed away, then ignore that particular + rename from the other side of history for any implicit directory + renames (but warn the user). + +Limitations -- detailed rules and testcases +------------------------------------------- + +t/t6043-merge-rename-directories.sh contains extensive tests and commentary +which generate and explore the rules listed above. It also lists a few +additional rules: + + a) If renames split a directory into two or more others, the directory + with the most renames, "wins". + + b) Avoid directory-rename-detection for a path, if that path is the + source of a rename on either side of a merge. + + c) Only apply implicit directory renames to directories if the other side + of history is the one doing the renaming. + +Limitations -- support in different commands +-------------------------------------------- + +Directory rename detection is supported by 'merge' and 'cherry-pick'. +Other git commands which users might be surprised to see limited or no +directory rename detection support in: + + * diff + + Folks have requested in the past that `git diff` detect directory + renames and somehow simplify its output. It is not clear whether this + would be desirable or how the output should be simplified, so this was + simply not implemented. Further, to implement this, directory rename + detection logic would need to move from merge-recursive to + diffcore-rename. + + * am + + git-am tries to avoid a full three way merge, instead calling + git-apply. That prevents us from detecting renames at all, which may + defeat the directory rename detection. There is a fallback, though; if + the initial git-apply fails and the user has specified the -3 option, + git-am will fall back to a three way merge. However, git-am lacks the + necessary information to do a "real" three way merge. Instead, it has + to use build_fake_ancestor() to get a merge base that is missing files + whose rename may have been important to detect for directory rename + detection to function. + + * rebase + + Since am-based rebases work by first generating a bunch of patches + (which no longer record what the original commits were and thus don't + have the necessary info from which we can find a real merge-base), and + then calling git-am, this implies that am-based rebases will not always + successfully detect directory renames either (see the 'am' section + above). merged-based rebases (rebase -m) and cherry-pick-based rebases + (rebase -i) are not affected by this shortcoming, and fully support + directory rename detection. diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt index 49bda76d23..09e4e0273f 100644 --- a/Documentation/technical/protocol-v2.txt +++ b/Documentation/technical/protocol-v2.txt @@ -64,9 +64,8 @@ When using the http:// or https:// transport a client makes a "smart" info/refs request as described in `http-protocol.txt` and requests that v2 be used by supplying "version=2" in the `Git-Protocol` header. - C: Git-Protocol: version=2 - C: C: GET $GIT_URL/info/refs?service=git-upload-pack HTTP/1.0 + C: Git-Protocol: version=2 A v2 server would reply: @@ -299,12 +298,21 @@ included in the client's request: for use with partial clone and partial fetch operations. See `rev-list` for possible "filter-spec" values. +If the 'ref-in-want' feature is advertised, the following argument can +be included in the client's request as well as the potential addition of +the 'wanted-refs' section in the server's response as explained below. + + want-ref <ref> + Indicates to the server that the client wants to retrieve a + particular ref, where <ref> is the full name of a ref on the + server. + The response of `fetch` is broken into a number of sections separated by delimiter packets (0001), with each section beginning with its section header. output = *section - section = (acknowledgments | shallow-info | packfile) + section = (acknowledgments | shallow-info | wanted-refs | packfile) (flush-pkt | delim-pkt) acknowledgments = PKT-LINE("acknowledgments" LF) @@ -319,6 +327,10 @@ header. shallow = "shallow" SP obj-id unshallow = "unshallow" SP obj-id + wanted-refs = PKT-LINE("wanted-refs" LF) + *PKT-LINE(wanted-ref LF) + wanted-ref = obj-id SP refname + packfile = PKT-LINE("packfile" LF) *PKT-LINE(%x01-03 *%x00-ff) @@ -379,6 +391,19 @@ header. * This section is only included if a packfile section is also included in the response. + wanted-refs section + * This section is only included if the client has requested a + ref using a 'want-ref' line and if a packfile section is also + included in the response. + + * Always begins with the section header "wanted-refs". + + * The server will send a ref listing ("<oid> <refname>") for + each reference requested using 'want-ref' lines. + + * The server MUST NOT send any refs which were not requested + using 'want-ref' lines. + packfile section * This section is only included if the client has sent 'want' lines in its request and either requested that no more diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index ed9d9e43a9..920678a14b 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.18.0 +DEF_VER=v2.18.GIT LF=' ' @@ -620,6 +620,7 @@ SCRIPT_LIB += git-mergetool--lib SCRIPT_LIB += git-parse-remote SCRIPT_LIB += git-rebase--am SCRIPT_LIB += git-rebase--interactive +SCRIPT_LIB += git-rebase--preserve-merges SCRIPT_LIB += git-rebase--merge SCRIPT_LIB += git-sh-setup SCRIPT_LIB += git-sh-i18n @@ -689,7 +690,6 @@ PROGRAM_OBJS += http-backend.o PROGRAM_OBJS += imap-send.o PROGRAM_OBJS += sh-i18n--envsubst.o PROGRAM_OBJS += shell.o -PROGRAM_OBJS += show-index.o PROGRAM_OBJS += remote-testsvn.o # Binary suffix, set to .exe for Windows builds @@ -1077,6 +1077,7 @@ BUILTIN_OBJS += builtin/send-pack.o BUILTIN_OBJS += builtin/serve.o BUILTIN_OBJS += builtin/shortlog.o BUILTIN_OBJS += builtin/show-branch.o +BUILTIN_OBJS += builtin/show-index.o BUILTIN_OBJS += builtin/show-ref.o BUILTIN_OBJS += builtin/stripspace.o BUILTIN_OBJS += builtin/submodule--helper.o @@ -2020,8 +2021,9 @@ version.sp version.s version.o: GIT-VERSION-FILE GIT-USER-AGENT version.sp version.s version.o: EXTRA_CPPFLAGS = \ '-DGIT_VERSION="$(GIT_VERSION)"' \ '-DGIT_USER_AGENT=$(GIT_USER_AGENT_CQ_SQ)' \ - '-DGIT_BUILT_FROM_COMMIT="$(shell GIT_CEILING_DIRECTORIES=\"$(CURDIR)/..\" \ - git rev-parse -q --verify HEAD || :)"' + '-DGIT_BUILT_FROM_COMMIT="$(shell \ + GIT_CEILING_DIRECTORIES="$(CURDIR)/.." \ + git rev-parse -q --verify HEAD 2>/dev/null)"' $(BUILT_INS): git$X $(QUIET_BUILT_IN)$(RM) $@ && \ @@ -2109,7 +2111,7 @@ $(SCRIPT_PERL_GEN): % : %.perl GIT-PERL-DEFINES GIT-PERL-HEADER GIT-VERSION-FILE $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1{' \ -e ' s|#!.*perl|#!$(PERL_PATH_SQ)|' \ - -e ' rGIT-PERL-HEADER' \ + -e ' r GIT-PERL-HEADER' \ -e ' G' \ -e '}' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ @@ -2397,6 +2399,7 @@ LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H) LOCALIZED_SH = $(SCRIPT_SH) LOCALIZED_SH += git-parse-remote.sh LOCALIZED_SH += git-rebase--interactive.sh +LOCALIZED_SH += git-rebase--preserve-merges.sh LOCALIZED_SH += git-sh-setup.sh LOCALIZED_PERL = $(SCRIPT_PERL) @@ -1 +1 @@ -Documentation/RelNotes/2.18.0.txt
\ No newline at end of file +Documentation/RelNotes/2.19.0.txt
\ No newline at end of file @@ -1,6 +1,7 @@ #include "cache.h" #include "config.h" #include "color.h" +#include "help.h" int advice_push_update_rejected = 1; int advice_push_non_ff_current = 1; @@ -16,6 +17,7 @@ int advice_implicit_identity = 1; int advice_detached_head = 1; int advice_set_upstream_failure = 1; int advice_object_name_warning = 1; +int advice_amworkdir = 1; int advice_rm_hints = 1; int advice_add_embedded_repo = 1; int advice_ignored_hook = 1; @@ -53,28 +55,29 @@ static struct { const char *name; int *preference; } advice_config[] = { - { "pushupdaterejected", &advice_push_update_rejected }, - { "pushnonffcurrent", &advice_push_non_ff_current }, - { "pushnonffmatching", &advice_push_non_ff_matching }, - { "pushalreadyexists", &advice_push_already_exists }, - { "pushfetchfirst", &advice_push_fetch_first }, - { "pushneedsforce", &advice_push_needs_force }, - { "statushints", &advice_status_hints }, - { "statusuoption", &advice_status_u_option }, - { "commitbeforemerge", &advice_commit_before_merge }, - { "resolveconflict", &advice_resolve_conflict }, - { "implicitidentity", &advice_implicit_identity }, - { "detachedhead", &advice_detached_head }, - { "setupstreamfailure", &advice_set_upstream_failure }, - { "objectnamewarning", &advice_object_name_warning }, - { "rmhints", &advice_rm_hints }, - { "addembeddedrepo", &advice_add_embedded_repo }, - { "ignoredhook", &advice_ignored_hook }, - { "waitingforeditor", &advice_waiting_for_editor }, - { "graftfiledeprecated", &advice_graft_file_deprecated }, + { "pushUpdateRejected", &advice_push_update_rejected }, + { "pushNonFFCurrent", &advice_push_non_ff_current }, + { "pushNonFFMatching", &advice_push_non_ff_matching }, + { "pushAlreadyExists", &advice_push_already_exists }, + { "pushFetchFirst", &advice_push_fetch_first }, + { "pushNeedsForce", &advice_push_needs_force }, + { "statusHints", &advice_status_hints }, + { "statusUoption", &advice_status_u_option }, + { "commitBeforeMerge", &advice_commit_before_merge }, + { "resolveConflict", &advice_resolve_conflict }, + { "implicitIdentity", &advice_implicit_identity }, + { "detachedHead", &advice_detached_head }, + { "setupStreamFailure", &advice_set_upstream_failure }, + { "objectNameWarning", &advice_object_name_warning }, + { "amWorkDir", &advice_amworkdir }, + { "rmHints", &advice_rm_hints }, + { "addEmbeddedRepo", &advice_add_embedded_repo }, + { "ignoredHook", &advice_ignored_hook }, + { "waitingForEditor", &advice_waiting_for_editor }, + { "graftFileDeprecated", &advice_graft_file_deprecated }, /* make this an alias for backward compatibility */ - { "pushnonfastforward", &advice_push_update_rejected } + { "pushNonFastForward", &advice_push_update_rejected } }; void advise(const char *advice, ...) @@ -122,7 +125,7 @@ int git_default_advice_config(const char *var, const char *value) return 0; for (i = 0; i < ARRAY_SIZE(advice_config); i++) { - if (strcmp(k, advice_config[i].name)) + if (strcasecmp(k, advice_config[i].name)) continue; *advice_config[i].preference = git_config_bool(var, value); return 0; @@ -131,6 +134,14 @@ int git_default_advice_config(const char *var, const char *value) return 0; } +void list_config_advices(struct string_list *list, const char *prefix) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(advice_config); i++) + list_config_item(list, prefix, advice_config[i].name); +} + int error_resolve_conflict(const char *me) { if (!strcmp(me, "cherry-pick")) @@ -17,6 +17,7 @@ extern int advice_implicit_identity; extern int advice_detached_head; extern int advice_set_upstream_failure; extern int advice_object_name_warning; +extern int advice_amworkdir; extern int advice_rm_hints; extern int advice_add_embedded_repo; extern int advice_ignored_hook; @@ -4,8 +4,7 @@ * Copyright (C) 2006 Linus Torvalds * * The standard malloc/free wastes too much space for objects, partly because - * it maintains all the allocation infrastructure (which isn't needed, since - * we never free an object descriptor anyway), but even more because it ends + * it maintains all the allocation infrastructure, but even more because it ends * up with maximal alignment because it doesn't know what the object alignment * for the new allocation is. */ @@ -15,6 +14,7 @@ #include "tree.h" #include "commit.h" #include "tag.h" +#include "alloc.h" #define BLOCKING 1024 @@ -30,8 +30,27 @@ struct alloc_state { int count; /* total number of nodes allocated */ int nr; /* number of nodes left in current allocation */ void *p; /* first free node in current allocation */ + + /* bookkeeping of allocations */ + void **slabs; + int slab_nr, slab_alloc; }; +void *allocate_alloc_state(void) +{ + return xcalloc(1, sizeof(struct alloc_state)); +} + +void clear_alloc_state(struct alloc_state *s) +{ + while (s->slab_nr > 0) { + s->slab_nr--; + free(s->slabs[s->slab_nr]); + } + + FREE_AND_NULL(s->slabs); +} + static inline void *alloc_node(struct alloc_state *s, size_t node_size) { void *ret; @@ -39,61 +58,59 @@ static inline void *alloc_node(struct alloc_state *s, size_t node_size) if (!s->nr) { s->nr = BLOCKING; s->p = xmalloc(BLOCKING * node_size); + + ALLOC_GROW(s->slabs, s->slab_nr + 1, s->slab_alloc); + s->slabs[s->slab_nr++] = s->p; } s->nr--; s->count++; ret = s->p; s->p = (char *)s->p + node_size; memset(ret, 0, node_size); + return ret; } -static struct alloc_state blob_state; -void *alloc_blob_node(void) +void *alloc_blob_node(struct repository *r) { - struct blob *b = alloc_node(&blob_state, sizeof(struct blob)); + struct blob *b = alloc_node(r->parsed_objects->blob_state, sizeof(struct blob)); b->object.type = OBJ_BLOB; return b; } -static struct alloc_state tree_state; -void *alloc_tree_node(void) +void *alloc_tree_node(struct repository *r) { - struct tree *t = alloc_node(&tree_state, sizeof(struct tree)); + struct tree *t = alloc_node(r->parsed_objects->tree_state, sizeof(struct tree)); t->object.type = OBJ_TREE; return t; } -static struct alloc_state tag_state; -void *alloc_tag_node(void) +void *alloc_tag_node(struct repository *r) { - struct tag *t = alloc_node(&tag_state, sizeof(struct tag)); + struct tag *t = alloc_node(r->parsed_objects->tag_state, sizeof(struct tag)); t->object.type = OBJ_TAG; return t; } -static struct alloc_state object_state; -void *alloc_object_node(void) +void *alloc_object_node(struct repository *r) { - struct object *obj = alloc_node(&object_state, sizeof(union any_object)); + struct object *obj = alloc_node(r->parsed_objects->object_state, sizeof(union any_object)); obj->type = OBJ_NONE; return obj; } -static struct alloc_state commit_state; - -unsigned int alloc_commit_index(void) +unsigned int alloc_commit_index(struct repository *r) { - static unsigned int count; - return count++; + return r->parsed_objects->commit_count++; } -void *alloc_commit_node(void) +void *alloc_commit_node(struct repository *r) { - struct commit *c = alloc_node(&commit_state, sizeof(struct commit)); + struct commit *c = alloc_node(r->parsed_objects->commit_state, sizeof(struct commit)); c->object.type = OBJ_COMMIT; - c->index = alloc_commit_index(); + c->index = alloc_commit_index(r); c->graph_pos = COMMIT_NOT_FROM_GRAPH; + c->generation = GENERATION_NUMBER_INFINITY; return c; } @@ -104,9 +121,10 @@ static void report(const char *name, unsigned int count, size_t size) } #define REPORT(name, type) \ - report(#name, name##_state.count, name##_state.count * sizeof(type) >> 10) + report(#name, r->parsed_objects->name##_state->count, \ + r->parsed_objects->name##_state->count * sizeof(type) >> 10) -void alloc_report(void) +void alloc_report(struct repository *r) { REPORT(blob, struct blob); REPORT(tree, struct tree); diff --git a/alloc.h b/alloc.h new file mode 100644 index 0000000000..3e4e828db4 --- /dev/null +++ b/alloc.h @@ -0,0 +1,19 @@ +#ifndef ALLOC_H +#define ALLOC_H + +struct tree; +struct commit; +struct tag; + +void *alloc_blob_node(struct repository *r); +void *alloc_tree_node(struct repository *r); +void *alloc_commit_node(struct repository *r); +void *alloc_tag_node(struct repository *r); +void *alloc_object_node(struct repository *r); +void alloc_report(struct repository *r); +unsigned int alloc_commit_index(struct repository *r); + +void *allocate_alloc_state(void); +void clear_alloc_state(struct alloc_state *s); + +#endif @@ -9,6 +9,7 @@ #include "cache.h" #include "config.h" +#include "object-store.h" #include "blob.h" #include "delta.h" #include "diff.h" @@ -141,6 +142,8 @@ int check_apply_state(struct apply_state *state, int force_apply) return error(_("--cached outside a repository")); state->check_index = 1; } + if (state->ita_only && (state->check_index || is_not_gitdir)) + state->ita_only = 0; if (state->check_index) state->unsafe_paths = 0; @@ -4053,7 +4056,7 @@ static int preimage_oid_in_gitlink_patch(struct patch *p, struct object_id *oid) return get_oid_hex(p->old_sha1_prefix, oid); } -/* Build an index that contains the just the files needed for a 3way merge */ +/* Build an index that contains just the files needed for a 3way merge */ static int build_fake_ancestor(struct apply_state *state, struct patch *list) { struct patch *patch; @@ -4242,7 +4245,7 @@ static void patch_stats(struct apply_state *state, struct patch *patch) static int remove_file(struct apply_state *state, struct patch *patch, int rmdir_empty) { - if (state->update_index) { + if (state->update_index && !state->ita_only) { if (remove_file_from_cache(patch->old_name) < 0) return error(_("unable to remove %s from index"), patch->old_name); } @@ -4265,15 +4268,15 @@ static int add_index_file(struct apply_state *state, int namelen = strlen(path); unsigned ce_size = cache_entry_size(namelen); - if (!state->update_index) - return 0; - ce = xcalloc(1, ce_size); memcpy(ce->name, path, namelen); ce->ce_mode = create_ce_mode(mode); ce->ce_flags = create_ce_flags(0); ce->ce_namelen = namelen; - if (S_ISGITLINK(mode)) { + if (state->ita_only) { + ce->ce_flags |= CE_INTENT_TO_ADD; + set_object_name_for_intent_to_add_entry(ce); + } else if (S_ISGITLINK(mode)) { const char *s; if (!skip_prefix(buf, "Subproject commit ", &s) || @@ -4465,8 +4468,9 @@ static int create_file(struct apply_state *state, struct patch *patch) if (patch->conflicted_threeway) return add_conflicted_stages_file(state, patch); - else + else if (state->update_index) return add_index_file(state, path, mode, buf, size); + return 0; } /* phase zero is to remove, phase one is to create */ @@ -4686,7 +4690,7 @@ static int apply_patch(struct apply_state *state, if (state->whitespace_error && (state->ws_error_action == die_on_ws_error)) state->apply = 0; - state->update_index = state->check_index && state->apply; + state->update_index = (state->check_index || state->ita_only) && state->apply; if (state->update_index && !is_lock_file_locked(&state->lock_file)) { if (state->index_file) hold_lock_file_for_update(&state->lock_file, @@ -4941,6 +4945,8 @@ int apply_parse_options(int argc, const char **argv, N_("instead of applying the patch, see if the patch is applicable")), OPT_BOOL(0, "index", &state->check_index, N_("make sure the patch is applicable to the current index")), + OPT_BOOL('N', "intent-to-add", &state->ita_only, + N_("mark new files with `git add --intent-to-add`")), OPT_BOOL(0, "cached", &state->cached, N_("apply a patch without touching the working tree")), OPT_BOOL_F(0, "unsafe-paths", &state->unsafe_paths, @@ -45,6 +45,7 @@ struct apply_state { int check; /* preimage must match working tree, don't actually apply */ int check_index; /* preimage must match the indexed version */ int update_index; /* check_index && apply */ + int ita_only; /* add intent-to-add entries to the index */ /* These control cosmetic aspect of the output */ int diffstat; /* just show a diffstat, and don't actually apply */ diff --git a/archive-tar.c b/archive-tar.c index b6f58ddf38..7df8565246 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -5,6 +5,7 @@ #include "config.h" #include "tar.h" #include "archive.h" +#include "object-store.h" #include "streaming.h" #include "run-command.h" diff --git a/archive-zip.c b/archive-zip.c index 74f3fe9103..abc556e5a7 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -6,6 +6,7 @@ #include "archive.h" #include "streaming.h" #include "utf8.h" +#include "object-store.h" #include "userdiff.h" #include "xdiff-interface.h" @@ -1,6 +1,7 @@ #include "cache.h" #include "config.h" #include "refs.h" +#include "object-store.h" #include "commit.h" #include "tree-walk.h" #include "attr.h" @@ -12,6 +12,7 @@ #include "bisect.h" #include "sha1-array.h" #include "argv-array.h" +#include "commit-slab.h" static struct oid_array good_revs; static struct oid_array skipped_revs; @@ -70,16 +71,19 @@ static void clear_distance(struct commit_list *list) } } +define_commit_slab(commit_weight, int *); +static struct commit_weight commit_weight; + #define DEBUG_BISECT 0 static inline int weight(struct commit_list *elem) { - return *((int*)(elem->item->util)); + return **commit_weight_at(&commit_weight, elem->item); } static inline void weight_set(struct commit_list *elem, int weight) { - *((int*)(elem->item->util)) = weight; + **commit_weight_at(&commit_weight, elem->item) = weight; } static int count_interesting_parents(struct commit *commit) @@ -265,7 +269,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list, struct commit *commit = p->item; unsigned flags = commit->object.flags; - p->item->util = &weights[n++]; + *commit_weight_at(&commit_weight, p->item) = &weights[n++]; switch (count_interesting_parents(commit)) { case 0: if (!(flags & TREESAME)) { @@ -372,6 +376,7 @@ void find_bisection(struct commit_list **commit_list, int *reaches, int *weights; show_list("bisection 2 entry", 0, 0, *commit_list); + init_commit_weight(&commit_weight); /* * Count the number of total and tree-changing items on the @@ -412,6 +417,7 @@ void find_bisection(struct commit_list **commit_list, int *reaches, } free(weights); *commit_list = best; + clear_commit_weight(&commit_weight); } static int register_ref(const char *refname, const struct object_id *oid, @@ -1,11 +1,31 @@ #include "cache.h" #include "refs.h" +#include "object-store.h" #include "cache-tree.h" #include "mergesort.h" #include "diff.h" #include "diffcore.h" #include "tag.h" #include "blame.h" +#include "alloc.h" +#include "commit-slab.h" + +define_commit_slab(blame_suspects, struct blame_origin *); +static struct blame_suspects blame_suspects; + +struct blame_origin *get_blame_suspects(struct commit *commit) +{ + struct blame_origin **result; + + result = blame_suspects_peek(&blame_suspects, commit); + + return result ? *result : NULL; +} + +static void set_blame_suspects(struct commit *commit, struct blame_origin *origin) +{ + *blame_suspects_at(&blame_suspects, commit) = origin; +} void blame_origin_decref(struct blame_origin *o) { @@ -15,12 +35,12 @@ void blame_origin_decref(struct blame_origin *o) blame_origin_decref(o->previous); free(o->file.ptr); /* Should be present exactly once in commit chain */ - for (p = o->commit->util; p; l = p, p = p->next) { + for (p = get_blame_suspects(o->commit); p; l = p, p = p->next) { if (p == o) { if (l) l->next = p->next; else - o->commit->util = p->next; + set_blame_suspects(o->commit, p->next); free(o); return; } @@ -41,8 +61,8 @@ static struct blame_origin *make_origin(struct commit *commit, const char *path) FLEX_ALLOC_STR(o, path, path); o->commit = commit; o->refcnt = 1; - o->next = commit->util; - commit->util = o; + o->next = get_blame_suspects(commit); + set_blame_suspects(commit, o); return o; } @@ -54,13 +74,13 @@ static struct blame_origin *get_origin(struct commit *commit, const char *path) { struct blame_origin *o, *l; - for (o = commit->util, l = NULL; o; l = o, o = o->next) { + for (o = get_blame_suspects(commit), l = NULL; o; l = o, o = o->next) { if (!strcmp(o->path, path)) { /* bump to front */ if (l) { l->next = o->next; - o->next = commit->util; - commit->util = o; + o->next = get_blame_suspects(commit); + set_blame_suspects(commit, o); } return blame_origin_incref(o); } @@ -110,17 +130,19 @@ static void append_merge_parents(struct commit_list **tail) int merge_head; struct strbuf line = STRBUF_INIT; - merge_head = open(git_path_merge_head(), O_RDONLY); + merge_head = open(git_path_merge_head(the_repository), O_RDONLY); if (merge_head < 0) { if (errno == ENOENT) return; - die("cannot open '%s' for reading", git_path_merge_head()); + die("cannot open '%s' for reading", + git_path_merge_head(the_repository)); } while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) { struct object_id oid; if (line.len < GIT_SHA1_HEXSZ || get_oid_hex(line.buf, &oid)) - die("unknown line in '%s': %s", git_path_merge_head(), line.buf); + die("unknown line in '%s': %s", + git_path_merge_head(the_repository), line.buf); tail = append_parent(tail, &oid); } close(merge_head); @@ -161,7 +183,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, read_cache(); time(&now); - commit = alloc_commit_node(); + commit = alloc_commit_node(the_repository); commit->object.parsed = 1; commit->date = now; parent_tail = &commit->parents; @@ -478,7 +500,7 @@ static void queue_blames(struct blame_scoreboard *sb, struct blame_origin *porig porigin->suspects = blame_merge(porigin->suspects, sorted); else { struct blame_origin *o; - for (o = porigin->commit->util; o; o = o->next) { + for (o = get_blame_suspects(porigin->commit); o; o = o->next) { if (o->suspects) { porigin->suspects = sorted; return; @@ -525,7 +547,7 @@ static struct blame_origin *find_origin(struct commit *parent, const char *paths[2]; /* First check any existing origins */ - for (porigin = parent->util; porigin; porigin = porigin->next) + for (porigin = get_blame_suspects(parent); porigin; porigin = porigin->next) if (!strcmp(porigin->path, origin->path)) { /* * The same path between origin and its parent @@ -1550,7 +1572,7 @@ void assign_blame(struct blame_scoreboard *sb, int opt) while (commit) { struct blame_entry *ent; - struct blame_origin *suspect = commit->util; + struct blame_origin *suspect = get_blame_suspects(commit); /* find one suspect to break down */ while (suspect && !suspect->suspects) @@ -1752,6 +1774,8 @@ void setup_scoreboard(struct blame_scoreboard *sb, const char *path, struct blam struct commit *final_commit = NULL; enum object_type type; + init_blame_suspects(&blame_suspects); + if (sb->reverse && sb->contents_from) die(_("--contents and --reverse do not blend well.")); @@ -1815,7 +1839,7 @@ void setup_scoreboard(struct blame_scoreboard *sb, const char *path, struct blam } if (is_null_oid(&sb->final->object.oid)) { - o = sb->final->util; + o = get_blame_suspects(sb->final); sb->final_buf = xmemdupz(o->file.ptr, o->file.size); sb->final_buf_size = o->file.size; } @@ -172,4 +172,6 @@ extern void setup_scoreboard(struct blame_scoreboard *sb, const char *path, stru extern struct blame_entry *blame_entry_prepend(struct blame_entry *head, long start, long end, struct blame_origin *o); +extern struct blame_origin *get_blame_suspects(struct commit *commit); + #endif /* BLAME_H */ @@ -1,5 +1,7 @@ #include "cache.h" #include "blob.h" +#include "repository.h" +#include "alloc.h" const char *blob_type = "blob"; @@ -7,7 +9,8 @@ struct blob *lookup_blob(const struct object_id *oid) { struct object *obj = lookup_object(oid->hash); if (!obj) - return create_object(oid->hash, alloc_blob_node()); + return create_object(the_repository, oid->hash, + alloc_blob_node(the_repository)); return object_as_type(obj, OBJ_BLOB, 0); } @@ -340,13 +340,13 @@ void create_branch(const char *name, const char *start_name, void remove_branch_state(void) { - unlink(git_path_cherry_pick_head()); - unlink(git_path_revert_head()); - unlink(git_path_merge_head()); - unlink(git_path_merge_rr()); - unlink(git_path_merge_msg()); - unlink(git_path_merge_mode()); - unlink(git_path_squash_msg()); + unlink(git_path_cherry_pick_head(the_repository)); + unlink(git_path_revert_head(the_repository)); + unlink(git_path_merge_head(the_repository)); + unlink(git_path_merge_rr(the_repository)); + unlink(git_path_merge_msg(the_repository)); + unlink(git_path_merge_mode(the_repository)); + unlink(git_path_squash_msg(the_repository)); } void die_if_checked_out(const char *branch, int ignore_current_worktree) @@ -220,6 +220,7 @@ extern int cmd_serve(int argc, const char **argv, const char *prefix); extern int cmd_shortlog(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); +extern int cmd_show_index(int argc, const char **argv, const char *prefix); extern int cmd_status(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix); diff --git a/builtin/am.c b/builtin/am.c index 2fc2d1e82c..6273ea5195 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1827,15 +1827,11 @@ static void am_run(struct am_state *state, int resume) } if (apply_status) { - int advice_amworkdir = 1; - printf_ln(_("Patch failed at %s %.*s"), msgnum(state), linelen(state->msg), state->msg); - git_config_get_bool("advice.amworkdir", &advice_amworkdir); - if (advice_amworkdir) - printf_ln(_("Use 'git am --show-current-patch' to see the failed patch")); + advise(_("Use 'git am --show-current-patch' to see the failed patch")); die_user_resolve(state); } diff --git a/builtin/blame.c b/builtin/blame.c index 3295718841..921d127f29 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -9,6 +9,7 @@ #include "config.h" #include "color.h" #include "builtin.h" +#include "repository.h" #include "commit.h" #include "diff.h" #include "revision.h" @@ -23,6 +24,7 @@ #include "line-log.h" #include "dir.h" #include "progress.h" +#include "object-store.h" #include "blame.h" #include "string-list.h" @@ -543,7 +545,7 @@ static void output(struct blame_scoreboard *sb, int option) struct commit *commit = ent->suspect->commit; if (commit->object.flags & MORE_THAN_ONE_PATH) continue; - for (suspect = commit->util; suspect; suspect = suspect->next) { + for (suspect = get_blame_suspects(commit); suspect; suspect = suspect->next) { if (suspect->guilty && count++) { commit->object.flags |= MORE_THAN_ONE_PATH; break; @@ -576,7 +578,7 @@ static int read_ancestry(const char *graft_file) /* The format is just "Commit Parent1 Parent2 ...\n" */ struct commit_graft *graft = read_graft_line(&buf); if (graft) - register_commit_graft(graft, 0); + register_commit_graft(the_repository, graft, 0); } fclose(fp); strbuf_release(&buf); diff --git a/builtin/branch.c b/builtin/branch.c index 5217ba3bde..0192d4a879 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -22,6 +22,7 @@ #include "wt-status.h" #include "ref-filter.h" #include "worktree.h" +#include "help.h" static const char * const builtin_branch_usage[] = { N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"), @@ -36,6 +37,7 @@ static const char * const builtin_branch_usage[] = { static const char *head; static struct object_id head_oid; +static int used_deprecated_reflog_option; static int branch_use_color = -1; static char branch_colors[][COLOR_MAXLEN] = { @@ -55,25 +57,19 @@ enum color_branch { BRANCH_COLOR_UPSTREAM = 5 }; +static const char *color_branch_slots[] = { + [BRANCH_COLOR_RESET] = "reset", + [BRANCH_COLOR_PLAIN] = "plain", + [BRANCH_COLOR_REMOTE] = "remote", + [BRANCH_COLOR_LOCAL] = "local", + [BRANCH_COLOR_CURRENT] = "current", + [BRANCH_COLOR_UPSTREAM] = "upstream", +}; + static struct string_list output = STRING_LIST_INIT_DUP; static unsigned int colopts; -static int parse_branch_color_slot(const char *slot) -{ - if (!strcasecmp(slot, "plain")) - return BRANCH_COLOR_PLAIN; - if (!strcasecmp(slot, "reset")) - return BRANCH_COLOR_RESET; - if (!strcasecmp(slot, "remote")) - return BRANCH_COLOR_REMOTE; - if (!strcasecmp(slot, "local")) - return BRANCH_COLOR_LOCAL; - if (!strcasecmp(slot, "current")) - return BRANCH_COLOR_CURRENT; - if (!strcasecmp(slot, "upstream")) - return BRANCH_COLOR_UPSTREAM; - return -1; -} +define_list_config_array(color_branch_slots); static int git_branch_config(const char *var, const char *value, void *cb) { @@ -86,7 +82,7 @@ static int git_branch_config(const char *var, const char *value, void *cb) return 0; } if (skip_prefix(var, "color.branch.", &slot_name)) { - int slot = parse_branch_color_slot(slot_name); + int slot = LOOKUP_CONFIG(color_branch_slots, slot_name); if (slot < 0) return 0; if (!value) @@ -573,6 +569,14 @@ static int edit_branch_description(const char *branch_name) return 0; } +static int deprecated_reflog_option_cb(const struct option *opt, + const char *arg, int unset) +{ + used_deprecated_reflog_option = 1; + *(int *)opt->value = !unset; + return 0; +} + int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, rename = 0, copy = 0, force = 0, list = 0; @@ -615,7 +619,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix) 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, "create-reflog", &reflog, N_("create the branch's reflog")), + { + OPTION_CALLBACK, 'l', NULL, &reflog, NULL, + N_("deprecated synonym for --create-reflog"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, + deprecated_reflog_option_cb + }, OPT_BOOL(0, "edit-description", &edit_description, N_("edit the description for the branch")), OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE), @@ -688,6 +698,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (list) setup_auto_pager("branch", 1); + if (used_deprecated_reflog_option && !list) { + warning("the '-l' alias for '--create-reflog' is deprecated;"); + warning("it will be removed in a future version of Git"); + } + if (delete) { if (!argc) die(_("branch name required")); diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 665b581949..4a44b2404f 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -13,6 +13,7 @@ #include "tree-walk.h" #include "sha1-array.h" #include "packfile.h" +#include "object-store.h" struct batch_options { int enabled; diff --git a/builtin/checkout.c b/builtin/checkout.c index 2e1d2376d2..28627650cd 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -4,6 +4,7 @@ #include "lockfile.h" #include "parse-options.h" #include "refs.h" +#include "object-store.h" #include "commit.h" #include "tree.h" #include "tree-walk.h" @@ -1120,10 +1121,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT_SET_INT('t', "track", &opts.track, N_("set upstream info for new branch"), BRANCH_TRACK_EXPLICIT), OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")), - OPT_SET_INT('2', "ours", &opts.writeout_stage, N_("checkout our version for unmerged files"), - 2), - OPT_SET_INT('3', "theirs", &opts.writeout_stage, N_("checkout their version for unmerged files"), - 3), + OPT_SET_INT_F('2', "ours", &opts.writeout_stage, + N_("checkout our version for unmerged files"), + 2, PARSE_OPT_NONEG), + OPT_SET_INT_F('3', "theirs", &opts.writeout_stage, + N_("checkout their version for unmerged files"), + 3, PARSE_OPT_NONEG), OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)"), PARSE_OPT_NOCOMPLETE), OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")), diff --git a/builtin/clean.c b/builtin/clean.c index fad533a0a7..ab402c204c 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -16,6 +16,7 @@ #include "column.h" #include "color.h" #include "pathspec.h" +#include "help.h" static int force = -1; /* unset */ static int interactive; @@ -42,6 +43,15 @@ enum color_clean { CLEAN_COLOR_ERROR = 5 }; +static const char *color_interactive_slots[] = { + [CLEAN_COLOR_ERROR] = "error", + [CLEAN_COLOR_HEADER] = "header", + [CLEAN_COLOR_HELP] = "help", + [CLEAN_COLOR_PLAIN] = "plain", + [CLEAN_COLOR_PROMPT] = "prompt", + [CLEAN_COLOR_RESET] = "reset", +}; + static int clean_use_color = -1; static char clean_colors[][COLOR_MAXLEN] = { [CLEAN_COLOR_ERROR] = GIT_COLOR_BOLD_RED, @@ -82,22 +92,7 @@ struct menu_stuff { void *stuff; }; -static int parse_clean_color_slot(const char *var) -{ - if (!strcasecmp(var, "reset")) - return CLEAN_COLOR_RESET; - if (!strcasecmp(var, "plain")) - return CLEAN_COLOR_PLAIN; - if (!strcasecmp(var, "prompt")) - return CLEAN_COLOR_PROMPT; - if (!strcasecmp(var, "header")) - return CLEAN_COLOR_HEADER; - if (!strcasecmp(var, "help")) - return CLEAN_COLOR_HELP; - if (!strcasecmp(var, "error")) - return CLEAN_COLOR_ERROR; - return -1; -} +define_list_config_array(color_interactive_slots); static int git_clean_config(const char *var, const char *value, void *cb) { @@ -113,7 +108,7 @@ static int git_clean_config(const char *var, const char *value, void *cb) return 0; } if (skip_prefix(var, "color.interactive.", &slot_name)) { - int slot = parse_clean_color_slot(slot_name); + int slot = LOOKUP_CONFIG(color_interactive_slots, slot_name); if (slot < 0) return 0; if (!value) diff --git a/builtin/clone.c b/builtin/clone.c index 99e73dae85..2f84717e11 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -15,6 +15,7 @@ #include "fetch-pack.h" #include "refs.h" #include "refspec.h" +#include "object-store.h" #include "tree.h" #include "tree-walk.h" #include "unpack-trees.h" @@ -1077,7 +1078,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_required_reference.nr || option_optional_reference.nr) setup_reference(); - refspec_item_init(&refspec, value.buf, REFSPEC_FETCH); + refspec_item_init_or_die(&refspec, value.buf, REFSPEC_FETCH); strbuf_reset(&value); @@ -1155,7 +1156,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } if (!is_local && !complete_refs_before_fetch) - transport_fetch_refs(transport, mapped_refs); + transport_fetch_refs(transport, mapped_refs, NULL); remote_head = find_ref_by_name(refs, "HEAD"); remote_head_points_at = @@ -1197,7 +1198,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (is_local) clone_local(path, git_dir); else if (refs && complete_refs_before_fetch) - transport_fetch_refs(transport, mapped_refs); + transport_fetch_refs(transport, mapped_refs, NULL); update_remote_refs(refs, mapped_refs, remote_head_points_at, branch_top.buf, reflog_msg.buf, transport, diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index ecf42191da..9fbd3529fb 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -5,6 +5,7 @@ */ #include "cache.h" #include "config.h" +#include "object-store.h" #include "commit.h" #include "tree.h" #include "builtin.h" diff --git a/builtin/commit.c b/builtin/commit.c index a842fea666..158e3f843a 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -32,6 +32,7 @@ #include "column.h" #include "sequencer.h" #include "mailmap.h" +#include "help.h" static const char * const builtin_commit_usage[] = { N_("git commit [<options>] [--] <pathspec>..."), @@ -66,6 +67,18 @@ N_("If you wish to skip this commit, use:\n" "Then \"git cherry-pick --continue\" will resume cherry-picking\n" "the remaining commits.\n"); +static const char *color_status_slots[] = { + [WT_STATUS_HEADER] = "header", + [WT_STATUS_UPDATED] = "updated", + [WT_STATUS_CHANGED] = "changed", + [WT_STATUS_UNTRACKED] = "untracked", + [WT_STATUS_NOBRANCH] = "noBranch", + [WT_STATUS_UNMERGED] = "unmerged", + [WT_STATUS_LOCAL_BRANCH] = "localBranch", + [WT_STATUS_REMOTE_BRANCH] = "remoteBranch", + [WT_STATUS_ONBRANCH] = "branch", +}; + static const char *use_message_buffer; static struct lock_file index_lock; /* real index */ static struct lock_file false_lock; /* used only for partial commits */ @@ -155,9 +168,9 @@ static int opt_parse_rename_score(const struct option *opt, const char *arg, int static void determine_whence(struct wt_status *s) { - if (file_exists(git_path_merge_head())) + if (file_exists(git_path_merge_head(the_repository))) whence = FROM_MERGE; - else if (file_exists(git_path_cherry_pick_head())) { + else if (file_exists(git_path_cherry_pick_head(the_repository))) { whence = FROM_CHERRY_PICK; if (file_exists(git_path_seq_dir())) sequencer_in_use = 1; @@ -705,21 +718,21 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (have_option_m) strbuf_addbuf(&sb, &message); hook_arg1 = "message"; - } else if (!stat(git_path_merge_msg(), &statbuf)) { + } else if (!stat(git_path_merge_msg(the_repository), &statbuf)) { /* * prepend SQUASH_MSG here if it exists and a * "merge --squash" was originally performed */ - if (!stat(git_path_squash_msg(), &statbuf)) { - if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0) + if (!stat(git_path_squash_msg(the_repository), &statbuf)) { + if (strbuf_read_file(&sb, git_path_squash_msg(the_repository), 0) < 0) die_errno(_("could not read SQUASH_MSG")); hook_arg1 = "squash"; } else hook_arg1 = "merge"; - if (strbuf_read_file(&sb, git_path_merge_msg(), 0) < 0) + if (strbuf_read_file(&sb, git_path_merge_msg(the_repository), 0) < 0) die_errno(_("could not read MERGE_MSG")); - } else if (!stat(git_path_squash_msg(), &statbuf)) { - if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0) + } else if (!stat(git_path_squash_msg(the_repository), &statbuf)) { + if (strbuf_read_file(&sb, git_path_squash_msg(the_repository), 0) < 0) die_errno(_("could not read SQUASH_MSG")); hook_arg1 = "squash"; } else if (template_file) { @@ -800,8 +813,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, " %s\n" "and try again.\n"), whence == FROM_MERGE ? - git_path_merge_head() : - git_path_cherry_pick_head()); + git_path_merge_head(the_repository) : + git_path_cherry_pick_head(the_repository)); } fprintf(s->fp, "\n"); @@ -1183,27 +1196,14 @@ static int dry_run_commit(int argc, const char **argv, const char *prefix, return commitable ? 0 : 1; } +define_list_config_array_extra(color_status_slots, {"added"}); + static int parse_status_slot(const char *slot) { - if (!strcasecmp(slot, "header")) - return WT_STATUS_HEADER; - if (!strcasecmp(slot, "branch")) - return WT_STATUS_ONBRANCH; - if (!strcasecmp(slot, "updated") || !strcasecmp(slot, "added")) + if (!strcasecmp(slot, "added")) return WT_STATUS_UPDATED; - if (!strcasecmp(slot, "changed")) - return WT_STATUS_CHANGED; - if (!strcasecmp(slot, "untracked")) - return WT_STATUS_UNTRACKED; - if (!strcasecmp(slot, "nobranch")) - return WT_STATUS_NOBRANCH; - if (!strcasecmp(slot, "unmerged")) - return WT_STATUS_UNMERGED; - if (!strcasecmp(slot, "localBranch")) - return WT_STATUS_LOCAL_BRANCH; - if (!strcasecmp(slot, "remoteBranch")) - return WT_STATUS_REMOTE_BRANCH; - return -1; + + return LOOKUP_CONFIG(color_status_slots, slot); } static int git_status_config(const char *k, const char *v, void *cb) @@ -1564,7 +1564,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!reflog_msg) reflog_msg = "commit (merge)"; pptr = commit_list_append(current_head, pptr); - fp = xfopen(git_path_merge_head(), "r"); + fp = xfopen(git_path_merge_head(the_repository), "r"); while (strbuf_getline_lf(&m, fp) != EOF) { struct commit *parent; @@ -1575,8 +1575,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } fclose(fp); strbuf_release(&m); - if (!stat(git_path_merge_mode(), &statbuf)) { - if (strbuf_read_file(&sb, git_path_merge_mode(), 0) < 0) + if (!stat(git_path_merge_mode(the_repository), &statbuf)) { + if (strbuf_read_file(&sb, git_path_merge_mode(the_repository), 0) < 0) die_errno(_("could not read MERGE_MODE")); if (!strcmp(sb.buf, "no-ff")) allow_fast_forward = 0; @@ -1639,12 +1639,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix) die("%s", err.buf); } - unlink(git_path_cherry_pick_head()); - unlink(git_path_revert_head()); - unlink(git_path_merge_head()); - unlink(git_path_merge_msg()); - unlink(git_path_merge_mode()); - unlink(git_path_squash_msg()); + unlink(git_path_cherry_pick_head(the_repository)); + unlink(git_path_revert_head(the_repository)); + unlink(git_path_merge_head(the_repository)); + unlink(git_path_merge_msg(the_repository)); + unlink(git_path_merge_mode(the_repository)); + unlink(git_path_squash_msg(the_repository)); if (commit_index_files()) die (_("Repository has been updated, but unable to write\n" diff --git a/builtin/config.c b/builtin/config.c index b29d26dede..2c93a289a7 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -67,7 +67,7 @@ static int show_origin; { OPTION_CALLBACK, (s), (l), (v), NULL, (h), PARSE_OPT_NOARG | \ PARSE_OPT_NONEG, option_parse_type, (i) } -static struct option builtin_config_options[]; +static NORETURN void usage_builtin_config(void); static int option_parse_type(const struct option *opt, const char *arg, int unset) @@ -111,8 +111,7 @@ static int option_parse_type(const struct option *opt, const char *arg, * --type=int'. */ error("only one type at a time."); - usage_with_options(builtin_config_usage, - builtin_config_options); + usage_builtin_config(); } *to_type = new_type; @@ -157,11 +156,16 @@ static struct option builtin_config_options[] = { OPT_END(), }; +static NORETURN void usage_builtin_config(void) +{ + usage_with_options(builtin_config_usage, builtin_config_options); +} + static void check_argc(int argc, int min, int max) { if (argc >= min && argc <= max) return; error("wrong number of arguments"); - usage_with_options(builtin_config_usage, builtin_config_options); + usage_builtin_config(); } static void show_config_origin(struct strbuf *buf) @@ -596,7 +600,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) if (use_global_config + use_system_config + use_local_config + !!given_config_source.file + !!given_config_source.blob > 1) { error("only one config file at a time."); - usage_with_options(builtin_config_usage, builtin_config_options); + usage_builtin_config(); } if (use_local_config && nongit) @@ -660,12 +664,12 @@ int cmd_config(int argc, const char **argv, const char *prefix) if ((actions & (ACTION_GET_COLOR|ACTION_GET_COLORBOOL)) && type) { error("--get-color and variable type are incoherent"); - usage_with_options(builtin_config_usage, builtin_config_options); + usage_builtin_config(); } if (HAS_MULTI_BITS(actions)) { error("only one action at a time."); - usage_with_options(builtin_config_usage, builtin_config_options); + usage_builtin_config(); } if (actions == 0) switch (argc) { @@ -673,25 +677,24 @@ int cmd_config(int argc, const char **argv, const char *prefix) case 2: actions = ACTION_SET; break; case 3: actions = ACTION_SET_ALL; break; default: - usage_with_options(builtin_config_usage, builtin_config_options); + usage_builtin_config(); } if (omit_values && !(actions == ACTION_LIST || actions == ACTION_GET_REGEXP)) { error("--name-only is only applicable to --list or --get-regexp"); - usage_with_options(builtin_config_usage, builtin_config_options); + usage_builtin_config(); } if (show_origin && !(actions & (ACTION_GET|ACTION_GET_ALL|ACTION_GET_REGEXP|ACTION_LIST))) { error("--show-origin is only applicable to --get, --get-all, " "--get-regexp, and --list."); - usage_with_options(builtin_config_usage, builtin_config_options); + usage_builtin_config(); } if (default_value && !(actions & ACTION_GET)) { error("--default is only applicable to --get"); - usage_with_options(builtin_config_usage, - builtin_config_options); + usage_builtin_config(); } if (actions & PAGING_ACTIONS) diff --git a/builtin/describe.c b/builtin/describe.c index cf1ae77d7c..1e87f68d5e 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -13,11 +13,15 @@ #include "hashmap.h" #include "argv-array.h" #include "run-command.h" +#include "object-store.h" #include "revision.h" #include "list-objects.h" +#include "commit-slab.h" #define MAX_TAGS (FLAG_BITS - 1) +define_commit_slab(commit_names, struct commit_name *); + static const char * const describe_usage[] = { N_("git describe [<options>] [<commit-ish>...]"), N_("git describe [<options>] --dirty"), @@ -37,6 +41,7 @@ static struct string_list patterns = STRING_LIST_INIT_NODUP; static struct string_list exclude_patterns = STRING_LIST_INIT_NODUP; static int always; static const char *suffix, *dirty, *broken; +static struct commit_names commit_names; /* diff-index command arguments to check if working tree is dirty. */ static const char *diff_index_args[] = { @@ -321,11 +326,14 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) if (!have_util) { struct hashmap_iter iter; struct commit *c; - struct commit_name *n = hashmap_iter_first(&names, &iter); + struct commit_name *n; + + init_commit_names(&commit_names); + n = hashmap_iter_first(&names, &iter); for (; n; n = hashmap_iter_next(&iter)) { c = lookup_commit_reference_gently(&n->peeled, 1); if (c) - c->util = n; + *commit_names_at(&commit_names, c) = n; } have_util = 1; } @@ -336,8 +344,11 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) while (list) { struct commit *c = pop_commit(&list); struct commit_list *parents = c->parents; + struct commit_name **slot; + seen_commits++; - n = c->util; + slot = commit_names_peek(&commit_names, c); + n = slot ? *slot : NULL; if (n) { if (!tags && !all && n->prio < 2) { unannotated_cnt++; diff --git a/builtin/diff.c b/builtin/diff.c index bfefff3a84..b709b6e984 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -352,6 +352,13 @@ int cmd_diff(int argc, const char **argv, const char *prefix) rev.diffopt.flags.allow_external = 1; rev.diffopt.flags.allow_textconv = 1; + /* + * Default to intent-to-add entries invisible in the + * index. This makes them show up as new files in diff-files + * and not at all in diff-cached. + */ + rev.diffopt.ita_invisible_in_index = 1; + if (nongit) die(_("Not a git repository")); argc = setup_revisions(argc, argv, &rev, NULL); diff --git a/builtin/difftool.c b/builtin/difftool.c index bc97d4aef2..51f6c9cdb4 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -20,6 +20,7 @@ #include "argv-array.h" #include "strbuf.h" #include "lockfile.h" +#include "object-store.h" #include "dir.h" static char *diff_gui_tool; diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 6c9768742f..9ee6a4d2e8 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -8,6 +8,7 @@ #include "config.h" #include "refs.h" #include "refspec.h" +#include "object-store.h" #include "commit.h" #include "object.h" #include "tag.h" @@ -22,6 +23,7 @@ #include "quote.h" #include "remote.h" #include "blob.h" +#include "commit-slab.h" static const char *fast_export_usage[] = { N_("git fast-export [rev-list-opts]"), @@ -38,6 +40,7 @@ static int full_tree; static struct string_list extra_refs = STRING_LIST_INIT_NODUP; static struct refspec refspecs = REFSPEC_INIT_FETCH; static int anonymize; +static struct revision_sources revision_sources; static int parse_opt_signed_tag_mode(const struct option *opt, const char *arg, int unset) @@ -589,7 +592,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode)) export_blob(&diff_queued_diff.queue[i]->two->oid); - refname = commit->util; + refname = *revision_sources_at(&revision_sources, commit); if (anonymize) { refname = anonymize_refname(refname); anonymize_ident_line(&committer, &committer_end); @@ -861,10 +864,11 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) * This ref will not be updated through a commit, lets make * sure it gets properly updated eventually. */ - if (commit->util || commit->object.flags & SHOWN) + if (*revision_sources_at(&revision_sources, commit) || + commit->object.flags & SHOWN) string_list_append(&extra_refs, full_name)->util = commit; - if (!commit->util) - commit->util = full_name; + if (!*revision_sources_at(&revision_sources, commit)) + *revision_sources_at(&revision_sources, commit) = full_name; } } @@ -1028,8 +1032,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); init_revisions(&revs, prefix); + init_revision_sources(&revision_sources); revs.topo_order = 1; - revs.show_source = 1; + revs.sources = &revision_sources; revs.rewrite_parents = 1; argc = parse_options(argc, argv, prefix, options, fast_export_usage, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN); diff --git a/builtin/fetch.c b/builtin/fetch.c index ea5b9669ad..ac06f6a576 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -6,6 +6,7 @@ #include "repository.h" #include "refs.h" #include "refspec.h" +#include "object-store.h" #include "commit.h" #include "builtin.h" #include "string-list.h" @@ -93,19 +94,6 @@ static int git_fetch_config(const char *k, const char *v, void *cb) return git_default_config(k, v, cb); } -static int gitmodules_fetch_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "submodule.fetchjobs")) { - max_children = parse_submodule_fetchjobs(var, value); - return 0; - } else if (!strcmp(var, "fetch.recursesubmodules")) { - recurse_submodules = parse_fetch_recurse_submodules_arg(var, value); - return 0; - } - - return 0; -} - static int parse_refmap_arg(const struct option *opt, const char *arg, int unset) { /* @@ -254,9 +242,9 @@ static int will_fetch(struct ref **head, const unsigned char *sha1) return 0; } -static void find_non_local_tags(struct transport *transport, - struct ref **head, - struct ref ***tail) +static void find_non_local_tags(const struct ref *refs, + struct ref **head, + struct ref ***tail) { struct string_list existing_refs = STRING_LIST_INIT_DUP; struct string_list remote_refs = STRING_LIST_INIT_NODUP; @@ -264,7 +252,7 @@ static void find_non_local_tags(struct transport *transport, struct string_list_item *item = NULL; for_each_ref(add_existing, &existing_refs); - for (ref = transport_get_remote_refs(transport, NULL); ref; ref = ref->next) { + for (ref = refs; ref; ref = ref->next) { if (!starts_with(ref->name, "refs/tags/")) continue; @@ -338,7 +326,8 @@ static void find_non_local_tags(struct transport *transport, string_list_clear(&remote_refs, 0); } -static struct ref *get_ref_map(struct transport *transport, +static struct ref *get_ref_map(struct remote *remote, + const struct ref *remote_refs, struct refspec *rs, int tags, int *autotags) { @@ -346,26 +335,11 @@ static struct ref *get_ref_map(struct transport *transport, struct ref *rm; struct ref *ref_map = NULL; struct ref **tail = &ref_map; - struct argv_array ref_prefixes = ARGV_ARRAY_INIT; /* opportunistically-updated references: */ struct ref *orefs = NULL, **oref_tail = &orefs; - const struct ref *remote_refs; - - if (rs->nr) - refspec_ref_prefixes(rs, &ref_prefixes); - else if (transport->remote && transport->remote->fetch.nr) - refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes); - - if (ref_prefixes.argc && - (tags == TAGS_SET || (tags == TAGS_DEFAULT && !rs->nr))) { - argv_array_push(&ref_prefixes, "refs/tags/"); - } - - remote_refs = transport_get_remote_refs(transport, &ref_prefixes); - - argv_array_clear(&ref_prefixes); + struct string_list existing_refs = STRING_LIST_INIT_DUP; if (rs->nr) { struct refspec *fetch_refspec; @@ -402,7 +376,7 @@ static struct ref *get_ref_map(struct transport *transport, if (refmap.nr) fetch_refspec = &refmap; else - fetch_refspec = &transport->remote->fetch; + fetch_refspec = &remote->fetch; for (i = 0; i < fetch_refspec->nr; i++) get_fetch_map(ref_map, &fetch_refspec->items[i], &oref_tail, 1); @@ -410,7 +384,6 @@ static struct ref *get_ref_map(struct transport *transport, die("--refmap option is only meaningful with command-line refspec(s)."); } else { /* Use the defaults */ - struct remote *remote = transport->remote; struct branch *branch = branch_get(NULL); int has_merge = branch_has_merge_config(branch); if (remote && @@ -449,7 +422,7 @@ static struct ref *get_ref_map(struct transport *transport, /* also fetch all tags */ get_fetch_map(remote_refs, tag_refspec, &tail, 0); else if (tags == TAGS_DEFAULT && *autotags) - find_non_local_tags(transport, &ref_map, &tail); + find_non_local_tags(remote_refs, &ref_map, &tail); /* Now append any refs to be updated opportunistically: */ *tail = orefs; @@ -458,7 +431,23 @@ static struct ref *get_ref_map(struct transport *transport, tail = &rm->next; } - return ref_remove_duplicates(ref_map); + ref_map = ref_remove_duplicates(ref_map); + + for_each_ref(add_existing, &existing_refs); + for (rm = ref_map; rm; rm = rm->next) { + if (rm->peer_ref) { + struct string_list_item *peer_item = + string_list_lookup(&existing_refs, + rm->peer_ref->name); + if (peer_item) { + struct object_id *old_oid = peer_item->util; + oidcpy(&rm->peer_ref->old_oid, old_oid); + } + } + } + string_list_clear(&existing_refs, 1); + + return ref_map; } #define STORE_REF_ERROR_OTHER 1 @@ -768,7 +757,7 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid) } static int store_updated_refs(const char *raw_url, const char *remote_name, - struct ref *ref_map) + int connectivity_checked, struct ref *ref_map) { FILE *fp; struct commit *commit; @@ -777,7 +766,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, const char *what, *kind; struct ref *rm; char *url; - const char *filename = dry_run ? "/dev/null" : git_path_fetch_head(); + const char *filename = dry_run ? "/dev/null" : git_path_fetch_head(the_repository); int want_status; int summary_width = transport_summary_width(ref_map); @@ -790,10 +779,12 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, else url = xstrdup("foreign"); - rm = ref_map; - if (check_connected(iterate_ref_map, &rm, NULL)) { - rc = error(_("%s did not send all necessary objects\n"), url); - goto abort; + if (!connectivity_checked) { + rm = ref_map; + if (check_connected(iterate_ref_map, &rm, NULL)) { + rc = error(_("%s did not send all necessary objects\n"), url); + goto abort; + } } prepare_format_display(ref_map); @@ -945,15 +936,32 @@ static int quickfetch(struct ref *ref_map) return check_connected(iterate_ref_map, &rm, &opt); } -static int fetch_refs(struct transport *transport, struct ref *ref_map) +static int fetch_refs(struct transport *transport, struct ref *ref_map, + struct ref **updated_remote_refs) { int ret = quickfetch(ref_map); if (ret) - ret = transport_fetch_refs(transport, ref_map); + ret = transport_fetch_refs(transport, ref_map, + updated_remote_refs); if (!ret) - ret |= store_updated_refs(transport->url, - transport->remote->name, - ref_map); + /* + * Keep the new pack's ".keep" file around to allow the caller + * time to update refs to reference the new objects. + */ + return 0; + transport_unlock_pack(transport); + return ret; +} + +/* Update local refs based on the ref values fetched from a remote */ +static int consume_refs(struct transport *transport, struct ref *ref_map) +{ + int connectivity_checked = transport->smart_options + ? transport->smart_options->connectivity_checked : 0; + int ret = store_updated_refs(transport->url, + transport->remote->name, + connectivity_checked, + ref_map); transport_unlock_pack(transport); return ret; } @@ -1029,7 +1037,7 @@ static void check_not_current_branch(struct ref *ref_map) static int truncate_fetch_head(void) { - const char *filename = git_path_fetch_head(); + const char *filename = git_path_fetch_head(the_repository); FILE *fp = fopen_for_writing(filename); if (!fp) @@ -1099,7 +1107,8 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map) transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); transport_set_option(transport, TRANS_OPT_DEPTH, "0"); transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); - fetch_refs(transport, ref_map); + if (!fetch_refs(transport, ref_map, NULL)) + consume_refs(transport, ref_map); if (gsecondary) { transport_disconnect(gsecondary); @@ -1110,13 +1119,12 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map) static int do_fetch(struct transport *transport, struct refspec *rs) { - struct string_list existing_refs = STRING_LIST_INIT_DUP; struct ref *ref_map; - struct ref *rm; int autotags = (transport->remote->fetch_tags == 1); int retcode = 0; - - for_each_ref(add_existing, &existing_refs); + const struct ref *remote_refs; + struct ref *updated_remote_refs = NULL; + struct argv_array ref_prefixes = ARGV_ARRAY_INIT; if (tags == TAGS_DEFAULT) { if (transport->remote->fetch_tags == 2) @@ -1132,22 +1140,24 @@ static int do_fetch(struct transport *transport, goto cleanup; } - ref_map = get_ref_map(transport, rs, tags, &autotags); - if (!update_head_ok) - check_not_current_branch(ref_map); + if (rs->nr) + refspec_ref_prefixes(rs, &ref_prefixes); + else if (transport->remote && transport->remote->fetch.nr) + refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes); - for (rm = ref_map; rm; rm = rm->next) { - if (rm->peer_ref) { - struct string_list_item *peer_item = - string_list_lookup(&existing_refs, - rm->peer_ref->name); - if (peer_item) { - struct object_id *old_oid = peer_item->util; - oidcpy(&rm->peer_ref->old_oid, old_oid); - } - } + if (ref_prefixes.argc && + (tags == TAGS_SET || (tags == TAGS_DEFAULT && !rs->nr))) { + argv_array_push(&ref_prefixes, "refs/tags/"); } + remote_refs = transport_get_remote_refs(transport, &ref_prefixes); + argv_array_clear(&ref_prefixes); + + ref_map = get_ref_map(transport->remote, remote_refs, rs, + tags, &autotags); + if (!update_head_ok) + check_not_current_branch(ref_map); + if (tags == TAGS_DEFAULT && autotags) transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); if (prune) { @@ -1164,7 +1174,24 @@ static int do_fetch(struct transport *transport, transport->url); } } - if (fetch_refs(transport, ref_map)) { + + if (fetch_refs(transport, ref_map, &updated_remote_refs)) { + free_refs(ref_map); + retcode = 1; + goto cleanup; + } + if (updated_remote_refs) { + /* + * Regenerate ref_map using the updated remote refs. This is + * to account for additional information which may be provided + * by the transport (e.g. shallow info). + */ + free_refs(ref_map); + ref_map = get_ref_map(transport->remote, updated_remote_refs, rs, + tags, &autotags); + free_refs(updated_remote_refs); + } + if (consume_refs(transport, ref_map)) { free_refs(ref_map); retcode = 1; goto cleanup; @@ -1176,14 +1203,13 @@ static int do_fetch(struct transport *transport, if (tags == TAGS_DEFAULT && autotags) { struct ref **tail = &ref_map; ref_map = NULL; - find_non_local_tags(transport, &ref_map, &tail); + find_non_local_tags(remote_refs, &ref_map, &tail); if (ref_map) backfill_tags(transport, ref_map); free_refs(ref_map); } cleanup: - string_list_clear(&existing_refs, 1); return retcode; } @@ -1433,7 +1459,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) for (i = 1; i < argc; i++) strbuf_addf(&default_rla, " %s", argv[i]); - config_from_gitmodules(gitmodules_fetch_config, NULL); + fetch_config_from_gitmodules(&max_children, &recurse_submodules); git_config(git_fetch_config, NULL); argc = parse_options(argc, argv, prefix, @@ -1449,7 +1475,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (unshallow) { if (depth) die(_("--depth and --unshallow cannot be used together")); - else if (!is_repository_shallow()) + else if (!is_repository_shallow(the_repository)) die(_("--unshallow on a complete repository does not make sense")); else depth = xstrfmt("%d", INFINITE_DEPTH); diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index bd680be687..1b526adb3a 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -2,6 +2,7 @@ #include "cache.h" #include "config.h" #include "refs.h" +#include "object-store.h" #include "commit.h" #include "diff.h" #include "revision.h" diff --git a/builtin/grep.c b/builtin/grep.c index ee753a403e..61bcaf6e58 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -828,6 +828,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) GREP_PATTERN_TYPE_PCRE), OPT_GROUP(""), OPT_BOOL('n', "line-number", &opt.linenum, N_("show line numbers")), + OPT_BOOL(0, "column", &opt.columnnum, N_("show column number of first match")), OPT_NEGBIT('h', NULL, &opt.pathname, N_("don't show filenames"), 1), OPT_BIT('H', NULL, &opt.pathname, N_("show filenames"), 1), OPT_NEGBIT(0, "full-name", &opt.relative, diff --git a/builtin/hash-object.c b/builtin/hash-object.c index a9a3a198c3..9ada4f4dfd 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -6,6 +6,7 @@ */ #include "builtin.h" #include "config.h" +#include "object-store.h" #include "blob.h" #include "quote.h" #include "parse-options.h" diff --git a/builtin/help.c b/builtin/help.c index 58e0a5507f..8d4f6dd301 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -37,6 +37,7 @@ static const char *html_path; static int show_all = 0; static int show_guides = 0; +static int show_config; static int verbose; static unsigned int colopts; static enum help_format help_format = HELP_FORMAT_NONE; @@ -45,6 +46,8 @@ static struct option builtin_help_options[] = { OPT_BOOL('a', "all", &show_all, N_("print all available commands")), OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")), OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")), + OPT_BOOL('c', "config", &show_config, N_("print all configuration variable names")), + OPT_SET_INT_F(0, "config-for-completion", &show_config, "", 2, PARSE_OPT_HIDDEN), OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN), OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"), HELP_FORMAT_WEB), @@ -444,6 +447,19 @@ int cmd_help(int argc, const char **argv, const char *prefix) list_commands(colopts, &main_cmds, &other_cmds); } + if (show_config) { + int for_human = show_config == 1; + + if (!for_human) { + list_config_help(for_human); + return 0; + } + setup_pager(); + list_config_help(for_human); + printf("\n%s\n", _("'git help config' for more information")); + return 0; + } + if (show_guides) list_common_guides_help(); diff --git a/builtin/log.c b/builtin/log.c index 4686f68594..805f89d7e1 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -7,6 +7,7 @@ #include "cache.h" #include "config.h" #include "refs.h" +#include "object-store.h" #include "color.h" #include "commit.h" #include "diff.h" @@ -28,6 +29,7 @@ #include "mailmap.h" #include "gpg-interface.h" #include "progress.h" +#include "commit-slab.h" #define MAIL_DEFAULT_WRAP 72 @@ -148,6 +150,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP; struct decoration_filter decoration_filter = {&decorate_refs_include, &decorate_refs_exclude}; + static struct revision_sources revision_sources; const struct option builtin_log_options[] = { OPT__QUIET(&quiet, N_("suppress diff output")), @@ -194,8 +197,10 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, rev->diffopt.filter || rev->diffopt.flags.follow_renames) rev->always_show_header = 0; - if (source) - rev->show_source = 1; + if (source) { + init_revision_sources(&revision_sources); + rev->sources = &revision_sources; + } if (mailmap) { rev->mailmap = xcalloc(1, sizeof(struct string_list)); @@ -1337,6 +1342,8 @@ static struct commit *get_base_commit(const char *base_commit, return base; } +define_commit_slab(commit_base, int); + static void prepare_bases(struct base_tree_info *bases, struct commit *base, struct commit **list, @@ -1345,11 +1352,13 @@ static void prepare_bases(struct base_tree_info *bases, struct commit *commit; struct rev_info revs; struct diff_options diffopt; + struct commit_base commit_base; int i; if (!base) return; + init_commit_base(&commit_base); diff_setup(&diffopt); diffopt.flags.recursive = 1; diff_setup_done(&diffopt); @@ -1362,7 +1371,7 @@ static void prepare_bases(struct base_tree_info *bases, for (i = 0; i < total; i++) { list[i]->object.flags &= ~UNINTERESTING; add_pending_object(&revs, &list[i]->object, "rev_list"); - list[i]->util = (void *)1; + *commit_base_at(&commit_base, list[i]) = 1; } base->object.flags |= UNINTERESTING; add_pending_object(&revs, &base->object, "base"); @@ -1376,7 +1385,7 @@ static void prepare_bases(struct base_tree_info *bases, while ((commit = get_revision(&revs)) != NULL) { struct object_id oid; struct object_id *patch_id; - if (commit->util) + if (*commit_base_at(&commit_base, commit)) continue; if (commit_patch_id(commit, &diffopt, &oid, 0)) die(_("cannot get patch id")); @@ -1385,6 +1394,7 @@ static void prepare_bases(struct base_tree_info *bases, oidcpy(patch_id, &oid); bases->nr_patch_id++; } + clear_commit_base(&commit_base); } static void print_bases(struct base_tree_info *bases, FILE *file) @@ -1746,6 +1756,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (base_commit || base_auto) { struct commit *base = get_base_commit(base_commit, list, nr); reset_revision_walk(); + clear_object_flags(UNINTERESTING); prepare_bases(&bases, base, list, nr); } diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 409da4e835..fe3b952cb3 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -5,6 +5,7 @@ */ #include "cache.h" #include "config.h" +#include "object-store.h" #include "blob.h" #include "tree.h" #include "commit.h" diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index bf01e05808..8a8d579752 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -1,6 +1,7 @@ #include "builtin.h" #include "tree-walk.h" #include "xdiff-interface.h" +#include "object-store.h" #include "blob.h" #include "exec-cmd.h" #include "merge-blobs.h" diff --git a/builtin/merge.c b/builtin/merge.c index b00d6f4821..d1b547d973 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -247,9 +247,9 @@ static struct option builtin_merge_options[] = { /* Cleans up metadata that is uninteresting after a succeeded merge. */ static void drop_save(void) { - unlink(git_path_merge_head()); - unlink(git_path_merge_msg()); - unlink(git_path_merge_mode()); + unlink(git_path_merge_head(the_repository)); + unlink(git_path_merge_msg(the_repository)); + unlink(git_path_merge_mode(the_repository)); } static int save_state(struct object_id *stash) @@ -382,7 +382,7 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead oid_to_hex(&commit->object.oid)); pretty_print_commit(&ctx, commit, &out); } - write_file_buf(git_path_squash_msg(), out.buf, out.len); + write_file_buf(git_path_squash_msg(the_repository), out.buf, out.len); strbuf_release(&out); } @@ -445,6 +445,7 @@ static void merge_name(const char *remote, struct strbuf *msg) struct object_id branch_head; struct strbuf buf = STRBUF_INIT; struct strbuf bname = STRBUF_INIT; + struct merge_remote_desc *desc; const char *ptr; char *found_ref; int len, early; @@ -517,16 +518,13 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_release(&truname); } - if (remote_head->util) { - struct merge_remote_desc *desc; - desc = merge_remote_util(remote_head); - if (desc && desc->obj && desc->obj->type == OBJ_TAG) { - strbuf_addf(msg, "%s\t\t%s '%s'\n", - oid_to_hex(&desc->obj->oid), - type_name(desc->obj->type), - remote); - goto cleanup; - } + desc = merge_remote_util(remote_head); + if (desc && desc->obj && desc->obj->type == OBJ_TAG) { + strbuf_addf(msg, "%s\t\t%s '%s'\n", + oid_to_hex(&desc->obj->oid), + type_name(desc->obj->type), + remote); + goto cleanup; } strbuf_addf(msg, "%s\t\tcommit '%s'\n", @@ -743,7 +741,7 @@ static void add_strategies(const char *string, unsigned attr) static void read_merge_msg(struct strbuf *msg) { - const char *filename = git_path_merge_msg(); + const char *filename = git_path_merge_msg(the_repository); strbuf_reset(msg); if (strbuf_read_file(msg, filename, 0) < 0) die_errno(_("Could not read from '%s'"), filename); @@ -780,18 +778,18 @@ static void prepare_to_commit(struct commit_list *remoteheads) if (signoff) append_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0); write_merge_heads(remoteheads); - write_file_buf(git_path_merge_msg(), msg.buf, msg.len); + write_file_buf(git_path_merge_msg(the_repository), msg.buf, msg.len); if (run_commit_hook(0 < option_edit, get_index_file(), "prepare-commit-msg", - git_path_merge_msg(), "merge", NULL)) + git_path_merge_msg(the_repository), "merge", NULL)) abort_commit(remoteheads, NULL); if (0 < option_edit) { - if (launch_editor(git_path_merge_msg(), NULL, NULL)) + if (launch_editor(git_path_merge_msg(the_repository), NULL, NULL)) abort_commit(remoteheads, NULL); } if (verify_msg && run_commit_hook(0 < option_edit, get_index_file(), "commit-msg", - git_path_merge_msg(), NULL)) + git_path_merge_msg(the_repository), NULL)) abort_commit(remoteheads, NULL); read_merge_msg(&msg); @@ -861,7 +859,7 @@ static int suggest_conflicts(void) FILE *fp; struct strbuf msgbuf = STRBUF_INIT; - filename = git_path_merge_msg(); + filename = git_path_merge_msg(the_repository); fp = xfopen(filename, "a"); append_conflicts_hint(&msgbuf); @@ -934,19 +932,22 @@ static void write_merge_heads(struct commit_list *remoteheads) for (j = remoteheads; j; j = j->next) { struct object_id *oid; struct commit *c = j->item; - if (c->util && merge_remote_util(c)->obj) { - oid = &merge_remote_util(c)->obj->oid; + struct merge_remote_desc *desc; + + desc = merge_remote_util(c); + if (desc && desc->obj) { + oid = &desc->obj->oid; } else { oid = &c->object.oid; } strbuf_addf(&buf, "%s\n", oid_to_hex(oid)); } - write_file_buf(git_path_merge_head(), buf.buf, buf.len); + write_file_buf(git_path_merge_head(the_repository), buf.buf, buf.len); strbuf_reset(&buf); if (fast_forward == FF_NO) strbuf_addstr(&buf, "no-ff"); - write_file_buf(git_path_merge_mode(), buf.buf, buf.len); + write_file_buf(git_path_merge_mode(the_repository), buf.buf, buf.len); strbuf_release(&buf); } @@ -954,7 +955,8 @@ static void write_merge_state(struct commit_list *remoteheads) { write_merge_heads(remoteheads); strbuf_addch(&merge_msg, '\n'); - write_file_buf(git_path_merge_msg(), merge_msg.buf, merge_msg.len); + write_file_buf(git_path_merge_msg(the_repository), merge_msg.buf, + merge_msg.len); } static int default_edit_option(void) @@ -1037,7 +1039,7 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge if (!merge_names) merge_names = &fetch_head_file; - filename = git_path_fetch_head(); + filename = git_path_fetch_head(the_repository); fd = open(filename, O_RDONLY); if (fd < 0) die_errno(_("could not open '%s' for reading"), filename); @@ -1185,14 +1187,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix) branch = branch_to_free = resolve_refdup("HEAD", 0, &head_oid, NULL); if (branch) skip_prefix(branch, "refs/heads/", &branch); + + init_diff_ui_defaults(); + git_config(git_merge_config, NULL); + if (!branch || is_null_oid(&head_oid)) head_commit = NULL; else head_commit = lookup_commit_or_die(&head_oid, "HEAD"); - init_diff_ui_defaults(); - git_config(git_merge_config, NULL); - if (branch_mergeoptions) parse_branch_merge_options(branch_mergeoptions); argc = parse_options(argc, argv, prefix, builtin_merge_options, @@ -1211,7 +1214,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) usage_msg_opt(_("--abort expects no arguments"), builtin_merge_usage, builtin_merge_options); - if (!file_exists(git_path_merge_head())) + if (!file_exists(git_path_merge_head(the_repository))) die(_("There is no merge to abort (MERGE_HEAD missing).")); /* Invoke 'git reset --merge' */ @@ -1227,7 +1230,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) usage_msg_opt(_("--continue expects no arguments"), builtin_merge_usage, builtin_merge_options); - if (!file_exists(git_path_merge_head())) + if (!file_exists(git_path_merge_head(the_repository))) die(_("There is no merge in progress (MERGE_HEAD missing).")); /* Invoke 'git commit' */ @@ -1238,7 +1241,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (read_cache_unmerged()) die_resolve_conflict("merge"); - if (file_exists(git_path_merge_head())) { + if (file_exists(git_path_merge_head(the_repository))) { /* * There is no unmerged entry, don't advise 'git * add/rm <file>', just 'git commit'. @@ -1249,7 +1252,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else die(_("You have not concluded your merge (MERGE_HEAD exists).")); } - if (file_exists(git_path_cherry_pick_head())) { + if (file_exists(git_path_cherry_pick_head(the_repository))) { if (advice_resolve_conflict) die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" "Please, commit your changes before you merge.")); diff --git a/builtin/mktag.c b/builtin/mktag.c index 82a6e86077..6fb7dc8578 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -1,6 +1,7 @@ #include "builtin.h" #include "tag.h" #include "replace-object.h" +#include "object-store.h" /* * A signature file has a very simple fixed format: four lines diff --git a/builtin/mktree.c b/builtin/mktree.c index bb76b469fd..2dc4ad6ba8 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -7,6 +7,7 @@ #include "quote.h" #include "tree.h" #include "parse-options.h" +#include "object-store.h" static struct treeent { unsigned mode; diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 387ddf85d2..0eb440359d 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -6,6 +6,7 @@ #include "refs.h" #include "parse-options.h" #include "sha1-lookup.h" +#include "commit-slab.h" #define CUTOFF_DATE_SLOP 86400 /* one day */ @@ -17,11 +18,26 @@ typedef struct rev_name { int from_tag; } rev_name; +define_commit_slab(commit_rev_name, struct rev_name *); + static timestamp_t cutoff = TIME_MAX; +static struct commit_rev_name rev_names; /* How many generations are maximally preferred over _one_ merge traversal? */ #define MERGE_TRAVERSAL_WEIGHT 65535 +static struct rev_name *get_commit_rev_name(struct commit *commit) +{ + struct rev_name **slot = commit_rev_name_peek(&rev_names, commit); + + return slot ? *slot : NULL; +} + +static void set_commit_rev_name(struct commit *commit, struct rev_name *name) +{ + *commit_rev_name_at(&rev_names, commit) = name; +} + static int is_better_name(struct rev_name *name, const char *tip_name, timestamp_t taggerdate, @@ -65,7 +81,7 @@ static void name_rev(struct commit *commit, int generation, int distance, int from_tag, int deref) { - struct rev_name *name = (struct rev_name *)commit->util; + struct rev_name *name = get_commit_rev_name(commit); struct commit_list *parents; int parent_number = 1; char *to_free = NULL; @@ -84,7 +100,7 @@ static void name_rev(struct commit *commit, if (name == NULL) { name = xmalloc(sizeof(rev_name)); - commit->util = name; + set_commit_rev_name(commit, name); goto copy_data; } else if (is_better_name(name, tip_name, taggerdate, generation, distance, from_tag)) { @@ -296,7 +312,7 @@ static const char *get_rev_name(const struct object *o, struct strbuf *buf) if (o->type != OBJ_COMMIT) return get_exact_ref_match(o); c = (struct commit *) o; - n = c->util; + n = get_commit_rev_name(c); if (!n) return NULL; @@ -413,6 +429,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) OPT_END(), }; + init_commit_rev_name(&rev_names); git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0); if (all + transform_stdin + !!argc > 1) { diff --git a/builtin/notes.c b/builtin/notes.c index 6981e2d906..a0a1840040 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -11,6 +11,7 @@ #include "config.h" #include "builtin.h" #include "notes.h" +#include "object-store.h" #include "blob.h" #include "pretty.h" #include "refs.h" diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 71056d8294..ebc8cefb53 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2929,11 +2929,13 @@ static int pack_options_allow_reuse(void) static int get_object_list_from_bitmap(struct rev_info *revs) { - if (prepare_bitmap_walk(revs) < 0) + struct bitmap_index *bitmap_git; + if (!(bitmap_git = prepare_bitmap_walk(revs))) return -1; if (pack_options_allow_reuse() && !reuse_partial_packfile_from_bitmap( + bitmap_git, &reuse_packfile, &reuse_packfile_objects, &reuse_packfile_offset)) { @@ -2942,7 +2944,8 @@ static int get_object_list_from_bitmap(struct rev_info *revs) display_progress(progress_state, nr_result); } - traverse_bitmap_commit_list(&add_object_entry_from_bitmap); + traverse_bitmap_commit_list(bitmap_git, &add_object_entry_from_bitmap); + free_bitmap_index(bitmap_git); return 0; } @@ -2969,7 +2972,7 @@ static void get_object_list(int ac, const char **av) setup_revisions(ac, av, &revs, NULL); /* make sure shallows are read */ - is_repository_shallow(); + is_repository_shallow(the_repository); while (fgets(line, sizeof(line), stdin) != NULL) { int len = strlen(line); @@ -2987,7 +2990,7 @@ static void get_object_list(int ac, const char **av) struct object_id oid; if (get_oid_hex(line + 10, &oid)) die("not an SHA-1 '%s'", line + 10); - register_shallow(&oid); + register_shallow(the_repository, &oid); use_bitmap_index = 0; continue; } @@ -3299,7 +3302,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) use_bitmap_index = use_bitmap_index_default; /* "hard" reasons not to use bitmaps; these just won't work at all */ - if (!use_internal_rev_list || (!pack_to_stdout && write_bitmap_index) || is_repository_shallow()) + if (!use_internal_rev_list || (!pack_to_stdout && write_bitmap_index) || is_repository_shallow(the_repository)) use_bitmap_index = 0; if (pack_to_stdout || !rev_list_all) diff --git a/builtin/prune.c b/builtin/prune.c index 518ffbea13..70ec35aa05 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -6,6 +6,7 @@ #include "reachable.h" #include "parse-options.h" #include "progress.h" +#include "object-store.h" static const char * const prune_usage[] = { N_("git prune [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]"), @@ -159,7 +160,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix) remove_temporary_files(s); free(s); - if (is_repository_shallow()) + if (is_repository_shallow(the_repository)) prune_shallow(show_only); return 0; diff --git a/builtin/pull.c b/builtin/pull.c index 49cc3beb4c..7197b22b16 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -356,7 +356,7 @@ static int git_pull_config(const char *var, const char *value, void *cb) */ static void get_merge_heads(struct oid_array *merge_heads) { - const char *filename = git_path_fetch_head(); + const char *filename = git_path_fetch_head(the_repository); FILE *fp; struct strbuf sb = STRBUF_INIT; struct object_id oid; @@ -684,7 +684,7 @@ static const char *get_tracking_branch(const char *remote, const char *refspec) const char *spec_src; const char *merge_branch; - refspec_item_init(&spec, refspec, REFSPEC_FETCH); + refspec_item_init_or_die(&spec, refspec, REFSPEC_FETCH); spec_src = spec.src; if (!*spec_src || !strcmp(spec_src, "HEAD")) spec_src = "HEAD"; @@ -864,7 +864,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (read_cache_unmerged()) die_resolve_conflict("pull"); - if (file_exists(git_path_merge_head())) + if (file_exists(git_path_merge_head(the_repository))) die_conclude_merge(); if (get_oid("HEAD", &orig_head)) diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 68d36e0a56..44c7c9ee82 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -25,6 +25,7 @@ #include "tmp-objdir.h" #include "oidset.h" #include "packfile.h" +#include "object-store.h" #include "protocol.h" static const char * const receive_pack_usage[] = { @@ -905,7 +906,7 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) * not lose these new roots.. */ for (i = 0; i < extra.nr; i++) - register_shallow(&extra.oid[i]); + register_shallow(the_repository, &extra.oid[i]); si->shallow_ref[cmd->index] = 0; oid_array_clear(&extra); diff --git a/builtin/reflog.c b/builtin/reflog.c index a48984d37e..0091192995 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -1,6 +1,7 @@ #include "builtin.h" #include "config.h" #include "lockfile.h" +#include "object-store.h" #include "commit.h" #include "refs.h" #include "dir.h" diff --git a/builtin/remote.c b/builtin/remote.c index 1a82d850a2..c74ee88690 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -8,6 +8,7 @@ #include "run-command.h" #include "refs.h" #include "refspec.h" +#include "object-store.h" #include "argv-array.h" static const char * const builtin_remote_usage[] = { diff --git a/builtin/replace.c b/builtin/replace.c index 6da2411e14..deabda2101 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -487,7 +487,7 @@ static int create_graft(int argc, const char **argv, int force, int gentle) static int convert_graft_file(int force) { - const char *graft_file = get_graft_file(); + const char *graft_file = get_graft_file(the_repository); FILE *fp = fopen_or_warn(graft_file, "r"); struct strbuf buf = STRBUF_INIT, err = STRBUF_INIT; struct argv_array args = ARGV_ARRAY_INIT; diff --git a/builtin/reset.c b/builtin/reset.c index a862c70fab..ffe41c924b 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -39,7 +39,7 @@ static const char *reset_type_names[] = { static inline int is_merge(void) { - return !access(git_path_merge_head(), F_OK); + return !access(git_path_merge_head(the_repository), F_OK); } static int reset_index(const struct object_id *oid, int reset_type, int quiet) diff --git a/builtin/rev-list.c b/builtin/rev-list.c index fadd3ec14c..6fcb0ff6d5 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -6,6 +6,7 @@ #include "list-objects.h" #include "list-objects-filter.h" #include "list-objects-filter-options.h" +#include "object-store.h" #include "pack.h" #include "pack-bitmap.h" #include "builtin.h" @@ -16,6 +17,7 @@ #include "reflog-walk.h" #include "oidset.h" #include "packfile.h" +#include "object-store.h" static const char rev_list_usage[] = "git rev-list [OPTION] <commit-id>... [ -- paths... ]\n" @@ -514,17 +516,21 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (revs.count && !revs.left_right && !revs.cherry_mark) { uint32_t commit_count; int max_count = revs.max_count; - if (!prepare_bitmap_walk(&revs)) { - count_bitmap_commit_list(&commit_count, NULL, NULL, NULL); + struct bitmap_index *bitmap_git; + if ((bitmap_git = prepare_bitmap_walk(&revs))) { + count_bitmap_commit_list(bitmap_git, &commit_count, NULL, NULL, NULL); if (max_count >= 0 && max_count < commit_count) commit_count = max_count; printf("%d\n", commit_count); + free_bitmap_index(bitmap_git); return 0; } } else if (revs.max_count < 0 && revs.tag_objects && revs.tree_objects && revs.blob_objects) { - if (!prepare_bitmap_walk(&revs)) { - traverse_bitmap_commit_list(&show_object_fast); + struct bitmap_index *bitmap_git; + if ((bitmap_git = prepare_bitmap_walk(&revs))) { + traverse_bitmap_commit_list(bitmap_git, &show_object_fast); + free_bitmap_index(bitmap_git); return 0; } } diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 4f49e96bfd..2a6cb298bd 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -883,7 +883,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--is-shallow-repository")) { - printf("%s\n", is_repository_shallow() ? "true" + printf("%s\n", + is_repository_shallow(the_repository) ? "true" : "false"); continue; } diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 4923b1058c..42fd8d1a35 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -121,7 +121,7 @@ static int send_pack_config(const char *k, const char *v, void *cb) } } } - return 0; + return git_default_config(k, v, cb); } int cmd_send_pack(int argc, const char **argv, const char *prefix) diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 6c2148b71d..f2e985c00a 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -7,6 +7,7 @@ #include "argv-array.h" #include "parse-options.h" #include "dir.h" +#include "commit-slab.h" static const char* show_branch_usage[] = { N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n" @@ -21,6 +22,11 @@ static int showbranch_use_color = -1; static struct argv_array default_args = ARGV_ARRAY_INIT; +/* + * TODO: convert this use of commit->object.flags to commit-slab + * instead to store a pointer to ref name directly. Then use the same + * UNINTERESTING definition from revision.h here. + */ #define UNINTERESTING 01 #define REV_SHIFT 2 @@ -59,15 +65,27 @@ struct commit_name { int generation; /* how many parents away from head_name */ }; +define_commit_slab(commit_name_slab, struct commit_name *); +static struct commit_name_slab name_slab; + +static struct commit_name *commit_to_name(struct commit *commit) +{ + return *commit_name_slab_at(&name_slab, commit); +} + + /* Name the commit as nth generation ancestor of head_name; * we count only the first-parent relationship for naming purposes. */ static void name_commit(struct commit *commit, const char *head_name, int nth) { struct commit_name *name; - if (!commit->util) - commit->util = xmalloc(sizeof(struct commit_name)); - name = commit->util; + + name = *commit_name_slab_at(&name_slab, commit); + if (!name) { + name = xmalloc(sizeof(*name)); + *commit_name_slab_at(&name_slab, commit) = name; + } name->head_name = head_name; name->generation = nth; } @@ -79,8 +97,8 @@ static void name_commit(struct commit *commit, const char *head_name, int nth) */ static void name_parent(struct commit *commit, struct commit *parent) { - struct commit_name *commit_name = commit->util; - struct commit_name *parent_name = parent->util; + struct commit_name *commit_name = commit_to_name(commit); + struct commit_name *parent_name = commit_to_name(parent); if (!commit_name) return; if (!parent_name || @@ -94,12 +112,12 @@ static int name_first_parent_chain(struct commit *c) int i = 0; while (c) { struct commit *p; - if (!c->util) + if (!commit_to_name(c)) break; if (!c->parents) break; p = c->parents->item; - if (!p->util) { + if (!commit_to_name(p)) { name_parent(c, p); i++; } @@ -122,7 +140,7 @@ static void name_commits(struct commit_list *list, /* First give names to the given heads */ for (cl = list; cl; cl = cl->next) { c = cl->item; - if (c->util) + if (commit_to_name(c)) continue; for (i = 0; i < num_rev; i++) { if (rev[i] == c) { @@ -148,9 +166,9 @@ static void name_commits(struct commit_list *list, struct commit_name *n; int nth; c = cl->item; - if (!c->util) + if (!commit_to_name(c)) continue; - n = c->util; + n = commit_to_name(c); parents = c->parents; nth = 0; while (parents) { @@ -158,7 +176,7 @@ static void name_commits(struct commit_list *list, struct strbuf newname = STRBUF_INIT; parents = parents->next; nth++; - if (p->util) + if (commit_to_name(p)) continue; switch (n->generation) { case 0: @@ -271,7 +289,7 @@ static void show_one_commit(struct commit *commit, int no_name) { struct strbuf pretty = STRBUF_INIT; const char *pretty_str = "(unavailable)"; - struct commit_name *name = commit->util; + struct commit_name *name = commit_to_name(commit); if (commit->object.parsed) { pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty); @@ -660,6 +678,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) OPT_END() }; + init_commit_name_slab(&name_slab); + git_config(git_show_branch_config, NULL); /* If nothing is specified, try the default first */ diff --git a/show-index.c b/builtin/show-index.c index 1ead41e211..a6e678809e 100644 --- a/show-index.c +++ b/builtin/show-index.c @@ -1,10 +1,11 @@ +#include "builtin.h" #include "cache.h" #include "pack.h" static const char show_index_usage[] = "git show-index"; -int cmd_main(int argc, const char **argv) +int cmd_show_index(int argc, const char **argv, const char *prefix) { int i; unsigned nr; diff --git a/builtin/show-ref.c b/builtin/show-ref.c index f2eb1a7724..2f13f1316f 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -1,6 +1,7 @@ #include "builtin.h" #include "cache.h" #include "refs.h" +#include "object-store.h" #include "object.h" #include "tag.h" #include "string-list.h" diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index bd250ca216..a3c4564c6c 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -55,7 +55,7 @@ static char *get_default_remote(void) static int print_default_remote(int argc, const char **argv, const char *prefix) { - const char *remote; + char *remote; if (argc != 1) die(_("submodule--helper print-default-remote takes no arguments")); @@ -64,6 +64,7 @@ static int print_default_remote(int argc, const char **argv, const char *prefix) if (remote) printf("%s\n", remote); + free(remote); return 0; } @@ -440,6 +441,149 @@ static void for_each_listed_submodule(const struct module_list *list, fn(list->entries[i], cb_data); } +struct cb_foreach { + int argc; + const char **argv; + const char *prefix; + int quiet; + int recursive; +}; +#define CB_FOREACH_INIT { 0 } + +static void runcommand_in_submodule_cb(const struct cache_entry *list_item, + void *cb_data) +{ + struct cb_foreach *info = cb_data; + const char *path = list_item->name; + const struct object_id *ce_oid = &list_item->oid; + + const struct submodule *sub; + struct child_process cp = CHILD_PROCESS_INIT; + char *displaypath; + + displaypath = get_submodule_displaypath(path, info->prefix); + + sub = submodule_from_path(the_repository, &null_oid, path); + + if (!sub) + die(_("No url found for submodule path '%s' in .gitmodules"), + displaypath); + + if (!is_submodule_populated_gently(path, NULL)) + goto cleanup; + + prepare_submodule_repo_env(&cp.env_array); + + /* + * For the purpose of executing <command> in the submodule, + * separate shell is used for the purpose of running the + * child process. + */ + cp.use_shell = 1; + cp.dir = path; + + /* + * NEEDSWORK: the command currently has access to the variables $name, + * $sm_path, $displaypath, $sha1 and $toplevel only when the command + * contains a single argument. This is done for maintaining a faithful + * translation from shell script. + */ + if (info->argc == 1) { + char *toplevel = xgetcwd(); + struct strbuf sb = STRBUF_INIT; + + argv_array_pushf(&cp.env_array, "name=%s", sub->name); + argv_array_pushf(&cp.env_array, "sm_path=%s", path); + argv_array_pushf(&cp.env_array, "displaypath=%s", displaypath); + argv_array_pushf(&cp.env_array, "sha1=%s", + oid_to_hex(ce_oid)); + argv_array_pushf(&cp.env_array, "toplevel=%s", toplevel); + + /* + * Since the path variable was accessible from the script + * before porting, it is also made available after porting. + * The environment variable "PATH" has a very special purpose + * on windows. And since environment variables are + * case-insensitive in windows, it interferes with the + * existing PATH variable. Hence, to avoid that, we expose + * path via the args argv_array and not via env_array. + */ + sq_quote_buf(&sb, path); + argv_array_pushf(&cp.args, "path=%s; %s", + sb.buf, info->argv[0]); + strbuf_release(&sb); + free(toplevel); + } else { + argv_array_pushv(&cp.args, info->argv); + } + + if (!info->quiet) + printf(_("Entering '%s'\n"), displaypath); + + if (info->argv[0] && run_command(&cp)) + die(_("run_command returned non-zero status for %s\n."), + displaypath); + + if (info->recursive) { + struct child_process cpr = CHILD_PROCESS_INIT; + + cpr.git_cmd = 1; + cpr.dir = path; + prepare_submodule_repo_env(&cpr.env_array); + + argv_array_pushl(&cpr.args, "--super-prefix", NULL); + argv_array_pushf(&cpr.args, "%s/", displaypath); + argv_array_pushl(&cpr.args, "submodule--helper", "foreach", "--recursive", + NULL); + + if (info->quiet) + argv_array_push(&cpr.args, "--quiet"); + + argv_array_pushv(&cpr.args, info->argv); + + if (run_command(&cpr)) + die(_("run_command returned non-zero status while" + "recursing in the nested submodules of %s\n."), + displaypath); + } + +cleanup: + free(displaypath); +} + +static int module_foreach(int argc, const char **argv, const char *prefix) +{ + struct cb_foreach info = CB_FOREACH_INIT; + struct pathspec pathspec; + struct module_list list = MODULE_LIST_INIT; + + struct option module_foreach_options[] = { + OPT__QUIET(&info.quiet, N_("Suppress output of entering each submodule command")), + OPT_BOOL(0, "recursive", &info.recursive, + N_("Recurse into nested submodules")), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper foreach [--quiet] [--recursive] <command>"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_foreach_options, + git_submodule_helper_usage, PARSE_OPT_KEEP_UNKNOWN); + + if (module_list_compute(0, NULL, prefix, &pathspec, &list) < 0) + return 1; + + info.argc = argc; + info.argv = argv; + info.prefix = prefix; + + for_each_listed_submodule(&list, runcommand_in_submodule_cb, &info); + + return 0; +} + struct init_cb { const char *prefix; unsigned int flags; @@ -980,6 +1124,8 @@ static void deinit_submodule(const char *path, const char *prefix, if (!(flags & OPT_QUIET)) printf(format, displaypath); + submodule_unset_core_worktree(sub); + strbuf_release(&sb_rm); } @@ -1562,8 +1708,8 @@ static int update_clone_task_finished(int result, return 0; } -static int gitmodules_update_clone_config(const char *var, const char *value, - void *cb) +static int git_update_clone_config(const char *var, const char *value, + void *cb) { int *max_jobs = cb; if (!strcmp(var, "submodule.fetchjobs")) @@ -1613,8 +1759,8 @@ static int update_clone(int argc, const char **argv, const char *prefix) }; suc.prefix = prefix; - config_from_gitmodules(gitmodules_update_clone_config, &max_jobs); - git_config(gitmodules_update_clone_config, &max_jobs); + update_clone_config_from_gitmodules(&max_jobs); + git_config(git_update_clone_config, &max_jobs); argc = parse_options(argc, argv, prefix, module_update_clone_options, git_submodule_helper_usage, 0); @@ -1860,6 +2006,29 @@ static int check_name(int argc, const char **argv, const char *prefix) return 0; } +static int connect_gitdir_workingtree(int argc, const char **argv, const char *prefix) +{ + struct strbuf sb = STRBUF_INIT; + const char *name, *path; + char *sm_gitdir; + + if (argc != 3) + BUG("submodule--helper connect-gitdir-workingtree <name> <path>"); + + name = argv[1]; + path = argv[2]; + + strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name); + sm_gitdir = absolute_pathdup(sb.buf); + + connect_work_tree_and_git_dir(path, sm_gitdir, 0); + + strbuf_release(&sb); + free(sm_gitdir); + + return 0; +} + #define SUPPORT_SUPER_PREFIX (1<<0) struct cmd_struct { @@ -1873,9 +2042,11 @@ static struct cmd_struct commands[] = { {"name", module_name, 0}, {"clone", module_clone, 0}, {"update-clone", update_clone, 0}, + {"connect-gitdir-workingtree", connect_gitdir_workingtree, 0}, {"relative-path", resolve_relative_path, 0}, {"resolve-relative-url", resolve_relative_url, 0}, {"resolve-relative-url-test", resolve_relative_url_test, 0}, + {"foreach", module_foreach, SUPPORT_SUPER_PREFIX}, {"init", module_init, SUPPORT_SUPER_PREFIX}, {"status", module_status, SUPPORT_SUPER_PREFIX}, {"print-default-remote", print_default_remote, 0}, diff --git a/builtin/tag.c b/builtin/tag.c index 5d0dd11240..9919b03b2d 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -10,6 +10,7 @@ #include "config.h" #include "builtin.h" #include "refs.h" +#include "object-store.h" #include "tag.h" #include "run-command.h" #include "parse-options.h" diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c index 300eb59657..58652229f2 100644 --- a/builtin/unpack-file.c +++ b/builtin/unpack-file.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "config.h" +#include "object-store.h" static char *create_temp_file(struct object_id *oid) { diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 6e81ca8ca2..cf585fcc5e 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -1,6 +1,7 @@ #include "builtin.h" #include "cache.h" #include "config.h" +#include "object-store.h" #include "object.h" #include "delta.h" #include "pack.h" diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 4b4714b3fd..4fa3c0a86f 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -311,11 +311,12 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction, static const char *parse_cmd_option(struct strbuf *input, const char *next) { - if (!strncmp(next, "no-deref", 8) && next[8] == line_termination) + const char *rest; + if (skip_prefix(next, "no-deref", &rest) && *rest == line_termination) update_flags |= REF_NO_DEREF; else die("option unknown: %s", next); - return next + 8; + return rest; } static void update_refs_stdin(struct ref_transaction *transaction) @@ -332,16 +333,16 @@ static void update_refs_stdin(struct ref_transaction *transaction) die("empty command in input"); else if (isspace(*next)) die("whitespace before command: %s", next); - else if (starts_with(next, "update ")) - next = parse_cmd_update(transaction, &input, next + 7); - else if (starts_with(next, "create ")) - next = parse_cmd_create(transaction, &input, next + 7); - else if (starts_with(next, "delete ")) - next = parse_cmd_delete(transaction, &input, next + 7); - else if (starts_with(next, "verify ")) - next = parse_cmd_verify(transaction, &input, next + 7); - else if (starts_with(next, "option ")) - next = parse_cmd_option(&input, next + 7); + else if (skip_prefix(next, "update ", &next)) + next = parse_cmd_update(transaction, &input, next); + else if (skip_prefix(next, "create ", &next)) + next = parse_cmd_create(transaction, &input, next); + else if (skip_prefix(next, "delete ", &next)) + next = parse_cmd_delete(transaction, &input, next); + else if (skip_prefix(next, "verify ", &next)) + next = parse_cmd_verify(transaction, &input, next); + else if (skip_prefix(next, "option ", &next)) + next = parse_cmd_option(&input, next); else die("unknown command: %s", next); diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c index dcdaada111..f6922da16d 100644 --- a/builtin/verify-commit.c +++ b/builtin/verify-commit.c @@ -8,6 +8,7 @@ #include "cache.h" #include "config.h" #include "builtin.h" +#include "object-store.h" #include "commit.h" #include "run-command.h" #include <signal.h> diff --git a/bulk-checkin.c b/bulk-checkin.c index b7e131c47a..9f3b644811 100644 --- a/bulk-checkin.c +++ b/bulk-checkin.c @@ -8,6 +8,7 @@ #include "pack.h" #include "strbuf.h" #include "packfile.h" +#include "object-store.h" static struct bulk_checkin_state { unsigned plugged:1; @@ -1,6 +1,7 @@ #include "cache.h" #include "lockfile.h" #include "bundle.h" +#include "object-store.h" #include "object.h" #include "commit.h" #include "diff.h" diff --git a/cache-tree.c b/cache-tree.c index 25663825b5..6b46711996 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -3,6 +3,7 @@ #include "tree.h" #include "tree-walk.h" #include "cache-tree.h" +#include "object-store.h" #ifndef DEBUG #define DEBUG 0 @@ -484,7 +484,7 @@ extern const char *get_git_dir(void); extern const char *get_git_common_dir(void); extern char *get_object_directory(void); extern char *get_index_file(void); -extern char *get_graft_file(void); +extern char *get_graft_file(struct repository *r); extern void set_git_dir(const char *path); extern int get_common_dir_noenv(struct strbuf *sb, const char *gitdir); extern int get_common_dir(struct strbuf *sb, const char *gitdir); @@ -1192,32 +1192,6 @@ extern char *xdg_config_home(const char *filename); */ extern char *xdg_cache_home(const char *filename); -extern void *read_object_file_extended(const struct object_id *oid, - enum object_type *type, - unsigned long *size, int lookup_replace); -static inline void *read_object_file(const struct object_id *oid, enum object_type *type, unsigned long *size) -{ - return read_object_file_extended(oid, type, size, 1); -} - -/* Read and unpack an object file into memory, write memory to an object file */ -int oid_object_info(struct repository *r, const struct object_id *, unsigned long *); - -extern int hash_object_file(const void *buf, unsigned long len, - const char *type, struct object_id *oid); - -extern int write_object_file(const void *buf, unsigned long len, - const char *type, struct object_id *oid); - -extern int hash_object_file_literally(const void *buf, unsigned long len, - const char *type, struct object_id *oid, - unsigned flags); - -extern int pretend_object_file(void *, unsigned long, enum object_type, - struct object_id *oid); - -extern int force_object_loose(const struct object_id *oid, time_t mtime); - extern int git_open_cloexec(const char *name, int flags); #define git_open(name) git_open_cloexec(name, O_RDONLY) extern int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz); @@ -1227,43 +1201,6 @@ extern int check_object_signature(const struct object_id *oid, void *buf, unsign extern int finalize_object_file(const char *tmpfile, const char *filename); -/* - * Open the loose object at path, check its hash, and return the contents, - * type, and size. If the object is a blob, then "contents" may return NULL, - * to allow streaming of large blobs. - * - * Returns 0 on success, negative on error (details may be written to stderr). - */ -int read_loose_object(const char *path, - const struct object_id *expected_oid, - enum object_type *type, - unsigned long *size, - void **contents); - -/* - * Convenience for sha1_object_info_extended() with a NULL struct - * object_info. OBJECT_INFO_SKIP_CACHED is automatically set; pass - * nonzero flags to also set other flags. - */ -extern int has_sha1_file_with_flags(const unsigned char *sha1, int flags); -static inline int has_sha1_file(const unsigned char *sha1) -{ - return has_sha1_file_with_flags(sha1, 0); -} - -/* Same as the above, except for struct object_id. */ -extern int has_object_file(const struct object_id *oid); -extern int has_object_file_with_flags(const struct object_id *oid, int flags); - -/* - * Return true iff an alternate object database has a loose object - * with the specified name. This function does not respect replace - * references. - */ -extern int has_loose_object_nonlocal(const struct object_id *oid); - -extern void assert_oid_type(const struct object_id *oid, enum object_type expect); - /* Helper to check and "touch" a file */ extern int check_and_freshen_file(const char *fn, int freshen); @@ -1631,60 +1568,6 @@ int for_each_loose_file_in_objdir_buf(struct strbuf *path, #define FOR_EACH_OBJECT_LOCAL_ONLY 0x1 extern int for_each_loose_object(each_loose_object_fn, void *, unsigned flags); -struct object_info { - /* Request */ - enum object_type *typep; - unsigned long *sizep; - off_t *disk_sizep; - unsigned char *delta_base_sha1; - struct strbuf *type_name; - void **contentp; - - /* Response */ - enum { - OI_CACHED, - OI_LOOSE, - OI_PACKED, - OI_DBCACHED - } whence; - union { - /* - * struct { - * ... Nothing to expose in this case - * } cached; - * struct { - * ... Nothing to expose in this case - * } loose; - */ - struct { - struct packed_git *pack; - off_t offset; - unsigned int is_delta; - } packed; - } u; -}; - -/* - * Initializer for a "struct object_info" that wants no items. You may - * also memset() the memory to all-zeroes. - */ -#define OBJECT_INFO_INIT {NULL} - -/* Invoke lookup_replace_object() on the given hash */ -#define OBJECT_INFO_LOOKUP_REPLACE 1 -/* Allow reading from a loose object file of unknown/bogus type */ -#define OBJECT_INFO_ALLOW_UNKNOWN_TYPE 2 -/* Do not check cached storage */ -#define OBJECT_INFO_SKIP_CACHED 4 -/* Do not retry packed storage after checking packed and loose storage */ -#define OBJECT_INFO_QUICK 8 -/* Do not check loose object */ -#define OBJECT_INFO_IGNORE_LOOSE 16 - -int oid_object_info_extended(struct repository *r, - const struct object_id *, - struct object_info *, unsigned flags); - /* * Set this to 0 to prevent sha1_object_info_extended() from fetching missing * blobs. This has a difference only if extensions.partialClone is set. @@ -1770,15 +1653,6 @@ extern const char *excludes_file; int decode_85(char *dst, const char *line, int linelen); void encode_85(char *buf, const unsigned char *data, int bytes); -/* alloc.c */ -extern void *alloc_blob_node(void); -extern void *alloc_tree_node(void); -extern void *alloc_commit_node(void); -extern void *alloc_tag_node(void); -extern void *alloc_object_node(void); -extern void alloc_report(void); -extern unsigned int alloc_commit_index(void); - /* pkt-line.c */ void packet_trace_identity(const char *prog); diff --git a/combine-diff.c b/combine-diff.c index 2ef495963f..de7695e728 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "object-store.h" #include "commit.h" #include "blob.h" #include "diff.h" diff --git a/commit-graph.c b/commit-graph.c index 4c6127088f..b63a1fc85e 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -1,5 +1,6 @@ #include "cache.h" #include "config.h" +#include "dir.h" #include "git-compat-util.h" #include "lockfile.h" #include "pack.h" @@ -248,6 +249,13 @@ static struct commit_list **insert_parent_or_die(struct commit_graph *g, return &commit_list_insert(c, pptr)->next; } +static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, uint32_t pos) +{ + const unsigned char *commit_data = g->chunk_commit_data + GRAPH_DATA_WIDTH * pos; + item->graph_pos = pos; + item->generation = get_be32(commit_data + g->hash_len + 8) >> 2; +} + static int fill_commit_in_graph(struct commit *item, struct commit_graph *g, uint32_t pos) { uint32_t edge_value; @@ -265,6 +273,8 @@ static int fill_commit_in_graph(struct commit *item, struct commit_graph *g, uin date_low = get_be32(commit_data + g->hash_len + 12); item->date = (timestamp_t)((date_high << 32) | date_low); + item->generation = get_be32(commit_data + g->hash_len + 8) >> 2; + pptr = &item->parents; edge_value = get_be32(commit_data + g->hash_len); @@ -293,31 +303,40 @@ static int fill_commit_in_graph(struct commit *item, struct commit_graph *g, uin return 1; } +static int find_commit_in_graph(struct commit *item, struct commit_graph *g, uint32_t *pos) +{ + if (item->graph_pos != COMMIT_NOT_FROM_GRAPH) { + *pos = item->graph_pos; + return 1; + } else { + return bsearch_graph(g, &(item->object.oid), pos); + } +} + int parse_commit_in_graph(struct commit *item) { + uint32_t pos; + if (!core_commit_graph) return 0; if (item->object.parsed) return 1; - prepare_commit_graph(); - if (commit_graph) { - uint32_t pos; - int found; - if (item->graph_pos != COMMIT_NOT_FROM_GRAPH) { - pos = item->graph_pos; - found = 1; - } else { - found = bsearch_graph(commit_graph, &(item->object.oid), &pos); - } - - if (found) - return fill_commit_in_graph(item, commit_graph, pos); - } - + if (commit_graph && find_commit_in_graph(item, commit_graph, &pos)) + return fill_commit_in_graph(item, commit_graph, pos); return 0; } +void load_commit_graph_info(struct commit *item) +{ + uint32_t pos; + if (!core_commit_graph) + return; + prepare_commit_graph(); + if (commit_graph && find_commit_in_graph(item, commit_graph, &pos)) + fill_commit_graph_info(item, commit_graph, pos); +} + static struct tree *load_tree_for_commit(struct commit_graph *g, struct commit *c) { struct object_id oid; @@ -440,6 +459,8 @@ static void write_graph_chunk_data(struct hashfile *f, int hash_len, else packedDate[0] = 0; + packedDate[0] |= htonl((*list)->generation << 2); + packedDate[1] = htonl((*list)->date); hashwrite(f, packedDate, 8); @@ -572,6 +593,45 @@ static void close_reachable(struct packed_oid_list *oids) } } +static void compute_generation_numbers(struct packed_commit_list* commits) +{ + int i; + struct commit_list *list = NULL; + + for (i = 0; i < commits->nr; i++) { + if (commits->list[i]->generation != GENERATION_NUMBER_INFINITY && + commits->list[i]->generation != GENERATION_NUMBER_ZERO) + continue; + + commit_list_insert(commits->list[i], &list); + while (list) { + struct commit *current = list->item; + struct commit_list *parent; + int all_parents_computed = 1; + uint32_t max_generation = 0; + + for (parent = current->parents; parent; parent = parent->next) { + if (parent->item->generation == GENERATION_NUMBER_INFINITY || + parent->item->generation == GENERATION_NUMBER_ZERO) { + all_parents_computed = 0; + commit_list_insert(parent->item, &list); + break; + } else if (parent->item->generation > max_generation) { + max_generation = parent->item->generation; + } + } + + if (all_parents_computed) { + current->generation = max_generation + 1; + pop_commit(&list); + + if (current->generation > GENERATION_NUMBER_MAX) + current->generation = GENERATION_NUMBER_MAX; + } + } + } +} + void write_commit_graph(const char *obj_dir, const char **pack_indexes, int nr_packs, @@ -584,7 +644,6 @@ void write_commit_graph(const char *obj_dir, struct hashfile *f; uint32_t i, count_distinct = 0; char *graph_name; - int fd; struct lock_file lk = LOCK_INIT; uint32_t chunk_ids[5]; uint64_t chunk_offsets[5]; @@ -695,24 +754,14 @@ void write_commit_graph(const char *obj_dir, if (commits.nr >= GRAPH_PARENT_MISSING) die(_("too many commits to write graph")); - graph_name = get_commit_graph_filename(obj_dir); - fd = hold_lock_file_for_update(&lk, graph_name, 0); - - if (fd < 0) { - struct strbuf folder = STRBUF_INIT; - strbuf_addstr(&folder, graph_name); - strbuf_setlen(&folder, strrchr(folder.buf, '/') - folder.buf); - - if (mkdir(folder.buf, 0777) < 0) - die_errno(_("cannot mkdir %s"), folder.buf); - strbuf_release(&folder); - - fd = hold_lock_file_for_update(&lk, graph_name, LOCK_DIE_ON_ERROR); + compute_generation_numbers(&commits); - if (fd < 0) - die_errno("unable to create '%s'", graph_name); - } + graph_name = get_commit_graph_filename(obj_dir); + if (safe_create_leading_directories(graph_name)) + die_errno(_("unable to create leading directories of %s"), + graph_name); + hold_lock_file_for_update(&lk, graph_name, LOCK_DIE_ON_ERROR); f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf); hashwrite_be32(f, GRAPH_SIGNATURE); diff --git a/commit-graph.h b/commit-graph.h index 260a468e73..96cccb10f3 100644 --- a/commit-graph.h +++ b/commit-graph.h @@ -17,6 +17,14 @@ char *get_commit_graph_filename(const char *obj_dir); */ int parse_commit_in_graph(struct commit *item); +/* + * It is possible that we loaded commit contents from the commit buffer, + * but we also want to ensure the commit-graph content is correctly + * checked and filled. Fill the graph_pos and generation members of + * the given commit. + */ +void load_commit_graph_info(struct commit *item); + struct tree *get_commit_tree_in_graph(const struct commit *c); struct commit_graph { diff --git a/commit-slab-decl.h b/commit-slab-decl.h new file mode 100644 index 0000000000..adc7b46c83 --- /dev/null +++ b/commit-slab-decl.h @@ -0,0 +1,43 @@ +#ifndef COMMIT_SLAB_HDR_H +#define COMMIT_SLAB_HDR_H + +/* allocate ~512kB at once, allowing for malloc overhead */ +#ifndef COMMIT_SLAB_SIZE +#define COMMIT_SLAB_SIZE (512*1024-32) +#endif + +#define declare_commit_slab(slabname, elemtype) \ + \ +struct slabname { \ + unsigned slab_size; \ + unsigned stride; \ + unsigned slab_count; \ + elemtype **slab; \ +} + +/* + * Statically initialize a commit slab named "var". Note that this + * evaluates "stride" multiple times! Example: + * + * struct indegree indegrees = COMMIT_SLAB_INIT(1, indegrees); + * + */ +#define COMMIT_SLAB_INIT(stride, var) { \ + COMMIT_SLAB_SIZE / sizeof(**((var).slab)) / (stride), \ + (stride), 0, NULL \ +} + +#define declare_commit_slab_prototypes(slabname, elemtype) \ + \ +void init_ ##slabname## _with_stride(struct slabname *s, unsigned stride); \ +void init_ ##slabname(struct slabname *s); \ +void clear_ ##slabname(struct slabname *s); \ +elemtype *slabname## _at_peek(struct slabname *s, const struct commit *c, int add_if_missing); \ +elemtype *slabname## _at(struct slabname *s, const struct commit *c); \ +elemtype *slabname## _peek(struct slabname *s, const struct commit *c) + +#define define_shared_commit_slab(slabname, elemtype) \ + declare_commit_slab(slabname, elemtype); \ + declare_commit_slab_prototypes(slabname, elemtype) + +#endif /* COMMIT_SLAB_HDR_H */ diff --git a/commit-slab-impl.h b/commit-slab-impl.h new file mode 100644 index 0000000000..87a9cadfcc --- /dev/null +++ b/commit-slab-impl.h @@ -0,0 +1,97 @@ +#ifndef COMMIT_SLAB_IMPL_H +#define COMMIT_SLAB_IMPL_H + +#define MAYBE_UNUSED __attribute__((__unused__)) + +#define implement_static_commit_slab(slabname, elemtype) \ + implement_commit_slab(slabname, elemtype, static MAYBE_UNUSED) + +#define implement_shared_commit_slab(slabname, elemtype) \ + implement_commit_slab(slabname, elemtype, ) + +#define implement_commit_slab(slabname, elemtype, scope) \ + \ +static int stat_ ##slabname## realloc; \ + \ +scope void init_ ##slabname## _with_stride(struct slabname *s, \ + unsigned stride) \ +{ \ + unsigned int elem_size; \ + if (!stride) \ + stride = 1; \ + s->stride = stride; \ + elem_size = sizeof(elemtype) * stride; \ + s->slab_size = COMMIT_SLAB_SIZE / elem_size; \ + s->slab_count = 0; \ + s->slab = NULL; \ +} \ + \ +scope void init_ ##slabname(struct slabname *s) \ +{ \ + init_ ##slabname## _with_stride(s, 1); \ +} \ + \ +scope void clear_ ##slabname(struct slabname *s) \ +{ \ + unsigned int i; \ + for (i = 0; i < s->slab_count; i++) \ + free(s->slab[i]); \ + s->slab_count = 0; \ + FREE_AND_NULL(s->slab); \ +} \ + \ +scope elemtype *slabname## _at_peek(struct slabname *s, \ + const struct commit *c, \ + int add_if_missing) \ +{ \ + 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) { \ + unsigned int i; \ + if (!add_if_missing) \ + return NULL; \ + REALLOC_ARRAY(s->slab, nth_slab + 1); \ + stat_ ##slabname## realloc++; \ + for (i = s->slab_count; i <= nth_slab; i++) \ + s->slab[i] = NULL; \ + s->slab_count = nth_slab + 1; \ + } \ + if (!s->slab[nth_slab]) { \ + if (!add_if_missing) \ + return NULL; \ + s->slab[nth_slab] = xcalloc(s->slab_size, \ + sizeof(**s->slab) * s->stride); \ + } \ + return &s->slab[nth_slab][nth_slot * s->stride]; \ +} \ + \ +scope elemtype *slabname## _at(struct slabname *s, \ + const struct commit *c) \ +{ \ + return slabname##_at_peek(s, c, 1); \ +} \ + \ +scope elemtype *slabname## _peek(struct slabname *s, \ + const struct commit *c) \ +{ \ + return slabname##_at_peek(s, c, 0); \ +} \ + \ +struct slabname + +/* + * Note that this redundant forward declaration is required + * to allow a terminating semicolon, which makes instantiations look + * like function declarations. I.e., the expansion of + * + * implement_commit_slab(indegree, int, static); + * + * ends in 'struct indegree;'. This would otherwise + * be a syntax error according (at least) to ISO C. It's hard to + * catch because GCC silently parses it by default. + */ + +#endif /* COMMIT_SLAB_IMPL_H */ diff --git a/commit-slab.h b/commit-slab.h index dcaab8ca04..69bf0c807c 100644 --- a/commit-slab.h +++ b/commit-slab.h @@ -1,6 +1,9 @@ #ifndef COMMIT_SLAB_H #define COMMIT_SLAB_H +#include "commit-slab-decl.h" +#include "commit-slab-impl.h" + /* * define_commit_slab(slabname, elemtype) creates boilerplate code to define * a new struct (struct slabname) that is used to associate a piece of data @@ -41,114 +44,8 @@ * leaking memory. */ -/* allocate ~512kB at once, allowing for malloc overhead */ -#ifndef COMMIT_SLAB_SIZE -#define COMMIT_SLAB_SIZE (512*1024-32) -#endif - -#define MAYBE_UNUSED __attribute__((__unused__)) - -#define define_commit_slab(slabname, elemtype) \ - \ -struct slabname { \ - unsigned slab_size; \ - unsigned stride; \ - unsigned slab_count; \ - elemtype **slab; \ -}; \ -static int stat_ ##slabname## realloc; \ - \ -static MAYBE_UNUSED void init_ ##slabname## _with_stride(struct slabname *s, \ - unsigned stride) \ -{ \ - unsigned int elem_size; \ - if (!stride) \ - stride = 1; \ - s->stride = stride; \ - elem_size = sizeof(elemtype) * stride; \ - s->slab_size = COMMIT_SLAB_SIZE / elem_size; \ - s->slab_count = 0; \ - s->slab = NULL; \ -} \ - \ -static MAYBE_UNUSED void init_ ##slabname(struct slabname *s) \ -{ \ - init_ ##slabname## _with_stride(s, 1); \ -} \ - \ -static MAYBE_UNUSED void clear_ ##slabname(struct slabname *s) \ -{ \ - unsigned int i; \ - for (i = 0; i < s->slab_count; i++) \ - free(s->slab[i]); \ - s->slab_count = 0; \ - FREE_AND_NULL(s->slab); \ -} \ - \ -static MAYBE_UNUSED elemtype *slabname## _at_peek(struct slabname *s, \ - const struct commit *c, \ - int add_if_missing) \ -{ \ - 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) { \ - unsigned int i; \ - if (!add_if_missing) \ - return NULL; \ - REALLOC_ARRAY(s->slab, nth_slab + 1); \ - stat_ ##slabname## realloc++; \ - for (i = s->slab_count; i <= nth_slab; i++) \ - s->slab[i] = NULL; \ - s->slab_count = nth_slab + 1; \ - } \ - if (!s->slab[nth_slab]) { \ - if (!add_if_missing) \ - return NULL; \ - s->slab[nth_slab] = xcalloc(s->slab_size, \ - sizeof(**s->slab) * s->stride); \ - } \ - return &s->slab[nth_slab][nth_slot * s->stride]; \ -} \ - \ -static MAYBE_UNUSED elemtype *slabname## _at(struct slabname *s, \ - const struct commit *c) \ -{ \ - return slabname##_at_peek(s, c, 1); \ -} \ - \ -static MAYBE_UNUSED elemtype *slabname## _peek(struct slabname *s, \ - const struct commit *c) \ -{ \ - return slabname##_at_peek(s, c, 0); \ -} \ - \ -struct slabname - -/* - * Note that this redundant forward declaration is required - * to allow a terminating semicolon, which makes instantiations look - * like function declarations. I.e., the expansion of - * - * define_commit_slab(indegree, int); - * - * ends in 'struct indegree;'. This would otherwise - * be a syntax error according (at least) to ISO C. It's hard to - * catch because GCC silently parses it by default. - */ - -/* - * Statically initialize a commit slab named "var". Note that this - * evaluates "stride" multiple times! Example: - * - * struct indegree indegrees = COMMIT_SLAB_INIT(1, indegrees); - * - */ -#define COMMIT_SLAB_INIT(stride, var) { \ - COMMIT_SLAB_SIZE / sizeof(**((var).slab)) / (stride), \ - (stride), 0, NULL \ -} +#define define_commit_slab(slabname, elemtype) \ + declare_commit_slab(slabname, elemtype); \ + implement_static_commit_slab(slabname, elemtype) #endif /* COMMIT_SLAB_H */ @@ -2,11 +2,14 @@ #include "tag.h" #include "commit.h" #include "commit-graph.h" +#include "repository.h" +#include "object-store.h" #include "pkt-line.h" #include "utf8.h" #include "diff.h" #include "revision.h" #include "notes.h" +#include "alloc.h" #include "gpg-interface.h" #include "mergesort.h" #include "commit-slab.h" @@ -52,7 +55,8 @@ struct commit *lookup_commit(const struct object_id *oid) { struct object *obj = lookup_object(oid->hash); if (!obj) - return create_object(oid->hash, alloc_commit_node()); + return create_object(the_repository, oid->hash, + alloc_commit_node(the_repository)); return object_as_type(obj, OBJ_COMMIT, 0); } @@ -96,41 +100,44 @@ static timestamp_t parse_commit_date(const char *buf, const char *tail) return parse_timestamp(dateptr, NULL, 10); } -static struct commit_graft **commit_graft; -static int commit_graft_alloc, commit_graft_nr; - static const unsigned char *commit_graft_sha1_access(size_t index, void *table) { struct commit_graft **commit_graft_table = table; return commit_graft_table[index]->oid.hash; } -static int commit_graft_pos(const unsigned char *sha1) +static int commit_graft_pos(struct repository *r, const unsigned char *sha1) { - return sha1_pos(sha1, commit_graft, commit_graft_nr, + return sha1_pos(sha1, r->parsed_objects->grafts, + r->parsed_objects->grafts_nr, commit_graft_sha1_access); } -int register_commit_graft(struct commit_graft *graft, int ignore_dups) +int register_commit_graft(struct repository *r, struct commit_graft *graft, + int ignore_dups) { - int pos = commit_graft_pos(graft->oid.hash); + int pos = commit_graft_pos(r, graft->oid.hash); if (0 <= pos) { if (ignore_dups) free(graft); else { - free(commit_graft[pos]); - commit_graft[pos] = graft; + free(r->parsed_objects->grafts[pos]); + r->parsed_objects->grafts[pos] = graft; } return 1; } pos = -pos - 1; - ALLOC_GROW(commit_graft, commit_graft_nr + 1, commit_graft_alloc); - commit_graft_nr++; - if (pos < commit_graft_nr) - MOVE_ARRAY(commit_graft + pos + 1, commit_graft + pos, - commit_graft_nr - pos - 1); - commit_graft[pos] = graft; + ALLOC_GROW(r->parsed_objects->grafts, + r->parsed_objects->grafts_nr + 1, + r->parsed_objects->grafts_alloc); + r->parsed_objects->grafts_nr++; + if (pos < r->parsed_objects->grafts_nr) + memmove(r->parsed_objects->grafts + pos + 1, + r->parsed_objects->grafts + pos, + (r->parsed_objects->grafts_nr - pos - 1) * + sizeof(*r->parsed_objects->grafts)); + r->parsed_objects->grafts[pos] = graft; return 0; } @@ -172,7 +179,7 @@ bad_graft_data: return NULL; } -static int read_graft_file(const char *graft_file) +static int read_graft_file(struct repository *r, const char *graft_file) { FILE *fp = fopen_or_warn(graft_file, "r"); struct strbuf buf = STRBUF_INIT; @@ -192,7 +199,7 @@ static int read_graft_file(const char *graft_file) struct commit_graft *graft = read_graft_line(&buf); if (!graft) continue; - if (register_commit_graft(graft, 1)) + if (register_commit_graft(r, graft, 1)) error("duplicate graft data: %s", buf.buf); } fclose(fp); @@ -200,50 +207,50 @@ static int read_graft_file(const char *graft_file) return 0; } -static void prepare_commit_graft(void) +static void prepare_commit_graft(struct repository *r) { - static int commit_graft_prepared; char *graft_file; - if (commit_graft_prepared) + if (r->parsed_objects->commit_graft_prepared) return; if (!startup_info->have_repository) return; - graft_file = get_graft_file(); - read_graft_file(graft_file); + graft_file = get_graft_file(r); + read_graft_file(r, graft_file); /* make sure shallows are read */ - is_repository_shallow(); - commit_graft_prepared = 1; + is_repository_shallow(r); + r->parsed_objects->commit_graft_prepared = 1; } -struct commit_graft *lookup_commit_graft(const struct object_id *oid) +struct commit_graft *lookup_commit_graft(struct repository *r, const struct object_id *oid) { int pos; - prepare_commit_graft(); - pos = commit_graft_pos(oid->hash); + prepare_commit_graft(r); + pos = commit_graft_pos(r, oid->hash); if (pos < 0) return NULL; - return commit_graft[pos]; + return r->parsed_objects->grafts[pos]; } int for_each_commit_graft(each_commit_graft_fn fn, void *cb_data) { int i, ret; - for (i = ret = 0; i < commit_graft_nr && !ret; i++) - ret = fn(commit_graft[i], cb_data); + for (i = ret = 0; i < the_repository->parsed_objects->grafts_nr && !ret; i++) + ret = fn(the_repository->parsed_objects->grafts[i], cb_data); return ret; } int unregister_shallow(const struct object_id *oid) { - int pos = commit_graft_pos(oid->hash); + int pos = commit_graft_pos(the_repository, oid->hash); if (pos < 0) return -1; - if (pos + 1 < commit_graft_nr) - MOVE_ARRAY(commit_graft + pos, commit_graft + pos + 1, - commit_graft_nr - pos - 1); - commit_graft_nr--; + if (pos + 1 < the_repository->parsed_objects->grafts_nr) + MOVE_ARRAY(the_repository->parsed_objects->grafts + pos, + the_repository->parsed_objects->grafts + pos + 1, + the_repository->parsed_objects->grafts_nr - pos - 1); + the_repository->parsed_objects->grafts_nr--; return 0; } @@ -325,6 +332,17 @@ struct object_id *get_commit_tree_oid(const struct commit *commit) return &get_commit_tree(commit)->object.oid; } +void release_commit_memory(struct commit *c) +{ + c->maybe_tree = NULL; + c->index = 0; + free_commit_buffer(c); + free_commit_list(c->parents); + /* TODO: what about commit->util? */ + + c->object.parsed = 0; +} + const void *detach_commit_buffer(struct commit *commit, unsigned long *sizep) { struct commit_buffer *v = buffer_slab_peek(&buffer_slab, commit); @@ -344,7 +362,7 @@ const void *detach_commit_buffer(struct commit *commit, unsigned long *sizep) return ret; } -int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size) +int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size, int check_graph) { const char *tail = buffer; const char *bufptr = buffer; @@ -368,7 +386,7 @@ int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long s bufptr += tree_entry_len + 1; /* "tree " + "hex sha1" + "\n" */ pptr = &item->parents; - graft = lookup_commit_graft(&item->object.oid); + graft = lookup_commit_graft(the_repository, &item->object.oid); while (bufptr + parent_entry_len < tail && !memcmp(bufptr, "parent ", 7)) { struct commit *new_parent; @@ -399,6 +417,9 @@ int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long s } item->date = parse_commit_date(bufptr, tail); + if (check_graph) + load_commit_graph_info(item); + return 0; } @@ -425,7 +446,7 @@ int parse_commit_gently(struct commit *item, int quiet_on_missing) return error("Object %s not a commit", oid_to_hex(&item->object.oid)); } - ret = parse_commit_buffer(item, buffer, size); + ret = parse_commit_buffer(item, buffer, size, 0); if (save_commit_buffer && !ret) { set_commit_buffer(item, buffer, size); return 0; @@ -653,6 +674,24 @@ static int compare_commits_by_author_date(const void *a_, const void *b_, return 0; } +int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void *unused) +{ + const struct commit *a = a_, *b = b_; + + /* newer commits first */ + if (a->generation < b->generation) + return 1; + else if (a->generation > b->generation) + return -1; + + /* use date as a heuristic when generations are equal */ + if (a->date < b->date) + return 1; + else if (a->date > b->date) + return -1; + return 0; +} + int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused) { const struct commit *a = a_, *b = b_; @@ -800,11 +839,14 @@ static int queue_has_nonstale(struct prio_queue *queue) } /* all input commits in one and twos[] must have been parsed! */ -static struct commit_list *paint_down_to_common(struct commit *one, int n, struct commit **twos) +static struct commit_list *paint_down_to_common(struct commit *one, int n, + struct commit **twos, + int min_generation) { - struct prio_queue queue = { compare_commits_by_commit_date }; + struct prio_queue queue = { compare_commits_by_gen_then_commit_date }; struct commit_list *result = NULL; int i; + uint32_t last_gen = GENERATION_NUMBER_INFINITY; one->object.flags |= PARENT1; if (!n) { @@ -823,6 +865,15 @@ static struct commit_list *paint_down_to_common(struct commit *one, int n, struc struct commit_list *parents; int flags; + if (commit->generation > last_gen) + BUG("bad generation skip %8x > %8x at %s", + commit->generation, last_gen, + oid_to_hex(&commit->object.oid)); + last_gen = commit->generation; + + if (commit->generation < min_generation) + break; + flags = commit->object.flags & (PARENT1 | PARENT2 | STALE); if (flags == (PARENT1 | PARENT2)) { if (!(commit->object.flags & RESULT)) { @@ -871,7 +922,7 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co return NULL; } - list = paint_down_to_common(one, n, twos); + list = paint_down_to_common(one, n, twos, 0); while (list) { struct commit *commit = pop_commit(&list); @@ -929,6 +980,7 @@ static int remove_redundant(struct commit **array, int cnt) parse_commit(array[i]); for (i = 0; i < cnt; i++) { struct commit_list *common; + uint32_t min_generation = array[i]->generation; if (redundant[i]) continue; @@ -937,8 +989,12 @@ static int remove_redundant(struct commit **array, int cnt) continue; filled_index[filled] = j; work[filled++] = array[j]; + + if (array[j]->generation < min_generation) + min_generation = array[j]->generation; } - common = paint_down_to_common(array[i], filled, work); + common = paint_down_to_common(array[i], filled, work, + min_generation); if (array[i]->object.flags & PARENT2) redundant[i] = 1; for (j = 0; j < filled; j++) @@ -1048,14 +1104,21 @@ int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit * { struct commit_list *bases; int ret = 0, i; + uint32_t min_generation = GENERATION_NUMBER_INFINITY; if (parse_commit(commit)) return ret; - for (i = 0; i < nr_reference; i++) + for (i = 0; i < nr_reference; i++) { if (parse_commit(reference[i])) return ret; + if (reference[i]->generation < min_generation) + min_generation = reference[i]->generation; + } + + if (commit->generation > min_generation) + return ret; - bases = paint_down_to_common(commit, nr_reference, reference); + bases = paint_down_to_common(commit, nr_reference, reference, commit->generation); if (commit->object.flags & PARENT2) ret = 1; clear_commit_marks(commit, all_flags); @@ -1605,13 +1668,21 @@ out: return result; } +define_commit_slab(merge_desc_slab, struct merge_remote_desc *); +static struct merge_desc_slab merge_desc_slab = COMMIT_SLAB_INIT(1, merge_desc_slab); + +struct merge_remote_desc *merge_remote_util(struct commit *commit) +{ + return *merge_desc_slab_at(&merge_desc_slab, commit); +} + void set_merge_remote_desc(struct commit *commit, const char *name, struct object *obj) { struct merge_remote_desc *desc; FLEX_ALLOC_STR(desc, name, name); desc->obj = obj; - commit->util = desc; + *merge_desc_slab_at(&merge_desc_slab, commit) = desc; } struct commit *get_merge_parent(const char *name) @@ -1623,7 +1694,7 @@ struct commit *get_merge_parent(const char *name) return NULL; obj = parse_object(&oid); commit = (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT); - if (commit && !commit->util) + if (commit && !merge_remote_util(commit)) set_merge_remote_desc(commit, name, obj); return commit; } @@ -10,15 +10,22 @@ #include "pretty.h" #define COMMIT_NOT_FROM_GRAPH 0xFFFFFFFF +#define GENERATION_NUMBER_INFINITY 0xFFFFFFFF +#define GENERATION_NUMBER_MAX 0x3FFFFFFF +#define GENERATION_NUMBER_ZERO 0 struct commit_list { struct commit *item; struct commit_list *next; }; +/* + * The size of this struct matters in full repo walk operations like + * 'git clone' or 'git gc'. Consider using commit-slab to attach data + * to a commit instead of adding new fields here. + */ struct commit { struct object object; - void *util; timestamp_t date; struct commit_list *parents; @@ -29,6 +36,7 @@ struct commit { */ struct tree *maybe_tree; uint32_t graph_pos; + uint32_t generation; unsigned int index; }; @@ -68,7 +76,7 @@ struct commit *lookup_commit_reference_by_name(const char *name); */ struct commit *lookup_commit_or_die(const struct object_id *oid, const char *ref_name); -int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size); +int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size, int check_graph); int parse_commit_gently(struct commit *item, int quiet_on_missing); static inline int parse_commit(struct commit *item) { @@ -112,6 +120,12 @@ struct tree *get_commit_tree(const struct commit *); struct object_id *get_commit_tree_oid(const struct commit *); /* + * Release memory related to a commit, including the parent list and + * any cached object buffer. + */ +void release_commit_memory(struct commit *c); + +/* * Disassociate any cached object buffer from the commit, but do not free it. * The buffer (or NULL, if none) is returned. */ @@ -180,8 +194,8 @@ struct commit_graft { typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *); struct commit_graft *read_graft_line(struct strbuf *line); -int register_commit_graft(struct commit_graft *, int); -struct commit_graft *lookup_commit_graft(const struct object_id *oid); +int register_commit_graft(struct repository *r, struct commit_graft *, int); +struct commit_graft *lookup_commit_graft(struct repository *r, const struct object_id *oid); extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2); extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos); @@ -195,15 +209,15 @@ extern struct commit_list *get_merge_bases_many_dirty(struct commit *one, int n, struct oid_array; struct ref; -extern int register_shallow(const struct object_id *oid); +extern int register_shallow(struct repository *r, const struct object_id *oid); extern int unregister_shallow(const struct object_id *oid); extern int for_each_commit_graft(each_commit_graft_fn, void *); -extern int is_repository_shallow(void); +extern int is_repository_shallow(struct repository *r); extern struct commit_list *get_shallow_commits(struct object_array *heads, int depth, int shallow_flag, int not_shallow_flag); extern struct commit_list *get_shallow_commits_by_rev_list( int ac, const char **av, int shallow_flag, int not_shallow_flag); -extern void set_alternate_shallow_file(const char *path, int override); +extern void set_alternate_shallow_file(struct repository *r, const char *path, int override); extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol, const struct oid_array *extra); extern void setup_alternate_shallow(struct lock_file *shallow_lock, @@ -312,7 +326,7 @@ struct merge_remote_desc { struct object *obj; /* the named object, could be a tag */ char name[FLEX_ARRAY]; }; -#define merge_remote_util(commit) ((struct merge_remote_desc *)((commit)->util)) +extern struct merge_remote_desc *merge_remote_util(struct commit *); extern void set_merge_remote_desc(struct commit *commit, const char *name, struct object *obj); @@ -337,6 +351,7 @@ extern int remove_signature(struct strbuf *buf); extern int check_commit_signature(const struct commit *commit, struct signature_check *sigc); int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused); +int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void *unused); LAST_ARG_MUST_BE_NULL extern int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...); @@ -14,6 +14,7 @@ #include "quote.h" #include "hashmap.h" #include "string-list.h" +#include "object-store.h" #include "utf8.h" #include "dir.h" #include "color.h" @@ -1233,7 +1234,7 @@ static int git_default_core_config(const char *var, const char *value) } eol_rndtrp_die = git_config_bool(var, value); global_conv_flags_eol = eol_rndtrp_die ? - CONV_EOL_RNDTRP_DIE : CONV_EOL_RNDTRP_WARN; + CONV_EOL_RNDTRP_DIE : 0; return 0; } @@ -2172,23 +2173,6 @@ int git_config_get_pathname(const char *key, const char **dest) return repo_config_get_pathname(the_repository, key, dest); } -/* - * Note: This function exists solely to maintain backward compatibility with - * 'fetch' and 'update_clone' storing configuration in '.gitmodules' and should - * NOT be used anywhere else. - * - * Runs the provided config function on the '.gitmodules' file found in the - * working directory. - */ -void config_from_gitmodules(config_fn_t fn, void *data) -{ - if (the_repository->worktree) { - char *file = repo_worktree_path(the_repository, GITMODULES_FILE); - git_config_from_file(fn, file, data); - free(file); - } -} - int git_config_get_expiry(const char *key, const char **output) { int ret = git_config_get_string_const(key, output); @@ -3245,3 +3229,16 @@ enum config_scope current_config_scope(void) else return current_parsing_scope; } + +int lookup_config(const char **mapping, int nr_mapping, const char *var) +{ + int i; + + for (i = 0; i < nr_mapping; i++) { + const char *name = mapping[i]; + + if (name && !strcasecmp(var, name)) + return i; + } + return -1; +} @@ -215,16 +215,6 @@ extern int repo_config_get_maybe_bool(struct repository *repo, extern int repo_config_get_pathname(struct repository *repo, const char *key, const char **dest); -/* - * Note: This function exists solely to maintain backward compatibility with - * 'fetch' and 'update_clone' storing configuration in '.gitmodules' and should - * NOT be used anywhere else. - * - * Runs the provided config function on the '.gitmodules' file found in the - * working directory. - */ -extern void config_from_gitmodules(config_fn_t fn, void *data); - extern int git_config_get_value(const char *key, const char **value); extern const struct string_list *git_config_get_value_multi(const char *key); extern void git_config_clear(void); @@ -257,4 +247,8 @@ struct key_value_info { extern NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3))); extern NORETURN void git_die_config_linenr(const char *key, const char *filename, int linenr); +#define LOOKUP_CONFIG(mapping, var) \ + lookup_config(mapping, ARRAY_SIZE(mapping), var) +int lookup_config(const char **mapping, int nr_mapping, const char *var); + #endif /* CONFIG_H */ diff --git a/connected.c b/connected.c index 91feb78815..1bba888eff 100644 --- a/connected.c +++ b/connected.c @@ -58,8 +58,10 @@ int check_connected(oid_iterate_fn fn, void *cb_data, argv_array_push(&rev_list.args, "--stdin"); if (repository_format_partial_clone) argv_array_push(&rev_list.args, "--exclude-promisor-objects"); - argv_array_push(&rev_list.args, "--not"); - argv_array_push(&rev_list.args, "--all"); + if (!opt->is_deepening_fetch) { + argv_array_push(&rev_list.args, "--not"); + argv_array_push(&rev_list.args, "--all"); + } argv_array_push(&rev_list.args, "--quiet"); if (opt->progress) argv_array_pushf(&rev_list.args, "--progress=%s", diff --git a/connected.h b/connected.h index a53f03a61a..322dc76372 100644 --- a/connected.h +++ b/connected.h @@ -38,6 +38,13 @@ struct check_connected_options { * Insert these variables into the environment of the child process. */ const char **env; + + /* + * If non-zero, check the ancestry chain completely, not stopping at + * any existing ref. This is necessary when deepening existing refs + * during a fetch. + */ + unsigned is_deepening_fetch : 1; }; #define CHECK_CONNECTED_INIT { 0 } diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index dd3e925843..94c95516eb 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -330,9 +330,32 @@ __gitcomp () case "$cur_" in --*=) ;; + --no-*) + local c i=0 IFS=$' \t\n' + for c in $1; do + if [[ $c == "--" ]]; then + continue + fi + c="$c${4-}" + if [[ $c == "$cur_"* ]]; then + case $c in + --*=*|*.) ;; + *) c="$c " ;; + esac + COMPREPLY[i++]="${2-}$c" + fi + done + ;; *) local c i=0 IFS=$' \t\n' for c in $1; do + if [[ $c == "--" ]]; then + c="--no-...${4-}" + if [[ $c == "$cur_"* ]]; then + COMPREPLY[i++]="${2-}$c " + fi + break + fi c="$c${4-}" if [[ $c == "$cur_"* ]]; then case $c in @@ -1161,7 +1184,7 @@ _git_am () return ;; --*) - __gitcomp_builtin am "--no-utf8" \ + __gitcomp_builtin am "" \ "$__git_am_inprogress_options" return esac @@ -1261,9 +1284,7 @@ _git_branch () __git_complete_refs --cur="${cur##--set-upstream-to=}" ;; --*) - __gitcomp_builtin branch "--no-color --no-abbrev - --no-track --no-column - " + __gitcomp_builtin branch ;; *) if [ $only_local_ref = "y" -a $has_r = "n" ]; then @@ -1304,7 +1325,7 @@ _git_checkout () __gitcomp "diff3 merge" "" "${cur##--conflict=}" ;; --*) - __gitcomp_builtin checkout "--no-track --no-recurse-submodules" + __gitcomp_builtin checkout ;; *) # check if --track, --no-track, or --no-guess was specified @@ -1367,7 +1388,7 @@ _git_clone () { case "$cur" in --*) - __gitcomp_builtin clone "--no-single-branch" + __gitcomp_builtin clone return ;; esac @@ -1400,7 +1421,7 @@ _git_commit () return ;; --*) - __gitcomp_builtin commit "--no-edit --verify" + __gitcomp_builtin commit return esac @@ -1503,7 +1524,7 @@ _git_fetch () return ;; --*) - __gitcomp_builtin fetch "--no-tags" + __gitcomp_builtin fetch return ;; esac @@ -1540,7 +1561,7 @@ _git_fsck () { case "$cur" in --*) - __gitcomp_builtin fsck "--no-reflogs" + __gitcomp_builtin fsck return ;; esac @@ -1646,7 +1667,7 @@ _git_ls_files () { case "$cur" in --*) - __gitcomp_builtin ls-files "--no-empty-directory" + __gitcomp_builtin ls-files return ;; esac @@ -1797,12 +1818,7 @@ _git_merge () case "$cur" in --*) - __gitcomp_builtin merge "--no-rerere-autoupdate - --no-commit --no-edit --no-ff - --no-log --no-progress - --no-squash --no-stat - --no-verify-signatures - " + __gitcomp_builtin merge return esac __git_complete_refs @@ -1901,10 +1917,7 @@ _git_pull () return ;; --*) - __gitcomp_builtin pull "--no-autostash --no-commit --no-edit - --no-ff --no-log --no-progress --no-rebase - --no-squash --no-stat --no-tags - --no-verify-signatures" + __gitcomp_builtin pull return ;; @@ -2095,7 +2108,7 @@ _git_status () return ;; --*) - __gitcomp_builtin status "--no-column" + __gitcomp_builtin status return ;; esac @@ -2142,9 +2155,24 @@ __git_config_get_set_variables () __git config $config_file --name-only --list } +__git_config_vars= +__git_compute_config_vars () +{ + test -n "$__git_config_vars" || + __git_config_vars="$(git help --config-for-completion | sort | uniq)" +} + _git_config () { - case "$prev" in + local varname + + if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then + varname="${prev,,}" + else + varname="$(echo "$prev" |tr A-Z a-z)" + fi + + case "$varname" in branch.*.remote|branch.*.pushremote) __gitcomp_nl "$(__git_remotes)" return @@ -2242,20 +2270,20 @@ _git_config () ;; branch.*.*) local pfx="${cur%.*}." cur_="${cur##*.}" - __gitcomp "remote pushremote merge mergeoptions rebase" "$pfx" "$cur_" + __gitcomp "remote pushRemote merge mergeOptions rebase" "$pfx" "$cur_" return ;; branch.*) local pfx="${cur%.*}." cur_="${cur#*.}" __gitcomp_direct "$(__git_heads "$pfx" "$cur_" ".")" - __gitcomp_nl_append $'autosetupmerge\nautosetuprebase\n' "$pfx" "$cur_" + __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" return ;; guitool.*.*) local pfx="${cur%.*}." cur_="${cur##*.}" __gitcomp " - argprompt cmd confirm needsfile noconsole norescan - prompt revprompt revunmerged title + argPrompt cmd confirm needsFile noConsole noRescan + prompt revPrompt revUnmerged title " "$pfx" "$cur_" return ;; @@ -2284,14 +2312,14 @@ _git_config () local pfx="${cur%.*}." cur_="${cur##*.}" __gitcomp " url proxy fetch push mirror skipDefaultUpdate - receivepack uploadpack tagopt pushurl + receivepack uploadpack tagOpt pushurl " "$pfx" "$cur_" return ;; remote.*) local pfx="${cur%.*}." cur_="${cur#*.}" __gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "." - __gitcomp_nl_append "pushdefault" "$pfx" "$cur_" + __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" return ;; url.*.*) @@ -2299,333 +2327,14 @@ _git_config () __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_" return ;; + *.*) + __git_compute_config_vars + __gitcomp "$__git_config_vars" + ;; + *) + __git_compute_config_vars + __gitcomp "$(echo "$__git_config_vars" | sed 's/\.[^ ]*/./g')" esac - __gitcomp " - add.ignoreErrors - advice.amWorkDir - advice.commitBeforeMerge - advice.detachedHead - advice.implicitIdentity - advice.pushAlreadyExists - advice.pushFetchFirst - advice.pushNeedsForce - advice.pushNonFFCurrent - advice.pushNonFFMatching - advice.pushUpdateRejected - advice.resolveConflict - advice.rmHints - advice.statusHints - advice.statusUoption - advice.ignoredHook - alias. - am.keepcr - am.threeWay - apply.ignorewhitespace - apply.whitespace - branch.autosetupmerge - branch.autosetuprebase - browser. - clean.requireForce - color.branch - color.branch.current - color.branch.local - color.branch.plain - color.branch.remote - color.decorate.HEAD - color.decorate.branch - color.decorate.remoteBranch - color.decorate.stash - color.decorate.tag - color.diff - color.diff.commit - color.diff.frag - color.diff.func - color.diff.meta - color.diff.new - color.diff.old - color.diff.plain - color.diff.whitespace - color.grep - color.grep.context - color.grep.filename - color.grep.function - color.grep.linenumber - color.grep.match - color.grep.selected - color.grep.separator - color.interactive - color.interactive.error - color.interactive.header - color.interactive.help - color.interactive.prompt - color.pager - color.showbranch - color.status - color.status.added - color.status.changed - color.status.header - color.status.localBranch - color.status.nobranch - color.status.remoteBranch - color.status.unmerged - color.status.untracked - color.status.updated - color.ui - commit.cleanup - commit.gpgSign - commit.status - commit.template - commit.verbose - core.abbrev - core.askpass - core.attributesfile - core.autocrlf - core.bare - core.bigFileThreshold - core.checkStat - core.commentChar - core.commitGraph - core.compression - core.createObject - core.deltaBaseCacheLimit - core.editor - core.eol - core.excludesfile - core.fileMode - core.fsyncobjectfiles - core.gitProxy - core.hideDotFiles - core.hooksPath - core.ignoreStat - core.ignorecase - core.logAllRefUpdates - core.loosecompression - core.notesRef - core.packedGitLimit - core.packedGitWindowSize - core.packedRefsTimeout - core.pager - core.precomposeUnicode - core.preferSymlinkRefs - core.preloadindex - core.protectHFS - core.protectNTFS - core.quotepath - core.repositoryFormatVersion - core.safecrlf - core.sharedRepository - core.sparseCheckout - core.splitIndex - core.sshCommand - core.symlinks - core.trustctime - core.untrackedCache - core.warnAmbiguousRefs - core.whitespace - core.worktree - credential.helper - credential.useHttpPath - credential.username - credentialCache.ignoreSIGHUP - diff.autorefreshindex - diff.external - diff.ignoreSubmodules - diff.mnemonicprefix - diff.noprefix - diff.renameLimit - diff.renames - diff.statGraphWidth - diff.submodule - diff.suppressBlankEmpty - diff.tool - diff.wordRegex - diff.algorithm - difftool. - difftool.prompt - fetch.recurseSubmodules - fetch.unpackLimit - format.attach - format.cc - format.coverLetter - format.from - format.headers - format.numbered - format.pretty - format.signature - format.signoff - format.subjectprefix - format.suffix - format.thread - format.to - gc. - gc.aggressiveDepth - gc.aggressiveWindow - gc.auto - gc.autoDetach - gc.autopacklimit - gc.logExpiry - gc.packrefs - gc.pruneexpire - gc.reflogexpire - gc.reflogexpireunreachable - gc.rerereresolved - gc.rerereunresolved - gc.worktreePruneExpire - gitcvs.allbinary - gitcvs.commitmsgannotation - gitcvs.dbTableNamePrefix - gitcvs.dbdriver - gitcvs.dbname - gitcvs.dbpass - gitcvs.dbuser - gitcvs.enabled - gitcvs.logfile - gitcvs.usecrlfattr - guitool. - gui.blamehistoryctx - gui.commitmsgwidth - gui.copyblamethreshold - gui.diffcontext - gui.encoding - gui.fastcopyblame - gui.matchtrackingbranch - gui.newbranchtemplate - gui.pruneduringfetch - gui.spellingdictionary - gui.trustmtime - help.autocorrect - help.browser - help.format - http.lowSpeedLimit - http.lowSpeedTime - http.maxRequests - http.minSessions - http.noEPSV - http.postBuffer - http.proxy - http.sslCipherList - http.sslVersion - http.sslCAInfo - http.sslCAPath - http.sslCert - http.sslCertPasswordProtected - http.sslKey - http.sslVerify - http.useragent - i18n.commitEncoding - i18n.logOutputEncoding - imap.authMethod - imap.folder - imap.host - imap.pass - imap.port - imap.preformattedHTML - imap.sslverify - imap.tunnel - imap.user - init.templatedir - instaweb.browser - instaweb.httpd - instaweb.local - instaweb.modulepath - instaweb.port - interactive.singlekey - log.date - log.decorate - log.showroot - mailmap.file - man. - man.viewer - merge. - merge.conflictstyle - merge.log - merge.renameLimit - merge.renormalize - merge.stat - merge.tool - merge.verbosity - mergetool. - mergetool.keepBackup - mergetool.keepTemporaries - mergetool.prompt - notes.displayRef - notes.rewrite. - notes.rewrite.amend - notes.rewrite.rebase - notes.rewriteMode - notes.rewriteRef - pack.compression - pack.deltaCacheLimit - pack.deltaCacheSize - pack.depth - pack.indexVersion - pack.packSizeLimit - pack.threads - pack.window - pack.windowMemory - pager. - pretty. - pull.octopus - pull.twohead - push.default - push.followTags - rebase.autosquash - rebase.stat - receive.autogc - receive.denyCurrentBranch - receive.denyDeleteCurrent - receive.denyDeletes - receive.denyNonFastForwards - receive.fsckObjects - receive.unpackLimit - receive.updateserverinfo - remote.pushdefault - remotes. - repack.usedeltabaseoffset - rerere.autoupdate - rerere.enabled - sendemail. - sendemail.aliasesfile - sendemail.aliasfiletype - sendemail.bcc - sendemail.cc - sendemail.cccmd - sendemail.chainreplyto - sendemail.confirm - sendemail.envelopesender - sendemail.from - sendemail.identity - sendemail.multiedit - sendemail.signedoffbycc - sendemail.smtpdomain - sendemail.smtpencryption - sendemail.smtppass - sendemail.smtpserver - sendemail.smtpserveroption - sendemail.smtpserverport - sendemail.smtpuser - sendemail.suppresscc - sendemail.suppressfrom - sendemail.thread - sendemail.to - sendemail.tocmd - sendemail.validate - sendemail.smtpbatchsize - sendemail.smtprelogindelay - showbranch.default - status.relativePaths - status.showUntrackedFiles - status.submodulesummary - submodule. - tar.umask - transfer.unpackLimit - url. - user.email - user.name - user.signingkey - web.browser - branch. remote. - " } _git_remote () @@ -2649,7 +2358,7 @@ _git_remote () case "$subcommand,$cur" in add,--*) - __gitcomp_builtin remote_add "--no-tags" + __gitcomp_builtin remote_add ;; add,*) ;; @@ -2666,7 +2375,7 @@ _git_remote () __gitcomp_builtin remote_update ;; update,*) - __gitcomp "$(__git_get_config_variables "remotes")" + __gitcomp "$(__git_remotes) $(__git_get_config_variables "remotes")" ;; set-url,--*) __gitcomp_builtin remote_set-url @@ -2729,7 +2438,7 @@ _git_revert () fi case "$cur" in --*) - __gitcomp_builtin revert "--no-edit" \ + __gitcomp_builtin revert "" \ "$__git_revert_inprogress_options" return ;; @@ -2799,7 +2508,7 @@ _git_show_branch () { case "$cur" in --*) - __gitcomp_builtin show-branch "--no-color" + __gitcomp_builtin show-branch return ;; esac diff --git a/contrib/credential/netrc/Makefile b/contrib/credential/netrc/Makefile index 0ffa407191..6174e3bb83 100644 --- a/contrib/credential/netrc/Makefile +++ b/contrib/credential/netrc/Makefile @@ -1,3 +1,6 @@ +# The default target of this Makefile is... +all:: + test: ./t-git-credential-netrc.sh diff --git a/contrib/credential/netrc/t-git-credential-netrc.sh b/contrib/credential/netrc/t-git-credential-netrc.sh index 58191a62f8..07227d0228 100755 --- a/contrib/credential/netrc/t-git-credential-netrc.sh +++ b/contrib/credential/netrc/t-git-credential-netrc.sh @@ -17,15 +17,16 @@ # set up test repository test_expect_success \ - 'set up test repository' \ - 'git config --add gpg.program test.git-config-gpg' + 'set up test repository' \ + 'git config --add gpg.program test.git-config-gpg' # The external test will outputs its own plan test_external_has_tap=1 + export PERL5LIB="$GITPERLLIB" test_external \ - 'git-credential-netrc' \ - perl "$TEST_DIRECTORY"/../contrib/credential/netrc/test.pl + 'git-credential-netrc' \ + perl "$GIT_BUILD_DIR"/contrib/credential/netrc/test.pl test_done ) diff --git a/contrib/credential/netrc/test.pl b/contrib/credential/netrc/test.pl index 1e1001030e..c0fb3718b2 100755 --- a/contrib/credential/netrc/test.pl +++ b/contrib/credential/netrc/test.pl @@ -1,5 +1,4 @@ #!/usr/bin/perl -use lib (split(/:/, $ENV{GITPERLLIB})); use warnings; use strict; @@ -12,7 +11,6 @@ BEGIN { # t-git-credential-netrc.sh kicks off our testing, so we have to go # from there. Test::More->builder->current_test(1); - Test::More->builder->no_ending(1); } my @global_credential_args = @ARGV; @@ -104,6 +102,9 @@ $cred = run_credential( ['-f', $netrcGpg, '-g', 'test.command-option-gpg', 'get' ok(scalar keys %$cred == 2, 'Got keys decrypted by command option'); +my $is_passing = eval { Test::More->is_passing }; +exit($is_passing ? 0 : 1) unless $@ =~ /Can't locate object method/; + sub run_credential { my $args = shift @_; diff --git a/contrib/git-jump/README b/contrib/git-jump/README index 4484bda410..2f618a7f97 100644 --- a/contrib/git-jump/README +++ b/contrib/git-jump/README @@ -25,6 +25,13 @@ git-jump will feed this to the editor: foo.c:2: printf("hello word!\n"); ----------------------------------- +Or, when running 'git jump grep', column numbers will also be emitted, +e.g. `git jump grep "hello"` would return: + +----------------------------------- +foo.c:2:9: printf("hello word!\n"); +----------------------------------- + Obviously this trivial case isn't that interesting; you could just open `foo.c` yourself. But when you have many changes scattered across a project, you can use the editor's support to "jump" from point to point. @@ -35,7 +42,8 @@ Git-jump can generate four types of interesting lists: 2. The beginning of any merge conflict markers. - 3. Any grep matches. + 3. Any grep matches, including the column of the first match on a + line. 4. Any whitespace errors detected by `git diff --check`. @@ -82,7 +90,7 @@ which does something similar to `git jump grep`. However, it is limited to positioning the cursor to the correct line in only the first file, leaving you to locate subsequent hits in that file or other files using the editor or pager. By contrast, git-jump provides the editor with a -complete list of files and line numbers for each match. +complete list of files, lines, and a column number for each match. Limitations diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump index 80ab0590bc..931b0fe3a9 100755 --- a/contrib/git-jump/git-jump +++ b/contrib/git-jump/git-jump @@ -52,7 +52,7 @@ mode_merge() { # editor shows them to us in the status bar. mode_grep() { cmd=$(git config jump.grepCmd) - test -n "$cmd" || cmd="git grep -n" + test -n "$cmd" || cmd="git grep -n --column" $cmd "$@" | perl -pe ' s/[ \t]+/ /g; @@ -1,6 +1,7 @@ #define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" +#include "object-store.h" #include "attr.h" #include "run-command.h" #include "quote.h" diff --git a/diff-lib.c b/diff-lib.c index 104f954a25..a9f38eb5a3 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -389,8 +389,12 @@ static void do_oneway_diff(struct unpack_trees_options *o, struct rev_info *revs = o->unpack_data; int match_missing, cached; - /* i-t-a entries do not actually exist in the index */ - if (revs->diffopt.ita_invisible_in_index && + /* + * i-t-a entries do not actually exist in the index (if we're + * looking at its content) + */ + if (o->index_only && + revs->diffopt.ita_invisible_in_index && idx && ce_intent_to_add(idx)) { idx = NULL; if (!tree) @@ -13,6 +13,7 @@ #include "attr.h" #include "run-command.h" #include "utf8.h" +#include "object-store.h" #include "userdiff.h" #include "submodule-config.h" #include "submodule.h" @@ -22,6 +23,7 @@ #include "argv-array.h" #include "graph.h" #include "packfile.h" +#include "help.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -69,46 +71,37 @@ static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_FAINT_ITALIC, /* NEW_MOVED_ALTERNATIVE_DIM */ }; +static const char *color_diff_slots[] = { + [DIFF_CONTEXT] = "context", + [DIFF_METAINFO] = "meta", + [DIFF_FRAGINFO] = "frag", + [DIFF_FILE_OLD] = "old", + [DIFF_FILE_NEW] = "new", + [DIFF_COMMIT] = "commit", + [DIFF_WHITESPACE] = "whitespace", + [DIFF_FUNCINFO] = "func", + [DIFF_FILE_OLD_MOVED] = "oldMoved", + [DIFF_FILE_OLD_MOVED_ALT] = "oldMovedAlternative", + [DIFF_FILE_OLD_MOVED_DIM] = "oldMovedDimmed", + [DIFF_FILE_OLD_MOVED_ALT_DIM] = "oldMovedAlternativeDimmed", + [DIFF_FILE_NEW_MOVED] = "newMoved", + [DIFF_FILE_NEW_MOVED_ALT] = "newMovedAlternative", + [DIFF_FILE_NEW_MOVED_DIM] = "newMovedDimmed", + [DIFF_FILE_NEW_MOVED_ALT_DIM] = "newMovedAlternativeDimmed", +}; + static NORETURN void die_want_option(const char *option_name) { die(_("option '%s' requires a value"), option_name); } +define_list_config_array_extra(color_diff_slots, {"plain"}); + static int parse_diff_color_slot(const char *var) { - if (!strcasecmp(var, "context") || !strcasecmp(var, "plain")) + if (!strcasecmp(var, "plain")) return DIFF_CONTEXT; - if (!strcasecmp(var, "meta")) - return DIFF_METAINFO; - if (!strcasecmp(var, "frag")) - return DIFF_FRAGINFO; - if (!strcasecmp(var, "old")) - return DIFF_FILE_OLD; - if (!strcasecmp(var, "new")) - return DIFF_FILE_NEW; - if (!strcasecmp(var, "commit")) - return DIFF_COMMIT; - if (!strcasecmp(var, "whitespace")) - return DIFF_WHITESPACE; - if (!strcasecmp(var, "func")) - return DIFF_FUNCINFO; - if (!strcasecmp(var, "oldmoved")) - return DIFF_FILE_OLD_MOVED; - if (!strcasecmp(var, "oldmovedalternative")) - return DIFF_FILE_OLD_MOVED_ALT; - if (!strcasecmp(var, "oldmoveddimmed")) - return DIFF_FILE_OLD_MOVED_DIM; - if (!strcasecmp(var, "oldmovedalternativedimmed")) - return DIFF_FILE_OLD_MOVED_ALT_DIM; - if (!strcasecmp(var, "newmoved")) - return DIFF_FILE_NEW_MOVED; - if (!strcasecmp(var, "newmovedalternative")) - return DIFF_FILE_NEW_MOVED_ALT; - if (!strcasecmp(var, "newmoveddimmed")) - return DIFF_FILE_NEW_MOVED_DIM; - if (!strcasecmp(var, "newmovedalternativedimmed")) - return DIFF_FILE_NEW_MOVED_ALT_DIM; - return -1; + return LOOKUP_CONFIG(color_diff_slots, var); } static int parse_dirstat_params(struct diff_options *options, const char *params_string, diff --git a/diffcore-rename.c b/diffcore-rename.c index 0b7e4989a8..d775183c2f 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -4,6 +4,7 @@ #include "cache.h" #include "diff.h" #include "diffcore.h" +#include "object-store.h" #include "hashmap.h" #include "progress.h" @@ -11,6 +11,7 @@ #include "cache.h" #include "config.h" #include "dir.h" +#include "object-store.h" #include "attr.h" #include "refs.h" #include "wildmatch.h" @@ -2497,7 +2498,7 @@ void setup_standard_excludes(struct dir_struct *dir) { dir->exclude_per_dir = ".gitignore"; - /* core.excludefile defaulting to $XDG_HOME/git/ignore */ + /* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */ if (!excludes_file) excludes_file = xdg_config_home("ignore"); if (excludes_file && !access_or_warn(excludes_file, R_OK, 0)) @@ -1,5 +1,6 @@ #include "cache.h" #include "blob.h" +#include "object-store.h" #include "dir.h" #include "streaming.h" #include "submodule.h" diff --git a/environment.c b/environment.c index 2a6de2330b..013e845235 100644 --- a/environment.c +++ b/environment.c @@ -192,7 +192,7 @@ void setup_git_env(const char *git_dir) git_namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT)); shallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT); if (shallow_file) - set_alternate_shallow_file(shallow_file, 0); + set_alternate_shallow_file(the_repository, shallow_file, 0); } int is_bare_repository(void) @@ -319,11 +319,11 @@ char *get_index_file(void) return the_repository->index_file; } -char *get_graft_file(void) +char *get_graft_file(struct repository *r) { - if (!the_repository->graft_file) + if (!r->graft_file) BUG("git environment hasn't been setup"); - return the_repository->graft_file; + return r->graft_file; } static void set_git_dir_1(const char *path) diff --git a/ewah/bitmap.c b/ewah/bitmap.c index 756bdd050e..52f1178db4 100644 --- a/ewah/bitmap.c +++ b/ewah/bitmap.c @@ -45,14 +45,6 @@ void bitmap_set(struct bitmap *self, size_t pos) self->words[block] |= EWAH_MASK(pos); } -void bitmap_clear(struct bitmap *self, size_t pos) -{ - size_t block = EWAH_BLOCK(pos); - - if (block < self->word_alloc) - self->words[block] &= ~EWAH_MASK(pos); -} - int bitmap_get(struct bitmap *self, size_t pos) { size_t block = EWAH_BLOCK(pos); @@ -137,30 +129,6 @@ void bitmap_or_ewah(struct bitmap *self, struct ewah_bitmap *other) self->words[i++] |= word; } -void bitmap_each_bit(struct bitmap *self, ewah_callback callback, void *data) -{ - size_t pos = 0, i; - - for (i = 0; i < self->word_alloc; ++i) { - eword_t word = self->words[i]; - uint32_t offset; - - if (word == (eword_t)~0) { - for (offset = 0; offset < BITS_IN_EWORD; ++offset) - callback(pos++, data); - } else { - for (offset = 0; offset < BITS_IN_EWORD; ++offset) { - if ((word >> offset) == 0) - break; - - offset += ewah_bit_ctz64(word >> offset); - callback(pos + offset, data); - } - pos += BITS_IN_EWORD; - } - } -} - size_t bitmap_popcount(struct bitmap *self) { size_t i, count = 0; diff --git a/ewah/ewah_bitmap.c b/ewah/ewah_bitmap.c index b9fdda1d3d..d59b1afe3d 100644 --- a/ewah/ewah_bitmap.c +++ b/ewah/ewah_bitmap.c @@ -276,6 +276,18 @@ void ewah_each_bit(struct ewah_bitmap *self, void (*callback)(size_t, void*), vo } } +/** + * Clear all the bits in the bitmap. Does not free or resize + * memory. + */ +static void ewah_clear(struct ewah_bitmap *self) +{ + self->buffer_size = 1; + self->buffer[0] = 0; + self->bit_size = 0; + self->rlw = self->buffer; +} + struct ewah_bitmap *ewah_new(void) { struct ewah_bitmap *self; @@ -288,14 +300,6 @@ struct ewah_bitmap *ewah_new(void) return self; } -void ewah_clear(struct ewah_bitmap *self) -{ - self->buffer_size = 1; - self->buffer[0] = 0; - self->bit_size = 0; - self->rlw = self->buffer; -} - void ewah_free(struct ewah_bitmap *self) { if (!self) @@ -376,25 +380,6 @@ void ewah_iterator_init(struct ewah_iterator *it, struct ewah_bitmap *parent) read_new_rlw(it); } -void ewah_not(struct ewah_bitmap *self) -{ - size_t pointer = 0; - - while (pointer < self->buffer_size) { - eword_t *word = &self->buffer[pointer]; - size_t literals, k; - - rlw_xor_run_bit(word); - ++pointer; - - literals = rlw_get_literal_words(word); - for (k = 0; k < literals; ++k) { - self->buffer[pointer] = ~self->buffer[pointer]; - ++pointer; - } - } -} - void ewah_xor( struct ewah_bitmap *ewah_i, struct ewah_bitmap *ewah_j, @@ -459,216 +444,6 @@ void ewah_xor( out->bit_size = max_size(ewah_i->bit_size, ewah_j->bit_size); } -void ewah_and( - struct ewah_bitmap *ewah_i, - struct ewah_bitmap *ewah_j, - struct ewah_bitmap *out) -{ - struct rlw_iterator rlw_i; - struct rlw_iterator rlw_j; - size_t literals; - - rlwit_init(&rlw_i, ewah_i); - rlwit_init(&rlw_j, ewah_j); - - while (rlwit_word_size(&rlw_i) > 0 && rlwit_word_size(&rlw_j) > 0) { - while (rlw_i.rlw.running_len > 0 || rlw_j.rlw.running_len > 0) { - struct rlw_iterator *prey, *predator; - - if (rlw_i.rlw.running_len < rlw_j.rlw.running_len) { - prey = &rlw_i; - predator = &rlw_j; - } else { - prey = &rlw_j; - predator = &rlw_i; - } - - if (predator->rlw.running_bit == 0) { - ewah_add_empty_words(out, 0, - predator->rlw.running_len); - rlwit_discard_first_words(prey, - predator->rlw.running_len); - rlwit_discard_first_words(predator, - predator->rlw.running_len); - } else { - size_t index = rlwit_discharge(prey, out, - predator->rlw.running_len, 0); - ewah_add_empty_words(out, 0, - predator->rlw.running_len - index); - rlwit_discard_first_words(predator, - predator->rlw.running_len); - } - } - - literals = min_size( - rlw_i.rlw.literal_words, - rlw_j.rlw.literal_words); - - if (literals) { - size_t k; - - for (k = 0; k < literals; ++k) { - ewah_add(out, - rlw_i.buffer[rlw_i.literal_word_start + k] & - rlw_j.buffer[rlw_j.literal_word_start + k] - ); - } - - rlwit_discard_first_words(&rlw_i, literals); - rlwit_discard_first_words(&rlw_j, literals); - } - } - - if (rlwit_word_size(&rlw_i) > 0) - rlwit_discharge_empty(&rlw_i, out); - else - rlwit_discharge_empty(&rlw_j, out); - - out->bit_size = max_size(ewah_i->bit_size, ewah_j->bit_size); -} - -void ewah_and_not( - struct ewah_bitmap *ewah_i, - struct ewah_bitmap *ewah_j, - struct ewah_bitmap *out) -{ - struct rlw_iterator rlw_i; - struct rlw_iterator rlw_j; - size_t literals; - - rlwit_init(&rlw_i, ewah_i); - rlwit_init(&rlw_j, ewah_j); - - while (rlwit_word_size(&rlw_i) > 0 && rlwit_word_size(&rlw_j) > 0) { - while (rlw_i.rlw.running_len > 0 || rlw_j.rlw.running_len > 0) { - struct rlw_iterator *prey, *predator; - - if (rlw_i.rlw.running_len < rlw_j.rlw.running_len) { - prey = &rlw_i; - predator = &rlw_j; - } else { - prey = &rlw_j; - predator = &rlw_i; - } - - if ((predator->rlw.running_bit && prey == &rlw_i) || - (!predator->rlw.running_bit && prey != &rlw_i)) { - ewah_add_empty_words(out, 0, - predator->rlw.running_len); - rlwit_discard_first_words(prey, - predator->rlw.running_len); - rlwit_discard_first_words(predator, - predator->rlw.running_len); - } else { - size_t index; - int negate_words; - - negate_words = (&rlw_i != prey); - index = rlwit_discharge(prey, out, - predator->rlw.running_len, negate_words); - ewah_add_empty_words(out, negate_words, - predator->rlw.running_len - index); - rlwit_discard_first_words(predator, - predator->rlw.running_len); - } - } - - literals = min_size( - rlw_i.rlw.literal_words, - rlw_j.rlw.literal_words); - - if (literals) { - size_t k; - - for (k = 0; k < literals; ++k) { - ewah_add(out, - rlw_i.buffer[rlw_i.literal_word_start + k] & - ~(rlw_j.buffer[rlw_j.literal_word_start + k]) - ); - } - - rlwit_discard_first_words(&rlw_i, literals); - rlwit_discard_first_words(&rlw_j, literals); - } - } - - if (rlwit_word_size(&rlw_i) > 0) - rlwit_discharge(&rlw_i, out, ~0, 0); - else - rlwit_discharge_empty(&rlw_j, out); - - out->bit_size = max_size(ewah_i->bit_size, ewah_j->bit_size); -} - -void ewah_or( - struct ewah_bitmap *ewah_i, - struct ewah_bitmap *ewah_j, - struct ewah_bitmap *out) -{ - struct rlw_iterator rlw_i; - struct rlw_iterator rlw_j; - size_t literals; - - rlwit_init(&rlw_i, ewah_i); - rlwit_init(&rlw_j, ewah_j); - - while (rlwit_word_size(&rlw_i) > 0 && rlwit_word_size(&rlw_j) > 0) { - while (rlw_i.rlw.running_len > 0 || rlw_j.rlw.running_len > 0) { - struct rlw_iterator *prey, *predator; - - if (rlw_i.rlw.running_len < rlw_j.rlw.running_len) { - prey = &rlw_i; - predator = &rlw_j; - } else { - prey = &rlw_j; - predator = &rlw_i; - } - - if (predator->rlw.running_bit) { - ewah_add_empty_words(out, 0, - predator->rlw.running_len); - rlwit_discard_first_words(prey, - predator->rlw.running_len); - rlwit_discard_first_words(predator, - predator->rlw.running_len); - } else { - size_t index = rlwit_discharge(prey, out, - predator->rlw.running_len, 0); - ewah_add_empty_words(out, 0, - predator->rlw.running_len - index); - rlwit_discard_first_words(predator, - predator->rlw.running_len); - } - } - - literals = min_size( - rlw_i.rlw.literal_words, - rlw_j.rlw.literal_words); - - if (literals) { - size_t k; - - for (k = 0; k < literals; ++k) { - ewah_add(out, - rlw_i.buffer[rlw_i.literal_word_start + k] | - rlw_j.buffer[rlw_j.literal_word_start + k] - ); - } - - rlwit_discard_first_words(&rlw_i, literals); - rlwit_discard_first_words(&rlw_j, literals); - } - } - - if (rlwit_word_size(&rlw_i) > 0) - rlwit_discharge(&rlw_i, out, ~0, 0); - else - rlwit_discharge(&rlw_j, out, ~0, 0); - - out->bit_size = max_size(ewah_i->bit_size, ewah_j->bit_size); -} - - #define BITMAP_POOL_MAX 16 static struct ewah_bitmap *bitmap_pool[BITMAP_POOL_MAX]; static size_t bitmap_pool_size; diff --git a/ewah/ewah_io.c b/ewah/ewah_io.c index 33c08c40f8..9035ee65ea 100644 --- a/ewah/ewah_io.c +++ b/ewah/ewah_io.c @@ -20,32 +20,6 @@ #include "ewok.h" #include "strbuf.h" -int ewah_serialize_native(struct ewah_bitmap *self, int fd) -{ - uint32_t write32; - size_t to_write = self->buffer_size * 8; - - /* 32 bit -- bit size for the map */ - write32 = (uint32_t)self->bit_size; - if (write(fd, &write32, 4) != 4) - return -1; - - /** 32 bit -- number of compressed 64-bit words */ - write32 = (uint32_t)self->buffer_size; - if (write(fd, &write32, 4) != 4) - return -1; - - if (write(fd, self->buffer, to_write) != to_write) - return -1; - - /** 32 bit -- position for the RLW */ - write32 = self->rlw - self->buffer; - if (write(fd, &write32, 4) != 4) - return -1; - - return (3 * 4) + to_write; -} - int ewah_serialize_to(struct ewah_bitmap *self, int (*write_fun)(void *, const void *, size_t), void *data) @@ -100,16 +74,6 @@ int ewah_serialize_to(struct ewah_bitmap *self, return (3 * 4) + (self->buffer_size * 8); } -static int write_helper(void *fd, const void *buf, size_t len) -{ - return write((intptr_t)fd, buf, len); -} - -int ewah_serialize(struct ewah_bitmap *self, int fd) -{ - return ewah_serialize_to(self, write_helper, (void *)(intptr_t)fd); -} - static int write_strbuf(void *user_data, const void *data, size_t len) { struct strbuf *sb = user_data; @@ -168,58 +132,3 @@ ssize_t ewah_read_mmap(struct ewah_bitmap *self, const void *map, size_t len) return ptr - (const uint8_t *)map; } - -int ewah_deserialize(struct ewah_bitmap *self, int fd) -{ - size_t i; - eword_t dump[2048]; - const size_t words_per_dump = sizeof(dump) / sizeof(eword_t); - uint32_t bitsize, word_count, rlw_pos; - - eword_t *buffer = NULL; - size_t words_left; - - ewah_clear(self); - - /* 32 bit -- bit size for the map */ - if (read(fd, &bitsize, 4) != 4) - return -1; - - self->bit_size = (size_t)ntohl(bitsize); - - /** 32 bit -- number of compressed 64-bit words */ - if (read(fd, &word_count, 4) != 4) - return -1; - - self->buffer_size = self->alloc_size = (size_t)ntohl(word_count); - REALLOC_ARRAY(self->buffer, self->alloc_size); - - /** 64 bit x N -- compressed words */ - buffer = self->buffer; - words_left = self->buffer_size; - - while (words_left >= words_per_dump) { - if (read(fd, dump, sizeof(dump)) != sizeof(dump)) - return -1; - - for (i = 0; i < words_per_dump; ++i, ++buffer) - *buffer = ntohll(dump[i]); - - words_left -= words_per_dump; - } - - if (words_left) { - if (read(fd, dump, words_left * 8) != words_left * 8) - return -1; - - for (i = 0; i < words_left; ++i, ++buffer) - *buffer = ntohll(dump[i]); - } - - /** 32 bit -- position for the RLW */ - if (read(fd, &rlw_pos, 4) != 4) - return -1; - - self->rlw = self->buffer + ntohl(rlw_pos); - return 0; -} diff --git a/ewah/ewah_rlw.c b/ewah/ewah_rlw.c index b9643b7d0f..5093d43e2f 100644 --- a/ewah/ewah_rlw.c +++ b/ewah/ewah_rlw.c @@ -104,11 +104,3 @@ size_t rlwit_discharge( return index; } - -void rlwit_discharge_empty(struct rlw_iterator *it, struct ewah_bitmap *out) -{ - while (rlwit_word_size(it) > 0) { - ewah_add_empty_words(out, 0, rlwit_word_size(it)); - rlwit_discard_first_words(it, rlwit_word_size(it)); - } -} diff --git a/ewah/ewok.h b/ewah/ewok.h index 357fd93c84..84b2a29faa 100644 --- a/ewah/ewok.h +++ b/ewah/ewok.h @@ -73,12 +73,6 @@ void ewah_pool_free(struct ewah_bitmap *self); struct ewah_bitmap *ewah_new(void); /** - * Clear all the bits in the bitmap. Does not free or resize - * memory. - */ -void ewah_clear(struct ewah_bitmap *self); - -/** * Free all the memory of the bitmap */ void ewah_free(struct ewah_bitmap *self); @@ -86,23 +80,13 @@ void ewah_free(struct ewah_bitmap *self); int ewah_serialize_to(struct ewah_bitmap *self, int (*write_fun)(void *out, const void *buf, size_t len), void *out); -int ewah_serialize(struct ewah_bitmap *self, int fd); -int ewah_serialize_native(struct ewah_bitmap *self, int fd); int ewah_serialize_strbuf(struct ewah_bitmap *self, struct strbuf *); -int ewah_deserialize(struct ewah_bitmap *self, int fd); ssize_t ewah_read_mmap(struct ewah_bitmap *self, const void *map, size_t len); uint32_t ewah_checksum(struct ewah_bitmap *self); /** - * Logical not (bitwise negation) in-place on the bitmap - * - * This operation is linear time based on the size of the bitmap. - */ -void ewah_not(struct ewah_bitmap *self); - -/** * Call the given callback with the position of every single bit * that has been set on the bitmap. * @@ -164,26 +148,11 @@ void ewah_iterator_init(struct ewah_iterator *it, struct ewah_bitmap *parent); */ int ewah_iterator_next(eword_t *next, struct ewah_iterator *it); -void ewah_or( - struct ewah_bitmap *ewah_i, - struct ewah_bitmap *ewah_j, - struct ewah_bitmap *out); - -void ewah_and_not( - struct ewah_bitmap *ewah_i, - struct ewah_bitmap *ewah_j, - struct ewah_bitmap *out); - void ewah_xor( struct ewah_bitmap *ewah_i, struct ewah_bitmap *ewah_j, struct ewah_bitmap *out); -void ewah_and( - struct ewah_bitmap *ewah_i, - struct ewah_bitmap *ewah_j, - struct ewah_bitmap *out); - /** * Direct word access */ @@ -204,7 +173,6 @@ struct bitmap { struct bitmap *bitmap_new(void); void bitmap_set(struct bitmap *self, size_t pos); -void bitmap_clear(struct bitmap *self, size_t pos); int bitmap_get(struct bitmap *self, size_t pos); void bitmap_reset(struct bitmap *self); void bitmap_free(struct bitmap *self); @@ -218,7 +186,6 @@ void bitmap_and_not(struct bitmap *self, struct bitmap *other); void bitmap_or_ewah(struct bitmap *self, struct ewah_bitmap *other); void bitmap_or(struct bitmap *self, const struct bitmap *other); -void bitmap_each_bit(struct bitmap *self, ewah_callback callback, void *data); size_t bitmap_popcount(struct bitmap *self); #endif diff --git a/ewah/ewok_rlw.h b/ewah/ewok_rlw.h index bb3c6ff7e0..7cdfdd0c02 100644 --- a/ewah/ewok_rlw.h +++ b/ewah/ewok_rlw.h @@ -98,7 +98,6 @@ void rlwit_init(struct rlw_iterator *it, struct ewah_bitmap *bitmap); void rlwit_discard_first_words(struct rlw_iterator *it, size_t x); size_t rlwit_discharge( struct rlw_iterator *it, struct ewah_bitmap *out, size_t max, int negate); -void rlwit_discharge_empty(struct rlw_iterator *it, struct ewah_bitmap *out); static inline size_t rlwit_word_size(struct rlw_iterator *it) { diff --git a/fast-import.c b/fast-import.c index 4d55910ab9..12195d54d7 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1076,7 +1076,7 @@ static int store_object( return 1; } - if (last && last->data.buf && last->depth < max_depth + if (last && last->data.len && last->data.buf && last->depth < max_depth && dat->len > the_hash_algo->rawsz) { delta_count_attempts_by_type[type]++; diff --git a/fetch-object.c b/fetch-object.c index 853624f811..48fe63dd6c 100644 --- a/fetch-object.c +++ b/fetch-object.c @@ -19,7 +19,7 @@ static void fetch_refs(const char *remote_name, struct ref *ref) transport_set_option(transport, TRANS_OPT_FROM_PROMISOR, "1"); transport_set_option(transport, TRANS_OPT_NO_DEPENDENTS, "1"); - transport_fetch_refs(transport, ref); + transport_fetch_refs(transport, ref, NULL); fetch_if_missing = original_fetch_if_missing; } diff --git a/fetch-pack.c b/fetch-pack.c index a320ce9872..7ccb9c0d45 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -19,6 +19,8 @@ #include "sha1-array.h" #include "oidset.h" #include "packfile.h" +#include "object-store.h" +#include "connected.h" static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; @@ -396,7 +398,7 @@ static int find_common(struct fetch_pack_args *args, return 1; } - if (is_repository_shallow()) + if (is_repository_shallow(the_repository)) write_shallow_commits(&req_buf, 1, NULL); if (args->depth > 0) packet_buf_write(&req_buf, "deepen %d", args->depth); @@ -427,7 +429,7 @@ static int find_common(struct fetch_pack_args *args, if (skip_prefix(line, "shallow ", &arg)) { if (get_oid_hex(arg, &oid)) die(_("invalid shallow line: %s"), line); - register_shallow(&oid); + register_shallow(the_repository, &oid); continue; } if (skip_prefix(line, "unshallow ", &arg)) { @@ -657,11 +659,11 @@ static void filter_refs(struct fetch_pack_args *args, } i++; } - } - if (!keep && args->fetch_all && - (!args->deepen || !starts_with(ref->name, "refs/tags/"))) - keep = 1; + if (!keep && args->fetch_all && + (!args->deepen || !starts_with(ref->name, "refs/tags/"))) + keep = 1; + } if (keep) { *newtail = ref; @@ -985,7 +987,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, sort_ref_list(&ref, ref_compare_name); QSORT(sought, nr_sought, cmp_ref_by_name); - if ((args->depth > 0 || is_repository_shallow()) && !server_supports("shallow")) + if ((args->depth > 0 || is_repository_shallow(the_repository)) && !server_supports("shallow")) die(_("Server does not support shallow clients")); if (args->depth > 0 || args->deepen_since || args->deepen_not) args->deepen = 1; @@ -1083,7 +1085,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, static void add_shallow_requests(struct strbuf *req_buf, const struct fetch_pack_args *args) { - if (is_repository_shallow()) + if (is_repository_shallow(the_repository)) write_shallow_commits(req_buf, 1, NULL); if (args->depth > 0) packet_buf_write(req_buf, "deepen %d", args->depth); @@ -1102,9 +1104,10 @@ static void add_shallow_requests(struct strbuf *req_buf, static void add_wants(const struct ref *wants, struct strbuf *req_buf) { + int use_ref_in_want = server_supports_feature("fetch", "ref-in-want", 0); + for ( ; wants ; wants = wants->next) { const struct object_id *remote = &wants->old_oid; - const char *remote_hex; struct object *o; /* @@ -1122,8 +1125,10 @@ static void add_wants(const struct ref *wants, struct strbuf *req_buf) continue; } - remote_hex = oid_to_hex(remote); - packet_buf_write(req_buf, "want %s\n", remote_hex); + if (!use_ref_in_want || wants->exact_oid) + packet_buf_write(req_buf, "want %s\n", oid_to_hex(remote)); + else + packet_buf_write(req_buf, "want-ref %s\n", wants->name); } } @@ -1195,7 +1200,7 @@ static int send_fetch_request(int fd_out, const struct fetch_pack_args *args, /* Add shallow-info and deepen request */ if (server_supports_feature("fetch", "shallow", 0)) add_shallow_requests(&req_buf, args); - else if (is_repository_shallow() || args->deepen) + else if (is_repository_shallow(the_repository) || args->deepen) die(_("Server does not support shallow requests")); /* Add filter */ @@ -1308,7 +1313,7 @@ static void receive_shallow_info(struct fetch_pack_args *args, if (skip_prefix(reader->line, "shallow ", &arg)) { if (get_oid_hex(arg, &oid)) die(_("invalid shallow line: %s"), reader->line); - register_shallow(&oid); + register_shallow(the_repository, &oid); continue; } if (skip_prefix(reader->line, "unshallow ", &arg)) { @@ -1334,6 +1339,32 @@ static void receive_shallow_info(struct fetch_pack_args *args, args->deepen = 1; } +static void receive_wanted_refs(struct packet_reader *reader, struct ref *refs) +{ + process_section_header(reader, "wanted-refs", 0); + while (packet_reader_read(reader) == PACKET_READ_NORMAL) { + struct object_id oid; + const char *end; + struct ref *r = NULL; + + if (parse_oid_hex(reader->line, &oid, &end) || *end++ != ' ') + die("expected wanted-ref, got '%s'", reader->line); + + for (r = refs; r; r = r->next) { + if (!strcmp(end, r->name)) { + oidcpy(&r->old_oid, &oid); + break; + } + } + + if (!r) + die("unexpected wanted-ref: '%s'", reader->line); + } + + if (reader->status != PACKET_READ_DELIM) + die("error processing wanted refs: %d", reader->status); +} + enum fetch_state { FETCH_CHECK_LOCAL = 0, FETCH_SEND_REQUEST, @@ -1408,6 +1439,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, if (process_section_header(&reader, "shallow-info", 1)) receive_shallow_info(args, &reader); + if (process_section_header(&reader, "wanted-refs", 1)) + receive_wanted_refs(&reader, ref); + /* get the pack */ process_section_header(&reader, "packfile", 0); if (get_pack(args, fd, pack_lockfile)) @@ -1470,16 +1504,17 @@ static int remove_duplicates_in_refs(struct ref **ref, int nr) } static void update_shallow(struct fetch_pack_args *args, - struct ref **sought, int nr_sought, + struct ref *refs, struct shallow_info *si) { struct oid_array ref = OID_ARRAY_INIT; int *status; int i; + struct ref *r; if (args->deepen && alternate_shallow_file) { if (*alternate_shallow_file == '\0') { /* --unshallow */ - unlink_or_warn(git_path_shallow()); + unlink_or_warn(git_path_shallow(the_repository)); rollback_lock_file(&shallow_lock); } else commit_lock_file(&shallow_lock); @@ -1517,8 +1552,8 @@ static void update_shallow(struct fetch_pack_args *args, remove_nonexistent_theirs_shallow(si); if (!si->nr_ours && !si->nr_theirs) return; - for (i = 0; i < nr_sought; i++) - oid_array_append(&ref, &sought[i]->old_oid); + for (r = refs; r; r = r->next) + oid_array_append(&ref, &r->old_oid); si->ref = &ref; if (args->update_shallow) { @@ -1552,17 +1587,29 @@ static void update_shallow(struct fetch_pack_args *args, * remote is also shallow, check what ref is safe to update * without updating .git/shallow */ - status = xcalloc(nr_sought, sizeof(*status)); + status = xcalloc(ref.nr, sizeof(*status)); assign_shallow_commits_to_refs(si, NULL, status); if (si->nr_ours || si->nr_theirs) { - for (i = 0; i < nr_sought; i++) + for (r = refs, i = 0; r; r = r->next, i++) if (status[i]) - sought[i]->status = REF_STATUS_REJECT_SHALLOW; + r->status = REF_STATUS_REJECT_SHALLOW; } free(status); oid_array_clear(&ref); } +static int iterate_ref_map(void *cb_data, struct object_id *oid) +{ + struct ref **rm = cb_data; + struct ref *ref = *rm; + + if (!ref) + return -1; /* end of the list */ + *rm = ref->next; + oidcpy(oid, &ref->old_oid); + return 0; +} + struct ref *fetch_pack(struct fetch_pack_args *args, int fd[], struct child_process *conn, const struct ref *ref, @@ -1591,7 +1638,25 @@ struct ref *fetch_pack(struct fetch_pack_args *args, ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought, &si, pack_lockfile); reprepare_packed_git(the_repository); - update_shallow(args, sought, nr_sought, &si); + + if (!args->cloning && args->deepen) { + struct check_connected_options opt = CHECK_CONNECTED_INIT; + struct ref *iterator = ref_cpy; + opt.shallow_file = alternate_shallow_file; + if (args->deepen) + opt.is_deepening_fetch = 1; + if (check_connected(iterate_ref_map, &iterator, &opt)) { + error(_("remote did not send all necessary objects")); + free_refs(ref_cpy); + ref_cpy = NULL; + rollback_lock_file(&shallow_lock); + goto cleanup; + } + args->connectivity_checked = 1; + } + + update_shallow(args, ref_cpy, &si); +cleanup: clear_shallow_info(&si); return ref_cpy; } diff --git a/fetch-pack.h b/fetch-pack.h index bb45a366a8..2160be9164 100644 --- a/fetch-pack.h +++ b/fetch-pack.h @@ -41,6 +41,21 @@ struct fetch_pack_args { * regardless of which object flags it uses (if any). */ unsigned no_dependents:1; + + /* + * Because fetch_pack() overwrites the shallow file upon a + * successful deepening non-clone fetch, if this struct + * specifies such a fetch, fetch_pack() needs to perform a + * connectivity check before deciding if a fetch is successful + * (and overwriting the shallow file). fetch_pack() sets this + * field to 1 if such a connectivity check was performed. + * + * This is different from check_self_contained_and_connected + * in that the former allows existing objects in the + * repository to satisfy connectivity needs, whereas the + * latter doesn't. + */ + unsigned connectivity_checked:1; }; /* @@ -1,4 +1,5 @@ #include "cache.h" +#include "object-store.h" #include "object.h" #include "blob.h" #include "tree.h" @@ -14,6 +15,7 @@ #include "packfile.h" #include "submodule-config.h" #include "config.h" +#include "help.h" static struct oidset gitmodules_found = OIDSET_INIT; static struct oidset gitmodules_done = OIDSET_INIT; @@ -86,37 +88,60 @@ enum fsck_msg_id { #undef MSG_ID #define STR(x) #x -#define MSG_ID(id, msg_type) { STR(id), NULL, FSCK_##msg_type }, +#define MSG_ID(id, msg_type) { STR(id), NULL, NULL, FSCK_##msg_type }, static struct { const char *id_string; const char *downcased; + const char *camelcased; int msg_type; } msg_id_info[FSCK_MSG_MAX + 1] = { FOREACH_MSG_ID(MSG_ID) - { NULL, NULL, -1 } + { NULL, NULL, NULL, -1 } }; #undef MSG_ID -static int parse_msg_id(const char *text) +static void prepare_msg_ids(void) { int i; - if (!msg_id_info[0].downcased) { - /* convert id_string to lower case, without underscores. */ - for (i = 0; i < FSCK_MSG_MAX; i++) { - const char *p = msg_id_info[i].id_string; - int len = strlen(p); - char *q = xmalloc(len); - - msg_id_info[i].downcased = q; - while (*p) - if (*p == '_') - p++; - else - *(q)++ = tolower(*(p)++); - *q = '\0'; + if (msg_id_info[0].downcased) + return; + + /* convert id_string to lower case, without underscores. */ + for (i = 0; i < FSCK_MSG_MAX; i++) { + const char *p = msg_id_info[i].id_string; + int len = strlen(p); + char *q = xmalloc(len); + + msg_id_info[i].downcased = q; + while (*p) + if (*p == '_') + p++; + else + *(q)++ = tolower(*(p)++); + *q = '\0'; + + p = msg_id_info[i].id_string; + q = xmalloc(len); + msg_id_info[i].camelcased = q; + while (*p) { + if (*p == '_') { + p++; + if (*p) + *q++ = *p++; + } else { + *q++ = tolower(*p++); + } } + *q = '\0'; } +} + +static int parse_msg_id(const char *text) +{ + int i; + + prepare_msg_ids(); for (i = 0; i < FSCK_MSG_MAX; i++) if (!strcmp(text, msg_id_info[i].downcased)) @@ -125,6 +150,16 @@ static int parse_msg_id(const char *text) return -1; } +void list_config_fsck_msg_ids(struct string_list *list, const char *prefix) +{ + int i; + + prepare_msg_ids(); + + for (i = 0; i < FSCK_MSG_MAX; i++) + list_config_item(list, prefix, msg_id_info[i].camelcased); +} + static int fsck_msg_type(enum fsck_msg_id msg_id, struct fsck_options *options) { @@ -281,6 +316,13 @@ static void append_msg_id(struct strbuf *sb, const char *msg_id) strbuf_addstr(sb, ": "); } +static int object_on_skiplist(struct fsck_options *opts, struct object *obj) +{ + if (opts && opts->skiplist && obj) + return oid_array_lookup(opts->skiplist, &obj->oid) >= 0; + return 0; +} + __attribute__((format (printf, 4, 5))) static int report(struct fsck_options *options, struct object *object, enum fsck_msg_id id, const char *fmt, ...) @@ -292,8 +334,7 @@ static int report(struct fsck_options *options, struct object *object, if (msg_type == FSCK_IGNORE) return 0; - if (options->skiplist && object && - oid_array_lookup(options->skiplist, &object->oid) >= 0) + if (object_on_skiplist(options, object)) return 0; if (msg_type == FSCK_FATAL) @@ -761,7 +802,7 @@ static int fsck_commit_buffer(struct commit *commit, const char *buffer, buffer = p + 1; parent_line_count++; } - graft = lookup_commit_graft(&commit->object.oid); + graft = lookup_commit_graft(the_repository, &commit->object.oid); parent_count = commit_list_count(commit->parents); if (graft) { if (graft->nr_parent == -1 && !parent_count) @@ -963,6 +1004,9 @@ static int fsck_blob(struct blob *blob, const char *buf, return 0; oidset_insert(&gitmodules_done, &blob->object.oid); + if (object_on_skiplist(options, &blob->object)) + return 0; + if (!buf) { /* * A missing buffer here is a sign that the caller found the diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh index 8d6d8b45ce..c4124acbe7 100755 --- a/generate-cmdlist.sh +++ b/generate-cmdlist.sh @@ -76,6 +76,23 @@ print_command_list () { echo "};" } +print_config_list () { + cat <<EOF +static const char *config_name_list[] = { +EOF + grep '^[a-zA-Z].*\..*::$' Documentation/config.txt | + sed '/deprecated/d; s/::$//; s/, */\n/g' | + sort | + while read line + do + echo " \"$line\"," + done + cat <<EOF + NULL, +}; +EOF +} + echo "/* Automatically generated by generate-cmdlist.sh */ struct cmdname_help { const char *name; @@ -88,3 +105,5 @@ echo define_category_names "$1" echo print_command_list "$1" +echo +print_config_list diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 36f38ced90..20eb81cc92 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -1062,7 +1062,7 @@ sub recount_edited_hunk { $o_cnt++; } elsif ($mode eq '+') { $n_cnt++; - } elsif ($mode eq ' ') { + } elsif ($mode eq ' ' or $mode eq "\n") { $o_cnt++; $n_cnt++; } diff --git a/git-filter-branch.sh b/git-filter-branch.sh index ccceaf19a7..5c5afa2b98 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -372,6 +372,7 @@ while read commit parents; do git_filter_branch__commit_count=$(($git_filter_branch__commit_count+1)) report_progress + test -f "$workdir"/../map/$commit && continue case "$filter_subdir" in "") @@ -27,6 +27,22 @@ import zlib import ctypes import errno +# support basestring in python3 +try: + unicode = unicode +except NameError: + # 'unicode' is undefined, must be Python 3 + str = str + unicode = str + bytes = bytes + basestring = (str,bytes) +else: + # 'unicode' exists, must be Python 2 + str = str + unicode = unicode + bytes = str + basestring = basestring + try: from subprocess import CalledProcessError except ImportError: @@ -767,7 +783,7 @@ def gitDeleteRef(ref): _gitConfig = {} def gitConfig(key, typeSpecifier=None): - if not _gitConfig.has_key(key): + if key not in _gitConfig: cmd = [ "git", "config" ] if typeSpecifier: cmd += [ typeSpecifier ] @@ -781,12 +797,12 @@ def gitConfigBool(key): variable is set to true, and False if set to false or not present in the config.""" - if not _gitConfig.has_key(key): + if key not in _gitConfig: _gitConfig[key] = gitConfig(key, '--bool') == "true" return _gitConfig[key] def gitConfigInt(key): - if not _gitConfig.has_key(key): + if key not in _gitConfig: cmd = [ "git", "config", "--int", key ] s = read_pipe(cmd, ignore_error=True) v = s.strip() @@ -797,7 +813,7 @@ def gitConfigInt(key): return _gitConfig[key] def gitConfigList(key): - if not _gitConfig.has_key(key): + if key not in _gitConfig: s = read_pipe(["git", "config", "--get-all", key], ignore_error=True) _gitConfig[key] = s.strip().splitlines() if _gitConfig[key] == ['']: @@ -855,7 +871,7 @@ def findUpstreamBranchPoint(head = "HEAD"): tip = branches[branch] log = extractLogMessageFromGitCommit(tip) settings = extractSettingsGitLog(log) - if settings.has_key("depot-paths"): + if "depot-paths" in settings: paths = ",".join(settings["depot-paths"]) branchByDepotPath[paths] = "remotes/p4/" + branch @@ -865,9 +881,9 @@ def findUpstreamBranchPoint(head = "HEAD"): commit = head + "~%s" % parent log = extractLogMessageFromGitCommit(commit) settings = extractSettingsGitLog(log) - if settings.has_key("depot-paths"): + if "depot-paths" in settings: paths = ",".join(settings["depot-paths"]) - if branchByDepotPath.has_key(paths): + if paths in branchByDepotPath: return [branchByDepotPath[paths], settings] parent = parent + 1 @@ -876,7 +892,7 @@ def findUpstreamBranchPoint(head = "HEAD"): def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True): if not silent: - print ("Creating/updating branch(es) in %s based on origin branch(es)" + print("Creating/updating branch(es) in %s based on origin branch(es)" % localRefPrefix) originPrefix = "origin/p4/" @@ -891,29 +907,29 @@ def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent originHead = line original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead)) - if (not original.has_key('depot-paths') - or not original.has_key('change')): + if ('depot-paths' not in original + or 'change' not in original): continue update = False if not gitBranchExists(remoteHead): if verbose: - print "creating %s" % remoteHead + print("creating %s" % remoteHead) update = True else: settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead)) - if settings.has_key('change') > 0: + if 'change' in settings: if settings['depot-paths'] == original['depot-paths']: originP4Change = int(original['change']) p4Change = int(settings['change']) if originP4Change > p4Change: - print ("%s (%s) is newer than %s (%s). " + print("%s (%s) is newer than %s (%s). " "Updating p4 branch from origin." % (originHead, originP4Change, remoteHead, p4Change)) update = True else: - print ("Ignoring: %s was imported from %s while " + print("Ignoring: %s was imported from %s while " "%s was imported from %s" % (originHead, ','.join(original['depot-paths']), remoteHead, ','.join(settings['depot-paths']))) @@ -1002,7 +1018,7 @@ def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize): # Insert changes in chronological order for entry in reversed(result): - if not entry.has_key('change'): + if 'change' not in entry: continue changes.add(int(entry['change'])) @@ -1312,7 +1328,7 @@ class P4UserMap: results = p4CmdList("user -o") for r in results: - if r.has_key('User'): + if 'User' in r: self.myP4UserId = r['User'] return r['User'] die("Could not find your p4 user id") @@ -1336,7 +1352,7 @@ class P4UserMap: self.emails = {} for output in p4CmdList("users"): - if not output.has_key("User"): + if "User" not in output: continue self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">" self.emails[output["Email"]] = output["User"] @@ -1381,9 +1397,9 @@ class P4Debug(Command): def run(self, args): j = 0 for output in p4CmdList(args): - print 'Element: %d' % j + print('Element: %d' % j) j += 1 - print output + print(output) return True class P4RollBack(Command): @@ -1424,14 +1440,14 @@ class P4RollBack(Command): if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange) for p in depotPaths]))) == 0: - print "Branch %s did not exist at change %s, deleting." % (ref, maxChange) + print("Branch %s did not exist at change %s, deleting." % (ref, maxChange)) system("git update-ref -d %s `git rev-parse %s`" % (ref, ref)) continue while change and int(change) > maxChange: changed = True if self.verbose: - print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange) + print("%s is at %s ; rewinding towards %s" % (ref, change, maxChange)) system("git update-ref %s \"%s^\"" % (ref, ref)) log = extractLogMessageFromGitCommit(ref) settings = extractSettingsGitLog(log) @@ -1441,7 +1457,7 @@ class P4RollBack(Command): change = settings['change'] if changed: - print "%s rewound to %s" % (ref, change) + print("%s rewound to %s" % (ref, change)) return True @@ -1577,10 +1593,10 @@ class P4Submit(Command, P4UserMap): except: # cleanup our temporary file os.unlink(outFileName) - print "Failed to strip RCS keywords in %s" % file + print("Failed to strip RCS keywords in %s" % file) raise - print "Patched up RCS keywords in %s" % file + print("Patched up RCS keywords in %s" % file) def p4UserForCommit(self,id): # Return the tuple (perforce user,git email) for a given git commit id @@ -1588,7 +1604,7 @@ class P4Submit(Command, P4UserMap): gitEmail = read_pipe(["git", "log", "--max-count=1", "--format=%ae", id]) gitEmail = gitEmail.strip() - if not self.emails.has_key(gitEmail): + if gitEmail not in self.emails: return (None,gitEmail) else: return (self.emails[gitEmail],gitEmail) @@ -1600,7 +1616,7 @@ class P4Submit(Command, P4UserMap): if not user: msg = "Cannot find p4 user for email %s in commit %s." % (email, id) if gitConfigBool("git-p4.allowMissingP4Users"): - print "%s" % msg + print("%s" % msg) else: die("Error: %s\nSet git-p4.allowMissingP4Users to true to allow this." % msg) @@ -1612,14 +1628,14 @@ class P4Submit(Command, P4UserMap): results = p4CmdList("client -o") # find the current client client = None for r in results: - if r.has_key('Client'): + if 'Client' in r: client = r['Client'] break if not client: die("could not get client spec") results = p4CmdList(["changes", "-c", client, "-m", "1"]) for r in results: - if r.has_key('change'): + if 'change' in r: return r['change'] die("Could not get changelist number for last submit - cannot patch up user details") @@ -1637,10 +1653,10 @@ class P4Submit(Command, P4UserMap): result = p4CmdList("change -f -i", stdin=input) for r in result: - if r.has_key('code'): + if 'code' in r: if r['code'] == 'error': die("Could not modify user field of changelist %s to %s:%s" % (changelist, newUser, r['data'])) - if r.has_key('data'): + if 'data' in r: print("Updated user field for changelist %s to %s" % (changelist, newUser)) return die("Could not modify user field of changelist %s to %s" % (changelist, newUser)) @@ -1650,7 +1666,7 @@ class P4Submit(Command, P4UserMap): # which are required to modify changelists. results = p4CmdList(["protects", self.depotPath]) for r in results: - if r.has_key('perm'): + if 'perm' in r: if r['perm'] == 'admin': return 1 if r['perm'] == 'super': @@ -1690,7 +1706,7 @@ class P4Submit(Command, P4UserMap): if changelist: args.append(str(changelist)) for entry in p4CmdList(args): - if not entry.has_key('code'): + if 'code' not in entry: continue if entry['code'] == 'stat': change_entry = entry @@ -1699,7 +1715,7 @@ class P4Submit(Command, P4UserMap): die('Failed to decode output of p4 change -o') for key, value in change_entry.iteritems(): if key.startswith('File'): - if settings.has_key('depot-paths'): + if 'depot-paths' in settings: if not [p for p in settings['depot-paths'] if p4PathStartsWith(value, p)]: continue @@ -1710,7 +1726,7 @@ class P4Submit(Command, P4UserMap): continue # Output in the order expected by prepareLogMessage for key in ['Change', 'Client', 'User', 'Status', 'Description', 'Jobs']: - if not change_entry.has_key(key): + if key not in change_entry: continue template += '\n' template += key + ':' @@ -1738,7 +1754,7 @@ class P4Submit(Command, P4UserMap): mtime = os.stat(template_file).st_mtime # invoke the editor - if os.environ.has_key("P4EDITOR") and (os.environ.get("P4EDITOR") != ""): + if "P4EDITOR" in os.environ and (os.environ.get("P4EDITOR") != ""): editor = os.environ.get("P4EDITOR") else: editor = read_pipe("git var GIT_EDITOR").strip() @@ -1762,7 +1778,7 @@ class P4Submit(Command, P4UserMap): def get_diff_description(self, editedFiles, filesToAdd, symlinks): # diff - if os.environ.has_key("P4DIFF"): + if "P4DIFF" in os.environ: del(os.environ["P4DIFF"]) diff = "" for editedFile in editedFiles: @@ -1792,8 +1808,8 @@ class P4Submit(Command, P4UserMap): def applyCommit(self, id): """Apply one commit, return True if it succeeded.""" - print "Applying", read_pipe(["git", "show", "-s", - "--format=format:%h %s", id]) + print("Applying", read_pipe(["git", "show", "-s", + "--format=format:%h %s", id])) (p4User, gitEmail) = self.p4UserForCommit(id) @@ -1825,7 +1841,7 @@ class P4Submit(Command, P4UserMap): filesToDelete.remove(path) dst_mode = int(diff['dst_mode'], 8) - if dst_mode == 0120000: + if dst_mode == 0o120000: symlinks.add(path) elif modifier == "D": @@ -1883,7 +1899,7 @@ class P4Submit(Command, P4UserMap): if os.system(tryPatchCmd) != 0: fixed_rcs_keywords = False patch_succeeded = False - print "Unfortunately applying the change failed!" + print("Unfortunately applying the change failed!") # Patch failed, maybe it's just RCS keyword woes. Look through # the patch to see if that's possible. @@ -1901,13 +1917,13 @@ class P4Submit(Command, P4UserMap): for line in read_pipe_lines(["git", "diff", "%s^..%s" % (id, id), file]): if regexp.search(line): if verbose: - print "got keyword match on %s in %s in %s" % (pattern, line, file) + print("got keyword match on %s in %s in %s" % (pattern, line, file)) kwfiles[file] = pattern break for file in kwfiles: if verbose: - print "zapping %s with %s" % (line,pattern) + print("zapping %s with %s" % (line,pattern)) # File is being deleted, so not open in p4. Must # disable the read-only bit on windows. if self.isWindows and file not in editedFiles: @@ -1916,7 +1932,7 @@ class P4Submit(Command, P4UserMap): fixed_rcs_keywords = True if fixed_rcs_keywords: - print "Retrying the patch with RCS keywords cleaned up" + print("Retrying the patch with RCS keywords cleaned up") if os.system(tryPatchCmd) == 0: patch_succeeded = True @@ -1984,34 +2000,34 @@ class P4Submit(Command, P4UserMap): # Leave the p4 tree prepared, and the submit template around # and let the user decide what to do next # - print - print "P4 workspace prepared for submission." - print "To submit or revert, go to client workspace" - print " " + self.clientPath - print - print "To submit, use \"p4 submit\" to write a new description," - print "or \"p4 submit -i <%s\" to use the one prepared by" \ - " \"git p4\"." % fileName - print "You can delete the file \"%s\" when finished." % fileName + print() + print("P4 workspace prepared for submission.") + print("To submit or revert, go to client workspace") + print(" " + self.clientPath) + print() + print("To submit, use \"p4 submit\" to write a new description,") + print("or \"p4 submit -i <%s\" to use the one prepared by" \ + " \"git p4\"." % fileName) + print("You can delete the file \"%s\" when finished." % fileName) if self.preserveUser and p4User and not self.p4UserIsMe(p4User): - print "To preserve change ownership by user %s, you must\n" \ + print("To preserve change ownership by user %s, you must\n" \ "do \"p4 change -f <change>\" after submitting and\n" \ - "edit the User field." + "edit the User field.") if pureRenameCopy: - print "After submitting, renamed files must be re-synced." - print "Invoke \"p4 sync -f\" on each of these files:" + print("After submitting, renamed files must be re-synced.") + print("Invoke \"p4 sync -f\" on each of these files:") for f in pureRenameCopy: - print " " + f + print(" " + f) - print - print "To revert the changes, use \"p4 revert ...\", and delete" - print "the submit template file \"%s\"" % fileName + print() + print("To revert the changes, use \"p4 revert ...\", and delete") + print("the submit template file \"%s\"" % fileName) if filesToAdd: - print "Since the commit adds new files, they must be deleted:" + print("Since the commit adds new files, they must be deleted:") for f in filesToAdd: - print " " + f - print + print(" " + f) + print() return True # @@ -2078,17 +2094,17 @@ class P4Submit(Command, P4UserMap): if not m.match(name): if verbose: - print "tag %s does not match regexp %s" % (name, validLabelRegexp) + print("tag %s does not match regexp %s" % (name, validLabelRegexp)) continue # Get the p4 commit this corresponds to logMessage = extractLogMessageFromGitCommit(name) values = extractSettingsGitLog(logMessage) - if not values.has_key('change'): + if 'change' not in values: # a tag pointing to something not sent to p4; ignore if verbose: - print "git tag %s does not give a p4 commit" % name + print("git tag %s does not give a p4 commit" % name) continue else: changelist = values['change'] @@ -2123,10 +2139,10 @@ class P4Submit(Command, P4UserMap): labelTemplate += "\t%s\n" % depot_side if self.dry_run: - print "Would create p4 label %s for tag" % name + print("Would create p4 label %s for tag" % name) elif self.prepare_p4_only: - print "Not creating p4 label %s for tag due to option" \ - " --prepare-p4-only" % name + print("Not creating p4 label %s for tag due to option" \ + " --prepare-p4-only" % name) else: p4_write_pipe(["label", "-i"], labelTemplate) @@ -2135,7 +2151,7 @@ class P4Submit(Command, P4UserMap): ["%s@%s" % (depot_side, changelist) for depot_side in clientSpec.mappings]) if verbose: - print "created p4 label for tag %s" % name + print("created p4 label for tag %s" % name) def run(self, args): if len(args) == 0: @@ -2179,10 +2195,10 @@ class P4Submit(Command, P4UserMap): self.conflict_behavior = val if self.verbose: - print "Origin branch is " + self.origin + print("Origin branch is " + self.origin) if len(self.depotPath) == 0: - print "Internal error: cannot locate perforce depot path from existing branches" + print("Internal error: cannot locate perforce depot path from existing branches") sys.exit(128) self.useClientSpec = False @@ -2203,7 +2219,7 @@ class P4Submit(Command, P4UserMap): if self.clientPath == "": die("Error: Cannot locate perforce checkout of %s in client view" % self.depotPath) - print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath) + print("Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)) self.oldWorkingDirectory = os.getcwd() # ensure the clientPath exists @@ -2214,9 +2230,9 @@ class P4Submit(Command, P4UserMap): chdir(self.clientPath, is_client_path=True) if self.dry_run: - print "Would synchronize p4 checkout in %s" % self.clientPath + print("Would synchronize p4 checkout in %s" % self.clientPath) else: - print "Synchronizing p4 checkout..." + print("Synchronizing p4 checkout...") if new_client_dir: # old one was destroyed, and maybe nobody told p4 p4_sync("...", "-f") @@ -2292,13 +2308,13 @@ class P4Submit(Command, P4UserMap): # continue to try the rest of the patches, or quit. # if self.dry_run: - print "Would apply" + print("Would apply") applied = [] last = len(commits) - 1 for i, commit in enumerate(commits): if self.dry_run: - print " ", read_pipe(["git", "show", "-s", - "--format=format:%h %s", commit]) + print(" ", read_pipe(["git", "show", "-s", + "--format=format:%h %s", commit])) ok = True else: ok = self.applyCommit(commit) @@ -2306,15 +2322,15 @@ class P4Submit(Command, P4UserMap): applied.append(commit) else: if self.prepare_p4_only and i < last: - print "Processing only the first commit due to option" \ - " --prepare-p4-only" + print("Processing only the first commit due to option" \ + " --prepare-p4-only") break if i < last: quit = False while True: # prompt for what to do, or use the option/variable if self.conflict_behavior == "ask": - print "What do you want to do?" + print("What do you want to do?") response = raw_input("[s]kip this commit but apply" " the rest, or [q]uit? ") if not response: @@ -2328,10 +2344,10 @@ class P4Submit(Command, P4UserMap): self.conflict_behavior) if response[0] == "s": - print "Skipping this commit, but applying the rest" + print("Skipping this commit, but applying the rest") break if response[0] == "q": - print "Quitting" + print("Quitting") quit = True break if quit: @@ -2344,7 +2360,7 @@ class P4Submit(Command, P4UserMap): elif self.prepare_p4_only: pass elif len(commits) == len(applied): - print ("All commits {0}!".format(shelved_applied)) + print("All commits {0}!".format(shelved_applied)) sync = P4Sync() if self.branch: @@ -2360,17 +2376,17 @@ class P4Submit(Command, P4UserMap): else: if len(applied) == 0: - print ("No commits {0}.".format(shelved_applied)) + print("No commits {0}.".format(shelved_applied)) else: - print ("{0} only the commits marked with '*':".format(shelved_applied.capitalize())) + print("{0} only the commits marked with '*':".format(shelved_applied.capitalize())) for c in commits: if c in applied: star = "*" else: star = " " - print star, read_pipe(["git", "show", "-s", - "--format=format:%h %s", c]) - print "You will have to do 'git p4 sync' and rebase." + print(star, read_pipe(["git", "show", "-s", + "--format=format:%h %s", c])) + print("You will have to do 'git p4 sync' and rebase.") if gitConfigBool("git-p4.exportLabels"): self.exportLabels = True @@ -2580,7 +2596,7 @@ class P4Sync(Command, P4UserMap): self.gitStream.write("progress checkpoint\n\n") out = self.gitOutput.readline() if self.verbose: - print "checkpoint finished: " + out + print("checkpoint finished: " + out) def cmp_shelved(self, path, filerev, revision): """ Determine if a path at revision #filerev is the same as the file @@ -2600,7 +2616,7 @@ class P4Sync(Command, P4UserMap): for path in self.cloneExclude] files = [] fnum = 0 - while commit.has_key("depotFile%s" % fnum): + while "depotFile%s" % fnum in commit: path = commit["depotFile%s" % fnum] if [p for p in self.cloneExclude @@ -2638,7 +2654,7 @@ class P4Sync(Command, P4UserMap): def extractJobsFromCommit(self, commit): jobs = [] jnum = 0 - while commit.has_key("job%s" % jnum): + while "job%s" % jnum in commit: job = commit["job%s" % jnum] jobs.append(job) jnum = jnum + 1 @@ -2686,7 +2702,7 @@ class P4Sync(Command, P4UserMap): branches = {} fnum = 0 - while commit.has_key("depotFile%s" % fnum): + while "depotFile%s" % fnum in commit: path = commit["depotFile%s" % fnum] found = [p for p in self.depotPaths if p4PathStartsWith(path, p)] @@ -2735,7 +2751,7 @@ class P4Sync(Command, P4UserMap): encoding = gitConfig('git-p4.pathEncoding') path = path.decode(encoding, 'replace').encode('utf8', 'replace') if self.verbose: - print 'Path with non-ASCII characters detected. Used %s to encode: %s ' % (encoding, path) + print('Path with non-ASCII characters detected. Used %s to encode: %s ' % (encoding, path)) return path # output one file from the P4 stream @@ -2764,7 +2780,7 @@ class P4Sync(Command, P4UserMap): # to nothing. This causes p4 errors when checking out such # a change, and errors here too. Work around it by ignoring # the bad symlink; hopefully a future change fixes it. - print "\nIgnoring empty symlink in %s" % file['depotFile'] + print("\nIgnoring empty symlink in %s" % file['depotFile']) return elif data[-1] == '\n': contents = [data[:-1]] @@ -2804,7 +2820,7 @@ class P4Sync(Command, P4UserMap): # Ideally, someday, this script can learn how to generate # appledouble files directly and import those to git, but # non-mac machines can never find a use for apple filetype. - print "\nIgnoring apple filetype file %s" % file['depotFile'] + print("\nIgnoring apple filetype file %s" % file['depotFile']) return # Note that we do not try to de-mangle keywords on utf16 files, @@ -2866,7 +2882,7 @@ class P4Sync(Command, P4UserMap): else: die("Error from p4 print: %s" % err) - if marshalled.has_key('depotFile') and self.stream_have_file_info: + if 'depotFile' in marshalled and self.stream_have_file_info: # start of a new file - output the old one first self.streamOneP4File(self.stream_file, self.stream_contents) self.stream_file = {} @@ -2938,7 +2954,7 @@ class P4Sync(Command, P4UserMap): cb=streamP4FilesCbSelf) # do the last chunk - if self.stream_file.has_key('depotFile'): + if 'depotFile' in self.stream_file: self.streamOneP4File(self.stream_file, self.stream_contents) def make_email(self, userid): @@ -2953,11 +2969,11 @@ class P4Sync(Command, P4UserMap): """ if verbose: - print "writing tag %s for commit %s" % (labelName, commit) + print("writing tag %s for commit %s" % (labelName, commit)) gitStream.write("tag %s\n" % labelName) gitStream.write("from %s\n" % commit) - if labelDetails.has_key('Owner'): + if 'Owner' in labelDetails: owner = labelDetails["Owner"] else: owner = None @@ -2972,8 +2988,8 @@ class P4Sync(Command, P4UserMap): gitStream.write("tagger %s\n" % tagger) - print "labelDetails=",labelDetails - if labelDetails.has_key('Description'): + print("labelDetails=",labelDetails) + if 'Description' in labelDetails: description = labelDetails['Description'] else: description = 'Label from git p4' @@ -3044,7 +3060,7 @@ class P4Sync(Command, P4UserMap): if len(parent) > 0: if self.verbose: - print "parent %s" % parent + print("parent %s" % parent) self.gitStream.write("from %s\n" % parent) self.streamP4Files(files) @@ -3052,12 +3068,12 @@ class P4Sync(Command, P4UserMap): change = int(details["change"]) - if self.labels.has_key(change): + if change in self.labels: label = self.labels[change] labelDetails = label[0] labelRevisions = label[1] if self.verbose: - print "Change %s is labelled %s" % (change, labelDetails) + print("Change %s is labelled %s" % (change, labelDetails)) files = p4CmdList(["files"] + ["%s...@%s" % (p, change) for p in self.branchPrefixes]) @@ -3075,12 +3091,12 @@ class P4Sync(Command, P4UserMap): else: if not self.silent: - print ("Tag %s does not match with change %s: files do not match." + print("Tag %s does not match with change %s: files do not match." % (labelDetails["label"], change)) else: if not self.silent: - print ("Tag %s does not match with change %s: file count is different." + print("Tag %s does not match with change %s: file count is different." % (labelDetails["label"], change)) # Build a dictionary of changelists and labels, for "detect-labels" option. @@ -3089,14 +3105,14 @@ class P4Sync(Command, P4UserMap): l = p4CmdList(["labels"] + ["%s..." % p for p in self.depotPaths]) if len(l) > 0 and not self.silent: - print "Finding files belonging to labels in %s" % `self.depotPaths` + print("Finding files belonging to labels in %s" % self.depotPaths) for output in l: label = output["label"] revisions = {} newestChange = 0 if self.verbose: - print "Querying files for label %s" % label + print("Querying files for label %s" % label) for file in p4CmdList(["files"] + ["%s...@%s" % (p, label) for p in self.depotPaths]): @@ -3108,7 +3124,7 @@ class P4Sync(Command, P4UserMap): self.labels[newestChange] = [output, revisions] if self.verbose: - print "Label changes: %s" % self.labels.keys() + print("Label changes: %s" % self.labels.keys()) # Import p4 labels as git tags. A direct mapping does not # exist, so assume that if all the files are at the same revision @@ -3116,7 +3132,7 @@ class P4Sync(Command, P4UserMap): # just ignore. def importP4Labels(self, stream, p4Labels): if verbose: - print "import p4 labels: " + ' '.join(p4Labels) + print("import p4 labels: " + ' '.join(p4Labels)) ignoredP4Labels = gitConfigList("git-p4.ignoredP4Labels") validLabelRegexp = gitConfig("git-p4.labelImportRegexp") @@ -3129,7 +3145,7 @@ class P4Sync(Command, P4UserMap): if not m.match(name): if verbose: - print "label %s does not match regexp %s" % (name,validLabelRegexp) + print("label %s does not match regexp %s" % (name,validLabelRegexp)) continue if name in ignoredP4Labels: @@ -3141,7 +3157,7 @@ class P4Sync(Command, P4UserMap): change = p4Cmd(["changes", "-m", "1"] + ["%s...@%s" % (p, name) for p in self.depotPaths]) - if change.has_key('change'): + if 'change' in change: # find the corresponding git commit; take the oldest commit changelist = int(change['change']) if changelist in self.committedChanges: @@ -3151,7 +3167,7 @@ class P4Sync(Command, P4UserMap): gitCommit = read_pipe(["git", "rev-list", "--max-count=1", "--reverse", ":/\[git-p4:.*change = %d\]" % changelist], ignore_error=True) if len(gitCommit) == 0: - print "importing label %s: could not find git commit for changelist %d" % (name, changelist) + print("importing label %s: could not find git commit for changelist %d" % (name, changelist)) else: commitFound = True gitCommit = gitCommit.strip() @@ -3161,16 +3177,16 @@ class P4Sync(Command, P4UserMap): try: tmwhen = time.strptime(labelDetails['Update'], "%Y/%m/%d %H:%M:%S") except ValueError: - print "Could not convert label time %s" % labelDetails['Update'] + print("Could not convert label time %s" % labelDetails['Update']) tmwhen = 1 when = int(time.mktime(tmwhen)) self.streamTag(stream, name, labelDetails, gitCommit, when) if verbose: - print "p4 label %s mapped to git commit %s" % (name, gitCommit) + print("p4 label %s mapped to git commit %s" % (name, gitCommit)) else: if verbose: - print "Label %s has no changelists - possibly deleted?" % name + print("Label %s has no changelists - possibly deleted?" % name) if not commitFound: # We can't import this label; don't try again as it will get very @@ -3200,7 +3216,7 @@ class P4Sync(Command, P4UserMap): for info in p4CmdList(command): details = p4Cmd(["branch", "-o", info["branch"]]) viewIdx = 0 - while details.has_key("View%s" % viewIdx): + while "View%s" % viewIdx in details: paths = details["View%s" % viewIdx].split(" ") viewIdx = viewIdx + 1 # require standard //depot/foo/... //depot/bar/... mapping @@ -3215,8 +3231,8 @@ class P4Sync(Command, P4UserMap): if destination in self.knownBranches: if not self.silent: - print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination) - print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination) + print("p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)) + print("but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)) continue self.knownBranches[destination] = source @@ -3266,7 +3282,7 @@ class P4Sync(Command, P4UserMap): d["options"] = ' '.join(sorted(option_keys.keys())) def readOptions(self, d): - self.keepRepoPath = (d.has_key('options') + self.keepRepoPath = ('options' in d and ('keepRepoPath' in d['options'])) def gitRefForBranch(self, branch): @@ -3280,28 +3296,28 @@ class P4Sync(Command, P4UserMap): def gitCommitByP4Change(self, ref, change): if self.verbose: - print "looking in ref " + ref + " for change %s using bisect..." % change + print("looking in ref " + ref + " for change %s using bisect..." % change) earliestCommit = "" latestCommit = parseRevision(ref) while True: if self.verbose: - print "trying: earliest %s latest %s" % (earliestCommit, latestCommit) + print("trying: earliest %s latest %s" % (earliestCommit, latestCommit)) next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip() if len(next) == 0: if self.verbose: - print "argh" + print("argh") return "" log = extractLogMessageFromGitCommit(next) settings = extractSettingsGitLog(log) currentChange = int(settings['change']) if self.verbose: - print "current change %s" % currentChange + print("current change %s" % currentChange) if currentChange == change: if self.verbose: - print "found %s" % next + print("found %s" % next) return next if currentChange < change: @@ -3347,7 +3363,7 @@ class P4Sync(Command, P4UserMap): if len(read_pipe(["git", "diff-tree", blob, target])) == 0: parentFound = True if self.verbose: - print "Found parent of %s in commit %s" % (branch, blob) + print("Found parent of %s in commit %s" % (branch, blob)) break if parentFound: return blob @@ -3378,7 +3394,7 @@ class P4Sync(Command, P4UserMap): filesForCommit = branches[branch] if self.verbose: - print "branch is %s" % branch + print("branch is %s" % branch) self.updatedBranches.add(branch) @@ -3399,13 +3415,13 @@ class P4Sync(Command, P4UserMap): print("\n Resuming with change %s" % change); if self.verbose: - print "parent determined through known branches: %s" % parent + print("parent determined through known branches: %s" % parent) branch = self.gitRefForBranch(branch) parent = self.gitRefForBranch(parent) if self.verbose: - print "looking for initial parent for %s; current parent is %s" % (branch, parent) + print("looking for initial parent for %s; current parent is %s" % (branch, parent)) if len(parent) == 0 and branch in self.initialParents: parent = self.initialParents[branch] @@ -3415,7 +3431,7 @@ class P4Sync(Command, P4UserMap): if len(parent) > 0: tempBranch = "%s/%d" % (self.tempBranchLocation, change) if self.verbose: - print "Creating temporary branch: " + tempBranch + print("Creating temporary branch: " + tempBranch) self.commit(description, filesForCommit, tempBranch) self.tempBranches.append(tempBranch) self.checkpoint() @@ -3424,7 +3440,7 @@ class P4Sync(Command, P4UserMap): self.commit(description, filesForCommit, branch, blob) else: if self.verbose: - print "Parent of %s not found. Committing into head of %s" % (branch, parent) + print("Parent of %s not found. Committing into head of %s" % (branch, parent)) self.commit(description, filesForCommit, branch, parent) else: files = self.extractFilesFromCommit(description, shelved, change, origin_revision) @@ -3433,7 +3449,7 @@ class P4Sync(Command, P4UserMap): # only needed once, to connect to the previous commit self.initialParent = "" except IOError: - print self.gitError.read() + print(self.gitError.read()) sys.exit(1) def sync_origin_only(self): @@ -3441,11 +3457,11 @@ class P4Sync(Command, P4UserMap): self.hasOrigin = originP4BranchesExist() if self.hasOrigin: if not self.silent: - print 'Syncing with origin first, using "git fetch origin"' + print('Syncing with origin first, using "git fetch origin"') system("git fetch origin") def importHeadRevision(self, revision): - print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch) + print("Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)) details = {} details["user"] = "git perforce import user" @@ -3497,8 +3513,8 @@ class P4Sync(Command, P4UserMap): try: self.commit(details, self.extractFilesFromCommit(details), self.branch) except IOError: - print "IO error with git fast-import. Is your git version recent enough?" - print self.gitError.read() + print("IO error with git fast-import. Is your git version recent enough?") + print(self.gitError.read()) def openStreams(self): self.importProcess = subprocess.Popen(["git", "fast-import"], @@ -3560,14 +3576,14 @@ class P4Sync(Command, P4UserMap): if len(self.p4BranchesInGit) > 1: if not self.silent: - print "Importing from/into multiple branches" + print("Importing from/into multiple branches") self.detectBranches = True for branch in branches.keys(): self.initialParents[self.refPrefix + branch] = \ branches[branch] if self.verbose: - print "branches: %s" % self.p4BranchesInGit + print("branches: %s" % self.p4BranchesInGit) p4Change = 0 for branch in self.p4BranchesInGit: @@ -3576,8 +3592,8 @@ class P4Sync(Command, P4UserMap): settings = extractSettingsGitLog(logMsg) self.readOptions(settings) - if (settings.has_key('depot-paths') - and settings.has_key ('change')): + if ('depot-paths' in settings + and 'change' in settings): change = int(settings['change']) + 1 p4Change = max(p4Change, change) @@ -3590,7 +3606,7 @@ class P4Sync(Command, P4UserMap): prev_list = prev.split("/") cur_list = cur.split("/") for i in range(0, min(len(cur_list), len(prev_list))): - if cur_list[i] <> prev_list[i]: + if cur_list[i] != prev_list[i]: i = i - 1 break @@ -3602,7 +3618,7 @@ class P4Sync(Command, P4UserMap): self.depotPaths = sorted(self.previousDepotPaths) self.changeRange = "@%s,#head" % p4Change if not self.silent and not self.detectBranches: - print "Performing incremental import into %s git branch" % self.branch + print("Performing incremental import into %s git branch" % self.branch) # accept multiple ref name abbreviations: # refs/foo/bar/branch -> use it exactly @@ -3619,10 +3635,10 @@ class P4Sync(Command, P4UserMap): if len(args) == 0 and self.depotPaths: if not self.silent: - print "Depot paths: %s" % ' '.join(self.depotPaths) + print("Depot paths: %s" % ' '.join(self.depotPaths)) else: if self.depotPaths and self.depotPaths != args: - print ("previous import used depot path %s and now %s was specified. " + print("previous import used depot path %s and now %s was specified. " "This doesn't work!" % (' '.join (self.depotPaths), ' '.join (args))) sys.exit(1) @@ -3689,8 +3705,8 @@ class P4Sync(Command, P4UserMap): else: self.getBranchMapping() if self.verbose: - print "p4-git branches: %s" % self.p4BranchesInGit - print "initial parents: %s" % self.initialParents + print("p4-git branches: %s" % self.p4BranchesInGit) + print("initial parents: %s" % self.initialParents) for b in self.p4BranchesInGit: if b != "master": @@ -3734,8 +3750,8 @@ class P4Sync(Command, P4UserMap): self.branch) if self.verbose: - print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths), - self.changeRange) + print("Getting p4 changes for %s...%s" % (', '.join(self.depotPaths), + self.changeRange)) changes = p4ChangesForPaths(self.depotPaths, self.changeRange, self.changes_block_size) if len(self.maxChanges) > 0: @@ -3743,10 +3759,10 @@ class P4Sync(Command, P4UserMap): if len(changes) == 0: if not self.silent: - print "No changes to import!" + print("No changes to import!") else: if not self.silent and not self.detectBranches: - print "Import destination: %s" % self.branch + print("Import destination: %s" % self.branch) self.updatedBranches = set() @@ -3761,7 +3777,7 @@ class P4Sync(Command, P4UserMap): self.importChanges(changes) if not self.silent: - print "" + print("") if len(self.updatedBranches) > 0: sys.stdout.write("Updated branches: ") for b in self.updatedBranches: @@ -3825,7 +3841,7 @@ class P4Rebase(Command): # the branchpoint may be p4/foo~3, so strip off the parent upstream = re.sub("~[0-9]+$", "", upstream) - print "Rebasing the current branch onto %s" % upstream + print("Rebasing the current branch onto %s" % upstream) oldHead = read_pipe("git rev-parse HEAD").strip() system("git rebase %s" % upstream) system("git diff-tree --stat --summary -M %s HEAD --" % oldHead) @@ -3879,7 +3895,7 @@ class P4Clone(P4Sync): if not self.cloneDestination: self.cloneDestination = self.defaultDestination(args) - print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination) + print("Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)) if not os.path.exists(self.cloneDestination): os.makedirs(self.cloneDestination) @@ -3901,8 +3917,8 @@ class P4Clone(P4Sync): if not self.cloneBare: system([ "git", "checkout", "-f" ]) else: - print 'Not checking out any branch, use ' \ - '"git checkout -q -b master <branch>"' + print('Not checking out any branch, use ' \ + '"git checkout -q -b master <branch>"') # auto-set this variable if invoked with --use-client-spec if self.useClientSpec_from_options: @@ -3950,7 +3966,7 @@ class P4Unshelve(Command): for parent in (range(65535)): log = extractLogMessageFromGitCommit("{0}^{1}".format(starting_point, parent)) settings = extractSettingsGitLog(log) - if settings.has_key('change'): + if 'change' in settings: return settings sys.exit("could not find git-p4 commits in {0}".format(self.origin)) @@ -4018,7 +4034,7 @@ class P4Branches(Command): log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch) settings = extractSettingsGitLog(log) - print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"]) + print("%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])) return True class HelpFormatter(optparse.IndentedHelpFormatter): @@ -4032,12 +4048,12 @@ class HelpFormatter(optparse.IndentedHelpFormatter): return "" def printUsage(commands): - print "usage: %s <command> [options]" % sys.argv[0] - print "" - print "valid commands: %s" % ", ".join(commands) - print "" - print "Try %s <command> --help for command specific help." % sys.argv[0] - print "" + print("usage: %s <command> [options]" % sys.argv[0]) + print("") + print("valid commands: %s" % ", ".join(commands)) + print("") + print("Try %s <command> --help for command specific help." % sys.argv[0]) + print("") commands = { "debug" : P4Debug, @@ -4062,8 +4078,8 @@ def main(): klass = commands[cmdName] cmd = klass() except KeyError: - print "unknown command %s" % cmdName - print "" + print("unknown command %s" % cmdName) + print("") printUsage(commands.keys()) sys.exit(2) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 06a7b79307..299ded2137 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -13,85 +13,6 @@ # file and written to the tail of $done. todo="$state_dir"/git-rebase-todo -# The rebase command lines that have already been processed. A line -# is moved here when it is first handled, before any associated user -# actions. -done="$state_dir"/done - -# The commit message that is planned to be used for any changes that -# need to be committed following a user interaction. -msg="$state_dir"/message - -# The file into which is accumulated the suggested commit message for -# squash/fixup commands. When the first of a series of squash/fixups -# is seen, the file is created and the commit message from the -# previous commit and from the first squash/fixup commit are written -# to it. The commit message for each subsequent squash/fixup commit -# is appended to the file as it is processed. -# -# The first line of the file is of the form -# # This is a combination of $count commits. -# where $count is the number of commits whose messages have been -# written to the file so far (including the initial "pick" commit). -# Each time that a commit message is processed, this line is read and -# updated. It is deleted just before the combined commit is made. -squash_msg="$state_dir"/message-squash - -# If the current series of squash/fixups has not yet included a squash -# command, then this file exists and holds the commit message of the -# original "pick" commit. (If the series ends without a "squash" -# command, then this can be used as the commit message of the combined -# commit without opening the editor.) -fixup_msg="$state_dir"/message-fixup - -# $rewritten is the name of a directory containing files for each -# commit that is reachable by at least one merge base of $head and -# $upstream. They are not necessarily rewritten, but their children -# might be. This ensures that commits on merged, but otherwise -# unrelated side branches are left alone. (Think "X" in the man page's -# example.) -rewritten="$state_dir"/rewritten - -dropped="$state_dir"/dropped - -end="$state_dir"/end -msgnum="$state_dir"/msgnum - -# A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and -# GIT_AUTHOR_DATE that will be used for the commit that is currently -# being rebased. -author_script="$state_dir"/author-script - -# When an "edit" rebase command is being processed, the SHA1 of the -# commit to be edited is recorded in this file. When "git rebase -# --continue" is executed, if there are any staged changes then they -# will be amended to the HEAD commit, but only provided the HEAD -# commit is still the commit to be edited. When any other rebase -# command is processed, this file is deleted. -amend="$state_dir"/amend - -# For the post-rewrite hook, we make a list of rewritten commits and -# their new sha1s. The rewritten-pending list keeps the sha1s of -# commits that have been processed, but not committed yet, -# e.g. because they are waiting for a 'squash' command. -rewritten_list="$state_dir"/rewritten-list -rewritten_pending="$state_dir"/rewritten-pending - -# Work around Git for Windows' Bash whose "read" does not strip CRLF -# and leaves CR at the end instead. -cr=$(printf "\015") - -empty_tree=$(git hash-object -t tree /dev/null) - -strategy_args=${strategy:+--strategy=$strategy} -test -n "$strategy_opts" && -eval ' - for strategy_opt in '"$strategy_opts"' - do - strategy_args="$strategy_args -X$(git rev-parse --sq-quote "${strategy_opt#--}")" - done -' - GIT_CHERRY_PICK_HELP="$resolvemsg" export GIT_CHERRY_PICK_HELP @@ -107,15 +28,6 @@ case "$comment_char" in ;; esac -warn () { - printf '%s\n' "$*" >&2 -} - -# Output the commit message for the specified commit. -commit_message () { - git cat-file commit "$1" | sed "1,/^$/d" -} - orig_reflog_action="$GIT_REFLOG_ACTION" comment_for_reflog () { @@ -127,33 +39,6 @@ comment_for_reflog () { esac } -last_count= -mark_action_done () { - sed -e 1q < "$todo" >> "$done" - sed -e 1d < "$todo" >> "$todo".new - mv -f "$todo".new "$todo" - new_count=$(( $(git stripspace --strip-comments <"$done" | wc -l) )) - echo $new_count >"$msgnum" - total=$(($new_count + $(git stripspace --strip-comments <"$todo" | wc -l))) - echo $total >"$end" - if test "$last_count" != "$new_count" - then - last_count=$new_count - eval_gettext "Rebasing (\$new_count/\$total)"; printf "\r" - test -z "$verbose" || echo - fi -} - -# Put the last action marked done at the beginning of the todo list -# again. If there has not been an action marked done yet, leave the list of -# items on the todo list unchanged. -reschedule_last_action () { - tail -n 1 "$done" | cat - "$todo" >"$todo".new - sed -e \$d <"$done" >"$done".new - mv -f "$todo".new "$todo" - mv -f "$done".new "$done" -} - append_todo_help () { gettext " Commands: @@ -186,50 +71,6 @@ If you remove a line here THAT COMMIT WILL BE LOST. fi } -make_patch () { - sha1_and_parents="$(git rev-list --parents -1 "$1")" - case "$sha1_and_parents" in - ?*' '?*' '?*) - git diff --cc $sha1_and_parents - ;; - ?*' '?*) - git diff-tree -p "$1^!" - ;; - *) - echo "Root commit" - ;; - esac > "$state_dir"/patch - test -f "$msg" || - commit_message "$1" > "$msg" - test -f "$author_script" || - get_author_ident_from_commit "$1" > "$author_script" -} - -die_with_patch () { - echo "$1" > "$state_dir"/stopped-sha - git update-ref REBASE_HEAD "$1" - make_patch "$1" - die "$2" -} - -exit_with_patch () { - echo "$1" > "$state_dir"/stopped-sha - git update-ref REBASE_HEAD "$1" - make_patch $1 - git rev-parse --verify HEAD > "$amend" - gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} - warn "$(eval_gettext "\ -You can amend the commit now, with - - git commit --amend \$gpg_sign_opt_quoted - -Once you are satisfied with your changes, run - - git rebase --continue")" - warn - exit $2 -} - die_abort () { apply_autostash rm -rf "$state_dir" @@ -240,30 +81,6 @@ has_action () { test -n "$(git stripspace --strip-comments <"$1")" } -is_empty_commit() { - tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null) || { - sha1=$1 - die "$(eval_gettext "\$sha1: not a commit that can be picked")" - } - ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null) || - ptree=$empty_tree - test "$tree" = "$ptree" -} - -is_merge_commit() -{ - git rev-parse --verify --quiet "$1"^2 >/dev/null 2>&1 -} - -# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and -# GIT_AUTHOR_DATE exported from the current environment. -do_with_author () { - ( - export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE - "$@" - ) -} - git_sequence_editor () { if test -z "$GIT_SEQUENCE_EDITOR" then @@ -277,455 +94,6 @@ git_sequence_editor () { eval "$GIT_SEQUENCE_EDITOR" '"$@"' } -pick_one () { - ff=--ff - - case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac - case "$force_rebase" in '') ;; ?*) ff= ;; esac - output git rev-parse --verify $sha1 || die "$(eval_gettext "Invalid commit name: \$sha1")" - - if is_empty_commit "$sha1" - then - empty_args="--allow-empty" - fi - - test -d "$rewritten" && - pick_one_preserving_merges "$@" && return - output eval git cherry-pick $allow_rerere_autoupdate $allow_empty_message \ - ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \ - $signoff "$strategy_args" $empty_args $ff "$@" - - # If cherry-pick dies it leaves the to-be-picked commit unrecorded. Reschedule - # previous task so this commit is not lost. - ret=$? - case "$ret" in [01]) ;; *) reschedule_last_action ;; esac - return $ret -} - -pick_one_preserving_merges () { - fast_forward=t - case "$1" in - -n) - fast_forward=f - sha1=$2 - ;; - *) - sha1=$1 - ;; - esac - sha1=$(git rev-parse $sha1) - - if test -f "$state_dir"/current-commit && test "$fast_forward" = t - then - while read current_commit - do - git rev-parse HEAD > "$rewritten"/$current_commit - done <"$state_dir"/current-commit - rm "$state_dir"/current-commit || - die "$(gettext "Cannot write current commit's replacement sha1")" - fi - - echo $sha1 >> "$state_dir"/current-commit - - # rewrite parents; if none were rewritten, we can fast-forward. - new_parents= - pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)" - if test "$pend" = " " - then - pend=" root" - fi - while [ "$pend" != "" ] - do - p=$(expr "$pend" : ' \([^ ]*\)') - pend="${pend# $p}" - - if test -f "$rewritten"/$p - then - new_p=$(cat "$rewritten"/$p) - - # If the todo reordered commits, and our parent is marked for - # rewriting, but hasn't been gotten to yet, assume the user meant to - # drop it on top of the current HEAD - if test -z "$new_p" - then - new_p=$(git rev-parse HEAD) - fi - - test $p != $new_p && fast_forward=f - case "$new_parents" in - *$new_p*) - ;; # do nothing; that parent is already there - *) - new_parents="$new_parents $new_p" - ;; - esac - else - if test -f "$dropped"/$p - then - fast_forward=f - replacement="$(cat "$dropped"/$p)" - test -z "$replacement" && replacement=root - pend=" $replacement$pend" - else - new_parents="$new_parents $p" - fi - fi - done - case $fast_forward in - t) - output warn "$(eval_gettext "Fast-forward to \$sha1")" - output git reset --hard $sha1 || - die "$(eval_gettext "Cannot fast-forward to \$sha1")" - ;; - f) - first_parent=$(expr "$new_parents" : ' \([^ ]*\)') - - if [ "$1" != "-n" ] - then - # detach HEAD to current parent - output git checkout $first_parent 2> /dev/null || - die "$(eval_gettext "Cannot move HEAD to \$first_parent")" - fi - - case "$new_parents" in - ' '*' '*) - test "a$1" = a-n && die "$(eval_gettext "Refusing to squash a merge: \$sha1")" - - # redo merge - author_script_content=$(get_author_ident_from_commit $sha1) - eval "$author_script_content" - msg_content="$(commit_message $sha1)" - # No point in merging the first parent, that's HEAD - new_parents=${new_parents# $first_parent} - merge_args="--no-log --no-ff" - if ! do_with_author output eval \ - git merge ${gpg_sign_opt:+$(git rev-parse \ - --sq-quote "$gpg_sign_opt")} \ - $allow_rerere_autoupdate "$merge_args" \ - "$strategy_args" \ - -m "$(git rev-parse --sq-quote "$msg_content")" \ - "$new_parents" - then - printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG - die_with_patch $sha1 "$(eval_gettext "Error redoing merge \$sha1")" - fi - echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list" - ;; - *) - output eval git cherry-pick $allow_rerere_autoupdate \ - $allow_empty_message \ - ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \ - "$strategy_args" "$@" || - die_with_patch $sha1 "$(eval_gettext "Could not pick \$sha1")" - ;; - esac - ;; - esac -} - -this_nth_commit_message () { - n=$1 - eval_gettext "This is the commit message #\${n}:" -} - -skip_nth_commit_message () { - n=$1 - eval_gettext "The commit message #\${n} will be skipped:" -} - -update_squash_messages () { - if test -f "$squash_msg"; then - mv "$squash_msg" "$squash_msg".bak || exit - count=$(($(sed -n \ - -e "1s/^$comment_char[^0-9]*\([0-9][0-9]*\).*/\1/p" \ - -e "q" < "$squash_msg".bak)+1)) - { - printf '%s\n' "$comment_char $(eval_ngettext \ - "This is a combination of \$count commit." \ - "This is a combination of \$count commits." \ - $count)" - sed -e 1d -e '2,/^./{ - /^$/d - }' <"$squash_msg".bak - } >"$squash_msg" - else - commit_message HEAD >"$fixup_msg" || - die "$(eval_gettext "Cannot write \$fixup_msg")" - count=2 - { - printf '%s\n' "$comment_char $(gettext "This is a combination of 2 commits.")" - printf '%s\n' "$comment_char $(gettext "This is the 1st commit message:")" - echo - cat "$fixup_msg" - } >"$squash_msg" - fi - case $1 in - squash) - rm -f "$fixup_msg" - echo - printf '%s\n' "$comment_char $(this_nth_commit_message $count)" - echo - commit_message $2 - ;; - fixup) - echo - printf '%s\n' "$comment_char $(skip_nth_commit_message $count)" - echo - # Change the space after the comment character to TAB: - commit_message $2 | git stripspace --comment-lines | sed -e 's/ / /' - ;; - esac >>"$squash_msg" -} - -peek_next_command () { - git stripspace --strip-comments <"$todo" | sed -n -e 's/ .*//p' -e q -} - -# A squash/fixup has failed. Prepare the long version of the squash -# commit message, then die_with_patch. This code path requires the -# user to edit the combined commit message for all commits that have -# been squashed/fixedup so far. So also erase the old squash -# messages, effectively causing the combined commit to be used as the -# new basis for any further squash/fixups. Args: sha1 rest -die_failed_squash() { - sha1=$1 - rest=$2 - mv "$squash_msg" "$msg" || exit - rm -f "$fixup_msg" - cp "$msg" "$GIT_DIR"/MERGE_MSG || exit - warn - warn "$(eval_gettext "Could not apply \$sha1... \$rest")" - die_with_patch $sha1 "" -} - -flush_rewritten_pending() { - test -s "$rewritten_pending" || return - newsha1="$(git rev-parse HEAD^0)" - sed "s/$/ $newsha1/" < "$rewritten_pending" >> "$rewritten_list" - rm -f "$rewritten_pending" -} - -record_in_rewritten() { - oldsha1="$(git rev-parse $1)" - echo "$oldsha1" >> "$rewritten_pending" - - case "$(peek_next_command)" in - squash|s|fixup|f) - ;; - *) - flush_rewritten_pending - ;; - esac -} - -do_pick () { - sha1=$1 - rest=$2 - if test "$(git rev-parse HEAD)" = "$squash_onto" - then - # Set the correct commit message and author info on the - # sentinel root before cherry-picking the original changes - # without committing (-n). Finally, update the sentinel again - # to include these changes. If the cherry-pick results in a - # conflict, this means our behaviour is similar to a standard - # failed cherry-pick during rebase, with a dirty index to - # resolve before manually running git commit --amend then git - # rebase --continue. - git commit --allow-empty --allow-empty-message --amend \ - --no-post-rewrite -n -q -C $sha1 $signoff && - pick_one -n $sha1 && - git commit --allow-empty --allow-empty-message \ - --amend --no-post-rewrite -n -q -C $sha1 $signoff \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_with_patch $sha1 "$(eval_gettext "Could not apply \$sha1... \$rest")" - else - pick_one $sha1 || - die_with_patch $sha1 "$(eval_gettext "Could not apply \$sha1... \$rest")" - fi -} - -do_next () { - rm -f "$msg" "$author_script" "$amend" "$state_dir"/stopped-sha || exit - read -r command sha1 rest < "$todo" - case "$command" in - "$comment_char"*|''|noop|drop|d) - mark_action_done - ;; - "$cr") - # Work around CR left by "read" (e.g. with Git for Windows' Bash). - mark_action_done - ;; - pick|p) - comment_for_reflog pick - - mark_action_done - do_pick $sha1 "$rest" - record_in_rewritten $sha1 - ;; - reword|r) - comment_for_reflog reword - - mark_action_done - do_pick $sha1 "$rest" - git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} \ - $allow_empty_message || { - warn "$(eval_gettext "\ -Could not amend commit after successfully picking \$sha1... \$rest -This is most likely due to an empty commit message, or the pre-commit hook -failed. If the pre-commit hook failed, you may need to resolve the issue before -you are able to reword the commit.")" - exit_with_patch $sha1 1 - } - record_in_rewritten $sha1 - ;; - edit|e) - comment_for_reflog edit - - mark_action_done - do_pick $sha1 "$rest" - sha1_abbrev=$(git rev-parse --short $sha1) - warn "$(eval_gettext "Stopped at \$sha1_abbrev... \$rest")" - exit_with_patch $sha1 0 - ;; - squash|s|fixup|f) - case "$command" in - squash|s) - squash_style=squash - ;; - fixup|f) - squash_style=fixup - ;; - esac - comment_for_reflog $squash_style - - test -f "$done" && has_action "$done" || - die "$(eval_gettext "Cannot '\$squash_style' without a previous commit")" - - mark_action_done - update_squash_messages $squash_style $sha1 - author_script_content=$(get_author_ident_from_commit HEAD) - echo "$author_script_content" > "$author_script" - eval "$author_script_content" - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "$rest" - fi - case "$(peek_next_command)" in - squash|s|fixup|f) - # This is an intermediate commit; its message will only be - # used in case of trouble. So use the long version: - do_with_author output git commit --amend --no-verify -F "$squash_msg" \ - ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message || - die_failed_squash $sha1 "$rest" - ;; - *) - # This is the final command of this squash/fixup group - if test -f "$fixup_msg" - then - do_with_author git commit --amend --no-verify -F "$fixup_msg" \ - ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message || - die_failed_squash $sha1 "$rest" - else - cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit - rm -f "$GIT_DIR"/MERGE_MSG - do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ - ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message || - die_failed_squash $sha1 "$rest" - fi - rm -f "$squash_msg" "$fixup_msg" - ;; - esac - record_in_rewritten $sha1 - ;; - x|"exec") - read -r command rest < "$todo" - mark_action_done - eval_gettextln "Executing: \$rest" - "${SHELL:-@SHELL_PATH@}" -c "$rest" # Actual execution - status=$? - # Run in subshell because require_clean_work_tree can die. - dirty=f - (require_clean_work_tree "rebase" 2>/dev/null) || dirty=t - if test "$status" -ne 0 - then - warn "$(eval_gettext "Execution failed: \$rest")" - test "$dirty" = f || - warn "$(gettext "and made changes to the index and/or the working tree")" - - warn "$(gettext "\ -You can fix the problem, and then run - - git rebase --continue")" - warn - if test $status -eq 127 # command not found - then - status=1 - fi - exit "$status" - elif test "$dirty" = t - then - # TRANSLATORS: after these lines is a command to be issued by the user - warn "$(eval_gettext "\ -Execution succeeded: \$rest -but left changes to the index and/or the working tree -Commit or stash your changes, and then run - - git rebase --continue")" - warn - exit 1 - fi - ;; - *) - warn "$(eval_gettext "Unknown command: \$command \$sha1 \$rest")" - fixtodo="$(gettext "Please fix this using 'git rebase --edit-todo'.")" - if git rev-parse --verify -q "$sha1" >/dev/null - then - die_with_patch $sha1 "$fixtodo" - else - die "$fixtodo" - fi - ;; - esac - test -s "$todo" && return - - comment_for_reflog finish && - newhead=$(git rev-parse HEAD) && - case $head_name in - refs/*) - message="$GIT_REFLOG_ACTION: $head_name onto $onto" && - git update-ref -m "$message" $head_name $newhead $orig_head && - git symbolic-ref \ - -m "$GIT_REFLOG_ACTION: returning to $head_name" \ - HEAD $head_name - ;; - esac && { - test ! -f "$state_dir"/verbose || - git diff-tree --stat $orig_head..HEAD - } && - { - test -s "$rewritten_list" && - git notes copy --for-rewrite=rebase < "$rewritten_list" || - true # we don't care if this copying failed - } && - hook="$(git rev-parse --git-path hooks/post-rewrite)" - if test -x "$hook" && test -s "$rewritten_list"; then - "$hook" rebase < "$rewritten_list" - true # we don't care if this hook failed - fi && - warn "$(eval_gettext "Successfully rebased and updated \$head_name.")" - - return 1 # not failure; just to break the do_rest loop -} - -# can only return 0, when the infinite loop breaks -do_rest () { - while : - do - do_next || break - done -} - expand_todo_ids() { git rebase--helper --expand-ids } @@ -763,77 +131,13 @@ get_missing_commit_check_level () { initiate_action () { case "$1" in continue) - if test ! -d "$rewritten" - then - exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \ - --continue - fi - # do we have anything to commit? - if git diff-index --cached --quiet HEAD -- - then - # Nothing to commit -- skip this commit - - test ! -f "$GIT_DIR"/CHERRY_PICK_HEAD || - rm "$GIT_DIR"/CHERRY_PICK_HEAD || - die "$(gettext "Could not remove CHERRY_PICK_HEAD")" - else - if ! test -f "$author_script" - then - gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} - die "$(eval_gettext "\ -You have staged changes in your working tree. -If these changes are meant to be -squashed into the previous commit, run: - - git commit --amend \$gpg_sign_opt_quoted - -If they are meant to go into a new commit, run: - - git commit \$gpg_sign_opt_quoted - -In both cases, once you're done, continue with: - - git rebase --continue -")" - fi - . "$author_script" || - die "$(gettext "Error trying to find the author identity to amend commit")" - if test -f "$amend" - then - current_head=$(git rev-parse --verify HEAD) - test "$current_head" = $(cat "$amend") || - die "$(gettext "\ -You have uncommitted changes in your working tree. Please commit them -first and then run 'git rebase --continue' again.")" - do_with_author git commit --amend --no-verify -F "$msg" -e \ - ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message || - die "$(gettext "Could not commit staged changes.")" - else - do_with_author git commit --no-verify -F "$msg" -e \ - ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message || - die "$(gettext "Could not commit staged changes.")" - fi - fi - - if test -r "$state_dir"/stopped-sha - then - record_in_rewritten "$(cat "$state_dir"/stopped-sha)" - fi - - require_clean_work_tree "rebase" - do_rest - return 0 + exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \ + --continue ;; skip) git rerere clear - - if test ! -d "$rewritten" - then - exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \ - --continue - fi - do_rest - return 0 + exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \ + --continue ;; edit-todo) git stripspace --strip-comments <"$todo" >"$todo".new @@ -947,18 +251,14 @@ EOF expand_todo_ids - test -d "$rewritten" || test -n "$force_rebase" || + test -n "$force_rebase" || onto="$(git rebase--helper --skip-unnecessary-picks)" || die "Could not skip unnecessary pick commands" checkout_onto - if test ! -d "$rewritten" - then - require_clean_work_tree "rebase" - exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \ - --continue - fi - do_rest + require_clean_work_tree "rebase" + exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \ + --continue } git_rebase__interactive () { @@ -981,91 +281,3 @@ git_rebase__interactive () { complete_action } - -git_rebase__interactive__preserve_merges () { - initiate_action "$action" - ret=$? - if test $ret = 0; then - return 0 - fi - - setup_reflog_action - init_basic_state - - if test -z "$rebase_root" - then - mkdir "$rewritten" && - for c in $(git merge-base --all $orig_head $upstream) - do - echo $onto > "$rewritten"/$c || - die "$(gettext "Could not init rewritten commits")" - done - else - mkdir "$rewritten" && - echo $onto > "$rewritten"/root || - die "$(gettext "Could not init rewritten commits")" - fi - - init_revisions_and_shortrevisions - - format=$(git config --get rebase.instructionFormat) - # the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse - git rev-list --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 -z "$rebase_root" - then - preserve=t - for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-) - do - if test -f "$rewritten"/$p - then - preserve=f - fi - done - else - preserve=f - fi - if test f = "$preserve" - then - touch "$rewritten"/$sha1 - printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo" - fi - done - - # Watch for commits that been dropped by --cherry-pick - mkdir "$dropped" - # Save all non-cherry-picked changes - git rev-list $revisions --left-right --cherry-pick | \ - sed -n "s/^>//p" > "$state_dir"/not-cherry-picks - # Now all commits and note which ones are missing in - # not-cherry-picks and hence being dropped - git rev-list $revisions | - while read rev - do - if test -f "$rewritten"/$rev && - ! sane_grep "$rev" "$state_dir"/not-cherry-picks >/dev/null - then - # Use -f2 because if rev-list is telling us this commit is - # not worthwhile, we don't want to track its multiple heads, - # just the history of its first-parent for others that will - # be rebasing on top of it - git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$dropped"/$rev - sha1=$(git rev-list -1 $rev) - sane_grep -v "^[a-z][a-z]* $sha1" <"$todo" > "${todo}2" ; mv "${todo}2" "$todo" - rm "$rewritten"/$rev - fi - done - - complete_action -} diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh index cf4c042214..aa2f2f0872 100644 --- a/git-rebase--merge.sh +++ b/git-rebase--merge.sh @@ -71,7 +71,7 @@ call_merge () { test -z "$strategy" && strategy=recursive # If cmt doesn't have a parent, don't include it as a base base=$(git rev-parse --verify --quiet $cmt^) - eval 'git-merge-$strategy' $strategy_opts $base ' -- "$hd" "$cmt"' + eval 'git merge-$strategy' $strategy_opts $base ' -- "$hd" "$cmt"' rv=$? case "$rv" in 0) @@ -88,7 +88,7 @@ call_merge () { ;; *) die "Unknown exit code ($rv) from command:" \ - "git-merge-$strategy $cmt^ -- HEAD $cmt" + "git merge-$strategy $cmt^ -- HEAD $cmt" ;; esac } diff --git a/git-rebase--preserve-merges.sh b/git-rebase--preserve-merges.sh new file mode 100644 index 0000000000..c214c5e4d6 --- /dev/null +++ b/git-rebase--preserve-merges.sh @@ -0,0 +1,1012 @@ +# This shell script fragment is sourced by git-rebase to implement its +# preserve-merges mode. +# +# Copyright (c) 2006 Johannes E. Schindelin +# +# The file containing rebase commands, comments, and empty lines. +# This file is created by "git rebase -i" then edited by the user. As +# the lines are processed, they are removed from the front of this +# file and written to the tail of $done. +todo="$state_dir"/git-rebase-todo + +# The rebase command lines that have already been processed. A line +# is moved here when it is first handled, before any associated user +# actions. +done="$state_dir"/done + +# The commit message that is planned to be used for any changes that +# need to be committed following a user interaction. +msg="$state_dir"/message + +# The file into which is accumulated the suggested commit message for +# squash/fixup commands. When the first of a series of squash/fixups +# is seen, the file is created and the commit message from the +# previous commit and from the first squash/fixup commit are written +# to it. The commit message for each subsequent squash/fixup commit +# is appended to the file as it is processed. +# +# The first line of the file is of the form +# # This is a combination of $count commits. +# where $count is the number of commits whose messages have been +# written to the file so far (including the initial "pick" commit). +# Each time that a commit message is processed, this line is read and +# updated. It is deleted just before the combined commit is made. +squash_msg="$state_dir"/message-squash + +# If the current series of squash/fixups has not yet included a squash +# command, then this file exists and holds the commit message of the +# original "pick" commit. (If the series ends without a "squash" +# command, then this can be used as the commit message of the combined +# commit without opening the editor.) +fixup_msg="$state_dir"/message-fixup + +# $rewritten is the name of a directory containing files for each +# commit that is reachable by at least one merge base of $head and +# $upstream. They are not necessarily rewritten, but their children +# might be. This ensures that commits on merged, but otherwise +# unrelated side branches are left alone. (Think "X" in the man page's +# example.) +rewritten="$state_dir"/rewritten + +dropped="$state_dir"/dropped + +end="$state_dir"/end +msgnum="$state_dir"/msgnum + +# A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and +# GIT_AUTHOR_DATE that will be used for the commit that is currently +# being rebased. +author_script="$state_dir"/author-script + +# When an "edit" rebase command is being processed, the SHA1 of the +# commit to be edited is recorded in this file. When "git rebase +# --continue" is executed, if there are any staged changes then they +# will be amended to the HEAD commit, but only provided the HEAD +# commit is still the commit to be edited. When any other rebase +# command is processed, this file is deleted. +amend="$state_dir"/amend + +# For the post-rewrite hook, we make a list of rewritten commits and +# their new sha1s. The rewritten-pending list keeps the sha1s of +# commits that have been processed, but not committed yet, +# e.g. because they are waiting for a 'squash' command. +rewritten_list="$state_dir"/rewritten-list +rewritten_pending="$state_dir"/rewritten-pending + +# Work around Git for Windows' Bash whose "read" does not strip CRLF +# and leaves CR at the end instead. +cr=$(printf "\015") + +strategy_args=${strategy:+--strategy=$strategy} +test -n "$strategy_opts" && +eval ' + for strategy_opt in '"$strategy_opts"' + do + strategy_args="$strategy_args -X$(git rev-parse --sq-quote "${strategy_opt#--}")" + done +' + +GIT_CHERRY_PICK_HELP="$resolvemsg" +export GIT_CHERRY_PICK_HELP + +comment_char=$(git config --get core.commentchar 2>/dev/null) +case "$comment_char" in +'' | auto) + comment_char="#" + ;; +?) + ;; +*) + comment_char=$(echo "$comment_char" | cut -c1) + ;; +esac + +warn () { + printf '%s\n' "$*" >&2 +} + +# Output the commit message for the specified commit. +commit_message () { + git cat-file commit "$1" | sed "1,/^$/d" +} + +orig_reflog_action="$GIT_REFLOG_ACTION" + +comment_for_reflog () { + case "$orig_reflog_action" in + ''|rebase*) + GIT_REFLOG_ACTION="rebase -i ($1)" + export GIT_REFLOG_ACTION + ;; + esac +} + +last_count= +mark_action_done () { + sed -e 1q < "$todo" >> "$done" + sed -e 1d < "$todo" >> "$todo".new + mv -f "$todo".new "$todo" + new_count=$(( $(git stripspace --strip-comments <"$done" | wc -l) )) + echo $new_count >"$msgnum" + total=$(($new_count + $(git stripspace --strip-comments <"$todo" | wc -l))) + echo $total >"$end" + if test "$last_count" != "$new_count" + then + last_count=$new_count + eval_gettext "Rebasing (\$new_count/\$total)"; printf "\r" + test -z "$verbose" || echo + fi +} + +# Put the last action marked done at the beginning of the todo list +# again. If there has not been an action marked done yet, leave the list of +# items on the todo list unchanged. +reschedule_last_action () { + tail -n 1 "$done" | cat - "$todo" >"$todo".new + sed -e \$d <"$done" >"$done".new + mv -f "$todo".new "$todo" + mv -f "$done".new "$done" +} + +append_todo_help () { + gettext " +Commands: +p, pick <commit> = use commit +r, reword <commit> = use commit, but edit the commit message +e, edit <commit> = use commit, but stop for amending +s, squash <commit> = use commit, but meld into previous commit +f, fixup <commit> = like \"squash\", but discard this commit's log message +x, exec <commit> = run command (the rest of the line) using shell +d, drop <commit> = remove commit +l, label <label> = label current HEAD with a name +t, reset <label> = reset HEAD to a label +m, merge [-C <commit> | -c <commit>] <label> [# <oneline>] +. create a merge commit using the original merge commit's +. message (or the oneline, if no original merge commit was +. specified). Use -c <commit> to reword the commit message. + +These lines can be re-ordered; they are executed from top to bottom. +" | git stripspace --comment-lines >>"$todo" + + if test $(get_missing_commit_check_level) = error + then + gettext " +Do not remove any line. Use 'drop' explicitly to remove a commit. +" | git stripspace --comment-lines >>"$todo" + else + gettext " +If you remove a line here THAT COMMIT WILL BE LOST. +" | git stripspace --comment-lines >>"$todo" + fi +} + +make_patch () { + sha1_and_parents="$(git rev-list --parents -1 "$1")" + case "$sha1_and_parents" in + ?*' '?*' '?*) + git diff --cc $sha1_and_parents + ;; + ?*' '?*) + git diff-tree -p "$1^!" + ;; + *) + echo "Root commit" + ;; + esac > "$state_dir"/patch + test -f "$msg" || + commit_message "$1" > "$msg" + test -f "$author_script" || + get_author_ident_from_commit "$1" > "$author_script" +} + +die_with_patch () { + echo "$1" > "$state_dir"/stopped-sha + git update-ref REBASE_HEAD "$1" + make_patch "$1" + die "$2" +} + +exit_with_patch () { + echo "$1" > "$state_dir"/stopped-sha + git update-ref REBASE_HEAD "$1" + make_patch $1 + git rev-parse --verify HEAD > "$amend" + gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} + warn "$(eval_gettext "\ +You can amend the commit now, with + + git commit --amend \$gpg_sign_opt_quoted + +Once you are satisfied with your changes, run + + git rebase --continue")" + warn + exit $2 +} + +die_abort () { + apply_autostash + rm -rf "$state_dir" + die "$1" +} + +has_action () { + test -n "$(git stripspace --strip-comments <"$1")" +} + +is_empty_commit() { + tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null) || { + sha1=$1 + die "$(eval_gettext "\$sha1: not a commit that can be picked")" + } + ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null) || + ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904 + test "$tree" = "$ptree" +} + +is_merge_commit() +{ + git rev-parse --verify --quiet "$1"^2 >/dev/null 2>&1 +} + +# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and +# GIT_AUTHOR_DATE exported from the current environment. +do_with_author () { + ( + export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE + "$@" + ) +} + +git_sequence_editor () { + if test -z "$GIT_SEQUENCE_EDITOR" + then + GIT_SEQUENCE_EDITOR="$(git config sequence.editor)" + if [ -z "$GIT_SEQUENCE_EDITOR" ] + then + GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $? + fi + fi + + eval "$GIT_SEQUENCE_EDITOR" '"$@"' +} + +pick_one () { + ff=--ff + + case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac + case "$force_rebase" in '') ;; ?*) ff= ;; esac + output git rev-parse --verify $sha1 || die "$(eval_gettext "Invalid commit name: \$sha1")" + + if is_empty_commit "$sha1" + then + empty_args="--allow-empty" + fi + + pick_one_preserving_merges "$@" +} + +pick_one_preserving_merges () { + fast_forward=t + case "$1" in + -n) + fast_forward=f + sha1=$2 + ;; + *) + sha1=$1 + ;; + esac + sha1=$(git rev-parse $sha1) + + if test -f "$state_dir"/current-commit && test "$fast_forward" = t + then + while read current_commit + do + git rev-parse HEAD > "$rewritten"/$current_commit + done <"$state_dir"/current-commit + rm "$state_dir"/current-commit || + die "$(gettext "Cannot write current commit's replacement sha1")" + fi + + echo $sha1 >> "$state_dir"/current-commit + + # rewrite parents; if none were rewritten, we can fast-forward. + new_parents= + pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)" + if test "$pend" = " " + then + pend=" root" + fi + while [ "$pend" != "" ] + do + p=$(expr "$pend" : ' \([^ ]*\)') + pend="${pend# $p}" + + if test -f "$rewritten"/$p + then + new_p=$(cat "$rewritten"/$p) + + # If the todo reordered commits, and our parent is marked for + # rewriting, but hasn't been gotten to yet, assume the user meant to + # drop it on top of the current HEAD + if test -z "$new_p" + then + new_p=$(git rev-parse HEAD) + fi + + test $p != $new_p && fast_forward=f + case "$new_parents" in + *$new_p*) + ;; # do nothing; that parent is already there + *) + new_parents="$new_parents $new_p" + ;; + esac + else + if test -f "$dropped"/$p + then + fast_forward=f + replacement="$(cat "$dropped"/$p)" + test -z "$replacement" && replacement=root + pend=" $replacement$pend" + else + new_parents="$new_parents $p" + fi + fi + done + case $fast_forward in + t) + output warn "$(eval_gettext "Fast-forward to \$sha1")" + output git reset --hard $sha1 || + die "$(eval_gettext "Cannot fast-forward to \$sha1")" + ;; + f) + first_parent=$(expr "$new_parents" : ' \([^ ]*\)') + + if [ "$1" != "-n" ] + then + # detach HEAD to current parent + output git checkout $first_parent 2> /dev/null || + die "$(eval_gettext "Cannot move HEAD to \$first_parent")" + fi + + case "$new_parents" in + ' '*' '*) + test "a$1" = a-n && die "$(eval_gettext "Refusing to squash a merge: \$sha1")" + + # redo merge + author_script_content=$(get_author_ident_from_commit $sha1) + eval "$author_script_content" + msg_content="$(commit_message $sha1)" + # No point in merging the first parent, that's HEAD + new_parents=${new_parents# $first_parent} + merge_args="--no-log --no-ff" + if ! do_with_author output eval \ + git merge ${gpg_sign_opt:+$(git rev-parse \ + --sq-quote "$gpg_sign_opt")} \ + $allow_rerere_autoupdate "$merge_args" \ + "$strategy_args" \ + -m "$(git rev-parse --sq-quote "$msg_content")" \ + "$new_parents" + then + printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG + die_with_patch $sha1 "$(eval_gettext "Error redoing merge \$sha1")" + fi + echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list" + ;; + *) + output eval git cherry-pick $allow_rerere_autoupdate \ + $allow_empty_message \ + ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \ + "$strategy_args" "$@" || + die_with_patch $sha1 "$(eval_gettext "Could not pick \$sha1")" + ;; + esac + ;; + esac +} + +this_nth_commit_message () { + n=$1 + eval_gettext "This is the commit message #\${n}:" +} + +skip_nth_commit_message () { + n=$1 + eval_gettext "The commit message #\${n} will be skipped:" +} + +update_squash_messages () { + if test -f "$squash_msg"; then + mv "$squash_msg" "$squash_msg".bak || exit + count=$(($(sed -n \ + -e "1s/^$comment_char[^0-9]*\([0-9][0-9]*\).*/\1/p" \ + -e "q" < "$squash_msg".bak)+1)) + { + printf '%s\n' "$comment_char $(eval_ngettext \ + "This is a combination of \$count commit." \ + "This is a combination of \$count commits." \ + $count)" + sed -e 1d -e '2,/^./{ + /^$/d + }' <"$squash_msg".bak + } >"$squash_msg" + else + commit_message HEAD >"$fixup_msg" || + die "$(eval_gettext "Cannot write \$fixup_msg")" + count=2 + { + printf '%s\n' "$comment_char $(gettext "This is a combination of 2 commits.")" + printf '%s\n' "$comment_char $(gettext "This is the 1st commit message:")" + echo + cat "$fixup_msg" + } >"$squash_msg" + fi + case $1 in + squash) + rm -f "$fixup_msg" + echo + printf '%s\n' "$comment_char $(this_nth_commit_message $count)" + echo + commit_message $2 + ;; + fixup) + echo + printf '%s\n' "$comment_char $(skip_nth_commit_message $count)" + echo + # Change the space after the comment character to TAB: + commit_message $2 | git stripspace --comment-lines | sed -e 's/ / /' + ;; + esac >>"$squash_msg" +} + +peek_next_command () { + git stripspace --strip-comments <"$todo" | sed -n -e 's/ .*//p' -e q +} + +# A squash/fixup has failed. Prepare the long version of the squash +# commit message, then die_with_patch. This code path requires the +# user to edit the combined commit message for all commits that have +# been squashed/fixedup so far. So also erase the old squash +# messages, effectively causing the combined commit to be used as the +# new basis for any further squash/fixups. Args: sha1 rest +die_failed_squash() { + sha1=$1 + rest=$2 + mv "$squash_msg" "$msg" || exit + rm -f "$fixup_msg" + cp "$msg" "$GIT_DIR"/MERGE_MSG || exit + warn + warn "$(eval_gettext "Could not apply \$sha1... \$rest")" + die_with_patch $sha1 "" +} + +flush_rewritten_pending() { + test -s "$rewritten_pending" || return + newsha1="$(git rev-parse HEAD^0)" + sed "s/$/ $newsha1/" < "$rewritten_pending" >> "$rewritten_list" + rm -f "$rewritten_pending" +} + +record_in_rewritten() { + oldsha1="$(git rev-parse $1)" + echo "$oldsha1" >> "$rewritten_pending" + + case "$(peek_next_command)" in + squash|s|fixup|f) + ;; + *) + flush_rewritten_pending + ;; + esac +} + +do_pick () { + sha1=$1 + rest=$2 + if test "$(git rev-parse HEAD)" = "$squash_onto" + then + # Set the correct commit message and author info on the + # sentinel root before cherry-picking the original changes + # without committing (-n). Finally, update the sentinel again + # to include these changes. If the cherry-pick results in a + # conflict, this means our behaviour is similar to a standard + # failed cherry-pick during rebase, with a dirty index to + # resolve before manually running git commit --amend then git + # rebase --continue. + git commit --allow-empty --allow-empty-message --amend \ + --no-post-rewrite -n -q -C $sha1 $signoff && + pick_one -n $sha1 && + git commit --allow-empty --allow-empty-message \ + --amend --no-post-rewrite -n -q -C $sha1 $signoff \ + ${gpg_sign_opt:+"$gpg_sign_opt"} || + die_with_patch $sha1 "$(eval_gettext "Could not apply \$sha1... \$rest")" + else + pick_one $sha1 || + die_with_patch $sha1 "$(eval_gettext "Could not apply \$sha1... \$rest")" + fi +} + +do_next () { + rm -f "$msg" "$author_script" "$amend" "$state_dir"/stopped-sha || exit + read -r command sha1 rest < "$todo" + case "$command" in + "$comment_char"*|''|noop|drop|d) + mark_action_done + ;; + "$cr") + # Work around CR left by "read" (e.g. with Git for Windows' Bash). + mark_action_done + ;; + pick|p) + comment_for_reflog pick + + mark_action_done + do_pick $sha1 "$rest" + record_in_rewritten $sha1 + ;; + reword|r) + comment_for_reflog reword + + mark_action_done + do_pick $sha1 "$rest" + git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} \ + $allow_empty_message || { + warn "$(eval_gettext "\ +Could not amend commit after successfully picking \$sha1... \$rest +This is most likely due to an empty commit message, or the pre-commit hook +failed. If the pre-commit hook failed, you may need to resolve the issue before +you are able to reword the commit.")" + exit_with_patch $sha1 1 + } + record_in_rewritten $sha1 + ;; + edit|e) + comment_for_reflog edit + + mark_action_done + do_pick $sha1 "$rest" + sha1_abbrev=$(git rev-parse --short $sha1) + warn "$(eval_gettext "Stopped at \$sha1_abbrev... \$rest")" + exit_with_patch $sha1 0 + ;; + squash|s|fixup|f) + case "$command" in + squash|s) + squash_style=squash + ;; + fixup|f) + squash_style=fixup + ;; + esac + comment_for_reflog $squash_style + + test -f "$done" && has_action "$done" || + die "$(eval_gettext "Cannot '\$squash_style' without a previous commit")" + + mark_action_done + update_squash_messages $squash_style $sha1 + author_script_content=$(get_author_ident_from_commit HEAD) + echo "$author_script_content" > "$author_script" + eval "$author_script_content" + if ! pick_one -n $sha1 + then + git rev-parse --verify HEAD >"$amend" + die_failed_squash $sha1 "$rest" + fi + case "$(peek_next_command)" in + squash|s|fixup|f) + # This is an intermediate commit; its message will only be + # used in case of trouble. So use the long version: + do_with_author output git commit --amend --no-verify -F "$squash_msg" \ + ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message || + die_failed_squash $sha1 "$rest" + ;; + *) + # This is the final command of this squash/fixup group + if test -f "$fixup_msg" + then + do_with_author git commit --amend --no-verify -F "$fixup_msg" \ + ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message || + die_failed_squash $sha1 "$rest" + else + cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit + rm -f "$GIT_DIR"/MERGE_MSG + do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ + ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message || + die_failed_squash $sha1 "$rest" + fi + rm -f "$squash_msg" "$fixup_msg" + ;; + esac + record_in_rewritten $sha1 + ;; + x|"exec") + read -r command rest < "$todo" + mark_action_done + eval_gettextln "Executing: \$rest" + "${SHELL:-@SHELL_PATH@}" -c "$rest" # Actual execution + status=$? + # Run in subshell because require_clean_work_tree can die. + dirty=f + (require_clean_work_tree "rebase" 2>/dev/null) || dirty=t + if test "$status" -ne 0 + then + warn "$(eval_gettext "Execution failed: \$rest")" + test "$dirty" = f || + warn "$(gettext "and made changes to the index and/or the working tree")" + + warn "$(gettext "\ +You can fix the problem, and then run + + git rebase --continue")" + warn + if test $status -eq 127 # command not found + then + status=1 + fi + exit "$status" + elif test "$dirty" = t + then + # TRANSLATORS: after these lines is a command to be issued by the user + warn "$(eval_gettext "\ +Execution succeeded: \$rest +but left changes to the index and/or the working tree +Commit or stash your changes, and then run + + git rebase --continue")" + warn + exit 1 + fi + ;; + *) + warn "$(eval_gettext "Unknown command: \$command \$sha1 \$rest")" + fixtodo="$(gettext "Please fix this using 'git rebase --edit-todo'.")" + if git rev-parse --verify -q "$sha1" >/dev/null + then + die_with_patch $sha1 "$fixtodo" + else + die "$fixtodo" + fi + ;; + esac + test -s "$todo" && return + + comment_for_reflog finish && + newhead=$(git rev-parse HEAD) && + case $head_name in + refs/*) + message="$GIT_REFLOG_ACTION: $head_name onto $onto" && + git update-ref -m "$message" $head_name $newhead $orig_head && + git symbolic-ref \ + -m "$GIT_REFLOG_ACTION: returning to $head_name" \ + HEAD $head_name + ;; + esac && { + test ! -f "$state_dir"/verbose || + git diff-tree --stat $orig_head..HEAD + } && + { + test -s "$rewritten_list" && + git notes copy --for-rewrite=rebase < "$rewritten_list" || + true # we don't care if this copying failed + } && + hook="$(git rev-parse --git-path hooks/post-rewrite)" + if test -x "$hook" && test -s "$rewritten_list"; then + "$hook" rebase < "$rewritten_list" + true # we don't care if this hook failed + fi && + warn "$(eval_gettext "Successfully rebased and updated \$head_name.")" + + return 1 # not failure; just to break the do_rest loop +} + +# can only return 0, when the infinite loop breaks +do_rest () { + while : + do + do_next || break + done +} + +expand_todo_ids() { + git rebase--helper --expand-ids +} + +collapse_todo_ids() { + git rebase--helper --shorten-ids +} + +# Switch to the branch in $into and notify it in the reflog +checkout_onto () { + GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" + output git checkout $onto || die_abort "$(gettext "could not detach HEAD")" + git update-ref ORIG_HEAD $orig_head +} + +get_missing_commit_check_level () { + check_level=$(git config --get rebase.missingCommitsCheck) + check_level=${check_level:-ignore} + # Don't be case sensitive + printf '%s' "$check_level" | tr 'A-Z' 'a-z' +} + +# Initiate an action. If the cannot be any +# further action it may exec a command +# or exit and not return. +# +# TODO: Consider a cleaner return model so it +# never exits and always return 0 if process +# is complete. +# +# Parameter 1 is the action to initiate. +# +# Returns 0 if the action was able to complete +# and if 1 if further processing is required. +initiate_action () { + case "$1" in + continue) + # do we have anything to commit? + if git diff-index --cached --quiet HEAD -- + then + # Nothing to commit -- skip this commit + + test ! -f "$GIT_DIR"/CHERRY_PICK_HEAD || + rm "$GIT_DIR"/CHERRY_PICK_HEAD || + die "$(gettext "Could not remove CHERRY_PICK_HEAD")" + else + if ! test -f "$author_script" + then + gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} + die "$(eval_gettext "\ +You have staged changes in your working tree. +If these changes are meant to be +squashed into the previous commit, run: + + git commit --amend \$gpg_sign_opt_quoted + +If they are meant to go into a new commit, run: + + git commit \$gpg_sign_opt_quoted + +In both cases, once you're done, continue with: + + git rebase --continue +")" + fi + . "$author_script" || + die "$(gettext "Error trying to find the author identity to amend commit")" + if test -f "$amend" + then + current_head=$(git rev-parse --verify HEAD) + test "$current_head" = $(cat "$amend") || + die "$(gettext "\ +You have uncommitted changes in your working tree. Please commit them +first and then run 'git rebase --continue' again.")" + do_with_author git commit --amend --no-verify -F "$msg" -e \ + ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message || + die "$(gettext "Could not commit staged changes.")" + else + do_with_author git commit --no-verify -F "$msg" -e \ + ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message || + die "$(gettext "Could not commit staged changes.")" + fi + fi + + if test -r "$state_dir"/stopped-sha + then + record_in_rewritten "$(cat "$state_dir"/stopped-sha)" + fi + + require_clean_work_tree "rebase" + do_rest + return 0 + ;; + skip) + git rerere clear + do_rest + return 0 + ;; + edit-todo) + git stripspace --strip-comments <"$todo" >"$todo".new + mv -f "$todo".new "$todo" + collapse_todo_ids + append_todo_help + gettext " +You are editing the todo file of an ongoing interactive rebase. +To continue rebase after editing, run: + git rebase --continue + +" | git stripspace --comment-lines >>"$todo" + + git_sequence_editor "$todo" || + die "$(gettext "Could not execute editor")" + expand_todo_ids + + exit + ;; + show-current-patch) + exec git show REBASE_HEAD -- + ;; + *) + return 1 # continue + ;; + esac +} + +setup_reflog_action () { + comment_for_reflog start + + if test ! -z "$switch_to" + then + GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to" + output git checkout "$switch_to" -- || + die "$(eval_gettext "Could not checkout \$switch_to")" + + comment_for_reflog start + fi +} + +init_basic_state () { + orig_head=$(git rev-parse --verify HEAD) || die "$(gettext "No HEAD?")" + mkdir -p "$state_dir" || die "$(eval_gettext "Could not create temporary \$state_dir")" + rm -f "$(git rev-parse --git-path REBASE_HEAD)" + + : > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")" + write_basic_state +} + +init_revisions_and_shortrevisions () { + shorthead=$(git rev-parse --short $orig_head) + shortonto=$(git rev-parse --short $onto) + if test -z "$rebase_root" + # this is now equivalent to ! -z "$upstream" + then + shortupstream=$(git rev-parse --short $upstream) + revisions=$upstream...$orig_head + shortrevisions=$shortupstream..$shorthead + else + revisions=$onto...$orig_head + shortrevisions=$shorthead + test -z "$squash_onto" || + echo "$squash_onto" >"$state_dir"/squash-onto + fi +} + +complete_action() { + test -s "$todo" || echo noop >> "$todo" + test -z "$autosquash" || git rebase--helper --rearrange-squash || exit + test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd" + + todocount=$(git stripspace --strip-comments <"$todo" | wc -l) + todocount=${todocount##* } + +cat >>"$todo" <<EOF + +$comment_char $(eval_ngettext \ + "Rebase \$shortrevisions onto \$shortonto (\$todocount command)" \ + "Rebase \$shortrevisions onto \$shortonto (\$todocount commands)" \ + "$todocount") +EOF + append_todo_help + gettext " +However, if you remove everything, the rebase will be aborted. + +" | git stripspace --comment-lines >>"$todo" + + if test -z "$keep_empty" + then + printf '%s\n' "$comment_char $(gettext "Note that empty commits are commented out")" >>"$todo" + fi + + + has_action "$todo" || + return 2 + + cp "$todo" "$todo".backup + collapse_todo_ids + git_sequence_editor "$todo" || + die_abort "$(gettext "Could not execute editor")" + + has_action "$todo" || + return 2 + + git rebase--helper --check-todo-list || { + ret=$? + checkout_onto + exit $ret + } + + expand_todo_ids + checkout_onto + do_rest +} + +git_rebase__preserve_merges () { + initiate_action "$action" + ret=$? + if test $ret = 0; then + return 0 + fi + + setup_reflog_action + init_basic_state + + if test -z "$rebase_root" + then + mkdir "$rewritten" && + for c in $(git merge-base --all $orig_head $upstream) + do + echo $onto > "$rewritten"/$c || + die "$(gettext "Could not init rewritten commits")" + done + else + mkdir "$rewritten" && + echo $onto > "$rewritten"/root || + die "$(gettext "Could not init rewritten commits")" + fi + + init_revisions_and_shortrevisions + + format=$(git config --get rebase.instructionFormat) + # the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse + git rev-list --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 -z "$rebase_root" + then + preserve=t + for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-) + do + if test -f "$rewritten"/$p + then + preserve=f + fi + done + else + preserve=f + fi + if test f = "$preserve" + then + touch "$rewritten"/$sha1 + printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo" + fi + done + + # Watch for commits that been dropped by --cherry-pick + mkdir "$dropped" + # Save all non-cherry-picked changes + git rev-list $revisions --left-right --cherry-pick | \ + sed -n "s/^>//p" > "$state_dir"/not-cherry-picks + # Now all commits and note which ones are missing in + # not-cherry-picks and hence being dropped + git rev-list $revisions | + while read rev + do + if test -f "$rewritten"/$rev && + ! sane_grep "$rev" "$state_dir"/not-cherry-picks >/dev/null + then + # Use -f2 because if rev-list is telling us this commit is + # not worthwhile, we don't want to track its multiple heads, + # just the history of its first-parent for others that will + # be rebasing on top of it + git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$dropped"/$rev + sha1=$(git rev-list -1 $rev) + sane_grep -v "^[a-z][a-z]* $sha1" <"$todo" > "${todo}2" ; mv "${todo}2" "$todo" + rm "$rewritten"/$rev + fi + done + + complete_action +} diff --git a/git-rebase.sh b/git-rebase.sh index 40be59ecc4..7973447645 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -20,23 +20,23 @@ onto=! rebase onto given branch instead of upstream r,rebase-merges? try to rebase merges instead of skipping them p,preserve-merges! try to recreate merges instead of ignoring them s,strategy=! use the given merge strategy +X,strategy-option=! pass the argument through to the merge strategy no-ff! cherry-pick all commits, even if unchanged +f,force-rebase! cherry-pick all commits, even if unchanged m,merge! use merging strategies to rebase i,interactive! let the user edit the list of commits to rebase x,exec=! add exec lines after each commit of the editable list k,keep-empty preserve empty commits during rebase allow-empty-message allow rebasing commits with empty messages -f,force-rebase! force rebase even if branch is up to date -X,strategy-option=! pass the argument through to the merge strategy stat! display a diffstat of what changed upstream n,no-stat! do not show diffstat of what changed upstream verify allow pre-rebase hook to run rerere-autoupdate allow rerere to update index with resolved conflicts root! rebase all reachable commits up to the root(s) autosquash move commits that begin with squash!/fixup! under -i +signoff add a Signed-off-by: line to each commit committer-date-is-author-date! passed to 'git am' ignore-date! passed to 'git am' -signoff passed to 'git am' whitespace=! passed to 'git apply' ignore-whitespace! passed to 'git apply' C=! passed to 'git apply' @@ -95,7 +95,7 @@ rebase_cousins= preserve_merges= autosquash= keep_empty= -allow_empty_message= +allow_empty_message=--allow-empty-message signoff= test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t case "$(git config --bool commit.gpgsign)" in @@ -207,7 +207,14 @@ run_specific_rebase () { autosquash= fi . git-rebase--$type - git_rebase__$type${preserve_merges:+__preserve_merges} + + if test -z "$preserve_merges" + then + git_rebase__$type + else + git_rebase__preserve_merges + fi + ret=$? if test $ret -eq 0 then @@ -239,7 +246,12 @@ then state_dir="$apply_dir" elif test -d "$merge_dir" then - if test -f "$merge_dir"/interactive + if test -d "$merge_dir"/rewritten + then + type=preserve-merges + interactive_rebase=explicit + preserve_merges=t + elif test -f "$merge_dir"/interactive then type=interactive interactive_rebase=explicit @@ -316,7 +328,7 @@ do do_merge=t ;; --strategy-option=*) - strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--${1#--strategy-option=}")" + strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--${1#--strategy-option=}" | sed -e s/^.//)" do_merge=t test -z "$strategy" && strategy=recursive ;; @@ -402,14 +414,14 @@ if test -n "$action" then test -z "$in_progress" && die "$(gettext "No rebase in progress?")" # Only interactive rebase uses detailed reflog messages - if test "$type" = interactive && test "$GIT_REFLOG_ACTION" = rebase + if test -n "$interactive_rebase" && test "$GIT_REFLOG_ACTION" = rebase then GIT_REFLOG_ACTION="rebase -i ($action)" export GIT_REFLOG_ACTION fi fi -if test "$action" = "edit-todo" && test "$type" != "interactive" +if test "$action" = "edit-todo" && test -z "$interactive_rebase" then die "$(gettext "The --edit-todo action can only be used during interactive rebase.")" fi @@ -487,7 +499,13 @@ fi if test -n "$interactive_rebase" then - type=interactive + if test -z "$preserve_merges" + then + type=interactive + else + type=preserve-merges + fi + state_dir="$merge_dir" elif test -n "$do_merge" then @@ -503,6 +521,24 @@ then git_format_patch_opt="$git_format_patch_opt --progress" fi +if test -n "$git_am_opt"; then + incompatible_opts=$(echo " $git_am_opt " | \ + sed -e 's/ -q / /g' -e 's/^ \(.*\) $/\1/') + if test -n "$interactive_rebase" + then + if test -n "$incompatible_opts" + then + die "$(gettext "error: cannot combine interactive options (--interactive, --exec, --rebase-merges, --preserve-merges, --keep-empty, --root + --onto) with am options ($incompatible_opts)")" + fi + fi + if test -n "$do_merge"; then + if test -n "$incompatible_opts" + then + die "$(gettext "error: cannot combine merge options (--merge, --strategy, --strategy-option) with am options ($incompatible_opts)")" + fi + fi +fi + if test -n "$signoff" then test -n "$preserve_merges" && @@ -511,6 +547,23 @@ then force_rebase=t fi +if test -n "$preserve_merges" +then + # Note: incompatibility with --signoff handled in signoff block above + # Note: incompatibility with --interactive is just a strong warning; + # git-rebase.txt caveats with "unless you know what you are doing" + test -n "$rebase_merges" && + die "$(gettext "error: cannot combine '--preserve_merges' with '--rebase-merges'")" +fi + +if test -n "$rebase_merges" +then + test -n "$strategy_opts" && + die "$(gettext "error: cannot combine '--rebase_merges' with '--strategy-option'")" + test -n "$strategy" && + die "$(gettext "error: cannot combine '--rebase_merges' with '--strategy'")" +fi + if test -z "$rebase_root" then case "$#" in @@ -647,7 +700,7 @@ require_clean_work_tree "rebase" "$(gettext "Please commit or stash them.")" # but this should be done only when upstream and onto are the same # and if this is not an interactive rebase. mb=$(git merge-base "$onto" "$orig_head") -if test "$type" != interactive && test "$upstream" = "$onto" && +if test -z "$interactive_rebase" && test "$upstream" = "$onto" && test "$mb" = "$onto" && test -z "$restrict_revision" && # linear history? ! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null @@ -691,7 +744,7 @@ then GIT_PAGER='' git diff --stat --summary "$mb" "$onto" fi -test "$type" = interactive && run_specific_rebase +test -n "$interactive_rebase" && run_specific_rebase # Detach HEAD and reset the tree say "$(gettext "First, rewinding head to replay your work on top of it...")" diff --git a/git-submodule.sh b/git-submodule.sh index 78073cd87d..8b5ad59bde 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -335,45 +335,7 @@ cmd_foreach() shift done - toplevel=$(pwd) - - # dup stdin so that it can be restored when running the external - # command in the subshell (and a recursive call to this function) - exec 3<&0 - - { - git submodule--helper list --prefix "$wt_prefix" || - echo "#unmatched" $? - } | - while read -r mode sha1 stage sm_path - do - die_if_unmatched "$mode" "$sha1" - if test -e "$sm_path"/.git - then - displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix") - say "$(eval_gettext "Entering '\$displaypath'")" - name=$(git submodule--helper name "$sm_path") - ( - prefix="$prefix$sm_path/" - sanitize_submodule_env - cd "$sm_path" && - sm_path=$(git submodule--helper relative-path "$sm_path" "$wt_prefix") && - # we make $path available to scripts ... - path=$sm_path && - if test $# -eq 1 - then - eval "$1" - else - "$@" - fi && - if test -n "$recursive" - then - cmd_foreach "--recursive" "$@" - fi - ) <&3 3<&- || - die "$(eval_gettext "Stopping at '\$displaypath'; script returned non-zero status.")" - fi - done + git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper foreach ${GIT_QUIET:+--quiet} ${recursive:+--recursive} "$@" } # @@ -615,6 +577,11 @@ cmd_update() die "$(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")" fi + if ! $(git config -f "$(git rev-parse --git-common-dir)/modules/$name/config" core.worktree) 2>/dev/null + then + git submodule--helper connect-gitdir-workingtree "$name" "$sm_path" + fi + if test "$subsha1" != "$sha1" || test -n "$force" then subforce=$force @@ -267,7 +267,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) } else if (!strcmp(cmd, "--shallow-file")) { (*argv)++; (*argc)--; - set_alternate_shallow_file((*argv)[0], 1); + set_alternate_shallow_file(the_repository, (*argv)[0], 1); if (envchanged) *envchanged = 1; } else if (!strcmp(cmd, "-C")) { @@ -537,6 +537,7 @@ static struct cmd_struct commands[] = { { "shortlog", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER }, { "show", cmd_show, RUN_SETUP }, { "show-branch", cmd_show_branch, RUN_SETUP }, + { "show-index", cmd_show_index }, { "show-ref", cmd_show_ref, RUN_SETUP }, { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE }, { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, @@ -1,18 +1,32 @@ #include "cache.h" #include "config.h" #include "grep.h" +#include "object-store.h" #include "userdiff.h" #include "xdiff-interface.h" #include "diff.h" #include "diffcore.h" #include "commit.h" #include "quote.h" +#include "help.h" static int grep_source_load(struct grep_source *gs); static int grep_source_is_binary(struct grep_source *gs); static struct grep_opt grep_defaults; +static const char *color_grep_slots[] = { + [GREP_COLOR_CONTEXT] = "context", + [GREP_COLOR_FILENAME] = "filename", + [GREP_COLOR_FUNCTION] = "function", + [GREP_COLOR_LINENO] = "lineNumber", + [GREP_COLOR_COLUMNNO] = "column", + [GREP_COLOR_MATCH_CONTEXT] = "matchContext", + [GREP_COLOR_MATCH_SELECTED] = "matchSelected", + [GREP_COLOR_SELECTED] = "selected", + [GREP_COLOR_SEP] = "separator", +}; + static void std_output(struct grep_opt *opt, const void *buf, size_t size) { fwrite(buf, size, 1, stdout); @@ -42,14 +56,15 @@ void init_grep_defaults(void) opt->pathname = 1; opt->max_depth = -1; opt->pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED; - color_set(opt->color_context, ""); - color_set(opt->color_filename, ""); - color_set(opt->color_function, ""); - color_set(opt->color_lineno, ""); - color_set(opt->color_match_context, GIT_COLOR_BOLD_RED); - color_set(opt->color_match_selected, GIT_COLOR_BOLD_RED); - color_set(opt->color_selected, ""); - color_set(opt->color_sep, GIT_COLOR_CYAN); + color_set(opt->colors[GREP_COLOR_CONTEXT], ""); + color_set(opt->colors[GREP_COLOR_FILENAME], ""); + color_set(opt->colors[GREP_COLOR_FUNCTION], ""); + color_set(opt->colors[GREP_COLOR_LINENO], ""); + color_set(opt->colors[GREP_COLOR_COLUMNNO], ""); + color_set(opt->colors[GREP_COLOR_MATCH_CONTEXT], GIT_COLOR_BOLD_RED); + color_set(opt->colors[GREP_COLOR_MATCH_SELECTED], GIT_COLOR_BOLD_RED); + color_set(opt->colors[GREP_COLOR_SELECTED], ""); + color_set(opt->colors[GREP_COLOR_SEP], GIT_COLOR_CYAN); opt->color = -1; opt->output = std_output; } @@ -69,6 +84,8 @@ static int parse_pattern_type_arg(const char *opt, const char *arg) die("bad %s argument: %s", opt, arg); } +define_list_config_array_extra(color_grep_slots, {"match"}); + /* * Read the configuration file once and store it in * the grep_defaults template. @@ -76,7 +93,7 @@ static int parse_pattern_type_arg(const char *opt, const char *arg) int grep_config(const char *var, const char *value, void *cb) { struct grep_opt *opt = &grep_defaults; - char *color = NULL; + const char *slot; if (userdiff_config(var, value) < 0) return -1; @@ -95,6 +112,10 @@ int grep_config(const char *var, const char *value, void *cb) opt->linenum = git_config_bool(var, value); return 0; } + if (!strcmp(var, "grep.column")) { + opt->columnnum = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "grep.fullname")) { opt->relative = !git_config_bool(var, value); @@ -103,32 +124,18 @@ int grep_config(const char *var, const char *value, void *cb) if (!strcmp(var, "color.grep")) opt->color = git_config_colorbool(var, value); - else if (!strcmp(var, "color.grep.context")) - color = opt->color_context; - else if (!strcmp(var, "color.grep.filename")) - color = opt->color_filename; - else if (!strcmp(var, "color.grep.function")) - color = opt->color_function; - else if (!strcmp(var, "color.grep.linenumber")) - color = opt->color_lineno; - else if (!strcmp(var, "color.grep.matchcontext")) - color = opt->color_match_context; - else if (!strcmp(var, "color.grep.matchselected")) - color = opt->color_match_selected; - else if (!strcmp(var, "color.grep.selected")) - color = opt->color_selected; - else if (!strcmp(var, "color.grep.separator")) - color = opt->color_sep; - else if (!strcmp(var, "color.grep.match")) { - int rc = 0; - if (!value) - return config_error_nonbool(var); - rc |= color_parse(value, opt->color_match_context); - rc |= color_parse(value, opt->color_match_selected); - return rc; - } - - if (color) { + if (!strcmp(var, "color.grep.match")) { + if (grep_config("color.grep.matchcontext", value, cb) < 0) + return -1; + if (grep_config("color.grep.matchselected", value, cb) < 0) + return -1; + } else if (skip_prefix(var, "color.grep.", &slot)) { + int i = LOOKUP_CONFIG(color_grep_slots, slot); + char *color; + + if (i < 0) + return -1; + color = opt->colors[i]; if (!value) return config_error_nonbool(var); return color_parse(value, color); @@ -144,6 +151,7 @@ int grep_config(const char *var, const char *value, void *cb) void grep_init(struct grep_opt *opt, const char *prefix) { struct grep_opt *def = &grep_defaults; + int i; memset(opt, 0, sizeof(*opt)); opt->prefix = prefix; @@ -155,19 +163,14 @@ void grep_init(struct grep_opt *opt, const char *prefix) opt->extended_regexp_option = def->extended_regexp_option; opt->pattern_type_option = def->pattern_type_option; opt->linenum = def->linenum; + opt->columnnum = def->columnnum; opt->max_depth = def->max_depth; opt->pathname = def->pathname; opt->relative = def->relative; opt->output = def->output; - color_set(opt->color_context, def->color_context); - color_set(opt->color_filename, def->color_filename); - color_set(opt->color_function, def->color_function); - color_set(opt->color_lineno, def->color_lineno); - color_set(opt->color_match_context, def->color_match_context); - color_set(opt->color_match_selected, def->color_match_selected); - color_set(opt->color_selected, def->color_selected); - color_set(opt->color_sep, def->color_sep); + for (i = 0; i < NR_GREP_COLORS; i++) + color_set(opt->colors[i], def->colors[i]); } static void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt) @@ -1098,12 +1101,12 @@ static void output_sep(struct grep_opt *opt, char sign) if (opt->null_following_name) opt->output(opt, "\0", 1); else - output_color(opt, &sign, 1, opt->color_sep); + output_color(opt, &sign, 1, opt->colors[GREP_COLOR_SEP]); } static void show_name(struct grep_opt *opt, const char *name) { - output_color(opt, name, strlen(name), opt->color_filename); + output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]); opt->output(opt, opt->null_following_name ? "\0" : "\n", 1); } @@ -1248,11 +1251,11 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol, return hit; } -static int match_expr_eval(struct grep_expr *x, char *bol, char *eol, - enum grep_context ctx, int collect_hits) +static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, char *bol, + char *eol, enum grep_context ctx, ssize_t *col, + ssize_t *icol, int collect_hits) { int h = 0; - regmatch_t match; if (!x) die("Not a valid grep expression"); @@ -1261,25 +1264,52 @@ static int match_expr_eval(struct grep_expr *x, char *bol, char *eol, h = 1; break; case GREP_NODE_ATOM: - h = match_one_pattern(x->u.atom, bol, eol, ctx, &match, 0); + { + regmatch_t tmp; + h = match_one_pattern(x->u.atom, bol, eol, ctx, + &tmp, 0); + if (h && (*col < 0 || tmp.rm_so < *col)) + *col = tmp.rm_so; + } break; case GREP_NODE_NOT: - h = !match_expr_eval(x->u.unary, bol, eol, ctx, 0); + /* + * Upon visiting a GREP_NODE_NOT, col and icol become swapped. + */ + h = !match_expr_eval(opt, x->u.unary, bol, eol, ctx, icol, col, + 0); break; case GREP_NODE_AND: - if (!match_expr_eval(x->u.binary.left, bol, eol, ctx, 0)) - return 0; - h = match_expr_eval(x->u.binary.right, bol, eol, ctx, 0); + h = match_expr_eval(opt, x->u.binary.left, bol, eol, ctx, col, + icol, 0); + if (h || opt->columnnum) { + /* + * Don't short-circuit AND when given --column, since a + * NOT earlier in the tree may turn this into an OR. In + * this case, see the below comment. + */ + h &= match_expr_eval(opt, x->u.binary.right, bol, eol, + ctx, col, icol, 0); + } break; case GREP_NODE_OR: - if (!collect_hits) - return (match_expr_eval(x->u.binary.left, - bol, eol, ctx, 0) || - match_expr_eval(x->u.binary.right, - bol, eol, ctx, 0)); - h = match_expr_eval(x->u.binary.left, bol, eol, ctx, 0); - x->u.binary.left->hit |= h; - h |= match_expr_eval(x->u.binary.right, bol, eol, ctx, 1); + if (!(collect_hits || opt->columnnum)) { + /* + * Don't short-circuit OR when given --column (or + * collecting hits) to ensure we don't skip a later + * child that would produce an earlier match. + */ + return (match_expr_eval(opt, x->u.binary.left, bol, eol, + ctx, col, icol, 0) || + match_expr_eval(opt, x->u.binary.right, bol, + eol, ctx, col, icol, 0)); + } + h = match_expr_eval(opt, x->u.binary.left, bol, eol, ctx, col, + icol, 0); + if (collect_hits) + x->u.binary.left->hit |= h; + h |= match_expr_eval(opt, x->u.binary.right, bol, eol, ctx, col, + icol, collect_hits); break; default: die("Unexpected node type (internal error) %d", x->node); @@ -1290,27 +1320,43 @@ static int match_expr_eval(struct grep_expr *x, char *bol, char *eol, } static int match_expr(struct grep_opt *opt, char *bol, char *eol, - enum grep_context ctx, int collect_hits) + enum grep_context ctx, ssize_t *col, + ssize_t *icol, int collect_hits) { struct grep_expr *x = opt->pattern_expression; - return match_expr_eval(x, bol, eol, ctx, collect_hits); + return match_expr_eval(opt, x, bol, eol, ctx, col, icol, collect_hits); } static int match_line(struct grep_opt *opt, char *bol, char *eol, + ssize_t *col, ssize_t *icol, enum grep_context ctx, int collect_hits) { struct grep_pat *p; - regmatch_t match; + int hit = 0; if (opt->extended) - return match_expr(opt, bol, eol, ctx, collect_hits); + return match_expr(opt, bol, eol, ctx, col, icol, + collect_hits); /* we do not call with collect_hits without being extended */ for (p = opt->pattern_list; p; p = p->next) { - if (match_one_pattern(p, bol, eol, ctx, &match, 0)) - return 1; + regmatch_t tmp; + if (match_one_pattern(p, bol, eol, ctx, &tmp, 0)) { + hit |= 1; + if (!opt->columnnum) { + /* + * Without --column, any single match on a line + * is enough to know that it needs to be + * printed. With --column, scan _all_ patterns + * to find the earliest. + */ + break; + } + if (*col < 0 || tmp.rm_so < *col) + *col = tmp.rm_so; + } } - return 0; + return hit; } static int match_next_pattern(struct grep_pat *p, char *bol, char *eol, @@ -1359,7 +1405,7 @@ static int next_match(struct grep_opt *opt, char *bol, char *eol, } static void show_line(struct grep_opt *opt, char *bol, char *eol, - const char *name, unsigned lno, char sign) + const char *name, unsigned lno, ssize_t cno, char sign) { int rest = eol - bol; const char *match_color, *line_color = NULL; @@ -1370,28 +1416,39 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, } else if (opt->pre_context || opt->post_context || opt->funcbody) { if (opt->last_shown == 0) { if (opt->show_hunk_mark) { - output_color(opt, "--", 2, opt->color_sep); + output_color(opt, "--", 2, opt->colors[GREP_COLOR_SEP]); opt->output(opt, "\n", 1); } } else if (lno > opt->last_shown + 1) { - output_color(opt, "--", 2, opt->color_sep); + output_color(opt, "--", 2, opt->colors[GREP_COLOR_SEP]); opt->output(opt, "\n", 1); } } if (opt->heading && opt->last_shown == 0) { - output_color(opt, name, strlen(name), opt->color_filename); + output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]); opt->output(opt, "\n", 1); } opt->last_shown = lno; if (!opt->heading && opt->pathname) { - output_color(opt, name, strlen(name), opt->color_filename); + output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]); output_sep(opt, sign); } if (opt->linenum) { char buf[32]; xsnprintf(buf, sizeof(buf), "%d", lno); - output_color(opt, buf, strlen(buf), opt->color_lineno); + output_color(opt, buf, strlen(buf), opt->colors[GREP_COLOR_LINENO]); + output_sep(opt, sign); + } + /* + * Treat 'cno' as the 1-indexed offset from the start of a non-context + * line to its first match. Otherwise, 'cno' is 0 indicating that we are + * being called with a context line. + */ + if (opt->columnnum && cno) { + char buf[32]; + xsnprintf(buf, sizeof(buf), "%"PRIuMAX, (uintmax_t)cno); + output_color(opt, buf, strlen(buf), opt->colors[GREP_COLOR_COLUMNNO]); output_sep(opt, sign); } if (opt->color) { @@ -1401,15 +1458,15 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, int eflags = 0; if (sign == ':') - match_color = opt->color_match_selected; + match_color = opt->colors[GREP_COLOR_MATCH_SELECTED]; else - match_color = opt->color_match_context; + match_color = opt->colors[GREP_COLOR_MATCH_CONTEXT]; if (sign == ':') - line_color = opt->color_selected; + line_color = opt->colors[GREP_COLOR_SELECTED]; else if (sign == '-') - line_color = opt->color_context; + line_color = opt->colors[GREP_COLOR_CONTEXT]; else if (sign == '=') - line_color = opt->color_function; + line_color = opt->colors[GREP_COLOR_FUNCTION]; *eol = '\0'; while (next_match(opt, bol, eol, ctx, &match, eflags)) { if (match.rm_so == match.rm_eo) @@ -1499,7 +1556,7 @@ static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs, break; if (match_funcname(opt, gs, bol, eol)) { - show_line(opt, bol, eol, gs->name, lno, '='); + show_line(opt, bol, eol, gs->name, lno, 0, '='); break; } } @@ -1564,7 +1621,7 @@ static void show_pre_context(struct grep_opt *opt, struct grep_source *gs, while (*eol != '\n') eol++; - show_line(opt, bol, eol, gs->name, cur, sign); + show_line(opt, bol, eol, gs->name, cur, 0, sign); bol = eol + 1; cur++; } @@ -1763,6 +1820,8 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle while (left) { char *eol, ch; int hit; + ssize_t cno; + ssize_t col = -1, icol = -1; /* * look_ahead() skips quickly to the line that possibly @@ -1786,7 +1845,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol)) ctx = GREP_CONTEXT_BODY; - hit = match_line(opt, bol, eol, ctx, collect_hits); + hit = match_line(opt, bol, eol, &col, &icol, ctx, collect_hits); *eol = ch; if (collect_hits) @@ -1816,7 +1875,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle if (binary_match_only) { opt->output(opt, "Binary file ", 12); output_color(opt, gs->name, strlen(gs->name), - opt->color_filename); + opt->colors[GREP_COLOR_FILENAME]); opt->output(opt, " matches\n", 9); return 1; } @@ -1827,7 +1886,18 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle show_pre_context(opt, gs, bol, eol, lno); else if (opt->funcname) show_funcname_line(opt, gs, bol, lno); - show_line(opt, bol, eol, gs->name, lno, ':'); + cno = opt->invert ? icol : col; + if (cno < 0) { + /* + * A negative cno indicates that there was no + * match on the line. We are thus inverted and + * being asked to show all lines that _don't_ + * match a given expression. Therefore, set cno + * to 0 to suggest the whole line matches. + */ + cno = 0; + } + show_line(opt, bol, eol, gs->name, lno, cno + 1, ':'); last_hit = lno; if (opt->funcbody) show_function = 1; @@ -1856,7 +1926,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle /* If the last hit is within the post context, * we need to show this line. */ - show_line(opt, bol, eol, gs->name, lno, '-'); + show_line(opt, bol, eol, gs->name, lno, col + 1, '-'); } next_line: @@ -1890,7 +1960,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle char buf[32]; if (opt->pathname) { output_color(opt, gs->name, strlen(gs->name), - opt->color_filename); + opt->colors[GREP_COLOR_FILENAME]); output_sep(opt, ':'); } xsnprintf(buf, sizeof(buf), "%u\n", count); @@ -62,6 +62,19 @@ enum grep_header_field { GREP_HEADER_FIELD_MAX }; +enum grep_color { + GREP_COLOR_CONTEXT, + GREP_COLOR_FILENAME, + GREP_COLOR_FUNCTION, + GREP_COLOR_LINENO, + GREP_COLOR_COLUMNNO, + GREP_COLOR_MATCH_CONTEXT, + GREP_COLOR_MATCH_SELECTED, + GREP_COLOR_SELECTED, + GREP_COLOR_SEP, + NR_GREP_COLORS +}; + struct grep_pat { struct grep_pat *next; const char *origin; @@ -127,6 +140,7 @@ struct grep_opt { int prefix_length; regex_t regexp; int linenum; + int columnnum; int invert; int ignore_case; int status_only; @@ -155,14 +169,7 @@ struct grep_opt { int funcbody; int extended_regexp_option; int pattern_type_option; - char color_context[COLOR_MAXLEN]; - char color_filename[COLOR_MAXLEN]; - char color_function[COLOR_MAXLEN]; - char color_lineno[COLOR_MAXLEN]; - char color_match_context[COLOR_MAXLEN]; - char color_match_selected[COLOR_MAXLEN]; - char color_selected[COLOR_MAXLEN]; - char color_sep[COLOR_MAXLEN]; + char colors[NR_GREP_COLORS][COLOR_MAXLEN]; unsigned pre_context; unsigned post_context; unsigned last_shown; @@ -409,6 +409,90 @@ void list_common_guides_help(void) putchar('\n'); } +struct slot_expansion { + const char *prefix; + const char *placeholder; + void (*fn)(struct string_list *list, const char *prefix); + int found; +}; + +void list_config_help(int for_human) +{ + struct slot_expansion slot_expansions[] = { + { "advice", "*", list_config_advices }, + { "color.branch", "<slot>", list_config_color_branch_slots }, + { "color.decorate", "<slot>", list_config_color_decorate_slots }, + { "color.diff", "<slot>", list_config_color_diff_slots }, + { "color.grep", "<slot>", list_config_color_grep_slots }, + { "color.interactive", "<slot>", list_config_color_interactive_slots }, + { "color.status", "<slot>", list_config_color_status_slots }, + { "fsck", "<msg-id>", list_config_fsck_msg_ids }, + { "receive.fsck", "<msg-id>", list_config_fsck_msg_ids }, + { NULL, NULL, NULL } + }; + const char **p; + struct slot_expansion *e; + struct string_list keys = STRING_LIST_INIT_DUP; + int i; + + for (p = config_name_list; *p; p++) { + const char *var = *p; + struct strbuf sb = STRBUF_INIT; + + for (e = slot_expansions; e->prefix; e++) { + + strbuf_reset(&sb); + strbuf_addf(&sb, "%s.%s", e->prefix, e->placeholder); + if (!strcasecmp(var, sb.buf)) { + e->fn(&keys, e->prefix); + e->found++; + break; + } + } + strbuf_release(&sb); + if (!e->prefix) + string_list_append(&keys, var); + } + + for (e = slot_expansions; e->prefix; e++) + if (!e->found) + BUG("slot_expansion %s.%s is not used", + e->prefix, e->placeholder); + + string_list_sort(&keys); + for (i = 0; i < keys.nr; i++) { + const char *var = keys.items[i].string; + const char *wildcard, *tag, *cut; + + if (for_human) { + puts(var); + continue; + } + + wildcard = strchr(var, '*'); + tag = strchr(var, '<'); + + if (!wildcard && !tag) { + puts(var); + continue; + } + + if (wildcard && !tag) + cut = wildcard; + else if (!wildcard && tag) + cut = tag; + else + cut = wildcard < tag ? wildcard : tag; + + /* + * We may produce duplicates, but that's up to + * git-completion.bash to handle + */ + printf("%.*s\n", (int)(cut - var), var); + } + string_list_clear(&keys, 0); +} + void list_all_cmds_help(void) { print_cmd_by_category(main_categories); @@ -1,7 +1,8 @@ #ifndef HELP_H #define HELP_H -struct string_list; +#include "string-list.h" +#include "strbuf.h" struct cmdnames { int alloc; @@ -21,6 +22,7 @@ static inline void mput_char(char c, unsigned int num) extern void list_common_cmds_help(void); extern void list_all_cmds_help(void); extern void list_common_guides_help(void); +extern void list_config_help(int for_human); extern void list_all_main_cmds(struct string_list *list); extern void list_all_other_cmds(struct string_list *list); @@ -42,4 +44,45 @@ extern void list_commands(unsigned int colopts, struct cmdnames *main_cmds, stru * ref to the command, to give suggested "correct" refs. */ extern void help_unknown_ref(const char *ref, const char *cmd, const char *error); + +static inline void list_config_item(struct string_list *list, + const char *prefix, + const char *str) +{ + string_list_append_nodup(list, xstrfmt("%s.%s", prefix, str)); +} + +#define define_list_config_array(array) \ +void list_config_##array(struct string_list *list, const char *prefix) \ +{ \ + int i; \ + for (i = 0; i < ARRAY_SIZE(array); i++) \ + if (array[i]) \ + list_config_item(list, prefix, array[i]); \ +} \ +struct string_list + +#define define_list_config_array_extra(array, values) \ +void list_config_##array(struct string_list *list, const char *prefix) \ +{ \ + int i; \ + static const char *extra[] = values; \ + for (i = 0; i < ARRAY_SIZE(extra); i++) \ + list_config_item(list, prefix, extra[i]); \ + for (i = 0; i < ARRAY_SIZE(array); i++) \ + if (array[i]) \ + list_config_item(list, prefix, array[i]); \ +} \ +struct string_list + +/* These are actually scattered over many C files */ +void list_config_advices(struct string_list *list, const char *prefix); +void list_config_color_branch_slots(struct string_list *list, const char *prefix); +void list_config_color_decorate_slots(struct string_list *list, const char *prefix); +void list_config_color_diff_slots(struct string_list *list, const char *prefix); +void list_config_color_grep_slots(struct string_list *list, const char *prefix); +void list_config_color_interactive_slots(struct string_list *list, const char *prefix); +void list_config_color_status_slots(struct string_list *list, const char *prefix); +void list_config_fsck_msg_ids(struct string_list *list, const char *prefix); + #endif /* HELP_H */ diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c index 6a3cc985c4..c0e2bd6a06 100644 --- a/list-objects-filter-options.c +++ b/list-objects-filter-options.c @@ -146,6 +146,8 @@ void partial_clone_get_default_filter_spec( /* * Parse default value, but silently ignore it if it is invalid. */ + if (!core_partial_clone_filter_default) + return; gently_parse_list_objects_filter(filter_options, core_partial_clone_filter_default, NULL); diff --git a/list-objects-filter.c b/list-objects-filter.c index 5b14d2711a..a0ba78b20c 100644 --- a/list-objects-filter.c +++ b/list-objects-filter.c @@ -11,6 +11,7 @@ #include "list-objects-filter.h" #include "list-objects-filter-options.h" #include "oidset.h" +#include "object-store.h" /* Remember to update object flag allocation in object.h */ /* diff --git a/list-objects.c b/list-objects.c index 3eec510357..3e5e1992eb 100644 --- a/list-objects.c +++ b/list-objects.c @@ -10,6 +10,7 @@ #include "list-objects-filter.h" #include "list-objects-filter-options.h" #include "packfile.h" +#include "object-store.h" static void process_blob(struct rev_info *revs, struct blob *blob, diff --git a/log-tree.c b/log-tree.c index 4aef85331e..4a3907fea0 100644 --- a/log-tree.c +++ b/log-tree.c @@ -1,6 +1,7 @@ #include "cache.h" #include "config.h" #include "diff.h" +#include "object-store.h" #include "commit.h" #include "tag.h" #include "graph.h" @@ -12,6 +13,7 @@ #include "gpg-interface.h" #include "sequencer.h" #include "line-log.h" +#include "help.h" static struct decoration name_decoration = { "object names" }; static int decoration_loaded; @@ -27,6 +29,15 @@ static char decoration_colors[][COLOR_MAXLEN] = { GIT_COLOR_BOLD_BLUE, /* GRAFTED */ }; +static const char *color_decorate_slots[] = { + [DECORATION_REF_LOCAL] = "branch", + [DECORATION_REF_REMOTE] = "remoteBranch", + [DECORATION_REF_TAG] = "tag", + [DECORATION_REF_STASH] = "stash", + [DECORATION_REF_HEAD] = "HEAD", + [DECORATION_GRAFTED] = "grafted", +}; + static const char *decorate_get_color(int decorate_use_color, enum decoration_type ix) { if (want_color(decorate_use_color)) @@ -34,34 +45,11 @@ static const char *decorate_get_color(int decorate_use_color, enum decoration_ty return ""; } -static int parse_decorate_color_slot(const char *slot) -{ - /* - * We're comparing with 'ignore-case' on - * (because config.c sets them all tolower), - * but let's match the letters in the literal - * string values here with how they are - * documented in Documentation/config.txt, for - * consistency. - * - * We love being consistent, don't we? - */ - if (!strcasecmp(slot, "branch")) - return DECORATION_REF_LOCAL; - if (!strcasecmp(slot, "remoteBranch")) - return DECORATION_REF_REMOTE; - if (!strcasecmp(slot, "tag")) - return DECORATION_REF_TAG; - if (!strcasecmp(slot, "stash")) - return DECORATION_REF_STASH; - if (!strcasecmp(slot, "HEAD")) - return DECORATION_REF_HEAD; - return -1; -} +define_list_config_array(color_decorate_slots); int parse_decorate_color_config(const char *var, const char *slot_name, const char *value) { - int slot = parse_decorate_color_slot(slot_name); + int slot = LOOKUP_CONFIG(color_decorate_slots, slot_name); if (slot < 0) return 0; if (!value) @@ -295,8 +283,12 @@ void show_decorations(struct rev_info *opt, struct commit *commit) { struct strbuf sb = STRBUF_INIT; - if (opt->show_source && commit->util) - fprintf(opt->diffopt.file, "\t%s", (char *) commit->util); + if (opt->sources) { + char **slot = revision_sources_peek(opt->sources, commit); + + if (slot && *slot) + fprintf(opt->diffopt.file, "\t%s", *slot); + } if (!opt->show_decorations) return; format_decorations(&sb, commit, opt->diffopt.use_color); @@ -1,6 +1,7 @@ #include "cache.h" #include "string-list.h" #include "mailmap.h" +#include "object-store.h" #define DEBUG_MAILMAP 0 #if DEBUG_MAILMAP diff --git a/match-trees.c b/match-trees.c index 72cc2baa3f..4cdeff53e1 100644 --- a/match-trees.c +++ b/match-trees.c @@ -1,6 +1,7 @@ #include "cache.h" #include "tree.h" #include "tree-walk.h" +#include "object-store.h" static int score_missing(unsigned mode, const char *path) { diff --git a/merge-blobs.c b/merge-blobs.c index fa49c17287..fabb8c19ce 100644 --- a/merge-blobs.c +++ b/merge-blobs.c @@ -4,6 +4,7 @@ #include "ll-merge.h" #include "blob.h" #include "merge-blobs.h" +#include "object-store.h" static int fill_mmfile_blob(mmfile_t *f, struct blob *obj) { diff --git a/merge-recursive.c b/merge-recursive.c index f110e1c5ec..113c1d6962 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -8,6 +8,7 @@ #include "advice.h" #include "lockfile.h" #include "cache-tree.h" +#include "object-store.h" #include "commit.h" #include "blob.h" #include "builtin.h" @@ -15,6 +16,7 @@ #include "diff.h" #include "diffcore.h" #include "tag.h" +#include "alloc.h" #include "unpack-trees.h" #include "string-list.h" #include "xdiff-interface.h" @@ -160,7 +162,7 @@ static struct tree *shift_tree_object(struct tree *one, struct tree *two, static struct commit *make_virtual_commit(struct tree *tree, const char *comment) { - struct commit *commit = alloc_commit_node(); + struct commit *commit = alloc_commit_node(the_repository); set_merge_remote_desc(commit, comment, (struct object *)commit); commit->maybe_tree = tree; @@ -181,7 +183,7 @@ static int oid_eq(const struct object_id *a, const struct object_id *b) enum rename_type { RENAME_NORMAL = 0, - RENAME_DIR, + RENAME_VIA_DIR, RENAME_DELETE, RENAME_ONE_FILE_TO_ONE, RENAME_ONE_FILE_TO_TWO, @@ -286,10 +288,12 @@ static void output(struct merge_options *o, int v, const char *fmt, ...) static void output_commit_title(struct merge_options *o, struct commit *commit) { + struct merge_remote_desc *desc; + strbuf_addchars(&o->obuf, ' ', o->call_depth * 2); - if (commit->util) - strbuf_addf(&o->obuf, "virtual %s\n", - merge_remote_util(commit)->name); + desc = merge_remote_util(commit); + if (desc) + strbuf_addf(&o->obuf, "virtual %s\n", desc->name); else { strbuf_add_unique_abbrev(&o->obuf, &commit->object.oid, DEFAULT_ABBREV); @@ -309,8 +313,8 @@ static void output_commit_title(struct merge_options *o, struct commit *commit) } static int add_cacheinfo(struct merge_options *o, - unsigned int mode, const struct object_id *oid, - const char *path, int stage, int refresh, int options) + unsigned int mode, const struct object_id *oid, + const char *path, int stage, int refresh, int options) { struct cache_entry *ce; int ret; @@ -417,8 +421,8 @@ struct tree *write_tree_from_memory(struct merge_options *o) } static int save_files_dirs(const struct object_id *oid, - struct strbuf *base, const char *path, - unsigned int mode, int stage, void *context) + struct strbuf *base, const char *path, + unsigned int mode, int stage, void *context) { struct path_hashmap_entry *entry; int baselen = base->len; @@ -539,7 +543,7 @@ static void record_df_conflict_files(struct merge_options *o, struct string_list *entries) { /* If there is a D/F conflict and the file for such a conflict - * currently exist in the working tree, we want to allow it to be + * currently exists in the working tree, we want to allow it to be * removed to make room for the corresponding directory if needed. * The files underneath the directories of such D/F conflicts will * be processed before the corresponding file involved in the D/F @@ -913,7 +917,7 @@ static int make_room_for_path(struct merge_options *o, const char *path) */ if (would_lose_untracked(path)) return err(o, _("refusing to lose untracked file at '%s'"), - path); + path); /* Successful unlink is good.. */ if (!unlink(path)) @@ -992,16 +996,16 @@ static int update_file_flags(struct merge_options *o, unlink(path); if (symlink(lnk, path)) ret = err(o, _("failed to symlink '%s': %s"), - path, strerror(errno)); + path, strerror(errno)); free(lnk); } else ret = err(o, _("do not know what to do with %06o %s '%s'"), mode, oid_to_hex(oid), path); - free_buf: + free_buf: free(buf); } - update_index: +update_index: if (!ret && update_cache) if (add_cacheinfo(o, mode, oid, path, 0, update_wd, ADD_CACHE_OK_TO_ADD)) @@ -1090,7 +1094,7 @@ static int merge_3way(struct merge_options *o, } static int find_first_merges(struct object_array *result, const char *path, - struct commit *a, struct commit *b) + struct commit *a, struct commit *b) { int i, j; struct object_array merges = OBJECT_ARRAY_INIT; @@ -1108,7 +1112,7 @@ static int find_first_merges(struct object_array *result, const char *path, /* get all revisions that merge commit a */ xsnprintf(merged_revision, sizeof(merged_revision), "^%s", - oid_to_hex(&a->object.oid)); + oid_to_hex(&a->object.oid)); init_revisions(&revs, NULL); rev_opts.submodule = path; /* FIXME: can't handle linked worktrees in submodules yet */ @@ -1250,12 +1254,12 @@ static int merge_submodule(struct merge_options *o, output(o, 2, _("Found a possible merge resolution for the submodule:\n")); print_commit((struct commit *) merges.objects[0].item); output(o, 2, _( - "If this is correct simply add it to the index " - "for example\n" - "by using:\n\n" - " git update-index --cacheinfo 160000 %s \"%s\"\n\n" - "which will accept this suggestion.\n"), - oid_to_hex(&merges.objects[0].item->oid), path); + "If this is correct simply add it to the index " + "for example\n" + "by using:\n\n" + " git update-index --cacheinfo 160000 %s \"%s\"\n\n" + "which will accept this suggestion.\n"), + oid_to_hex(&merges.objects[0].item->oid), path); break; default: @@ -1332,10 +1336,10 @@ static int merge_file_1(struct merge_options *o, result->clean = (merge_status == 0); } else if (S_ISGITLINK(a->mode)) { result->clean = merge_submodule(o, &result->oid, - one->path, - &one->oid, - &a->oid, - &b->oid); + one->path, + &one->oid, + &a->oid, + &b->oid); } else if (S_ISLNK(a->mode)) { switch (o->recursive_variant) { case MERGE_RECURSIVE_NORMAL: @@ -1410,11 +1414,17 @@ static int merge_file_one(struct merge_options *o, return merge_file_1(o, &one, &a, &b, path, branch1, branch2, mfi); } -static int conflict_rename_dir(struct merge_options *o, - struct diff_filepair *pair, - const char *rename_branch, - const char *other_branch) +static int handle_rename_via_dir(struct merge_options *o, + struct diff_filepair *pair, + const char *rename_branch, + const char *other_branch) { + /* + * Handle file adds that need to be renamed due to directory rename + * detection. This differs from handle_rename_normal, because + * there is no content merge to do; just move the file into the + * desired final location. + */ const struct diff_filespec *dest = pair->two; if (!o->call_depth && would_lose_untracked(dest->path)) { @@ -1443,13 +1453,13 @@ static int conflict_rename_dir(struct merge_options *o, } static int handle_change_delete(struct merge_options *o, - const char *path, const char *old_path, - const struct object_id *o_oid, int o_mode, - const struct object_id *changed_oid, - int changed_mode, - const char *change_branch, - const char *delete_branch, - const char *change, const char *change_past) + const char *path, const char *old_path, + const struct object_id *o_oid, int o_mode, + const struct object_id *changed_oid, + int changed_mode, + const char *change_branch, + const char *delete_branch, + const char *change, const char *change_past) { char *alt_path = NULL; const char *update_path = path; @@ -1470,6 +1480,21 @@ static int handle_change_delete(struct merge_options *o, if (!ret) ret = update_file(o, 0, o_oid, o_mode, update_path); } else { + /* + * Despite the four nearly duplicate messages and argument + * lists below and the ugliness of the nested if-statements, + * having complete messages makes the job easier for + * translators. + * + * The slight variance among the cases is due to the fact + * that: + * 1) directory/file conflicts (in effect if + * !alt_path) could cause us to need to write the + * file to a different path. + * 2) renames (in effect if !old_path) could mean that + * there are two names for the path that the user + * may know the file by. + */ if (!alt_path) { if (!old_path) { output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s " @@ -1509,10 +1534,10 @@ static int handle_change_delete(struct merge_options *o, return ret; } -static int conflict_rename_delete(struct merge_options *o, - struct diff_filepair *pair, - const char *rename_branch, - const char *delete_branch) +static int handle_rename_delete(struct merge_options *o, + struct diff_filepair *pair, + const char *rename_branch, + const char *delete_branch) { const struct diff_filespec *orig = pair->one; const struct diff_filespec *dest = pair->two; @@ -1614,8 +1639,8 @@ static int handle_file(struct merge_options *o, return ret; } -static int conflict_rename_rename_1to2(struct merge_options *o, - struct rename_conflict_info *ci) +static int handle_rename_rename_1to2(struct merge_options *o, + struct rename_conflict_info *ci) { /* One file was renamed in both branches, but to different names. */ struct diff_filespec *one = ci->pair1->one; @@ -1676,8 +1701,8 @@ static int conflict_rename_rename_1to2(struct merge_options *o, return 0; } -static int conflict_rename_rename_2to1(struct merge_options *o, - struct rename_conflict_info *ci) +static int handle_rename_rename_2to1(struct merge_options *o, + struct rename_conflict_info *ci) { /* Two files, a & b, were renamed to the same thing, c. */ struct diff_filespec *a = ci->pair1->one; @@ -2419,7 +2444,7 @@ static void apply_directory_rename_modifications(struct merge_options *o, * "NOTE" in update_stages(), doing so will modify the current * in-memory index which will break calls to would_lose_untracked() * that we need to make. Instead, we need to just make sure that - * the various conflict_rename_*() functions update the index + * the various handle_rename_*() functions update the index * explicitly rather than relying on unpack_trees() to have done it. */ get_tree_entry(&tree->object.oid, @@ -2692,7 +2717,7 @@ static int process_renames(struct merge_options *o, if (oid_eq(&src_other.oid, &null_oid) && ren1->add_turned_into_rename) { - setup_rename_conflict_info(RENAME_DIR, + setup_rename_conflict_info(RENAME_VIA_DIR, ren1->pair, NULL, branch1, @@ -2823,12 +2848,12 @@ static void initial_cleanup_rename(struct diff_queue_struct *pairs, free(pairs); } -static int handle_renames(struct merge_options *o, - struct tree *common, - struct tree *head, - struct tree *merge, - struct string_list *entries, - struct rename_info *ri) +static int detect_and_process_renames(struct merge_options *o, + struct tree *common, + struct tree *head, + struct tree *merge, + struct string_list *entries, + struct rename_info *ri) { struct diff_queue_struct *head_pairs, *merge_pairs; struct hashmap *dir_re_head, *dir_re_merge; @@ -2904,7 +2929,8 @@ static struct object_id *stage_oid(const struct object_id *oid, unsigned mode) } static int read_oid_strbuf(struct merge_options *o, - const struct object_id *oid, struct strbuf *dst) + const struct object_id *oid, + struct strbuf *dst) { void *buf; enum object_type type; @@ -2957,10 +2983,10 @@ error_return: } static int handle_modify_delete(struct merge_options *o, - const char *path, - struct object_id *o_oid, int o_mode, - struct object_id *a_oid, int a_mode, - struct object_id *b_oid, int b_mode) + const char *path, + struct object_id *o_oid, int o_mode, + struct object_id *a_oid, int a_mode, + struct object_id *b_oid, int b_mode) { const char *modify_branch, *delete_branch; struct object_id *changed_oid; @@ -3098,12 +3124,12 @@ static int merge_content(struct merge_options *o, return !is_dirty && mfi.clean; } -static int conflict_rename_normal(struct merge_options *o, - const char *path, - struct object_id *o_oid, unsigned int o_mode, - struct object_id *a_oid, unsigned int a_mode, - struct object_id *b_oid, unsigned int b_mode, - struct rename_conflict_info *ci) +static int handle_rename_normal(struct merge_options *o, + const char *path, + struct object_id *o_oid, unsigned int o_mode, + struct object_id *a_oid, unsigned int a_mode, + struct object_id *b_oid, unsigned int b_mode, + struct rename_conflict_info *ci) { /* Merge the content and write it out */ return merge_content(o, path, was_dirty(o, path), @@ -3130,37 +3156,37 @@ static int process_entry(struct merge_options *o, switch (conflict_info->rename_type) { case RENAME_NORMAL: case RENAME_ONE_FILE_TO_ONE: - clean_merge = conflict_rename_normal(o, - path, - o_oid, o_mode, - a_oid, a_mode, - b_oid, b_mode, - conflict_info); + clean_merge = handle_rename_normal(o, + path, + o_oid, o_mode, + a_oid, a_mode, + b_oid, b_mode, + conflict_info); break; - case RENAME_DIR: + case RENAME_VIA_DIR: clean_merge = 1; - if (conflict_rename_dir(o, - conflict_info->pair1, - conflict_info->branch1, - conflict_info->branch2)) + if (handle_rename_via_dir(o, + conflict_info->pair1, + conflict_info->branch1, + conflict_info->branch2)) clean_merge = -1; break; case RENAME_DELETE: clean_merge = 0; - if (conflict_rename_delete(o, - conflict_info->pair1, - conflict_info->branch1, - conflict_info->branch2)) + if (handle_rename_delete(o, + conflict_info->pair1, + conflict_info->branch1, + conflict_info->branch2)) clean_merge = -1; break; case RENAME_ONE_FILE_TO_TWO: clean_merge = 0; - if (conflict_rename_rename_1to2(o, conflict_info)) + if (handle_rename_rename_1to2(o, conflict_info)) clean_merge = -1; break; case RENAME_TWO_FILES_TO_ONE: clean_merge = 0; - if (conflict_rename_rename_2to1(o, conflict_info)) + if (handle_rename_rename_2to1(o, conflict_info)) clean_merge = -1; break; default: @@ -3300,8 +3326,8 @@ int merge_trees(struct merge_options *o, get_files_dirs(o, merge); entries = get_unmerged(); - clean = handle_renames(o, common, head, merge, entries, - &re_info); + clean = detect_and_process_renames(o, common, head, merge, + entries, &re_info); record_df_conflict_files(o, entries); if (clean < 0) goto cleanup; @@ -3325,7 +3351,7 @@ int merge_trees(struct merge_options *o, entries->items[i].string); } -cleanup: + cleanup: final_cleanup_renames(&re_info); string_list_clear(entries, 1); @@ -3493,14 +3519,14 @@ int merge_recursive_generic(struct merge_options *o, struct commit *base; if (!(base = get_ref(base_list[i], oid_to_hex(base_list[i])))) return err(o, _("Could not parse object '%s'"), - oid_to_hex(base_list[i])); + oid_to_hex(base_list[i])); commit_list_insert(base, &ca); } } hold_locked_index(&lock, LOCK_DIE_ON_ERROR); clean = merge_recursive(o, head_commit, next_commit, ca, - result); + result); if (clean < 0) { rollback_lock_file(&lock); return clean; diff --git a/notes-cache.c b/notes-cache.c index e61988e503..d577003177 100644 --- a/notes-cache.c +++ b/notes-cache.c @@ -1,5 +1,6 @@ #include "cache.h" #include "notes-cache.h" +#include "object-store.h" #include "commit.h" #include "refs.h" diff --git a/notes-merge.c b/notes-merge.c index d613e06574..9cc2ee16a8 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -1,6 +1,7 @@ #include "cache.h" #include "commit.h" #include "refs.h" +#include "object-store.h" #include "diff.h" #include "diffcore.h" #include "xdiff-interface.h" @@ -1,6 +1,7 @@ #include "cache.h" #include "config.h" #include "notes.h" +#include "object-store.h" #include "blob.h" #include "tree.h" #include "utf8.h" diff --git a/object-store.h b/object-store.h index d683112fd7..a3db17bbf5 100644 --- a/object-store.h +++ b/object-store.h @@ -139,4 +139,121 @@ void sha1_file_name(struct repository *r, struct strbuf *buf, const unsigned cha void *map_sha1_file(struct repository *r, const unsigned char *sha1, unsigned long *size); +extern void *read_object_file_extended(const struct object_id *oid, + enum object_type *type, + unsigned long *size, int lookup_replace); +static inline void *read_object_file(const struct object_id *oid, enum object_type *type, unsigned long *size) +{ + return read_object_file_extended(oid, type, size, 1); +} + +/* Read and unpack an object file into memory, write memory to an object file */ +int oid_object_info(struct repository *r, const struct object_id *, unsigned long *); + +extern int hash_object_file(const void *buf, unsigned long len, + const char *type, struct object_id *oid); + +extern int write_object_file(const void *buf, unsigned long len, + const char *type, struct object_id *oid); + +extern int hash_object_file_literally(const void *buf, unsigned long len, + const char *type, struct object_id *oid, + unsigned flags); + +extern int pretend_object_file(void *, unsigned long, enum object_type, + struct object_id *oid); + +extern int force_object_loose(const struct object_id *oid, time_t mtime); + +/* + * Open the loose object at path, check its hash, and return the contents, + * type, and size. If the object is a blob, then "contents" may return NULL, + * to allow streaming of large blobs. + * + * Returns 0 on success, negative on error (details may be written to stderr). + */ +int read_loose_object(const char *path, + const struct object_id *expected_oid, + enum object_type *type, + unsigned long *size, + void **contents); + +/* + * Convenience for sha1_object_info_extended() with a NULL struct + * object_info. OBJECT_INFO_SKIP_CACHED is automatically set; pass + * nonzero flags to also set other flags. + */ +extern int has_sha1_file_with_flags(const unsigned char *sha1, int flags); +static inline int has_sha1_file(const unsigned char *sha1) +{ + return has_sha1_file_with_flags(sha1, 0); +} + +/* Same as the above, except for struct object_id. */ +extern int has_object_file(const struct object_id *oid); +extern int has_object_file_with_flags(const struct object_id *oid, int flags); + +/* + * Return true iff an alternate object database has a loose object + * with the specified name. This function does not respect replace + * references. + */ +extern int has_loose_object_nonlocal(const struct object_id *); + +extern void assert_oid_type(const struct object_id *oid, enum object_type expect); + +struct object_info { + /* Request */ + enum object_type *typep; + unsigned long *sizep; + off_t *disk_sizep; + unsigned char *delta_base_sha1; + struct strbuf *type_name; + void **contentp; + + /* Response */ + enum { + OI_CACHED, + OI_LOOSE, + OI_PACKED, + OI_DBCACHED + } whence; + union { + /* + * struct { + * ... Nothing to expose in this case + * } cached; + * struct { + * ... Nothing to expose in this case + * } loose; + */ + struct { + struct packed_git *pack; + off_t offset; + unsigned int is_delta; + } packed; + } u; +}; + +/* + * Initializer for a "struct object_info" that wants no items. You may + * also memset() the memory to all-zeroes. + */ +#define OBJECT_INFO_INIT {NULL} + +/* Invoke lookup_replace_object() on the given hash */ +#define OBJECT_INFO_LOOKUP_REPLACE 1 +/* Allow reading from a loose object file of unknown/bogus type */ +#define OBJECT_INFO_ALLOW_UNKNOWN_TYPE 2 +/* Do not check cached storage */ +#define OBJECT_INFO_SKIP_CACHED 4 +/* Do not retry packed storage after checking packed and loose storage */ +#define OBJECT_INFO_QUICK 8 +/* Do not check loose object */ +#define OBJECT_INFO_IGNORE_LOOSE 16 + +int oid_object_info_extended(struct repository *r, + const struct object_id *, + struct object_info *, unsigned flags); + #endif /* OBJECT_STORE_H */ @@ -1,24 +1,23 @@ #include "cache.h" #include "object.h" #include "replace-object.h" +#include "object-store.h" #include "blob.h" #include "tree.h" #include "commit.h" #include "tag.h" +#include "alloc.h" #include "object-store.h" #include "packfile.h" -static struct object **obj_hash; -static int nr_objs, obj_hash_size; - unsigned int get_max_object_index(void) { - return obj_hash_size; + return the_repository->parsed_objects->obj_hash_size; } struct object *get_indexed_object(unsigned int idx) { - return obj_hash[idx]; + return the_repository->parsed_objects->obj_hash[idx]; } static const char *object_type_strings[] = { @@ -90,15 +89,16 @@ struct object *lookup_object(const unsigned char *sha1) unsigned int i, first; struct object *obj; - if (!obj_hash) + if (!the_repository->parsed_objects->obj_hash) return NULL; - first = i = hash_obj(sha1, obj_hash_size); - while ((obj = obj_hash[i]) != NULL) { + first = i = hash_obj(sha1, + the_repository->parsed_objects->obj_hash_size); + while ((obj = the_repository->parsed_objects->obj_hash[i]) != NULL) { if (!hashcmp(sha1, obj->oid.hash)) break; i++; - if (i == obj_hash_size) + if (i == the_repository->parsed_objects->obj_hash_size) i = 0; } if (obj && i != first) { @@ -107,7 +107,8 @@ struct object *lookup_object(const unsigned char *sha1) * that we do not need to walk the hash table the next * time we look for it. */ - SWAP(obj_hash[i], obj_hash[first]); + SWAP(the_repository->parsed_objects->obj_hash[i], + the_repository->parsed_objects->obj_hash[first]); } return obj; } @@ -117,29 +118,30 @@ struct object *lookup_object(const unsigned char *sha1) * power of 2 (but at least 32). Copy the existing values to the new * hash map. */ -static void grow_object_hash(void) +static void grow_object_hash(struct repository *r) { int i; /* * Note that this size must always be power-of-2 to match hash_obj * above. */ - int new_hash_size = obj_hash_size < 32 ? 32 : 2 * obj_hash_size; + int new_hash_size = r->parsed_objects->obj_hash_size < 32 ? 32 : 2 * r->parsed_objects->obj_hash_size; struct object **new_hash; new_hash = xcalloc(new_hash_size, sizeof(struct object *)); - for (i = 0; i < obj_hash_size; i++) { - struct object *obj = obj_hash[i]; + for (i = 0; i < r->parsed_objects->obj_hash_size; i++) { + struct object *obj = r->parsed_objects->obj_hash[i]; + if (!obj) continue; insert_obj_hash(obj, new_hash, new_hash_size); } - free(obj_hash); - obj_hash = new_hash; - obj_hash_size = new_hash_size; + free(r->parsed_objects->obj_hash); + r->parsed_objects->obj_hash = new_hash; + r->parsed_objects->obj_hash_size = new_hash_size; } -void *create_object(const unsigned char *sha1, void *o) +void *create_object(struct repository *r, const unsigned char *sha1, void *o) { struct object *obj = o; @@ -147,11 +149,12 @@ void *create_object(const unsigned char *sha1, void *o) obj->flags = 0; hashcpy(obj->oid.hash, sha1); - if (obj_hash_size - 1 <= nr_objs * 2) - grow_object_hash(); + if (r->parsed_objects->obj_hash_size - 1 <= r->parsed_objects->nr_objs * 2) + grow_object_hash(r); - insert_obj_hash(obj, obj_hash, obj_hash_size); - nr_objs++; + insert_obj_hash(obj, r->parsed_objects->obj_hash, + r->parsed_objects->obj_hash_size); + r->parsed_objects->nr_objs++; return obj; } @@ -161,7 +164,7 @@ void *object_as_type(struct object *obj, enum object_type type, int quiet) return obj; else if (obj->type == OBJ_NONE) { if (type == OBJ_COMMIT) - ((struct commit *)obj)->index = alloc_commit_index(); + ((struct commit *)obj)->index = alloc_commit_index(the_repository); obj->type = type; return obj; } @@ -178,7 +181,8 @@ struct object *lookup_unknown_object(const unsigned char *sha1) { struct object *obj = lookup_object(sha1); if (!obj) - obj = create_object(sha1, alloc_object_node()); + obj = create_object(the_repository, sha1, + alloc_object_node(the_repository)); return obj; } @@ -210,7 +214,7 @@ struct object *parse_object_buffer(const struct object_id *oid, enum object_type } else if (type == OBJ_COMMIT) { struct commit *commit = lookup_commit(oid); if (commit) { - if (parse_commit_buffer(commit, buffer, size)) + if (parse_commit_buffer(commit, buffer, size, 1)) return NULL; if (!get_cached_commit_buffer(commit, NULL)) { set_commit_buffer(commit, buffer, size); @@ -431,8 +435,8 @@ void clear_object_flags(unsigned flags) { int i; - for (i=0; i < obj_hash_size; i++) { - struct object *obj = obj_hash[i]; + for (i=0; i < the_repository->parsed_objects->obj_hash_size; i++) { + struct object *obj = the_repository->parsed_objects->obj_hash[i]; if (obj) obj->flags &= ~flags; } @@ -442,13 +446,30 @@ void clear_commit_marks_all(unsigned int flags) { int i; - for (i = 0; i < obj_hash_size; i++) { - struct object *obj = obj_hash[i]; + for (i = 0; i < the_repository->parsed_objects->obj_hash_size; i++) { + struct object *obj = the_repository->parsed_objects->obj_hash[i]; if (obj && obj->type == OBJ_COMMIT) obj->flags &= ~flags; } } +struct parsed_object_pool *parsed_object_pool_new(void) +{ + struct parsed_object_pool *o = xmalloc(sizeof(*o)); + memset(o, 0, sizeof(*o)); + + o->blob_state = allocate_alloc_state(); + o->tree_state = allocate_alloc_state(); + o->commit_state = allocate_alloc_state(); + o->tag_state = allocate_alloc_state(); + o->object_state = allocate_alloc_state(); + + o->is_shallow = -1; + o->shallow_stat = xcalloc(1, sizeof(*o->shallow_stat)); + + return o; +} + struct raw_object_store *raw_object_store_new(void) { struct raw_object_store *o = xmalloc(sizeof(*o)); @@ -491,3 +512,43 @@ void raw_object_store_clear(struct raw_object_store *o) close_all_packs(o); o->packed_git = NULL; } + +void parsed_object_pool_clear(struct parsed_object_pool *o) +{ + /* + * As objects are allocated in slabs (see alloc.c), we do + * not need to free each object, but each slab instead. + * + * Before doing so, we need to free any additional memory + * the objects may hold. + */ + unsigned i; + + for (i = 0; i < o->obj_hash_size; i++) { + struct object *obj = o->obj_hash[i]; + + if (!obj) + continue; + + if (obj->type == OBJ_TREE) + free_tree_buffer((struct tree*)obj); + else if (obj->type == OBJ_COMMIT) + release_commit_memory((struct commit*)obj); + else if (obj->type == OBJ_TAG) + release_tag_memory((struct tag*)obj); + } + + FREE_AND_NULL(o->obj_hash); + o->obj_hash_size = 0; + + clear_alloc_state(o->blob_state); + clear_alloc_state(o->tree_state); + clear_alloc_state(o->commit_state); + clear_alloc_state(o->tag_state); + clear_alloc_state(o->object_state); + FREE_AND_NULL(o->blob_state); + FREE_AND_NULL(o->tree_state); + FREE_AND_NULL(o->commit_state); + FREE_AND_NULL(o->tag_state); + FREE_AND_NULL(o->object_state); +} @@ -1,6 +1,32 @@ #ifndef OBJECT_H #define OBJECT_H +struct parsed_object_pool { + struct object **obj_hash; + int nr_objs, obj_hash_size; + + /* TODO: migrate alloc_states to mem-pool? */ + struct alloc_state *blob_state; + struct alloc_state *tree_state; + struct alloc_state *commit_state; + struct alloc_state *tag_state; + struct alloc_state *object_state; + unsigned commit_count; + + /* parent substitutions from .git/info/grafts and .git/shallow */ + struct commit_graft **grafts; + int grafts_alloc, grafts_nr; + + int is_shallow; + struct stat_validity *shallow_stat; + char *alternate_shallow_file; + + int commit_graft_prepared; +}; + +struct parsed_object_pool *parsed_object_pool_new(void); +void parsed_object_pool_clear(struct parsed_object_pool *o); + struct object_list { struct object *item; struct object_list *next; @@ -42,6 +68,7 @@ struct object_array { * builtin/index-pack.c: 2021 * builtin/pack-objects.c: 20 * builtin/reflog.c: 10--12 + * builtin/show-branch.c: 0-------------------------------------------26 * builtin/unpack-objects.c: 2021 */ #define FLAG_BITS 27 @@ -84,7 +111,7 @@ extern struct object *get_indexed_object(unsigned int); */ struct object *lookup_object(const unsigned char *sha1); -extern void *create_object(const unsigned char *sha1, void *obj); +extern void *create_object(struct repository *r, const unsigned char *sha1, void *obj); void *object_as_type(struct object *obj, enum object_type type, int quiet); diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c index 7b2dc3e7dc..d977e9bacb 100644 --- a/pack-bitmap-write.c +++ b/pack-bitmap-write.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "object-store.h" #include "commit.h" #include "tag.h" #include "diff.h" @@ -360,11 +361,17 @@ static int date_compare(const void *_a, const void *_b) void bitmap_writer_reuse_bitmaps(struct packing_data *to_pack) { - if (prepare_bitmap_git() < 0) + struct bitmap_index *bitmap_git; + if (!(bitmap_git = prepare_bitmap_git())) return; writer.reused = kh_init_sha1(); - rebuild_existing_bitmaps(to_pack, writer.reused, writer.show_progress); + rebuild_existing_bitmaps(bitmap_git, to_pack, writer.reused, + writer.show_progress); + /* + * NEEDSWORK: rebuild_existing_bitmaps() makes writer.reused reference + * some bitmaps in bitmap_git, so we can't free the latter. + */ } static struct ewah_bitmap *find_reused_bitmap(const unsigned char *sha1) diff --git a/pack-bitmap.c b/pack-bitmap.c index 18f8b22aeb..f0a1937a1c 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -25,14 +25,14 @@ struct stored_bitmap { }; /* - * The currently active bitmap index. By design, repositories only have + * The active bitmap index for a repository. By design, repositories only have * a single bitmap index available (the index for the biggest packfile in * the repository), since bitmap indexes need full closure. * * If there is more than one bitmap index available (e.g. because of alternates), * the active bitmap index is the largest one. */ -static struct bitmap_index { +struct bitmap_index { /* Packfile to which this bitmap index belongs to */ struct packed_git *pack; @@ -66,7 +66,7 @@ static struct bitmap_index { /* Number of bitmapped commits */ uint32_t entry_count; - /* Name-hash cache (or NULL if not present). */ + /* If not NULL, this is a name-hash cache pointing into map. */ uint32_t *hashes; /* @@ -90,8 +90,7 @@ static struct bitmap_index { unsigned int version; unsigned loaded : 1; - -} bitmap_git; +}; static struct ewah_bitmap *lookup_stored_bitmap(struct stored_bitmap *st) { @@ -259,7 +258,7 @@ static char *pack_bitmap_filename(struct packed_git *p) return xstrfmt("%.*s.bitmap", (int)len, p->pack_name); } -static int open_pack_bitmap_1(struct packed_git *packfile) +static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git *packfile) { int fd; struct stat st; @@ -280,117 +279,122 @@ static int open_pack_bitmap_1(struct packed_git *packfile) return -1; } - if (bitmap_git.pack) { + if (bitmap_git->pack) { warning("ignoring extra bitmap file: %s", packfile->pack_name); close(fd); return -1; } - bitmap_git.pack = packfile; - bitmap_git.map_size = xsize_t(st.st_size); - bitmap_git.map = xmmap(NULL, bitmap_git.map_size, PROT_READ, MAP_PRIVATE, fd, 0); - bitmap_git.map_pos = 0; + bitmap_git->pack = packfile; + bitmap_git->map_size = xsize_t(st.st_size); + bitmap_git->map = xmmap(NULL, bitmap_git->map_size, PROT_READ, MAP_PRIVATE, fd, 0); + bitmap_git->map_pos = 0; close(fd); - if (load_bitmap_header(&bitmap_git) < 0) { - munmap(bitmap_git.map, bitmap_git.map_size); - bitmap_git.map = NULL; - bitmap_git.map_size = 0; + if (load_bitmap_header(bitmap_git) < 0) { + munmap(bitmap_git->map, bitmap_git->map_size); + bitmap_git->map = NULL; + bitmap_git->map_size = 0; return -1; } return 0; } -static int load_pack_bitmap(void) +static int load_pack_bitmap(struct bitmap_index *bitmap_git) { - assert(bitmap_git.map && !bitmap_git.loaded); + assert(bitmap_git->map && !bitmap_git->loaded); - bitmap_git.bitmaps = kh_init_sha1(); - bitmap_git.ext_index.positions = kh_init_sha1_pos(); - load_pack_revindex(bitmap_git.pack); + bitmap_git->bitmaps = kh_init_sha1(); + bitmap_git->ext_index.positions = kh_init_sha1_pos(); + load_pack_revindex(bitmap_git->pack); - if (!(bitmap_git.commits = read_bitmap_1(&bitmap_git)) || - !(bitmap_git.trees = read_bitmap_1(&bitmap_git)) || - !(bitmap_git.blobs = read_bitmap_1(&bitmap_git)) || - !(bitmap_git.tags = read_bitmap_1(&bitmap_git))) + if (!(bitmap_git->commits = read_bitmap_1(bitmap_git)) || + !(bitmap_git->trees = read_bitmap_1(bitmap_git)) || + !(bitmap_git->blobs = read_bitmap_1(bitmap_git)) || + !(bitmap_git->tags = read_bitmap_1(bitmap_git))) goto failed; - if (load_bitmap_entries_v1(&bitmap_git) < 0) + if (load_bitmap_entries_v1(bitmap_git) < 0) goto failed; - bitmap_git.loaded = 1; + bitmap_git->loaded = 1; return 0; failed: - munmap(bitmap_git.map, bitmap_git.map_size); - bitmap_git.map = NULL; - bitmap_git.map_size = 0; + munmap(bitmap_git->map, bitmap_git->map_size); + bitmap_git->map = NULL; + bitmap_git->map_size = 0; return -1; } -static int open_pack_bitmap(void) +static int open_pack_bitmap(struct bitmap_index *bitmap_git) { struct packed_git *p; int ret = -1; - assert(!bitmap_git.map && !bitmap_git.loaded); + assert(!bitmap_git->map && !bitmap_git->loaded); for (p = get_packed_git(the_repository); p; p = p->next) { - if (open_pack_bitmap_1(p) == 0) + if (open_pack_bitmap_1(bitmap_git, p) == 0) ret = 0; } return ret; } -int prepare_bitmap_git(void) +struct bitmap_index *prepare_bitmap_git(void) { - if (bitmap_git.loaded) - return 0; + struct bitmap_index *bitmap_git = xcalloc(1, sizeof(*bitmap_git)); - if (!open_pack_bitmap()) - return load_pack_bitmap(); + if (!open_pack_bitmap(bitmap_git) && !load_pack_bitmap(bitmap_git)) + return bitmap_git; - return -1; + free_bitmap_index(bitmap_git); + return NULL; } struct include_data { + struct bitmap_index *bitmap_git; struct bitmap *base; struct bitmap *seen; }; -static inline int bitmap_position_extended(const unsigned char *sha1) +static inline int bitmap_position_extended(struct bitmap_index *bitmap_git, + const unsigned char *sha1) { - khash_sha1_pos *positions = bitmap_git.ext_index.positions; + khash_sha1_pos *positions = bitmap_git->ext_index.positions; khiter_t pos = kh_get_sha1_pos(positions, sha1); if (pos < kh_end(positions)) { int bitmap_pos = kh_value(positions, pos); - return bitmap_pos + bitmap_git.pack->num_objects; + return bitmap_pos + bitmap_git->pack->num_objects; } return -1; } -static inline int bitmap_position_packfile(const unsigned char *sha1) +static inline int bitmap_position_packfile(struct bitmap_index *bitmap_git, + const unsigned char *sha1) { - off_t offset = find_pack_entry_one(sha1, bitmap_git.pack); + off_t offset = find_pack_entry_one(sha1, bitmap_git->pack); if (!offset) return -1; - return find_revindex_position(bitmap_git.pack, offset); + return find_revindex_position(bitmap_git->pack, offset); } -static int bitmap_position(const unsigned char *sha1) +static int bitmap_position(struct bitmap_index *bitmap_git, + const unsigned char *sha1) { - int pos = bitmap_position_packfile(sha1); - return (pos >= 0) ? pos : bitmap_position_extended(sha1); + int pos = bitmap_position_packfile(bitmap_git, sha1); + return (pos >= 0) ? pos : bitmap_position_extended(bitmap_git, sha1); } -static int ext_index_add_object(struct object *object, const char *name) +static int ext_index_add_object(struct bitmap_index *bitmap_git, + struct object *object, const char *name) { - struct eindex *eindex = &bitmap_git.ext_index; + struct eindex *eindex = &bitmap_git->ext_index; khiter_t hash_pos; int hash_ret; @@ -413,27 +417,34 @@ static int ext_index_add_object(struct object *object, const char *name) bitmap_pos = kh_value(eindex->positions, hash_pos); } - return bitmap_pos + bitmap_git.pack->num_objects; + return bitmap_pos + bitmap_git->pack->num_objects; } -static void show_object(struct object *object, const char *name, void *data) +struct bitmap_show_data { + struct bitmap_index *bitmap_git; + struct bitmap *base; +}; + +static void show_object(struct object *object, const char *name, void *data_) { - struct bitmap *base = data; + struct bitmap_show_data *data = data_; int bitmap_pos; - bitmap_pos = bitmap_position(object->oid.hash); + bitmap_pos = bitmap_position(data->bitmap_git, object->oid.hash); if (bitmap_pos < 0) - bitmap_pos = ext_index_add_object(object, name); + bitmap_pos = ext_index_add_object(data->bitmap_git, object, + name); - bitmap_set(base, bitmap_pos); + bitmap_set(data->base, bitmap_pos); } static void show_commit(struct commit *commit, void *data) { } -static int add_to_include_set(struct include_data *data, +static int add_to_include_set(struct bitmap_index *bitmap_git, + struct include_data *data, const unsigned char *sha1, int bitmap_pos) { @@ -445,9 +456,9 @@ static int add_to_include_set(struct include_data *data, if (bitmap_get(data->base, bitmap_pos)) return 0; - hash_pos = kh_get_sha1(bitmap_git.bitmaps, sha1); - if (hash_pos < kh_end(bitmap_git.bitmaps)) { - struct stored_bitmap *st = kh_value(bitmap_git.bitmaps, hash_pos); + hash_pos = kh_get_sha1(bitmap_git->bitmaps, sha1); + if (hash_pos < kh_end(bitmap_git->bitmaps)) { + struct stored_bitmap *st = kh_value(bitmap_git->bitmaps, hash_pos); bitmap_or_ewah(data->base, lookup_stored_bitmap(st)); return 0; } @@ -461,11 +472,14 @@ static int should_include(struct commit *commit, void *_data) struct include_data *data = _data; int bitmap_pos; - bitmap_pos = bitmap_position(commit->object.oid.hash); + bitmap_pos = bitmap_position(data->bitmap_git, commit->object.oid.hash); if (bitmap_pos < 0) - bitmap_pos = ext_index_add_object((struct object *)commit, NULL); + bitmap_pos = ext_index_add_object(data->bitmap_git, + (struct object *)commit, + NULL); - if (!add_to_include_set(data, commit->object.oid.hash, bitmap_pos)) { + if (!add_to_include_set(data->bitmap_git, data, commit->object.oid.hash, + bitmap_pos)) { struct commit_list *parent = commit->parents; while (parent) { @@ -479,7 +493,8 @@ static int should_include(struct commit *commit, void *_data) return 1; } -static struct bitmap *find_objects(struct rev_info *revs, +static struct bitmap *find_objects(struct bitmap_index *bitmap_git, + struct rev_info *revs, struct object_list *roots, struct bitmap *seen) { @@ -501,10 +516,10 @@ static struct bitmap *find_objects(struct rev_info *revs, roots = roots->next; if (object->type == OBJ_COMMIT) { - khiter_t pos = kh_get_sha1(bitmap_git.bitmaps, object->oid.hash); + khiter_t pos = kh_get_sha1(bitmap_git->bitmaps, object->oid.hash); - if (pos < kh_end(bitmap_git.bitmaps)) { - struct stored_bitmap *st = kh_value(bitmap_git.bitmaps, pos); + if (pos < kh_end(bitmap_git->bitmaps)) { + struct stored_bitmap *st = kh_value(bitmap_git->bitmaps, pos); struct ewah_bitmap *or_with = lookup_stored_bitmap(st); if (base == NULL) @@ -543,7 +558,7 @@ static struct bitmap *find_objects(struct rev_info *revs, int pos; roots = roots->next; - pos = bitmap_position(object->oid.hash); + pos = bitmap_position(bitmap_git, object->oid.hash); if (pos < 0 || base == NULL || !bitmap_get(base, pos)) { object->flags &= ~UNINTERESTING; @@ -556,10 +571,12 @@ static struct bitmap *find_objects(struct rev_info *revs, if (needs_walk) { struct include_data incdata; + struct bitmap_show_data show_data; if (base == NULL) base = bitmap_new(); + incdata.bitmap_git = bitmap_git; incdata.base = base; incdata.seen = seen; @@ -569,22 +586,27 @@ static struct bitmap *find_objects(struct rev_info *revs, if (prepare_revision_walk(revs)) die("revision walk setup failed"); - traverse_commit_list(revs, show_commit, show_object, base); + show_data.bitmap_git = bitmap_git; + show_data.base = base; + + traverse_commit_list(revs, show_commit, show_object, + &show_data); } return base; } -static void show_extended_objects(struct bitmap *objects, +static void show_extended_objects(struct bitmap_index *bitmap_git, show_reachable_fn show_reach) { - struct eindex *eindex = &bitmap_git.ext_index; + struct bitmap *objects = bitmap_git->result; + struct eindex *eindex = &bitmap_git->ext_index; uint32_t i; for (i = 0; i < eindex->count; ++i) { struct object *obj; - if (!bitmap_get(objects, bitmap_git.pack->num_objects + i)) + if (!bitmap_get(objects, bitmap_git->pack->num_objects + i)) continue; obj = eindex->objects[i]; @@ -593,7 +615,7 @@ static void show_extended_objects(struct bitmap *objects, } static void show_objects_for_type( - struct bitmap *objects, + struct bitmap_index *bitmap_git, struct ewah_bitmap *type_filter, enum object_type object_type, show_reachable_fn show_reach) @@ -604,7 +626,9 @@ static void show_objects_for_type( struct ewah_iterator it; eword_t filter; - if (bitmap_git.reuse_objects == bitmap_git.pack->num_objects) + struct bitmap *objects = bitmap_git->result; + + if (bitmap_git->reuse_objects == bitmap_git->pack->num_objects) return; ewah_iterator_init(&it, type_filter); @@ -622,16 +646,16 @@ static void show_objects_for_type( offset += ewah_bit_ctz64(word >> offset); - if (pos + offset < bitmap_git.reuse_objects) + if (pos + offset < bitmap_git->reuse_objects) continue; - entry = &bitmap_git.pack->revindex[pos + offset]; - nth_packed_object_oid(&oid, bitmap_git.pack, entry->nr); + entry = &bitmap_git->pack->revindex[pos + offset]; + nth_packed_object_oid(&oid, bitmap_git->pack, entry->nr); - if (bitmap_git.hashes) - hash = get_be32(bitmap_git.hashes + entry->nr); + if (bitmap_git->hashes) + hash = get_be32(bitmap_git->hashes + entry->nr); - show_reach(&oid, object_type, 0, hash, bitmap_git.pack, entry->offset); + show_reach(&oid, object_type, 0, hash, bitmap_git->pack, entry->offset); } pos += BITS_IN_EWORD; @@ -639,20 +663,21 @@ static void show_objects_for_type( } } -static int in_bitmapped_pack(struct object_list *roots) +static int in_bitmapped_pack(struct bitmap_index *bitmap_git, + struct object_list *roots) { while (roots) { struct object *object = roots->item; roots = roots->next; - if (find_pack_entry_one(object->oid.hash, bitmap_git.pack) > 0) + if (find_pack_entry_one(object->oid.hash, bitmap_git->pack) > 0) return 1; } return 0; } -int prepare_bitmap_walk(struct rev_info *revs) +struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs) { unsigned int i; @@ -662,12 +687,11 @@ int prepare_bitmap_walk(struct rev_info *revs) struct bitmap *wants_bitmap = NULL; struct bitmap *haves_bitmap = NULL; - if (!bitmap_git.loaded) { - /* try to open a bitmapped pack, but don't parse it yet - * because we may not need to use it */ - if (open_pack_bitmap() < 0) - return -1; - } + struct bitmap_index *bitmap_git = xcalloc(1, sizeof(*bitmap_git)); + /* try to open a bitmapped pack, but don't parse it yet + * because we may not need to use it */ + if (open_pack_bitmap(bitmap_git) < 0) + goto cleanup; for (i = 0; i < revs->pending.nr; ++i) { struct object *object = revs->pending.objects[i].item; @@ -699,26 +723,26 @@ int prepare_bitmap_walk(struct rev_info *revs) * in the packfile that has a bitmap, we don't have anything to * optimize here */ - if (haves && !in_bitmapped_pack(haves)) - return -1; + if (haves && !in_bitmapped_pack(bitmap_git, haves)) + goto cleanup; /* if we don't want anything, we're done here */ if (!wants) - return -1; + goto cleanup; /* * now we're going to use bitmaps, so load the actual bitmap entries * from disk. this is the point of no return; after this the rev_list * becomes invalidated and we must perform the revwalk through bitmaps */ - if (!bitmap_git.loaded && load_pack_bitmap() < 0) - return -1; + if (!bitmap_git->loaded && load_pack_bitmap(bitmap_git) < 0) + goto cleanup; object_array_clear(&revs->pending); if (haves) { revs->ignore_missing_links = 1; - haves_bitmap = find_objects(revs, haves, NULL); + haves_bitmap = find_objects(bitmap_git, revs, haves, NULL); reset_revision_walk(); revs->ignore_missing_links = 0; @@ -726,7 +750,7 @@ int prepare_bitmap_walk(struct rev_info *revs) BUG("failed to perform bitmap walk"); } - wants_bitmap = find_objects(revs, wants, haves_bitmap); + wants_bitmap = find_objects(bitmap_git, revs, wants, haves_bitmap); if (!wants_bitmap) BUG("failed to perform bitmap walk"); @@ -734,13 +758,18 @@ int prepare_bitmap_walk(struct rev_info *revs) if (haves_bitmap) bitmap_and_not(wants_bitmap, haves_bitmap); - bitmap_git.result = wants_bitmap; + bitmap_git->result = wants_bitmap; bitmap_free(haves_bitmap); - return 0; + return bitmap_git; + +cleanup: + free_bitmap_index(bitmap_git); + return NULL; } -int reuse_partial_packfile_from_bitmap(struct packed_git **packfile, +int reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git, + struct packed_git **packfile, uint32_t *entries, off_t *up_to) { @@ -750,7 +779,7 @@ int reuse_partial_packfile_from_bitmap(struct packed_git **packfile, */ static const double REUSE_PERCENT = 0.9; - struct bitmap *result = bitmap_git.result; + struct bitmap *result = bitmap_git->result; uint32_t reuse_threshold; uint32_t i, reuse_objects = 0; @@ -770,8 +799,8 @@ int reuse_partial_packfile_from_bitmap(struct packed_git **packfile, const unsigned char *sha1; struct revindex_entry *entry; - entry = &bitmap_git.reverse_index->revindex[reuse_objects]; - sha1 = nth_packed_object_sha1(bitmap_git.pack, entry->nr); + entry = &bitmap_git->reverse_index->revindex[reuse_objects]; + sha1 = nth_packed_object_sha1(bitmap_git->pack, entry->nr); fprintf(stderr, "Failed to reuse at %d (%016llx)\n", reuse_objects, result->words[i]); @@ -782,48 +811,50 @@ int reuse_partial_packfile_from_bitmap(struct packed_git **packfile, if (!reuse_objects) return -1; - if (reuse_objects >= bitmap_git.pack->num_objects) { - bitmap_git.reuse_objects = *entries = bitmap_git.pack->num_objects; + if (reuse_objects >= bitmap_git->pack->num_objects) { + bitmap_git->reuse_objects = *entries = bitmap_git->pack->num_objects; *up_to = -1; /* reuse the full pack */ - *packfile = bitmap_git.pack; + *packfile = bitmap_git->pack; return 0; } - reuse_threshold = bitmap_popcount(bitmap_git.result) * REUSE_PERCENT; + reuse_threshold = bitmap_popcount(bitmap_git->result) * REUSE_PERCENT; if (reuse_objects < reuse_threshold) return -1; - bitmap_git.reuse_objects = *entries = reuse_objects; - *up_to = bitmap_git.pack->revindex[reuse_objects].offset; - *packfile = bitmap_git.pack; + bitmap_git->reuse_objects = *entries = reuse_objects; + *up_to = bitmap_git->pack->revindex[reuse_objects].offset; + *packfile = bitmap_git->pack; return 0; } -void traverse_bitmap_commit_list(show_reachable_fn show_reachable) +void traverse_bitmap_commit_list(struct bitmap_index *bitmap_git, + show_reachable_fn show_reachable) { - assert(bitmap_git.result); + assert(bitmap_git->result); - show_objects_for_type(bitmap_git.result, bitmap_git.commits, + show_objects_for_type(bitmap_git, bitmap_git->commits, OBJ_COMMIT, show_reachable); - show_objects_for_type(bitmap_git.result, bitmap_git.trees, + show_objects_for_type(bitmap_git, bitmap_git->trees, OBJ_TREE, show_reachable); - show_objects_for_type(bitmap_git.result, bitmap_git.blobs, + show_objects_for_type(bitmap_git, bitmap_git->blobs, OBJ_BLOB, show_reachable); - show_objects_for_type(bitmap_git.result, bitmap_git.tags, + show_objects_for_type(bitmap_git, bitmap_git->tags, OBJ_TAG, show_reachable); - show_extended_objects(bitmap_git.result, show_reachable); + show_extended_objects(bitmap_git, show_reachable); - bitmap_free(bitmap_git.result); - bitmap_git.result = NULL; + bitmap_free(bitmap_git->result); + bitmap_git->result = NULL; } -static uint32_t count_object_type(struct bitmap *objects, +static uint32_t count_object_type(struct bitmap_index *bitmap_git, enum object_type type) { - struct eindex *eindex = &bitmap_git.ext_index; + struct bitmap *objects = bitmap_git->result; + struct eindex *eindex = &bitmap_git->ext_index; uint32_t i = 0, count = 0; struct ewah_iterator it; @@ -831,19 +862,19 @@ static uint32_t count_object_type(struct bitmap *objects, switch (type) { case OBJ_COMMIT: - ewah_iterator_init(&it, bitmap_git.commits); + ewah_iterator_init(&it, bitmap_git->commits); break; case OBJ_TREE: - ewah_iterator_init(&it, bitmap_git.trees); + ewah_iterator_init(&it, bitmap_git->trees); break; case OBJ_BLOB: - ewah_iterator_init(&it, bitmap_git.blobs); + ewah_iterator_init(&it, bitmap_git->blobs); break; case OBJ_TAG: - ewah_iterator_init(&it, bitmap_git.tags); + ewah_iterator_init(&it, bitmap_git->tags); break; default: @@ -857,32 +888,34 @@ static uint32_t count_object_type(struct bitmap *objects, for (i = 0; i < eindex->count; ++i) { if (eindex->objects[i]->type == type && - bitmap_get(objects, bitmap_git.pack->num_objects + i)) + bitmap_get(objects, bitmap_git->pack->num_objects + i)) count++; } return count; } -void count_bitmap_commit_list(uint32_t *commits, uint32_t *trees, +void count_bitmap_commit_list(struct bitmap_index *bitmap_git, + uint32_t *commits, uint32_t *trees, uint32_t *blobs, uint32_t *tags) { - assert(bitmap_git.result); + assert(bitmap_git->result); if (commits) - *commits = count_object_type(bitmap_git.result, OBJ_COMMIT); + *commits = count_object_type(bitmap_git, OBJ_COMMIT); if (trees) - *trees = count_object_type(bitmap_git.result, OBJ_TREE); + *trees = count_object_type(bitmap_git, OBJ_TREE); if (blobs) - *blobs = count_object_type(bitmap_git.result, OBJ_BLOB); + *blobs = count_object_type(bitmap_git, OBJ_BLOB); if (tags) - *tags = count_object_type(bitmap_git.result, OBJ_TAG); + *tags = count_object_type(bitmap_git, OBJ_TAG); } struct bitmap_test_data { + struct bitmap_index *bitmap_git; struct bitmap *base; struct progress *prg; size_t seen; @@ -894,7 +927,7 @@ static void test_show_object(struct object *object, const char *name, struct bitmap_test_data *tdata = data; int bitmap_pos; - bitmap_pos = bitmap_position(object->oid.hash); + bitmap_pos = bitmap_position(tdata->bitmap_git, object->oid.hash); if (bitmap_pos < 0) die("Object not in bitmap: %s\n", oid_to_hex(&object->oid)); @@ -907,7 +940,8 @@ static void test_show_commit(struct commit *commit, void *data) struct bitmap_test_data *tdata = data; int bitmap_pos; - bitmap_pos = bitmap_position(commit->object.oid.hash); + bitmap_pos = bitmap_position(tdata->bitmap_git, + commit->object.oid.hash); if (bitmap_pos < 0) die("Object not in bitmap: %s\n", oid_to_hex(&commit->object.oid)); @@ -922,21 +956,22 @@ void test_bitmap_walk(struct rev_info *revs) khiter_t pos; size_t result_popcnt; struct bitmap_test_data tdata; + struct bitmap_index *bitmap_git; - if (prepare_bitmap_git()) + if (!(bitmap_git = prepare_bitmap_git())) die("failed to load bitmap indexes"); if (revs->pending.nr != 1) die("you must specify exactly one commit to test"); fprintf(stderr, "Bitmap v%d test (%d entries loaded)\n", - bitmap_git.version, bitmap_git.entry_count); + bitmap_git->version, bitmap_git->entry_count); root = revs->pending.objects[0].item; - pos = kh_get_sha1(bitmap_git.bitmaps, root->oid.hash); + pos = kh_get_sha1(bitmap_git->bitmaps, root->oid.hash); - if (pos < kh_end(bitmap_git.bitmaps)) { - struct stored_bitmap *st = kh_value(bitmap_git.bitmaps, pos); + if (pos < kh_end(bitmap_git->bitmaps)) { + struct stored_bitmap *st = kh_value(bitmap_git->bitmaps, pos); struct ewah_bitmap *bm = lookup_stored_bitmap(st); fprintf(stderr, "Found bitmap for %s. %d bits / %08x checksum\n", @@ -957,6 +992,7 @@ void test_bitmap_walk(struct rev_info *revs) if (prepare_revision_walk(revs)) die("revision walk setup failed"); + tdata.bitmap_git = bitmap_git; tdata.base = bitmap_new(); tdata.prg = start_progress("Verifying bitmap entries", result_popcnt); tdata.seen = 0; @@ -970,7 +1006,7 @@ void test_bitmap_walk(struct rev_info *revs) else fprintf(stderr, "Mismatch!\n"); - bitmap_free(result); + free_bitmap_index(bitmap_git); } static int rebuild_bitmap(uint32_t *reposition, @@ -1004,7 +1040,8 @@ static int rebuild_bitmap(uint32_t *reposition, return 0; } -int rebuild_existing_bitmaps(struct packing_data *mapping, +int rebuild_existing_bitmaps(struct bitmap_index *bitmap_git, + struct packing_data *mapping, khash_sha1 *reused_bitmaps, int show_progress) { @@ -1017,10 +1054,7 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, khiter_t hash_pos; int hash_ret; - if (prepare_bitmap_git() < 0) - return -1; - - num_objects = bitmap_git.pack->num_objects; + num_objects = bitmap_git->pack->num_objects; reposition = xcalloc(num_objects, sizeof(uint32_t)); for (i = 0; i < num_objects; ++i) { @@ -1028,8 +1062,8 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, struct revindex_entry *entry; struct object_entry *oe; - entry = &bitmap_git.pack->revindex[i]; - sha1 = nth_packed_object_sha1(bitmap_git.pack, entry->nr); + entry = &bitmap_git->pack->revindex[i]; + sha1 = nth_packed_object_sha1(bitmap_git->pack, entry->nr); oe = packlist_find(mapping, sha1, NULL); if (oe) @@ -1042,7 +1076,7 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, if (show_progress) progress = start_progress("Reusing bitmaps", 0); - kh_foreach_value(bitmap_git.bitmaps, stored, { + kh_foreach_value(bitmap_git->bitmaps, stored, { if (stored->flags & BITMAP_FLAG_REUSE) { if (!rebuild_bitmap(reposition, lookup_stored_bitmap(stored), @@ -1064,3 +1098,21 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, bitmap_free(rebuild); return 0; } + +void free_bitmap_index(struct bitmap_index *b) +{ + if (!b) + return; + + if (b->map) + munmap(b->map, b->map_size); + ewah_pool_free(b->commits); + ewah_pool_free(b->trees); + ewah_pool_free(b->blobs); + ewah_pool_free(b->tags); + kh_destroy_sha1(b->bitmaps); + free(b->ext_index.objects); + free(b->ext_index.hashes); + bitmap_free(b->result); + free(b); +} diff --git a/pack-bitmap.h b/pack-bitmap.h index 5ded2f139a..4555907dee 100644 --- a/pack-bitmap.h +++ b/pack-bitmap.h @@ -34,13 +34,21 @@ typedef int (*show_reachable_fn)( struct packed_git *found_pack, off_t found_offset); -int prepare_bitmap_git(void); -void count_bitmap_commit_list(uint32_t *commits, uint32_t *trees, uint32_t *blobs, uint32_t *tags); -void traverse_bitmap_commit_list(show_reachable_fn show_reachable); +struct bitmap_index; + +struct bitmap_index *prepare_bitmap_git(void); +void count_bitmap_commit_list(struct bitmap_index *, uint32_t *commits, + uint32_t *trees, uint32_t *blobs, uint32_t *tags); +void traverse_bitmap_commit_list(struct bitmap_index *, + show_reachable_fn show_reachable); void test_bitmap_walk(struct rev_info *revs); -int prepare_bitmap_walk(struct rev_info *revs); -int reuse_partial_packfile_from_bitmap(struct packed_git **packfile, uint32_t *entries, off_t *up_to); -int rebuild_existing_bitmaps(struct packing_data *mapping, khash_sha1 *reused_bitmaps, int show_progress); +struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs); +int reuse_partial_packfile_from_bitmap(struct bitmap_index *, + struct packed_git **packfile, + uint32_t *entries, off_t *up_to); +int rebuild_existing_bitmaps(struct bitmap_index *, struct packing_data *mapping, + khash_sha1 *reused_bitmaps, int show_progress); +void free_bitmap_index(struct bitmap_index *); void bitmap_writer_show_progress(int show); void bitmap_writer_set_checksum(unsigned char *sha1); diff --git a/packfile.h b/packfile.h index e0a38aba93..cc7eaffe1b 100644 --- a/packfile.h +++ b/packfile.h @@ -3,6 +3,11 @@ #include "oidset.h" +/* in object-store.h */ +struct packed_git; +struct object_info; +enum object_type; + /* * Generate the filename to be used for a pack file with checksum "sha1" and * extension "ext". The result is written into the strbuf "buf", overwriting diff --git a/parse-options.c b/parse-options.c index 0f7059a8ab..7db84227ab 100644 --- a/parse-options.c +++ b/parse-options.c @@ -427,13 +427,59 @@ void parse_options_start(struct parse_opt_ctx_t *ctx, parse_options_check(options); } -/* - * TODO: we are not completing the --no-XXX form yet because there are - * many options that do not suppress it properly. - */ +static void show_negated_gitcomp(const struct option *opts, int nr_noopts) +{ + int printed_dashdash = 0; + + for (; opts->type != OPTION_END; opts++) { + int has_unset_form = 0; + const char *name; + + if (!opts->long_name) + continue; + if (opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE)) + continue; + if (opts->flags & PARSE_OPT_NONEG) + continue; + + switch (opts->type) { + case OPTION_STRING: + case OPTION_FILENAME: + case OPTION_INTEGER: + case OPTION_MAGNITUDE: + case OPTION_CALLBACK: + case OPTION_BIT: + case OPTION_NEGBIT: + case OPTION_COUNTUP: + case OPTION_SET_INT: + has_unset_form = 1; + break; + default: + break; + } + if (!has_unset_form) + continue; + + if (skip_prefix(opts->long_name, "no-", &name)) { + if (nr_noopts < 0) + printf(" --%s", name); + } else if (nr_noopts >= 0) { + if (nr_noopts && !printed_dashdash) { + printf(" --"); + printed_dashdash = 1; + } + printf(" --no-%s", opts->long_name); + nr_noopts++; + } + } +} + static int show_gitcomp(struct parse_opt_ctx_t *ctx, const struct option *opts) { + const struct option *original_opts = opts; + int nr_noopts = 0; + for (; opts->type != OPTION_END; opts++) { const char *suffix = ""; @@ -463,8 +509,12 @@ static int show_gitcomp(struct parse_opt_ctx_t *ctx, } if (opts->flags & PARSE_OPT_COMP_ARG) suffix = "="; + if (starts_with(opts->long_name, "no-")) + nr_noopts++; printf(" --%s%s", opts->long_name, suffix); } + show_negated_gitcomp(original_opts, -1); + show_negated_gitcomp(original_opts, nr_noopts); fputc('\n', stdout); exit(0); } @@ -1442,12 +1442,12 @@ char *xdg_cache_home(const char *filename) return NULL; } -GIT_PATH_FUNC(git_path_cherry_pick_head, "CHERRY_PICK_HEAD") -GIT_PATH_FUNC(git_path_revert_head, "REVERT_HEAD") -GIT_PATH_FUNC(git_path_squash_msg, "SQUASH_MSG") -GIT_PATH_FUNC(git_path_merge_msg, "MERGE_MSG") -GIT_PATH_FUNC(git_path_merge_rr, "MERGE_RR") -GIT_PATH_FUNC(git_path_merge_mode, "MERGE_MODE") -GIT_PATH_FUNC(git_path_merge_head, "MERGE_HEAD") -GIT_PATH_FUNC(git_path_fetch_head, "FETCH_HEAD") -GIT_PATH_FUNC(git_path_shallow, "shallow") +REPO_GIT_PATH_FUNC(cherry_pick_head, "CHERRY_PICK_HEAD") +REPO_GIT_PATH_FUNC(revert_head, "REVERT_HEAD") +REPO_GIT_PATH_FUNC(squash_msg, "SQUASH_MSG") +REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG") +REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR") +REPO_GIT_PATH_FUNC(merge_mode, "MERGE_MODE") +REPO_GIT_PATH_FUNC(merge_head, "MERGE_HEAD") +REPO_GIT_PATH_FUNC(fetch_head, "FETCH_HEAD") +REPO_GIT_PATH_FUNC(shallow, "shallow") @@ -160,14 +160,36 @@ extern void report_linked_checkout_garbage(void); return ret; \ } -const char *git_path_cherry_pick_head(void); -const char *git_path_revert_head(void); -const char *git_path_squash_msg(void); -const char *git_path_merge_msg(void); -const char *git_path_merge_rr(void); -const char *git_path_merge_mode(void); -const char *git_path_merge_head(void); -const char *git_path_fetch_head(void); -const char *git_path_shallow(void); +#define REPO_GIT_PATH_FUNC(var, filename) \ + const char *git_path_##var(struct repository *r) \ + { \ + if (!r->cached_paths.var) \ + r->cached_paths.var = git_pathdup(filename); \ + return r->cached_paths.var; \ + } + +struct path_cache { + const char *cherry_pick_head; + const char *revert_head; + const char *squash_msg; + const char *merge_msg; + const char *merge_rr; + const char *merge_mode; + const char *merge_head; + const char *fetch_head; + const char *shallow; +}; + +#define PATH_CACHE_INIT { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } + +const char *git_path_cherry_pick_head(struct repository *r); +const char *git_path_revert_head(struct repository *r); +const char *git_path_squash_msg(struct repository *r); +const char *git_path_merge_msg(struct repository *r); +const char *git_path_merge_rr(struct repository *r); +const char *git_path_merge_mode(struct repository *r); +const char *git_path_merge_head(struct repository *r); +const char *git_path_fetch_head(struct repository *r); +const char *git_path_shallow(struct repository *r); #endif /* PATH_H */ diff --git a/read-cache.c b/read-cache.c index 372588260e..e865254bea 100644 --- a/read-cache.c +++ b/read-cache.c @@ -11,6 +11,7 @@ #include "cache-tree.h" #include "refs.h" #include "dir.h" +#include "object-store.h" #include "tree.h" #include "commit.h" #include "blob.h" diff --git a/ref-filter.c b/ref-filter.c index 01c1a82075..492f2b770d 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -3,6 +3,7 @@ #include "parse-options.h" #include "refs.h" #include "wildmatch.h" +#include "object-store.h" #include "commit.h" #include "remote.h" #include "color.h" @@ -16,6 +17,7 @@ #include "trailer.h" #include "wt-status.h" #include "commit-slab.h" +#include "commit-graph.h" static struct ref_msg { const char *gone; @@ -1662,12 +1664,13 @@ static int in_commit_list(const struct commit_list *want, struct commit *c) } /* - * Test whether the candidate or one of its parents is contained in the list. + * Test whether the candidate is contained in the list. * Do not recurse to find out, though, but return -1 if inconclusive. */ static enum contains_result contains_test(struct commit *candidate, const struct commit_list *want, - struct contains_cache *cache) + struct contains_cache *cache, + uint32_t cutoff) { enum contains_result *cached = contains_cache_at(cache, candidate); @@ -1683,6 +1686,10 @@ static enum contains_result contains_test(struct commit *candidate, /* Otherwise, we don't know; prepare to recurse */ parse_commit_or_die(candidate); + + if (candidate->generation < cutoff) + return CONTAINS_NO; + return CONTAINS_UNKNOWN; } @@ -1698,8 +1705,18 @@ static enum contains_result contains_tag_algo(struct commit *candidate, struct contains_cache *cache) { struct contains_stack contains_stack = { 0, 0, NULL }; - enum contains_result result = contains_test(candidate, want, cache); + enum contains_result result; + uint32_t cutoff = GENERATION_NUMBER_INFINITY; + const struct commit_list *p; + + for (p = want; p; p = p->next) { + struct commit *c = p->item; + load_commit_graph_info(c); + if (c->generation < cutoff) + cutoff = c->generation; + } + result = contains_test(candidate, want, cache, cutoff); if (result != CONTAINS_UNKNOWN) return result; @@ -1717,7 +1734,7 @@ static enum contains_result contains_tag_algo(struct commit *candidate, * If we just popped the stack, parents->item has been marked, * therefore contains_test will return a meaningful yes/no. */ - else switch (contains_test(parents->item, want, cache)) { + else switch (contains_test(parents->item, want, cache, cutoff)) { case CONTAINS_YES: *contains_cache_at(cache, commit) = CONTAINS_YES; contains_stack.nr--; @@ -1731,7 +1748,7 @@ static enum contains_result contains_tag_algo(struct commit *candidate, } } free(contains_stack.contains_stack); - return contains_test(candidate, want, cache); + return contains_test(candidate, want, cache, cutoff); } static int commit_contains(struct ref_filter *filter, struct commit *commit, @@ -1797,7 +1814,7 @@ static int match_name_as_path(const struct ref_filter *filter, const char *refna refname[plen] == '/' || p[plen-1] == '/')) return 1; - if (!wildmatch(p, refname, WM_PATHNAME)) + if (!wildmatch(p, refname, flags)) return 1; } return 0; @@ -1852,6 +1869,15 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter, return for_each_fullref_in("", cb, cb_data, broken); } + if (filter->ignore_case) { + /* + * we can't handle case-insensitive comparisons, + * so just return everything and let the caller + * sort it out. + */ + return for_each_fullref_in("", cb, cb_data, broken); + } + if (!filter->name_patterns[0]) { /* no patterns; we have to look at everything */ return for_each_fullref_in("", cb, cb_data, broken); @@ -9,6 +9,7 @@ #include "iterator.h" #include "refs.h" #include "refs/refs-internal.h" +#include "object-store.h" #include "object.h" #include "tag.h" #include "submodule.h" diff --git a/refs/packed-backend.c b/refs/packed-backend.c index cec3fb9e00..d447a731da 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -499,6 +499,7 @@ static int load_contents(struct snapshot *snapshot) size = xsize_t(st.st_size); if (!size) { + close(fd); return 0; } else if (mmap_strategy == MMAP_NONE || size <= SMALL_FILE_SIZE) { snapshot->buf = xmalloc(size); @@ -124,11 +124,16 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet return 1; } -void refspec_item_init(struct refspec_item *item, const char *refspec, int fetch) +int refspec_item_init(struct refspec_item *item, const char *refspec, int fetch) { memset(item, 0, sizeof(*item)); + return parse_refspec(item, refspec, fetch); +} - if (!parse_refspec(item, refspec, fetch)) +void refspec_item_init_or_die(struct refspec_item *item, const char *refspec, + int fetch) +{ + if (!refspec_item_init(item, refspec, fetch)) die("Invalid refspec '%s'", refspec); } @@ -152,7 +157,7 @@ void refspec_append(struct refspec *rs, const char *refspec) { struct refspec_item item; - refspec_item_init(&item, refspec, rs->fetch); + refspec_item_init_or_die(&item, refspec, rs->fetch); ALLOC_GROW(rs->items, rs->nr + 1, rs->alloc); rs->items[rs->nr++] = item; @@ -191,7 +196,7 @@ void refspec_clear(struct refspec *rs) int valid_fetch_refspec(const char *fetch_refspec_str) { struct refspec_item refspec; - int ret = parse_refspec(&refspec, fetch_refspec_str, REFSPEC_FETCH); + int ret = refspec_item_init(&refspec, fetch_refspec_str, REFSPEC_FETCH); refspec_item_clear(&refspec); return ret; } @@ -32,7 +32,10 @@ struct refspec { int fetch; }; -void refspec_item_init(struct refspec_item *item, const char *refspec, int fetch); +int refspec_item_init(struct refspec_item *item, const char *refspec, + int fetch); +void refspec_item_init_or_die(struct refspec_item *item, const char *refspec, + int fetch); void refspec_item_clear(struct refspec_item *item); void refspec_init(struct refspec *rs, int fetch); void refspec_append(struct refspec *rs, const char *refspec); diff --git a/remote-testsvn.c b/remote-testsvn.c index 444d98059f..3af708c5b6 100644 --- a/remote-testsvn.c +++ b/remote-testsvn.c @@ -1,6 +1,7 @@ #include "cache.h" #include "refs.h" #include "remote.h" +#include "object-store.h" #include "strbuf.h" #include "url.h" #include "exec-cmd.h" @@ -3,6 +3,7 @@ #include "remote.h" #include "refs.h" #include "refspec.h" +#include "object-store.h" #include "commit.h" #include "diff.h" #include "revision.h" @@ -1735,6 +1736,7 @@ int get_fetch_map(const struct ref *remote_refs, if (refspec->exact_sha1) { ref_map = alloc_ref(name); get_oid_hex(name, &ref_map->old_oid); + ref_map->exact_oid = 1; } else { ref_map = get_remote_ref(remote_refs, name); } @@ -73,6 +73,7 @@ struct ref { force:1, forced_update:1, expect_old_sha1:1, + exact_oid:1, deletion:1; enum { diff --git a/repository.c b/repository.c index 02fe884603..5dd1486718 100644 --- a/repository.c +++ b/repository.c @@ -2,6 +2,7 @@ #include "repository.h" #include "object-store.h" #include "config.h" +#include "object.h" #include "submodule-config.h" /* The main repository */ @@ -14,6 +15,8 @@ void initialize_the_repository(void) the_repo.index = &the_index; the_repo.objects = raw_object_store_new(); + the_repo.parsed_objects = parsed_object_pool_new(); + repo_set_hash_algo(&the_repo, GIT_HASH_SHA1); } @@ -143,6 +146,7 @@ int repo_init(struct repository *repo, memset(repo, 0, sizeof(*repo)); repo->objects = raw_object_store_new(); + repo->parsed_objects = parsed_object_pool_new(); if (repo_init_gitdir(repo, gitdir)) goto error; @@ -226,6 +230,9 @@ void repo_clear(struct repository *repo) raw_object_store_clear(repo->objects); FREE_AND_NULL(repo->objects); + parsed_object_pool_clear(repo->parsed_objects); + FREE_AND_NULL(repo->parsed_objects); + if (repo->config) { git_configset_clear(repo->config); FREE_AND_NULL(repo->config); diff --git a/repository.h b/repository.h index f2646f0c52..b9413be50c 100644 --- a/repository.h +++ b/repository.h @@ -26,10 +26,24 @@ struct repository { */ struct raw_object_store *objects; + /* + * All objects in this repository that have been parsed. This structure + * owns all objects it references, so users of "struct object *" + * generally do not need to free them; instead, when a repository is no + * longer used, call parsed_object_pool_clear() on this structure, which + * is called by the repositories repo_clear on its desconstruction. + */ + struct parsed_object_pool *parsed_objects; + /* The store in which the refs are held. */ struct ref_store *refs; /* + * Contains path to often used file names. + */ + struct path_cache cached_paths; + + /* * Path to the repository's graft file. * Cannot be NULL after initialization. */ @@ -9,6 +9,7 @@ #include "ll-merge.h" #include "attr.h" #include "pathspec.h" +#include "object-store.h" #include "sha1-lookup.h" #define RESOLVED 0 @@ -200,7 +201,7 @@ static struct rerere_id *new_rerere_id(unsigned char *sha1) static void read_rr(struct string_list *rr) { struct strbuf buf = STRBUF_INIT; - FILE *in = fopen_or_warn(git_path_merge_rr(), "r"); + FILE *in = fopen_or_warn(git_path_merge_rr(the_repository), "r"); if (!in) return; @@ -895,7 +896,8 @@ int setup_rerere(struct string_list *merge_rr, int flags) if (flags & RERERE_READONLY) fd = 0; else - fd = hold_lock_file_for_update(&write_lock, git_path_merge_rr(), + fd = hold_lock_file_for_update(&write_lock, + git_path_merge_rr(the_repository), LOCK_DIE_ON_ERROR); read_rr(merge_rr); return fd; @@ -1245,6 +1247,6 @@ void rerere_clear(struct string_list *merge_rr) rmdir(rerere_path(id, NULL)); } } - unlink_or_warn(git_path_merge_rr()); + unlink_or_warn(git_path_merge_rr(the_repository)); rollback_lock_file(&write_lock); } diff --git a/revision.c b/revision.c index 40fd91ff2b..72abe235e4 100644 --- a/revision.c +++ b/revision.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "object-store.h" #include "tag.h" #include "blob.h" #include "tree.h" @@ -29,6 +30,8 @@ volatile show_early_output_fn_t show_early_output; static const char *term_bad; static const char *term_good; +implement_shared_commit_slab(revision_sources, char *); + void show_object_with_name(FILE *out, struct object *obj, const char *name) { const char *p; @@ -265,14 +268,19 @@ static struct commit *handle_commit(struct rev_info *revs, */ if (object->type == OBJ_COMMIT) { struct commit *commit = (struct commit *)object; + if (parse_commit(commit) < 0) die("unable to parse commit %s", name); if (flags & UNINTERESTING) { mark_parents_uninteresting(commit); revs->limited = 1; } - if (revs->show_source && !commit->util) - commit->util = xstrdup(name); + if (revs->sources) { + char **slot = revision_sources_at(revs->sources, commit); + + if (!*slot) + *slot = xstrdup(name); + } return commit; } @@ -824,8 +832,12 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, } return -1; } - if (revs->show_source && !p->util) - p->util = commit->util; + if (revs->sources) { + char **slot = revision_sources_at(revs->sources, p); + + if (!*slot) + *slot = *revision_sources_at(revs->sources, commit); + } p->object.flags |= left_flag; if (!(p->object.flags & SEEN)) { p->object.flags |= SEEN; diff --git a/revision.h b/revision.h index b8c47b98e2..bf2239f876 100644 --- a/revision.h +++ b/revision.h @@ -6,6 +6,7 @@ #include "notes.h" #include "pretty.h" #include "diff.h" +#include "commit-slab-decl.h" /* Remember to update object flag allocation in object.h */ #define SEEN (1u<<0) @@ -29,6 +30,7 @@ struct rev_info; struct log_info; struct string_list; struct saved_parents; +define_shared_commit_slab(revision_sources, char *); struct rev_cmdline_info { unsigned int nr; @@ -111,7 +113,6 @@ struct rev_info { right_only:1, rewrite_parents:1, print_parents:1, - show_source:1, show_decorations:1, reverse:1, reverse_output_stage:1, @@ -224,6 +225,8 @@ struct rev_info { struct commit_list *previous_parents; const char *break_bar; + + struct revision_sources *sources; }; extern int ref_excluded(struct string_list *, const char *path); diff --git a/send-pack.c b/send-pack.c index 19025a7aca..e920ca57df 100644 --- a/send-pack.c +++ b/send-pack.c @@ -2,6 +2,7 @@ #include "config.h" #include "commit.h" #include "refs.h" +#include "object-store.h" #include "pkt-line.h" #include "sideband.h" #include "run-command.h" @@ -75,7 +76,7 @@ static int pack_objects(int fd, struct ref *refs, struct oid_array *extra, struc argv_array_push(&po.args, "-q"); if (args->progress) argv_array_push(&po.args, "--progress"); - if (is_repository_shallow()) + if (is_repository_shallow(the_repository)) argv_array_push(&po.args, "--shallow"); po.in = -1; po.out = args->stateless_rpc ? -1 : fd; @@ -220,7 +221,7 @@ static int advertise_shallow_grafts_cb(const struct commit_graft *graft, void *c static void advertise_shallow_grafts_buf(struct strbuf *sb) { - if (!is_repository_shallow()) + if (!is_repository_shallow(the_repository)) return; for_each_commit_graft(advertise_shallow_grafts_cb, sb); } @@ -537,7 +538,7 @@ int send_pack(struct send_pack_args *args, } if (args->stateless_rpc) { - if (!args->dry_run && (cmds_sent || is_repository_shallow())) { + if (!args->dry_run && (cmds_sent || is_repository_shallow(the_repository))) { packet_buf_flush(&req_buf); send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX); } diff --git a/sequencer.c b/sequencer.c index 0e9b1d99c9..349ae04ffd 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2,6 +2,7 @@ #include "config.h" #include "lockfile.h" #include "dir.h" +#include "object-store.h" #include "object.h" #include "commit.h" #include "sequencer.h" @@ -27,6 +28,7 @@ #include "worktree.h" #include "oidmap.h" #include "oidset.h" +#include "commit-slab.h" #include "alias.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -175,6 +177,7 @@ static int git_sequencer_config(const char *k, const char *v, void *cb) warning(_("invalid commit message cleanup mode '%s'"), s); + free((char *)s); return status; } @@ -355,7 +358,7 @@ static void print_advice(int show_hint, struct replay_opts *opts) * (typically rebase --interactive) wants to take care * of the commit itself so remove CHERRY_PICK_HEAD */ - unlink(git_path_cherry_pick_head()); + unlink(git_path_cherry_pick_head(the_repository)); return; } @@ -1322,8 +1325,8 @@ static int do_commit(const char *msg_file, const char *author, &oid); strbuf_release(&sb); if (!res) { - unlink(git_path_cherry_pick_head()); - unlink(git_path_merge_msg()); + unlink(git_path_cherry_pick_head(the_repository)); + unlink(git_path_merge_msg(the_repository)); if (!is_rebase_i(opts)) print_commit_summary(NULL, &oid, SUMMARY_SHOW_AUTHOR_DATE); @@ -1611,7 +1614,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, struct replay_opts *opts, int final_fixup) { unsigned int flags = opts->edit ? EDIT_MSG : 0; - const char *msg_file = opts->edit ? NULL : git_path_merge_msg(); + const char *msg_file = opts->edit ? NULL : git_path_merge_msg(the_repository); struct object_id head; struct commit *base, *next, *parent; const char *base_label, *next_label; @@ -1753,12 +1756,12 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, flags |= CLEANUP_MSG; msg_file = rebase_path_fixup_msg(); } else { - const char *dest = git_path_squash_msg(); + const char *dest = git_path_squash_msg(the_repository); unlink(dest); if (copy_file(dest, rebase_path_squash_msg(), 0666)) return error(_("could not rename '%s' to '%s'"), rebase_path_squash_msg(), dest); - unlink(git_path_merge_msg()); + unlink(git_path_merge_msg(the_repository)); msg_file = dest; flags |= EDIT_MSG; } @@ -1773,15 +1776,16 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, res = do_recursive_merge(base, next, base_label, next_label, &head, &msgbuf, opts); if (res < 0) - return res; + goto leave; + res |= write_message(msgbuf.buf, msgbuf.len, - git_path_merge_msg(), 0); + git_path_merge_msg(the_repository), 0); } else { struct commit_list *common = NULL; struct commit_list *remotes = NULL; res = write_message(msgbuf.buf, msgbuf.len, - git_path_merge_msg(), 0); + git_path_merge_msg(the_repository), 0); commit_list_insert(base, &common); commit_list_insert(next, &remotes); @@ -2203,6 +2207,7 @@ static int populate_opts_cb(const char *key, const char *value, void *data) static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf) { int i; + char *strategy_opts_string; strbuf_reset(buf); if (!read_oneliner(buf, rebase_path_strategy(), 0)) @@ -2211,7 +2216,11 @@ static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf) if (!read_oneliner(buf, rebase_path_strategy_opts(), 0)) return; - opts->xopts_nr = split_cmdline(buf->buf, (const char ***)&opts->xopts); + strategy_opts_string = buf->buf; + if (*strategy_opts_string == ' ') + strategy_opts_string++; + opts->xopts_nr = split_cmdline(strategy_opts_string, + (const char ***)&opts->xopts); for (i = 0; i < opts->xopts_nr; i++) { const char *arg = opts->xopts[i]; @@ -2391,8 +2400,8 @@ static int rollback_single_pick(void) { struct object_id head_oid; - if (!file_exists(git_path_cherry_pick_head()) && - !file_exists(git_path_revert_head())) + if (!file_exists(git_path_cherry_pick_head(the_repository)) && + !file_exists(git_path_revert_head(the_repository))) return error(_("no cherry-pick or revert in progress")); if (read_ref_full("HEAD", 0, &head_oid, NULL)) return error(_("cannot resolve HEAD")); @@ -2617,10 +2626,11 @@ static int error_failed_squash(struct commit *commit, if (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666)) return error(_("could not copy '%s' to '%s'"), rebase_path_squash_msg(), rebase_path_message()); - unlink(git_path_merge_msg()); - if (copy_file(git_path_merge_msg(), rebase_path_message(), 0666)) + unlink(git_path_merge_msg(the_repository)); + if (copy_file(git_path_merge_msg(the_repository), rebase_path_message(), 0666)) return error(_("could not copy '%s' to '%s'"), - rebase_path_message(), git_path_merge_msg()); + rebase_path_message(), + git_path_merge_msg(the_repository)); return error_with_patch(commit, subject, subject_len, opts, 1, 0); } @@ -2910,11 +2920,11 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, write_author_script(message); find_commit_subject(message, &body); len = strlen(body); - ret = write_message(body, len, git_path_merge_msg(), 0); + ret = write_message(body, len, git_path_merge_msg(the_repository), 0); unuse_commit_buffer(commit, message); if (ret) { error_errno(_("could not write '%s'"), - git_path_merge_msg()); + git_path_merge_msg(the_repository)); goto leave_merge; } } else { @@ -2935,11 +2945,11 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, len = buf.len; } - ret = write_message(p, len, git_path_merge_msg(), 0); + ret = write_message(p, len, git_path_merge_msg(the_repository), 0); strbuf_release(&buf); if (ret) { error_errno(_("could not write '%s'"), - git_path_merge_msg()); + git_path_merge_msg(the_repository)); goto leave_merge; } } @@ -2976,8 +2986,8 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, } write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ, - git_path_merge_head(), 0); - write_message("no-ff", 5, git_path_merge_mode(), 0); + git_path_merge_head(the_repository), 0); + write_message("no-ff", 5, git_path_merge_mode(the_repository), 0); bases = get_merge_bases(head_commit, merge_commit); if (bases && !oidcmp(&merge_commit->object.oid, @@ -3031,7 +3041,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, * value (a negative one would indicate that the `merge` * command needs to be rescheduled). */ - ret = !!run_git_commit(git_path_merge_msg(), opts, + ret = !!run_git_commit(git_path_merge_msg(the_repository), opts, run_commit_flags); leave_merge: @@ -3214,10 +3224,27 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) intend_to_amend(); return error_failed_squash(item->commit, opts, item->arg_len, item->arg); - } else if (res && is_rebase_i(opts) && item->commit) + } else if (res && is_rebase_i(opts) && item->commit) { + int to_amend = 0; + struct object_id oid; + + /* + * If we are rewording and have either + * fast-forwarded already, or are about to + * create a new root commit, we want to amend, + * otherwise we do not. + */ + if (item->command == TODO_REWORD && + !get_oid("HEAD", &oid) && + (!oidcmp(&item->commit->object.oid, &oid) || + (opts->have_squash_onto && + !oidcmp(&opts->squash_onto, &oid)))) + to_amend = 1; + return res | error_with_patch(item->commit, - item->arg, item->arg_len, opts, res, - item->command == TODO_REWORD); + item->arg, item->arg_len, opts, + res, to_amend); + } } else if (item->command == TODO_EXEC) { char *end_of_arg = (char *)(item->arg + item->arg_len); int saved = *end_of_arg; @@ -3398,8 +3425,8 @@ static int continue_single_pick(void) { const char *argv[] = { "commit", NULL }; - if (!file_exists(git_path_cherry_pick_head()) && - !file_exists(git_path_revert_head())) + if (!file_exists(git_path_cherry_pick_head(the_repository)) && + !file_exists(git_path_revert_head(the_repository))) return error(_("no cherry-pick or revert in progress")); return run_command_v_opt(argv, RUN_GIT_CMD); } @@ -3502,7 +3529,7 @@ static int commit_staged_changes(struct replay_opts *opts, } if (is_clean) { - const char *cherry_pick_head = git_path_cherry_pick_head(); + const char *cherry_pick_head = git_path_cherry_pick_head(the_repository); if (file_exists(cherry_pick_head) && unlink(cherry_pick_head)) return error(_("could not remove CHERRY_PICK_HEAD")); @@ -3552,8 +3579,8 @@ int sequencer_continue(struct replay_opts *opts) if (!is_rebase_i(opts)) { /* Verify that the conflict has been resolved */ - if (file_exists(git_path_cherry_pick_head()) || - file_exists(git_path_revert_head())) { + if (file_exists(git_path_cherry_pick_head(the_repository)) || + file_exists(git_path_revert_head(the_repository))) { res = continue_single_pick(); if (res) goto release_todo_list; @@ -4238,6 +4265,7 @@ static enum check_level get_missing_commit_check_level(void) return CHECK_IGNORE; } +define_commit_slab(commit_seen, unsigned char); /* * Check if the user dropped some commits by mistake * Behaviour determined by rebase.missingCommitsCheck. @@ -4251,6 +4279,9 @@ int check_todo_list(void) struct todo_list todo_list = TODO_LIST_INIT; struct strbuf missing = STRBUF_INIT; int advise_to_edit_todo = 0, res = 0, i; + struct commit_seen commit_seen; + + init_commit_seen(&commit_seen); strbuf_addstr(&todo_file, rebase_path_todo()); if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) { @@ -4267,7 +4298,7 @@ int check_todo_list(void) for (i = 0; i < todo_list.nr; i++) { struct commit *commit = todo_list.items[i].commit; if (commit) - commit->util = (void *)1; + *commit_seen_at(&commit_seen, commit) = 1; } todo_list_release(&todo_list); @@ -4283,11 +4314,11 @@ int check_todo_list(void) 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) { + if (commit && !*commit_seen_at(&commit_seen, commit)) { strbuf_addf(&missing, " - %s %.*s\n", short_commit_name(commit), item->arg_len, item->arg); - commit->util = (void *)1; + *commit_seen_at(&commit_seen, commit) = 1; } } @@ -4313,6 +4344,7 @@ int check_todo_list(void) "The possible behaviours are: ignore, warn, error.\n\n")); leave_check: + clear_commit_seen(&commit_seen); strbuf_release(&todo_file); todo_list_release(&todo_list); @@ -4433,6 +4465,8 @@ static int subject2item_cmp(const void *fndata, return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject); } +define_commit_slab(commit_todo_item, struct todo_item *); + /* * 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 @@ -4449,6 +4483,7 @@ int rearrange_squash(void) struct hashmap subject2item; int res = 0, rearranged = 0, *next, *tail, i; char **subjects; + struct commit_todo_item commit_todo; if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0) return -1; @@ -4457,6 +4492,7 @@ int rearrange_squash(void) return -1; } + init_commit_todo_item(&commit_todo); /* * The hashmap maps onelines to the respective todo list index. * @@ -4487,10 +4523,11 @@ int rearrange_squash(void) if (is_fixup(item->command)) { todo_list_release(&todo_list); + clear_commit_todo_item(&commit_todo); return error(_("the script was already rearranged.")); } - item->commit->util = item; + *commit_todo_item_at(&commit_todo, item->commit) = item; parse_commit(item->commit); commit_buffer = get_commit_buffer(item->commit, NULL); @@ -4517,9 +4554,9 @@ int rearrange_squash(void) else if (!strchr(p, ' ') && (commit2 = lookup_commit_reference_by_name(p)) && - commit2->util) + *commit_todo_item_at(&commit_todo, commit2)) /* found by commit name */ - i2 = (struct todo_item *)commit2->util + i2 = *commit_todo_item_at(&commit_todo, commit2) - todo_list.items; else { /* copy can be a prefix of the commit subject */ @@ -4596,5 +4633,6 @@ int rearrange_squash(void) hashmap_free(&subject2item, 1); todo_list_release(&todo_list); + clear_commit_todo_item(&commit_todo); return res; } diff --git a/sha1-file.c b/sha1-file.c index 695e5c6276..de4839e634 100644 --- a/sha1-file.c +++ b/sha1-file.c @@ -1801,7 +1801,7 @@ static void check_commit(const void *buf, size_t size) { struct commit c; memset(&c, 0, sizeof(c)); - if (parse_commit_buffer(&c, buf, size)) + if (parse_commit_buffer(&c, buf, size, 0)) die("corrupt commit"); } @@ -1,6 +1,8 @@ #include "cache.h" +#include "repository.h" #include "tempfile.h" #include "lockfile.h" +#include "object-store.h" #include "commit.h" #include "tag.h" #include "pkt-line.h" @@ -12,22 +14,20 @@ #include "commit-slab.h" #include "revision.h" #include "list-objects.h" +#include "commit-slab.h" +#include "repository.h" -static int is_shallow = -1; -static struct stat_validity shallow_stat; -static char *alternate_shallow_file; - -void set_alternate_shallow_file(const char *path, int override) +void set_alternate_shallow_file(struct repository *r, const char *path, int override) { - if (is_shallow != -1) + if (r->parsed_objects->is_shallow != -1) BUG("is_repository_shallow must not be called before set_alternate_shallow_file"); - if (alternate_shallow_file && !override) + if (r->parsed_objects->alternate_shallow_file && !override) return; - free(alternate_shallow_file); - alternate_shallow_file = xstrdup_or_null(path); + free(r->parsed_objects->alternate_shallow_file); + r->parsed_objects->alternate_shallow_file = xstrdup_or_null(path); } -int register_shallow(const struct object_id *oid) +int register_shallow(struct repository *r, const struct object_id *oid) { struct commit_graft *graft = xmalloc(sizeof(struct commit_graft)); @@ -37,43 +37,48 @@ int register_shallow(const struct object_id *oid) graft->nr_parent = -1; if (commit && commit->object.parsed) commit->parents = NULL; - return register_commit_graft(graft, 0); + return register_commit_graft(r, graft, 0); } -int is_repository_shallow(void) +int is_repository_shallow(struct repository *r) { FILE *fp; char buf[1024]; - const char *path = alternate_shallow_file; + const char *path = r->parsed_objects->alternate_shallow_file; - if (is_shallow >= 0) - return is_shallow; + if (r->parsed_objects->is_shallow >= 0) + return r->parsed_objects->is_shallow; if (!path) - path = git_path_shallow(); + path = git_path_shallow(r); /* * fetch-pack sets '--shallow-file ""' as an indicator that no * shallow file should be used. We could just open it and it * will likely fail. But let's do an explicit check instead. */ if (!*path || (fp = fopen(path, "r")) == NULL) { - stat_validity_clear(&shallow_stat); - is_shallow = 0; - return is_shallow; + stat_validity_clear(r->parsed_objects->shallow_stat); + r->parsed_objects->is_shallow = 0; + return r->parsed_objects->is_shallow; } - stat_validity_update(&shallow_stat, fileno(fp)); - is_shallow = 1; + stat_validity_update(r->parsed_objects->shallow_stat, fileno(fp)); + r->parsed_objects->is_shallow = 1; while (fgets(buf, sizeof(buf), fp)) { struct object_id oid; if (get_oid_hex(buf, &oid)) die("bad shallow line: %s", buf); - register_shallow(&oid); + register_shallow(r, &oid); } fclose(fp); - return is_shallow; + return r->parsed_objects->is_shallow; } +/* + * TODO: use "int" elemtype instead of "int *" when/if commit-slab + * supports a "valid" flag. + */ +define_commit_slab(commit_depth, int *); struct commit_list *get_shallow_commits(struct object_array *heads, int depth, int shallow_flag, int not_shallow_flag) { @@ -82,32 +87,36 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth, struct object_array stack = OBJECT_ARRAY_INIT; struct commit *commit = NULL; struct commit_graft *graft; + struct commit_depth depths; + init_commit_depth(&depths); while (commit || i < heads->nr || stack.nr) { struct commit_list *p; if (!commit) { if (i < heads->nr) { + int **depth_slot; commit = (struct commit *) deref_tag(heads->objects[i++].item, NULL, 0); if (!commit || commit->object.type != OBJ_COMMIT) { commit = NULL; continue; } - if (!commit->util) - commit->util = xmalloc(sizeof(int)); - *(int *)commit->util = 0; + depth_slot = commit_depth_at(&depths, commit); + if (!*depth_slot) + *depth_slot = xmalloc(sizeof(int)); + **depth_slot = 0; cur_depth = 0; } else { commit = (struct commit *) object_array_pop(&stack); - cur_depth = *(int *)commit->util; + cur_depth = **commit_depth_at(&depths, commit); } } parse_commit_or_die(commit); cur_depth++; if ((depth != INFINITE_DEPTH && cur_depth >= depth) || - (is_repository_shallow() && !commit->parents && - (graft = lookup_commit_graft(&commit->object.oid)) != NULL && + (is_repository_shallow(the_repository) && !commit->parents && + (graft = lookup_commit_graft(the_repository, &commit->object.oid)) != NULL && graft->nr_parent < 0)) { commit_list_insert(commit, &result); commit->object.flags |= shallow_flag; @@ -116,25 +125,31 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth, } commit->object.flags |= not_shallow_flag; for (p = commit->parents, commit = NULL; p; p = p->next) { - if (!p->item->util) { - int *pointer = xmalloc(sizeof(int)); - p->item->util = pointer; - *pointer = cur_depth; + int **depth_slot = commit_depth_at(&depths, p->item); + if (!*depth_slot) { + *depth_slot = xmalloc(sizeof(int)); + **depth_slot = cur_depth; } else { - int *pointer = p->item->util; - if (cur_depth >= *pointer) + if (cur_depth >= **depth_slot) continue; - *pointer = cur_depth; + **depth_slot = cur_depth; } if (p->next) add_object_array(&p->item->object, NULL, &stack); else { commit = p->item; - cur_depth = *(int *)commit->util; + cur_depth = **commit_depth_at(&depths, commit); } } } + for (i = 0; i < depths.slab_count; i++) { + int j; + + for (j = 0; j < depths.slab_size; j++) + free(depths.slab[i][j]); + } + clear_commit_depth(&depths); return result; } @@ -165,7 +180,7 @@ struct commit_list *get_shallow_commits_by_rev_list(int ac, const char **av, */ clear_object_flags(both_flags); - is_repository_shallow(); /* make sure shallows are read */ + is_repository_shallow(the_repository); /* make sure shallows are read */ init_revisions(&revs, NULL); save_commit_buffer = 0; @@ -175,6 +190,9 @@ struct commit_list *get_shallow_commits_by_rev_list(int ac, const char **av, die("revision walk setup failed"); traverse_commit_list(&revs, show_commit, NULL, ¬_shallow_list); + if (!not_shallow_list) + die("no commits selected for shallow requests"); + /* Mark all reachable commits as NOT_SHALLOW */ for (p = not_shallow_list; p; p = p->next) p->item->object.flags |= not_shallow_flag; @@ -215,12 +233,12 @@ struct commit_list *get_shallow_commits_by_rev_list(int ac, const char **av, return result; } -static void check_shallow_file_for_update(void) +static void check_shallow_file_for_update(struct repository *r) { - if (is_shallow == -1) + if (r->parsed_objects->is_shallow == -1) BUG("shallow must be initialized by now"); - if (!stat_validity_check(&shallow_stat, git_path_shallow())) + if (!stat_validity_check(r->parsed_objects->shallow_stat, git_path_shallow(the_repository))) die("shallow file has changed since we read it"); } @@ -315,9 +333,10 @@ void setup_alternate_shallow(struct lock_file *shallow_lock, struct strbuf sb = STRBUF_INIT; int fd; - fd = hold_lock_file_for_update(shallow_lock, git_path_shallow(), + fd = hold_lock_file_for_update(shallow_lock, + git_path_shallow(the_repository), LOCK_DIE_ON_ERROR); - check_shallow_file_for_update(); + check_shallow_file_for_update(the_repository); if (write_shallow_commits(&sb, 0, extra)) { if (write_in_full(fd, sb.buf, sb.len) < 0) die_errno("failed to write to %s", @@ -342,7 +361,7 @@ static int advertise_shallow_grafts_cb(const struct commit_graft *graft, void *c void advertise_shallow_grafts(int fd) { - if (!is_repository_shallow()) + if (!is_repository_shallow(the_repository)) return; for_each_commit_graft(advertise_shallow_grafts_cb, &fd); } @@ -362,16 +381,17 @@ void prune_shallow(int show_only) strbuf_release(&sb); return; } - fd = hold_lock_file_for_update(&shallow_lock, git_path_shallow(), + fd = hold_lock_file_for_update(&shallow_lock, + git_path_shallow(the_repository), LOCK_DIE_ON_ERROR); - check_shallow_file_for_update(); + check_shallow_file_for_update(the_repository); if (write_shallow_commits_1(&sb, 0, NULL, SEEN_ONLY)) { if (write_in_full(fd, sb.buf, sb.len) < 0) die_errno("failed to write to %s", get_lock_file_path(&shallow_lock)); commit_lock_file(&shallow_lock); } else { - unlink(git_path_shallow()); + unlink(git_path_shallow(the_repository)); rollback_lock_file(&shallow_lock); } strbuf_release(&sb); @@ -396,7 +416,8 @@ void prepare_shallow_info(struct shallow_info *info, struct oid_array *sa) for (i = 0; i < sa->nr; i++) { if (has_object_file(sa->oid + i)) { struct commit_graft *graft; - graft = lookup_commit_graft(&sa->oid[i]); + graft = lookup_commit_graft(the_repository, + &sa->oid[i]); if (graft && graft->nr_parent < 0) continue; info->ours[info->nr_ours++] = i; diff --git a/submodule-config.c b/submodule-config.c index 388ef1f892..2a7259ba8b 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -4,6 +4,7 @@ #include "submodule-config.h" #include "submodule.h" #include "strbuf.h" +#include "object-store.h" #include "parse-options.h" /* @@ -591,6 +592,23 @@ static void submodule_cache_check_init(struct repository *repo) submodule_cache_init(repo->submodule_cache); } +/* + * Note: This function is private for a reason, the '.gitmodules' file should + * not be used as as a mechanism to retrieve arbitrary configuration stored in + * the repository. + * + * Runs the provided config function on the '.gitmodules' file found in the + * working directory. + */ +static void config_from_gitmodules(config_fn_t fn, struct repository *repo, void *data) +{ + if (repo->worktree) { + char *file = repo_worktree_path(repo, GITMODULES_FILE); + git_config_from_file(fn, file, data); + free(file); + } +} + static int gitmodules_cb(const char *var, const char *value, void *data) { struct repository *repo = data; @@ -608,19 +626,11 @@ void repo_read_gitmodules(struct repository *repo) { submodule_cache_check_init(repo); - if (repo->worktree) { - char *gitmodules; - - if (repo_read_index(repo) < 0) - return; - - gitmodules = repo_worktree_path(repo, GITMODULES_FILE); - - if (!is_gitmodules_unmerged(repo->index)) - git_config_from_file(gitmodules_cb, gitmodules, repo); + if (repo_read_index(repo) < 0) + return; - free(gitmodules); - } + if (!is_gitmodules_unmerged(repo->index)) + config_from_gitmodules(gitmodules_cb, repo, repo); repo->submodule_cache->gitmodules_read = 1; } @@ -671,3 +681,45 @@ void submodule_free(struct repository *r) if (r->submodule_cache) submodule_cache_clear(r->submodule_cache); } + +struct fetch_config { + int *max_children; + int *recurse_submodules; +}; + +static int gitmodules_fetch_config(const char *var, const char *value, void *cb) +{ + struct fetch_config *config = cb; + if (!strcmp(var, "submodule.fetchjobs")) { + *(config->max_children) = parse_submodule_fetchjobs(var, value); + return 0; + } else if (!strcmp(var, "fetch.recursesubmodules")) { + *(config->recurse_submodules) = parse_fetch_recurse_submodules_arg(var, value); + return 0; + } + + return 0; +} + +void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules) +{ + struct fetch_config config = { + .max_children = max_children, + .recurse_submodules = recurse_submodules + }; + config_from_gitmodules(gitmodules_fetch_config, the_repository, &config); +} + +static int gitmodules_update_clone_config(const char *var, const char *value, + void *cb) +{ + int *max_jobs = cb; + if (!strcmp(var, "submodule.fetchjobs")) + *max_jobs = parse_submodule_fetchjobs(var, value); + return 0; +} + +void update_clone_config_from_gitmodules(int *max_jobs) +{ + config_from_gitmodules(gitmodules_update_clone_config, the_repository, &max_jobs); +} diff --git a/submodule-config.h b/submodule-config.h index ca1f94e2d2..dc7278eea4 100644 --- a/submodule-config.h +++ b/submodule-config.h @@ -2,6 +2,7 @@ #define SUBMODULE_CONFIG_CACHE_H #include "cache.h" +#include "config.h" #include "hashmap.h" #include "submodule.h" #include "strbuf.h" @@ -55,4 +56,15 @@ void submodule_free(struct repository *r); */ int check_submodule_name(const char *name); +/* + * Note: these helper functions exist solely to maintain backward + * compatibility with 'fetch' and 'update_clone' storing configuration in + * '.gitmodules'. + * + * New helpers to retrieve arbitrary configuration from the '.gitmodules' file + * should NOT be added. + */ +extern void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules); +extern void update_clone_config_from_gitmodules(int *max_jobs); + #endif /* SUBMODULE_CONFIG_H */ diff --git a/submodule.c b/submodule.c index 939d6870ec..2a6381864e 100644 --- a/submodule.c +++ b/submodule.c @@ -740,12 +740,14 @@ static void collect_changed_submodules_cb(struct diff_queue_struct *q, else { name = default_name_or_path(p->two->path); /* make sure name does not collide with existing one */ - submodule = submodule_from_name(the_repository, commit_oid, name); + if (name) + submodule = submodule_from_name(the_repository, + commit_oid, name); if (submodule) { warning("Submodule in commit %s at path: " "'%s' collides with a submodule named " "the same. Skipping it.", - oid_to_hex(commit_oid), name); + oid_to_hex(commit_oid), p->two->path); name = NULL; } } @@ -1532,6 +1534,18 @@ out: return ret; } +void submodule_unset_core_worktree(const struct submodule *sub) +{ + char *config_path = xstrfmt("%s/modules/%s/config", + get_git_common_dir(), sub->name); + + if (git_config_set_in_file_gently(config_path, "core.worktree", NULL)) + warning(_("Could not unset core.worktree setting in submodule '%s'"), + sub->path); + + free(config_path); +} + static const char *get_super_prefix_or_empty(void) { const char *s = get_super_prefix(); @@ -1668,7 +1682,7 @@ int submodule_move_head(const char *path, argv_array_push(&cp.args, new_head ? new_head : empty_tree_oid_hex()); if (run_command(&cp)) { - ret = -1; + ret = error(_("Submodule '%s' could not be updated."), path); goto out; } @@ -1697,6 +1711,8 @@ int submodule_move_head(const char *path, if (is_empty_dir(path)) rmdir_or_warn(path); + + submodule_unset_core_worktree(sub); } } out: diff --git a/submodule.h b/submodule.h index 7856b8a0b3..4644683e6c 100644 --- a/submodule.h +++ b/submodule.h @@ -121,6 +121,8 @@ extern int submodule_move_head(const char *path, const char *new_head, unsigned flags); +void submodule_unset_core_worktree(const struct submodule *sub); + /* * Prepare the "env_array" parameter of a "struct child_process" for executing * a submodule by clearing any repo-specific environment variables, but diff --git a/t/helper/test-pkt-line.c b/t/helper/test-pkt-line.c index 0f19e53c75..30775f986f 100644 --- a/t/helper/test-pkt-line.c +++ b/t/helper/test-pkt-line.c @@ -1,3 +1,4 @@ +#include "cache.h" #include "pkt-line.h" static void pack_line(const char *line) @@ -48,6 +49,36 @@ static void unpack(void) } } +static void unpack_sideband(void) +{ + struct packet_reader reader; + packet_reader_init(&reader, 0, NULL, 0, + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_CHOMP_NEWLINE); + + while (packet_reader_read(&reader) != PACKET_READ_EOF) { + int band; + int fd; + + switch (reader.status) { + case PACKET_READ_EOF: + break; + case PACKET_READ_NORMAL: + band = reader.line[0] & 0xff; + if (band < 1 || band > 2) + die("unexpected side band %d", band); + fd = band; + + write_or_die(fd, reader.line + 1, reader.pktlen - 1); + break; + case PACKET_READ_FLUSH: + return; + case PACKET_READ_DELIM: + break; + } + } +} + int cmd_main(int argc, const char **argv) { if (argc < 2) @@ -57,6 +88,8 @@ int cmd_main(int argc, const char **argv) pack(argc - 2, argv + 2); else if (!strcmp(argv[1], "unpack")) unpack(); + else if (!strcmp(argv[1], "unpack-sideband")) + unpack_sideband(); else die("invalid argument '%s'", argv[1]); diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index 435a37465a..ed41b155af 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -132,6 +132,7 @@ prepare_httpd() { cp "$TEST_PATH"/passwd "$HTTPD_ROOT_PATH" install_script broken-smart-http.sh install_script error.sh + install_script apply-one-time-sed.sh ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules" diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 724d9ae462..581c010d8f 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -111,9 +111,14 @@ Alias /auth/dumb/ www/auth/dumb/ SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} SetEnv GIT_HTTP_EXPORT_ALL </LocationMatch> +<LocationMatch /one_time_sed/> + SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} + SetEnv GIT_HTTP_EXPORT_ALL +</LocationMatch> ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1 ScriptAlias /broken_smart/ broken-smart-http.sh/ ScriptAlias /error/ error.sh/ +ScriptAliasMatch /one_time_sed/(.*) apply-one-time-sed.sh/$1 <Directory ${GIT_EXEC_PATH}> Options FollowSymlinks </Directory> @@ -123,6 +128,9 @@ ScriptAlias /error/ error.sh/ <Files error.sh> Options ExecCGI </Files> +<Files apply-one-time-sed.sh> + Options ExecCGI +</Files> <Files ${GIT_EXEC_PATH}/git-http-backend> Options ExecCGI </Files> diff --git a/t/lib-httpd/apply-one-time-sed.sh b/t/lib-httpd/apply-one-time-sed.sh new file mode 100644 index 0000000000..fcef728925 --- /dev/null +++ b/t/lib-httpd/apply-one-time-sed.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# If "one-time-sed" exists in $HTTPD_ROOT_PATH, run sed on the HTTP response, +# using the contents of "one-time-sed" as the sed command to be run. If the +# response was modified as a result, delete "one-time-sed" so that subsequent +# HTTP responses are no longer modified. +# +# This can be used to simulate the effects of the repository changing in +# between HTTP request-response pairs. +if [ -e one-time-sed ]; then + "$GIT_EXEC_PATH/git-http-backend" >out + sed "$(cat one-time-sed)" <out >out_modified + + if diff out out_modified >/dev/null; then + cat out + else + cat out_modified + rm one-time-sed + fi +else + "$GIT_EXEC_PATH/git-http-backend" +fi diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index 1f38a85371..be78cdc1ff 100755 --- a/t/lib-submodule-update.sh +++ b/t/lib-submodule-update.sh @@ -235,7 +235,7 @@ reset_work_tree_to_interested () { then mkdir -p submodule_update/.git/modules/sub1/modules && cp -r submodule_update_repo/.git/modules/sub1/modules/sub2 submodule_update/.git/modules/sub1/modules/sub2 - GIT_WORK_TREE=. git -C submodule_update/.git/modules/sub1/modules/sub2 config --unset core.worktree + # core.worktree is unset for sub2 as it is not checked out fi && # indicate we are interested in the submodule: git -C submodule_update config submodule.sub1.url "bogus" && @@ -709,7 +709,8 @@ test_submodule_recursing_with_args_common() { git branch -t remove_sub1 origin/remove_sub1 && $command remove_sub1 && test_superproject_content origin/remove_sub1 && - ! test -e sub1 + ! test -e sub1 && + test_must_fail git config -f .git/modules/sub1/config core.worktree ) ' # ... absorbing a .git directory along the way. @@ -781,7 +782,8 @@ test_submodule_recursing_with_args_common() { ( cd submodule_update && git branch -t invalid_sub1 origin/invalid_sub1 && - test_must_fail $command invalid_sub1 && + test_must_fail $command invalid_sub1 2>err && + test_i18ngrep sub1 err && test_superproject_content origin/add_sub1 && test_submodule_content sub1 origin/add_sub1 ) diff --git a/t/t0001-init.sh b/t/t0001-init.sh index c413bff9cf..4c865051e7 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -287,6 +287,7 @@ test_expect_success 'init notices EEXIST (2)' ' ' test_expect_success POSIXPERM,SANITY 'init notices EPERM' ' + test_when_finished "chmod +w newdir" && rm -fr newdir && mkdir newdir && chmod -w newdir && diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index 71350e0657..5f056982a5 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -98,6 +98,16 @@ test_expect_success 'safecrlf: git diff demotes safecrlf=true to warn' ' ' +test_expect_success 'safecrlf: no warning with safecrlf=false' ' + git config core.autocrlf input && + git config core.safecrlf false && + + for w in I am all CRLF; do echo $w; done | append_cr >allcrlf && + git add allcrlf 2>err && + test_must_be_empty err +' + + test_expect_success 'switch off autocrlf, safecrlf, reset HEAD' ' git config core.autocrlf false && git config core.safecrlf false && diff --git a/t/t0070-fundamental.sh b/t/t0070-fundamental.sh index 23fbe6434a..7b111a56fd 100755 --- a/t/t0070-fundamental.sh +++ b/t/t0070-fundamental.sh @@ -19,8 +19,8 @@ test_expect_success 'mktemp to nonexistent directory prints filename' ' test_expect_success POSIXPERM,SANITY 'mktemp to unwritable directory prints filename' ' mkdir cannotwrite && - chmod -w cannotwrite && test_when_finished "chmod +w cannotwrite" && + chmod -w cannotwrite && test_must_fail test-tool mktemp cannotwrite/testXXXXXX 2>err && grep "cannotwrite/test" err ' diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh index cc18b75c03..4984ca583d 100755 --- a/t/t0410-partial-clone.sh +++ b/t/t0410-partial-clone.sh @@ -23,7 +23,15 @@ promise_and_delete () { delete_object repo "$HASH" } +test_expect_success 'extensions.partialclone without filter' ' + test_create_repo server && + git clone --filter="blob:none" "file://$(pwd)/server" client && + git -C client config --unset core.partialclonefilter && + git -C client fetch origin +' + test_expect_success 'missing reflog object, but promised by a commit, passes fsck' ' + rm -rf repo && test_create_repo repo && test_commit -C repo my_commit && diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh index c7ce5d8bb5..826cd32e23 100755 --- a/t/t1004-read-tree-m-u-wf.sh +++ b/t/t1004-read-tree-m-u-wf.sh @@ -179,6 +179,8 @@ test_expect_success 'funny symlink in work tree' ' test_expect_success SANITY 'funny symlink in work tree, un-unlink-able' ' + test_when_finished "chmod u+w a 2>/dev/null; rm -fr a b" && + rm -fr a b && git reset --hard && @@ -188,10 +190,6 @@ test_expect_success SANITY 'funny symlink in work tree, un-unlink-able' ' ' -# clean-up from the above test -chmod a+w a 2>/dev/null -rm -fr a b - test_expect_success 'D/F setup' ' git reset --hard && diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index 553e26d9ce..8293131001 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -339,8 +339,8 @@ test_expect_failure 'reflog with non-commit entries displays all entries' ' ' test_expect_success 'reflog expire operates on symref not referrent' ' - git branch -l the_symref && - git branch -l referrent && + git branch --create-reflog the_symref && + git branch --create-reflog referrent && git update-ref referrent HEAD && git symbolic-ref refs/heads/the_symref refs/heads/referrent && test_when_finished "rm -f .git/refs/heads/referrent.lock" && diff --git a/t/t2203-add-intent.sh b/t/t2203-add-intent.sh index 04d840a544..e7a400b4c7 100755 --- a/t/t2203-add-intent.sh +++ b/t/t2203-add-intent.sh @@ -70,8 +70,7 @@ test_expect_success 'i-t-a entry is simply ignored' ' git commit -m second && test $(git ls-tree HEAD -- nitfol | wc -l) = 0 && test $(git diff --name-only HEAD -- nitfol | wc -l) = 1 && - test $(git diff --name-only --ita-invisible-in-index HEAD -- nitfol | wc -l) = 0 && - test $(git diff --name-only --ita-invisible-in-index -- nitfol | wc -l) = 1 + test $(git diff --name-only -- nitfol | wc -l) = 1 ' test_expect_success 'can commit with an unrelated i-t-a entry in index' ' @@ -99,13 +98,13 @@ test_expect_success 'cache-tree invalidates i-t-a paths' ' : >dir/bar && git add -N dir/bar && - git diff --cached --name-only >actual && + git diff --name-only >actual && echo dir/bar >expect && test_cmp expect actual && git write-tree >/dev/null && - git diff --cached --name-only >actual && + git diff --name-only >actual && echo dir/bar >expect && test_cmp expect actual ' @@ -186,7 +185,19 @@ test_expect_success 'rename detection finds the right names' ' cat >expected.3 <<-EOF && 2 .R N... 100644 100644 100644 $hash $hash R100 third first EOF - test_cmp expected.3 actual.3 + test_cmp expected.3 actual.3 && + + git diff --stat >actual.4 && + cat >expected.4 <<-EOF && + first => third | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + EOF + test_cmp expected.4 actual.4 && + + git diff --cached --stat >actual.5 && + : >expected.5 && + test_cmp expected.5 actual.5 + ) ' @@ -222,5 +233,46 @@ test_expect_success 'double rename detection in status' ' ) ' -test_done +test_expect_success 'diff-files/diff-cached shows ita as new/not-new files' ' + git reset --hard && + echo new >new-ita && + git add -N new-ita && + git diff --summary >actual && + echo " create mode 100644 new-ita" >expected && + test_cmp expected actual && + git diff --cached --summary >actual2 && + : >expected2 && + test_cmp expected2 actual2 +' + +test_expect_success '"diff HEAD" includes ita as new files' ' + git reset --hard && + echo new >new-ita && + git add -N new-ita && + git diff HEAD >actual && + cat >expected <<-\EOF && + diff --git a/new-ita b/new-ita + new file mode 100644 + index 0000000..3e75765 + --- /dev/null + +++ b/new-ita + @@ -0,0 +1 @@ + +new + EOF + test_cmp expected actual +' + +test_expect_success 'apply --intent-to-add' ' + git reset --hard && + echo new >new-ita && + git add -N new-ita && + git diff >expected && + grep "new file" expected && + git reset --hard && + git apply --intent-to-add expected && + git diff >actual && + test_cmp expected actual +' + +test_done diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 08467982f6..dbca665da4 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -49,9 +49,9 @@ test_expect_success 'git branch HEAD should fail' ' cat >expect <<EOF $ZERO_OID $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master EOF -test_expect_success 'git branch -l d/e/f should create a branch and a log' ' +test_expect_success 'git branch --create-reflog d/e/f should create a branch and a log' ' GIT_COMMITTER_DATE="2005-05-26 23:30" \ - git branch -l d/e/f && + git -c core.logallrefupdates=false branch --create-reflog d/e/f && test_path_is_file .git/refs/heads/d/e/f && test_path_is_file .git/logs/refs/heads/d/e/f && test_cmp expect .git/logs/refs/heads/d/e/f @@ -82,7 +82,7 @@ test_expect_success 'git branch -m dumps usage' ' test_expect_success 'git branch -m m broken_symref should work' ' test_when_finished "git branch -D broken_symref" && - git branch -l m && + git branch --create-reflog m && git symbolic-ref refs/heads/broken_symref refs/heads/i_am_broken && git branch -m m broken_symref && git reflog exists refs/heads/broken_symref && @@ -90,13 +90,13 @@ test_expect_success 'git branch -m m broken_symref should work' ' ' test_expect_success 'git branch -m m m/m should work' ' - git branch -l m && + git branch --create-reflog m && git branch -m m m/m && git reflog exists refs/heads/m/m ' test_expect_success 'git branch -m n/n n should work' ' - git branch -l n/n && + git branch --create-reflog n/n && git branch -m n/n n && git reflog exists refs/heads/n ' @@ -378,9 +378,9 @@ mv .git/config-saved .git/config git config branch.s/s.dummy Hello test_expect_success 'git branch -m s/s s should work when s/t is deleted' ' - git branch -l s/s && + git branch --create-reflog s/s && git reflog exists refs/heads/s/s && - git branch -l s/t && + git branch --create-reflog s/t && git reflog exists refs/heads/s/t && git branch -d s/t && git branch -m s/s s && @@ -444,7 +444,7 @@ test_expect_success 'git branch --copy dumps usage' ' ' test_expect_success 'git branch -c d e should work' ' - git branch -l d && + git branch --create-reflog d && git reflog exists refs/heads/d && git config branch.d.dummy Hello && git branch -c d e && @@ -459,7 +459,7 @@ test_expect_success 'git branch -c d e should work' ' ' test_expect_success 'git branch --copy is a synonym for -c' ' - git branch -l copy && + git branch --create-reflog copy && git reflog exists refs/heads/copy && git config branch.copy.dummy Hello && git branch --copy copy copy-to && @@ -486,7 +486,7 @@ test_expect_success 'git branch -c ee ef should copy ee to create branch ef' ' ' test_expect_success 'git branch -c f/f g/g should work' ' - git branch -l f/f && + git branch --create-reflog f/f && git reflog exists refs/heads/f/f && git config branch.f/f.dummy Hello && git branch -c f/f g/g && @@ -497,7 +497,7 @@ test_expect_success 'git branch -c f/f g/g should work' ' ' test_expect_success 'git branch -c m2 m2 should work' ' - git branch -l m2 && + git branch --create-reflog m2 && git reflog exists refs/heads/m2 && git config branch.m2.dummy Hello && git branch -c m2 m2 && @@ -506,18 +506,18 @@ test_expect_success 'git branch -c m2 m2 should work' ' ' test_expect_success 'git branch -c zz zz/zz should fail' ' - git branch -l zz && + git branch --create-reflog 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 && + git branch --create-reflog 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 branch --create-reflog o/q && git reflog exists refs/heads/o/q && git reflog exists refs/heads/o/p && git branch -C o/q o/p @@ -570,10 +570,10 @@ test_expect_success 'git branch -C master5 master5 should work when master is ch ' test_expect_success 'git branch -C ab cd should overwrite existing config for cd' ' - git branch -l cd && + git branch --create-reflog cd && git reflog exists refs/heads/cd && git config branch.cd.dummy CD && - git branch -l ab && + git branch --create-reflog ab && git reflog exists refs/heads/ab && git config branch.ab.dummy AB && git branch -C ab cd && @@ -685,7 +685,7 @@ test_expect_success 'renaming a symref is not allowed' ' ' test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' ' - git branch -l u && + git branch --create-reflog u && mv .git/logs/refs/heads/u real-u && ln -s real-u .git/logs/refs/heads/u && test_must_fail git branch -m u v diff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh new file mode 100755 index 0000000000..8f832957fc --- /dev/null +++ b/t/t3401-rebase-and-am-rename.sh @@ -0,0 +1,105 @@ +#!/bin/sh + +test_description='git rebase + directory rename tests' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + +test_expect_success 'setup testcase' ' + test_create_repo dir-rename && + ( + cd dir-rename && + + mkdir x && + test_seq 1 10 >x/a && + test_seq 11 20 >x/b && + test_seq 21 30 >x/c && + test_write_lines a b c d e f g h i >l && + git add x l && + git commit -m "Initial" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv x y && + git mv l letters && + git commit -m "Rename x to y, l to letters" && + + git checkout B && + echo j >>l && + test_seq 31 40 >x/d && + git add l x/d && + git commit -m "Modify l, add x/d" + ) +' + +test_expect_success 'rebase --interactive: directory rename detected' ' + ( + cd dir-rename && + + git checkout B^0 && + + set_fake_editor && + FAKE_LINES="1" git rebase --interactive A && + + git ls-files -s >out && + test_line_count = 5 out && + + test_path_is_file y/d && + test_path_is_missing x/d + ) +' + +test_expect_failure 'rebase (am): directory rename detected' ' + ( + cd dir-rename && + + git checkout B^0 && + + git rebase A && + + git ls-files -s >out && + test_line_count = 5 out && + + test_path_is_file y/d && + test_path_is_missing x/d + ) +' + +test_expect_success 'rebase --merge: directory rename detected' ' + ( + cd dir-rename && + + git checkout B^0 && + + git rebase --merge A && + + git ls-files -s >out && + test_line_count = 5 out && + + test_path_is_file y/d && + test_path_is_missing x/d + ) +' + +test_expect_failure 'am: directory rename detected' ' + ( + cd dir-rename && + + git checkout A^0 && + + git format-patch -1 B && + + git am --3way 0001*.patch && + + git ls-files -s >out && + test_line_count = 5 out && + + test_path_is_file y/d && + test_path_is_missing x/d + ) +' + +test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 352a52e59d..c5d39e2b23 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -553,15 +553,16 @@ test_expect_success '--continue tries to commit, even for "edit"' ' ' test_expect_success 'aborted --continue does not squash commits after "edit"' ' + test_when_finished "git rebase --abort" && old=$(git rev-parse HEAD) && test_tick && set_fake_editor && FAKE_LINES="edit 1" git rebase -i HEAD^ && echo "edited again" > file7 && git add file7 && - test_must_fail env FAKE_COMMIT_MESSAGE=" " git rebase --continue && - test $old = $(git rev-parse HEAD) && - git rebase --abort + echo all the things >>conflict && + test_must_fail git rebase --continue && + test $old = $(git rev-parse HEAD) ' test_expect_success 'auto-amend only edited commits after "edit"' ' @@ -981,7 +982,35 @@ test_expect_success 'rebase -i --root reword root commit' ' test -z "$(git show -s --format=%p HEAD^)" ' +test_expect_success 'rebase -i --root when root has untracked file confilct' ' + test_when_finished "reset_rebase" && + git checkout -b failing-root-pick A && + echo x >file2 && + git rm file1 && + git commit -m "remove file 1 add file 2" && + echo z >file1 && + set_fake_editor && + test_must_fail env FAKE_LINES="1 2" git rebase -i --root && + rm file1 && + git rebase --continue && + test "$(git log -1 --format=%B)" = "remove file 1 add file 2" && + test "$(git rev-list --count HEAD)" = 2 +' + +test_expect_success 'rebase -i --root reword root when root has untracked file conflict' ' + test_when_finished "reset_rebase" && + echo z>file1 && + set_fake_editor && + test_must_fail env FAKE_LINES="reword 1 2" \ + FAKE_COMMIT_MESSAGE="Modified A" git rebase -i --root && + rm file1 && + FAKE_COMMIT_MESSAGE="Reworded A" git rebase --continue && + test "$(git log -1 --format=%B HEAD^)" = "Reworded A" && + test "$(git rev-list --count HEAD)" = 2 +' + test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-interactive rebase' ' + git checkout reword-root-branch && git reset --hard && git checkout conflict-branch && set_fake_editor && diff --git a/t/t3405-rebase-malformed.sh b/t/t3405-rebase-malformed.sh index cb7c6de84a..da94dddc86 100755 --- a/t/t3405-rebase-malformed.sh +++ b/t/t3405-rebase-malformed.sh @@ -77,19 +77,14 @@ test_expect_success 'rebase commit with diff in message' ' ' test_expect_success 'rebase -m commit with empty message' ' - test_must_fail git rebase -m master empty-message-merge && - git rebase --abort && - git rebase -m --allow-empty-message master empty-message-merge + git rebase -m master empty-message-merge ' test_expect_success 'rebase -i commit with empty message' ' git checkout diff-in-message && set_fake_editor && - test_must_fail env FAKE_COMMIT_MESSAGE=" " FAKE_LINES="reword 1" \ - git rebase -i HEAD^ && - git rebase --abort && - FAKE_COMMIT_MESSAGE=" " FAKE_LINES="reword 1" \ - git rebase -i --allow-empty-message HEAD^ + env FAKE_COMMIT_MESSAGE=" " FAKE_LINES="reword 1" \ + git rebase -i HEAD^ ' test_done diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index 03bf1b8a3b..c145dbac38 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -74,6 +74,38 @@ test_expect_success 'rebase --continue remembers merge strategy and options' ' test -f funny.was.run ' +test_expect_success 'rebase -i --continue handles merge strategy and options' ' + rm -fr .git/rebase-* && + git reset --hard commit-new-file-F2-on-topic-branch && + test_commit "commit-new-file-F3-on-topic-branch-for-dash-i" F3 32 && + test_when_finished "rm -fr test-bin funny.was.run funny.args" && + mkdir test-bin && + cat >test-bin/git-merge-funny <<-EOF && + #!$SHELL_PATH + echo "\$@" >>funny.args + case "\$1" in --opt) ;; *) exit 2 ;; esac + case "\$2" in --foo) ;; *) exit 2 ;; esac + case "\$4" in --) ;; *) exit 2 ;; esac + shift 2 && + >funny.was.run && + exec git merge-recursive "\$@" + EOF + chmod +x test-bin/git-merge-funny && + ( + PATH=./test-bin:$PATH && + test_must_fail git rebase -i -s funny -Xopt -Xfoo master topic + ) && + test -f funny.was.run && + rm funny.was.run && + echo "Resolved" >F2 && + git add F2 && + ( + PATH=./test-bin:$PATH && + git rebase --continue + ) && + test -f funny.was.run +' + test_expect_success 'rebase passes merge strategy options correctly' ' rm -fr .git/rebase-* && git reset --hard commit-new-file-F3-on-topic-branch && diff --git a/t/t3422-rebase-incompatible-options.sh b/t/t3422-rebase-incompatible-options.sh new file mode 100755 index 0000000000..bb78a6ec86 --- /dev/null +++ b/t/t3422-rebase-incompatible-options.sh @@ -0,0 +1,88 @@ +#!/bin/sh + +test_description='test if rebase detects and aborts on incompatible options' +. ./test-lib.sh + +test_expect_success 'setup' ' + test_seq 2 9 >foo && + git add foo && + git commit -m orig && + + git branch A && + git branch B && + + git checkout A && + test_seq 1 9 >foo && + git add foo && + git commit -m A && + + git checkout B && + echo "q qfoo();" | q_to_tab >>foo && + git add foo && + git commit -m B +' + +# +# Rebase has lots of useful options like --whitepsace=fix, which are +# actually all built in terms of flags to git-am. Since neither +# --merge nor --interactive (nor any options that imply those two) use +# git-am, using them together will result in flags like --whitespace=fix +# being ignored. Make sure rebase warns the user and aborts instead. +# + +test_rebase_am_only () { + opt=$1 + shift + test_expect_success "$opt incompatible with --merge" " + git checkout B^0 && + test_must_fail git rebase $opt --merge A + " + + test_expect_success "$opt incompatible with --strategy=ours" " + git checkout B^0 && + test_must_fail git rebase $opt --strategy=ours A + " + + test_expect_success "$opt incompatible with --strategy-option=ours" " + git checkout B^0 && + test_must_fail git rebase $opt --strategy-option=ours A + " + + test_expect_success "$opt incompatible with --interactive" " + git checkout B^0 && + test_must_fail git rebase $opt --interactive A + " + + test_expect_success "$opt incompatible with --exec" " + git checkout B^0 && + test_must_fail git rebase $opt --exec 'true' A + " + +} + +test_rebase_am_only --whitespace=fix +test_rebase_am_only --ignore-whitespace +test_rebase_am_only --committer-date-is-author-date +test_rebase_am_only -C4 + +test_expect_success '--preserve-merges incompatible with --signoff' ' + git checkout B^0 && + test_must_fail git rebase --preserve-merges --signoff A +' + +test_expect_success '--preserve-merges incompatible with --rebase-merges' ' + git checkout B^0 && + test_must_fail git rebase --preserve-merges --rebase-merges A +' + +test_expect_success '--rebase-merges incompatible with --strategy' ' + git checkout B^0 && + test_must_fail git rebase --rebase-merges -s resolve A +' + +test_expect_success '--rebase-merges incompatible with --strategy-option' ' + git checkout B^0 && + test_must_fail git rebase --rebase-merges -Xignore-space-change A +' + +test_done diff --git a/t/t3423-rebase-reword.sh b/t/t3423-rebase-reword.sh new file mode 100755 index 0000000000..6963750794 --- /dev/null +++ b/t/t3423-rebase-reword.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +test_description='git rebase interactive with rewording' + +. ./test-lib.sh + +. "$TEST_DIRECTORY"/lib-rebase.sh + +test_expect_success 'setup' ' + test_commit master file-1 test && + + git checkout -b stuff && + + test_commit feature_a file-2 aaa && + test_commit feature_b file-2 ddd +' + +test_expect_success 'reword without issues functions as intended' ' + test_when_finished "reset_rebase" && + + git checkout stuff^0 && + + set_fake_editor && + FAKE_LINES="pick 1 reword 2" FAKE_COMMIT_MESSAGE="feature_b_reworded" \ + git rebase -i -v master && + + test "$(git log -1 --format=%B)" = "feature_b_reworded" && + test $(git rev-list --count HEAD) = 3 +' + +test_expect_success 'reword after a conflict preserves commit' ' + test_when_finished "reset_rebase" && + + git checkout stuff^0 && + + set_fake_editor && + test_must_fail env FAKE_LINES="reword 2" \ + git rebase -i -v master && + + git checkout --theirs file-2 && + git add file-2 && + FAKE_COMMIT_MESSAGE="feature_b_reworded" git rebase --continue && + + test "$(git log -1 --format=%B)" = "feature_b_reworded" && + test $(git rev-list --count HEAD) = 2 +' + +test_done diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index b170fb02b8..3e9139dca8 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -175,6 +175,49 @@ test_expect_success 'real edit works' ' diff_cmp expected output ' +test_expect_success 'setup file' ' + test_write_lines a "" b "" c >file && + git add file && + test_write_lines a "" d "" c >file +' + +test_expect_success 'setup patch' ' + SP=" " && + NULL="" && + cat >patch <<-EOF + @@ -1,4 +1,4 @@ + a + $NULL + -b + +f + $SP + c + EOF +' + +test_expect_success 'setup expected' ' + cat >expected <<-EOF + diff --git a/file b/file + index b5dd6c9..f910ae9 100644 + --- a/file + +++ b/file + @@ -1,5 +1,5 @@ + a + $SP + -f + +d + $SP + c + EOF +' + +test_expect_success 'edit can strip spaces from empty context lines' ' + test_write_lines e n q | git add -p 2>error && + test_must_be_empty error && + git diff >output && + diff_cmp expected output +' + test_expect_success 'skip files similarly as commit -a' ' git reset && echo file >.gitignore && diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh index cf0f3a1ee7..108c012a3a 100755 --- a/t/t4011-diff-symlink.sh +++ b/t/t4011-diff-symlink.sh @@ -139,11 +139,13 @@ test_expect_success SYMLINKS 'setup symlinks with attributes' ' test_expect_success SYMLINKS 'symlinks do not respect userdiff config by path' ' cat >expect <<-\EOF && diff --git a/file.bin b/file.bin - index e69de29..d95f3ad 100644 - Binary files a/file.bin and b/file.bin differ + new file mode 100644 + index 0000000..d95f3ad + Binary files /dev/null and b/file.bin differ diff --git a/link.bin b/link.bin - index e69de29..dce41ec 120000 - --- a/link.bin + new file mode 120000 + index 0000000..dce41ec + --- /dev/null +++ b/link.bin @@ -0,0 +1 @@ +file.bin diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 028d5507a6..53880da7bb 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -1554,13 +1554,15 @@ test_expect_success 'format-patch -o overrides format.outputDirectory' ' test_expect_success 'format-patch --base' ' git checkout side && - git format-patch --stdout --base=HEAD~3 -1 | tail -n 7 >actual && + git format-patch --stdout --base=HEAD~3 -1 | tail -n 7 >actual1 && + git format-patch --stdout --base=HEAD~3 HEAD~.. | tail -n 7 >actual2 && echo >expected && echo "base-commit: $(git rev-parse HEAD~3)" >>expected && echo "prerequisite-patch-id: $(git show --patch HEAD~2 | git patch-id --stable | awk "{print \$1}")" >>expected && echo "prerequisite-patch-id: $(git show --patch HEAD~1 | git patch-id --stable | awk "{print \$1}")" >>expected && signature >> expected && - test_cmp expected actual + test_cmp expected actual1 && + test_cmp expected actual2 ' test_expect_success 'format-patch --base errors out when base commit is in revision list' ' diff --git a/t/t4018/php-abstract-class b/t/t4018/php-abstract-class new file mode 100644 index 0000000000..5213e12494 --- /dev/null +++ b/t/t4018/php-abstract-class @@ -0,0 +1,4 @@ +abstract class RIGHT +{ + const FOO = 'ChangeMe'; +} diff --git a/t/t4018/php-class b/t/t4018/php-class new file mode 100644 index 0000000000..7785b6303c --- /dev/null +++ b/t/t4018/php-class @@ -0,0 +1,4 @@ +class RIGHT +{ + const FOO = 'ChangeMe'; +} diff --git a/t/t4018/php-final-class b/t/t4018/php-final-class new file mode 100644 index 0000000000..69f5710552 --- /dev/null +++ b/t/t4018/php-final-class @@ -0,0 +1,4 @@ +final class RIGHT +{ + const FOO = 'ChangeMe'; +} diff --git a/t/t4018/php-function b/t/t4018/php-function new file mode 100644 index 0000000000..35717c51c3 --- /dev/null +++ b/t/t4018/php-function @@ -0,0 +1,4 @@ +function RIGHT() +{ + return 'ChangeMe'; +} diff --git a/t/t4018/php-interface b/t/t4018/php-interface new file mode 100644 index 0000000000..86b49ad5d9 --- /dev/null +++ b/t/t4018/php-interface @@ -0,0 +1,4 @@ +interface RIGHT +{ + public function foo($ChangeMe); +} diff --git a/t/t4018/php-method b/t/t4018/php-method new file mode 100644 index 0000000000..03af1a6d9d --- /dev/null +++ b/t/t4018/php-method @@ -0,0 +1,7 @@ +class Klass +{ + public static function RIGHT() + { + return 'ChangeMe'; + } +} diff --git a/t/t4018/php-trait b/t/t4018/php-trait new file mode 100644 index 0000000000..65b8c82a61 --- /dev/null +++ b/t/t4018/php-trait @@ -0,0 +1,7 @@ +trait RIGHT +{ + public function foo($ChangeMe) + { + return 'foo'; + } +} diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh index 168739c721..fd3bdbfe2c 100755 --- a/t/t4254-am-corrupt.sh +++ b/t/t4254-am-corrupt.sh @@ -25,7 +25,7 @@ test_expect_success setup ' # fatal: unable to write file '(null)' mode 100644: Bad address # Also, it had the unwanted side-effect of deleting f. test_expect_success 'try to apply corrupted patch' ' - test_must_fail git am bad-patch.diff 2>actual + test_must_fail git -c advice.amWorkDir=false am bad-patch.diff 2>actual ' test_expect_success 'compare diagnostic; ensure file is still here' ' diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index a380419b65..77d85aefe7 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -221,4 +221,13 @@ test_expect_success 'write graph in bare repo' ' graph_git_behavior 'bare repo with graph, commit 8 vs merge 1' bare commits/8 merge/1 graph_git_behavior 'bare repo with graph, commit 8 vs merge 2' bare commits/8 merge/2 +test_expect_success 'perform fast-forward merge in full repo' ' + cd "$TRASH_DIRECTORY/full" && + git checkout -b merge-5-to-8 commits/5 && + git merge commits/8 && + git show-ref -s merge-5-to-8 >output && + git show-ref -s commits/8 >expect && + test_cmp expect output +' + test_done diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh index 7a48236e87..9b2a274c71 100755 --- a/t/t5407-post-rewrite-hook.sh +++ b/t/t5407-post-rewrite-hook.sh @@ -113,7 +113,7 @@ test_expect_success 'git rebase -m' ' test_expect_success 'git rebase -m --skip' ' git reset --hard D && clear_hook_input && - test_must_fail git rebase --onto A B && + test_must_fail git rebase -m --onto A B && test_must_fail git rebase --skip && echo D > foo && git add foo && diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index d4f435155f..3d33ab3875 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -518,6 +518,54 @@ test_expect_success 'test --all, --depth, and explicit tag' ' ) >out-adt 2>error-adt ' +test_expect_success 'test --all with tag to non-tip' ' + git commit --allow-empty -m non-tip && + git commit --allow-empty -m tip && + git tag -m "annotated" non-tip HEAD^ && + ( + cd client && + git fetch-pack --all .. + ) +' + +test_expect_success 'test --all wrt tag to non-commits' ' + # create tag-to-{blob,tree,commit,tag}, making sure all tagged objects + # are reachable only via created tag references. + blob=$(echo "hello blob" | git hash-object -t blob -w --stdin) && + git tag -a -m "tag -> blob" tag-to-blob $blob && + + tree=$(printf "100644 blob $blob\tfile" | git mktree) && + git tag -a -m "tag -> tree" tag-to-tree $tree && + + tree2=$(printf "100644 blob $blob\tfile2" | git mktree) && + commit=$(git commit-tree -m "hello commit" $tree) && + git tag -a -m "tag -> commit" tag-to-commit $commit && + + blob2=$(echo "hello blob2" | git hash-object -t blob -w --stdin) && + tag=$(git mktag <<-EOF + object $blob2 + type blob + tag tag-to-blob2 + tagger author A U Thor <author@example.com> 0 +0000 + + hello tag + EOF + ) && + git tag -a -m "tag -> tag" tag-to-tag $tag && + + # `fetch-pack --all` should succeed fetching all those objects. + mkdir fetchall && + ( + cd fetchall && + git init && + git fetch-pack --all .. && + git cat-file blob $blob >/dev/null && + git cat-file tree $tree >/dev/null && + git cat-file commit $commit >/dev/null && + git cat-file tag $tag >/dev/null + ) +' + test_expect_success 'shallow fetch with tags does not break the repository' ' mkdir repo1 && ( @@ -711,6 +759,17 @@ test_expect_success 'fetch shallow since ...' ' test_cmp expected actual ' +test_expect_success 'clone shallow since selects no commits' ' + test_create_repo shallow-since-the-future && + ( + cd shallow-since-the-future && + GIT_COMMITTER_DATE="100000000 +0700" git commit --allow-empty -m one && + GIT_COMMITTER_DATE="200000000 +0700" git commit --allow-empty -m two && + GIT_COMMITTER_DATE="300000000 +0700" git commit --allow-empty -m three && + test_must_fail git clone --shallow-since "900000000 +0700" "file://$(pwd)/." ../shallow111 + ) +' + test_expect_success 'shallow clone exclude tag two' ' test_create_repo shallow-exclude && ( diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index 9cc4b569c0..359e03ff83 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -574,11 +574,7 @@ test_expect_success "fetch new commits when submodule got renamed" ' git clone . downstream_rename && ( cd downstream_rename && - git submodule update --init && -# NEEDSWORK: we omitted --recursive for the submodule update here since -# that does not work. See test 7001 for mv "moving nested submodules" -# for details. Once that is fixed we should add the --recursive option -# here. + git submodule update --init --recursive && git checkout -b rename && git mv submodule submodule_renamed && ( diff --git a/t/t5537-fetch-shallow.sh b/t/t5537-fetch-shallow.sh index df8d2f095a..7045685e2d 100755 --- a/t/t5537-fetch-shallow.sh +++ b/t/t5537-fetch-shallow.sh @@ -175,8 +175,8 @@ EOF test_expect_success POSIXPERM,SANITY 'shallow fetch from a read-only repo' ' cp -R .git read-only.git && - find read-only.git -print | xargs chmod -w && test_when_finished "find read-only.git -type d -print | xargs chmod +w" && + find read-only.git -print | xargs chmod -w && git clone --no-local --depth=2 read-only.git from-read-only && git --git-dir=from-read-only/.git log --format=%s >actual && cat >expect <<EOF && @@ -186,4 +186,47 @@ EOF test_cmp expect actual ' +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +REPO="$HTTPD_DOCUMENT_ROOT_PATH/repo" + +test_expect_success 'shallow fetches check connectivity before writing shallow file' ' + rm -rf "$REPO" client && + + git init "$REPO" && + test_commit -C "$REPO" one && + test_commit -C "$REPO" two && + test_commit -C "$REPO" three && + + git init client && + + # Use protocol v2 to ensure that shallow information is sent exactly + # once by the server, since we are planning to manipulate it. + git -C "$REPO" config protocol.version 2 && + git -C client config protocol.version 2 && + + git -C client fetch --depth=2 "$HTTPD_URL/one_time_sed/repo" master:a_branch && + + # Craft a situation in which the server sends back an unshallow request + # with an empty packfile. This is done by refetching with a shorter + # depth (to ensure that the packfile is empty), and overwriting the + # shallow line in the response with the unshallow line we want. + printf "s/0034shallow %s/0036unshallow %s/" \ + "$(git -C "$REPO" rev-parse HEAD)" \ + "$(git -C "$REPO" rev-parse HEAD^)" \ + >"$HTTPD_ROOT_PATH/one-time-sed" && + test_must_fail git -C client fetch --depth=1 "$HTTPD_URL/one_time_sed/repo" \ + master:a_branch && + + # Ensure that the one-time-sed script was used. + ! test -e "$HTTPD_ROOT_PATH/one-time-sed" && + + # Ensure that the resulting repo is consistent, despite our failure to + # fetch. + git -C client fsck +' + +stop_httpd + test_done diff --git a/t/t5573-pull-verify-signatures.sh b/t/t5573-pull-verify-signatures.sh index 9594e891f4..747775c147 100755 --- a/t/t5573-pull-verify-signatures.sh +++ b/t/t5573-pull-verify-signatures.sh @@ -29,7 +29,7 @@ test_expect_success GPG 'create repositories with signed commits' ' echo 4 >d && git add d && test_tick && git commit -S -m "bad" && git cat-file commit HEAD >raw && - sed -e "s/bad/forged bad/" raw >forged && + sed -e "s/^bad/forged bad/" raw >forged && git hash-object -w -t commit forged >forged.commit && git checkout $(cat forged.commit) ) && diff --git a/t/t5703-upload-pack-ref-in-want.sh b/t/t5703-upload-pack-ref-in-want.sh new file mode 100755 index 0000000000..a73c55a47e --- /dev/null +++ b/t/t5703-upload-pack-ref-in-want.sh @@ -0,0 +1,377 @@ +#!/bin/sh + +test_description='upload-pack ref-in-want' + +. ./test-lib.sh + +get_actual_refs () { + sed -n -e '/wanted-refs/,/0001/{ + /wanted-refs/d + /0001/d + p + }' <out | test-pkt-line unpack >actual_refs +} + +get_actual_commits () { + sed -n -e '/packfile/,/0000/{ + /packfile/d + p + }' <out | test-pkt-line unpack-sideband >o.pack && + git index-pack o.pack && + git verify-pack -v o.idx | grep commit | cut -c-40 | sort >actual_commits +} + +check_output () { + get_actual_refs && + test_cmp expected_refs actual_refs && + get_actual_commits && + test_cmp expected_commits actual_commits +} + +# c(o/foo) d(o/bar) +# \ / +# b e(baz) f(master) +# \__ | __/ +# \ | / +# a +test_expect_success 'setup repository' ' + test_commit a && + git checkout -b o/foo && + test_commit b && + test_commit c && + git checkout -b o/bar b && + test_commit d && + git checkout -b baz a && + test_commit e && + git checkout master && + test_commit f +' + +test_expect_success 'config controls ref-in-want advertisement' ' + git serve --advertise-capabilities >out && + ! grep -a ref-in-want out && + + git config uploadpack.allowRefInWant false && + git serve --advertise-capabilities >out && + ! grep -a ref-in-want out && + + git config uploadpack.allowRefInWant true && + git serve --advertise-capabilities >out && + grep -a ref-in-want out +' + +test_expect_success 'invalid want-ref line' ' + test-pkt-line pack >in <<-EOF && + command=fetch + 0001 + no-progress + want-ref refs/heads/non-existent + done + 0000 + EOF + + test_must_fail git serve --stateless-rpc 2>out <in && + grep "unknown ref" out +' + +test_expect_success 'basic want-ref' ' + cat >expected_refs <<-EOF && + $(git rev-parse f) refs/heads/master + EOF + git rev-parse f | sort >expected_commits && + + test-pkt-line pack >in <<-EOF && + command=fetch + 0001 + no-progress + want-ref refs/heads/master + have $(git rev-parse a) + done + 0000 + EOF + + git serve --stateless-rpc >out <in && + check_output +' + +test_expect_success 'multiple want-ref lines' ' + cat >expected_refs <<-EOF && + $(git rev-parse c) refs/heads/o/foo + $(git rev-parse d) refs/heads/o/bar + EOF + git rev-parse c d | sort >expected_commits && + + test-pkt-line pack >in <<-EOF && + command=fetch + 0001 + no-progress + want-ref refs/heads/o/foo + want-ref refs/heads/o/bar + have $(git rev-parse b) + done + 0000 + EOF + + git serve --stateless-rpc >out <in && + check_output +' + +test_expect_success 'mix want and want-ref' ' + cat >expected_refs <<-EOF && + $(git rev-parse f) refs/heads/master + EOF + git rev-parse e f | sort >expected_commits && + + test-pkt-line pack >in <<-EOF && + command=fetch + 0001 + no-progress + want-ref refs/heads/master + want $(git rev-parse e) + have $(git rev-parse a) + done + 0000 + EOF + + git serve --stateless-rpc >out <in && + check_output +' + +test_expect_success 'want-ref with ref we already have commit for' ' + cat >expected_refs <<-EOF && + $(git rev-parse c) refs/heads/o/foo + EOF + >expected_commits && + + test-pkt-line pack >in <<-EOF && + command=fetch + 0001 + no-progress + want-ref refs/heads/o/foo + have $(git rev-parse c) + done + 0000 + EOF + + git serve --stateless-rpc >out <in && + check_output +' + +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +REPO="$HTTPD_DOCUMENT_ROOT_PATH/repo" +LOCAL_PRISTINE="$(pwd)/local_pristine" + +test_expect_success 'setup repos for change-while-negotiating test' ' + ( + git init "$REPO" && + cd "$REPO" && + >.git/git-daemon-export-ok && + test_commit m1 && + git tag -d m1 && + + # Local repo with many commits (so that negotiation will take + # more than 1 request/response pair) + git clone "http://127.0.0.1:$LIB_HTTPD_PORT/smart/repo" "$LOCAL_PRISTINE" && + cd "$LOCAL_PRISTINE" && + git checkout -b side && + for i in $(seq 1 33); do test_commit s$i; done && + + # Add novel commits to upstream + git checkout master && + cd "$REPO" && + test_commit m2 && + test_commit m3 && + git tag -d m2 m3 + ) && + git -C "$LOCAL_PRISTINE" remote set-url origin "http://127.0.0.1:$LIB_HTTPD_PORT/one_time_sed/repo" && + git -C "$LOCAL_PRISTINE" config protocol.version 2 +' + +inconsistency () { + # Simulate that the server initially reports $2 as the ref + # corresponding to $1, and after that, $1 as the ref corresponding to + # $1. This corresponds to the real-life situation where the server's + # repository appears to change during negotiation, for example, when + # different servers in a load-balancing arrangement serve (stateless) + # RPCs during a single negotiation. + printf "s/%s/%s/" \ + $(git -C "$REPO" rev-parse $1 | tr -d "\n") \ + $(git -C "$REPO" rev-parse $2 | tr -d "\n") \ + >"$HTTPD_ROOT_PATH/one-time-sed" +} + +test_expect_success 'server is initially ahead - no ref in want' ' + git -C "$REPO" config uploadpack.allowRefInWant false && + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + inconsistency master 1234567890123456789012345678901234567890 && + test_must_fail git -C local fetch 2>err && + grep "ERR upload-pack: not our ref" err +' + +test_expect_success 'server is initially ahead - ref in want' ' + git -C "$REPO" config uploadpack.allowRefInWant true && + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + inconsistency master 1234567890123456789012345678901234567890 && + git -C local fetch && + + git -C "$REPO" rev-parse --verify master >expected && + git -C local rev-parse --verify refs/remotes/origin/master >actual && + test_cmp expected actual +' + +test_expect_success 'server is initially behind - no ref in want' ' + git -C "$REPO" config uploadpack.allowRefInWant false && + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + inconsistency master "master^" && + git -C local fetch && + + git -C "$REPO" rev-parse --verify "master^" >expected && + git -C local rev-parse --verify refs/remotes/origin/master >actual && + test_cmp expected actual +' + +test_expect_success 'server is initially behind - ref in want' ' + git -C "$REPO" config uploadpack.allowRefInWant true && + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + inconsistency master "master^" && + git -C local fetch && + + git -C "$REPO" rev-parse --verify "master" >expected && + git -C local rev-parse --verify refs/remotes/origin/master >actual && + test_cmp expected actual +' + +test_expect_success 'server loses a ref - ref in want' ' + git -C "$REPO" config uploadpack.allowRefInWant true && + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + echo "s/master/raster/" >"$HTTPD_ROOT_PATH/one-time-sed" && + test_must_fail git -C local fetch 2>err && + + grep "ERR unknown ref refs/heads/raster" err +' + +stop_httpd + +REPO="$(pwd)/repo" +LOCAL_PRISTINE="$(pwd)/local_pristine" + +# $REPO +# c(o/foo) d(o/bar) +# \ / +# b e(baz) f(master) +# \__ | __/ +# \ | / +# a +# +# $LOCAL_PRISTINE +# s32(side) +# | +# . +# . +# | +# a(master) +test_expect_success 'setup repos for fetching with ref-in-want tests' ' + ( + git init "$REPO" && + cd "$REPO" && + test_commit a && + + # Local repo with many commits (so that negotiation will take + # more than 1 request/response pair) + rm -rf "$LOCAL_PRISTINE" && + git clone "file://$REPO" "$LOCAL_PRISTINE" && + cd "$LOCAL_PRISTINE" && + git checkout -b side && + for i in $(seq 1 33); do test_commit s$i; done && + + # Add novel commits to upstream + git checkout master && + cd "$REPO" && + git checkout -b o/foo && + test_commit b && + test_commit c && + git checkout -b o/bar b && + test_commit d && + git checkout -b baz a && + test_commit e && + git checkout master && + test_commit f + ) && + git -C "$REPO" config uploadpack.allowRefInWant true && + git -C "$LOCAL_PRISTINE" config protocol.version 2 +' + +test_expect_success 'fetching with exact OID' ' + test_when_finished "rm -f log" && + + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin \ + $(git -C "$REPO" rev-parse d):refs/heads/actual && + + git -C "$REPO" rev-parse "d" >expected && + git -C local rev-parse refs/heads/actual >actual && + test_cmp expected actual && + grep "want $(git -C "$REPO" rev-parse d)" log +' + +test_expect_success 'fetching multiple refs' ' + test_when_finished "rm -f log" && + + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin master baz && + + git -C "$REPO" rev-parse "master" "baz" >expected && + git -C local rev-parse refs/remotes/origin/master refs/remotes/origin/baz >actual && + test_cmp expected actual && + grep "want-ref refs/heads/master" log && + grep "want-ref refs/heads/baz" log +' + +test_expect_success 'fetching ref and exact OID' ' + test_when_finished "rm -f log" && + + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin \ + master $(git -C "$REPO" rev-parse b):refs/heads/actual && + + git -C "$REPO" rev-parse "master" "b" >expected && + git -C local rev-parse refs/remotes/origin/master refs/heads/actual >actual && + test_cmp expected actual && + grep "want $(git -C "$REPO" rev-parse b)" log && + grep "want-ref refs/heads/master" log +' + +test_expect_success 'fetching with wildcard that does not match any refs' ' + test_when_finished "rm -f log" && + + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + git -C local fetch origin refs/heads/none*:refs/heads/* >out && + test_must_be_empty out +' + +test_expect_success 'fetching with wildcard that matches multiple refs' ' + test_when_finished "rm -f log" && + + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin refs/heads/o*:refs/heads/o* && + + git -C "$REPO" rev-parse "o/foo" "o/bar" >expected && + git -C local rev-parse "o/foo" "o/bar" >actual && + test_cmp expected actual && + grep "want-ref refs/heads/o/foo" log && + grep "want-ref refs/heads/o/bar" log +' + +test_done diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh index 18aa88b5c0..b5621303d6 100755 --- a/t/t6036-recursive-corner-cases.sh +++ b/t/t6036-recursive-corner-cases.sh @@ -4,12 +4,6 @@ test_description='recursive merge corner cases involving criss-cross merges' . ./test-lib.sh -get_clean_checkout () { - git reset --hard && - git clean -fdqx && - git checkout "$1" -} - # # L1 L2 # o---o @@ -21,51 +15,66 @@ get_clean_checkout () { # test_expect_success 'setup basic criss-cross + rename with no modifications' ' - ten="0 1 2 3 4 5 6 7 8 9" && - for i in $ten - do - echo line $i in a sample file - done >one && - for i in $ten - do - echo line $i in another sample file - done >two && - git add one two && - test_tick && git commit -m initial && - - git branch L1 && - git checkout -b R1 && - git mv one three && - test_tick && git commit -m R1 && - - git checkout L1 && - git mv two three && - test_tick && git commit -m L1 && - - git checkout L1^0 && - test_tick && git merge -s ours R1 && - git tag L2 && - - git checkout R1^0 && - test_tick && git merge -s ours L1 && - git tag R2 + test_create_repo basic-rename && + ( + cd basic-rename && + + ten="0 1 2 3 4 5 6 7 8 9" && + for i in $ten + do + echo line $i in a sample file + done >one && + for i in $ten + do + echo line $i in another sample file + done >two && + git add one two && + test_tick && git commit -m initial && + + git branch L1 && + git checkout -b R1 && + git mv one three && + test_tick && git commit -m R1 && + + git checkout L1 && + git mv two three && + test_tick && git commit -m L1 && + + git checkout L1^0 && + test_tick && git merge -s ours R1 && + git tag L2 && + + git checkout R1^0 && + test_tick && git merge -s ours L1 && + git tag R2 + ) ' test_expect_success 'merge simple rename+criss-cross with no modifications' ' - git reset --hard && - git checkout L2^0 && - - test_must_fail git merge -s recursive R2^0 && - - test 2 = $(git ls-files -s | wc -l) && - test 2 = $(git ls-files -u | wc -l) && - test 2 = $(git ls-files -o | wc -l) && - - test $(git rev-parse :2:three) = $(git rev-parse L2:three) && - test $(git rev-parse :3:three) = $(git rev-parse R2:three) && - - test $(git rev-parse L2:three) = $(git hash-object three~HEAD) && - test $(git rev-parse R2:three) = $(git hash-object three~R2^0) + ( + cd basic-rename && + + git reset --hard && + git checkout L2^0 && + + test_must_fail git merge -s recursive R2^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >expect \ + L2:three R2:three \ + L2:three R2:three && + git rev-parse >actual \ + :2:three :3:three && + git hash-object >>actual \ + three~HEAD three~R2^0 + test_cmp expect actual + ) ' # @@ -81,58 +90,67 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' ' # test_expect_success 'setup criss-cross + rename merges with basic modification' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - ten="0 1 2 3 4 5 6 7 8 9" && - for i in $ten - do - echo line $i in a sample file - done >one && - for i in $ten - do - echo line $i in another sample file - done >two && - git add one two && - test_tick && git commit -m initial && - - git branch L1 && - git checkout -b R1 && - git mv one three && - echo more >>two && - git add two && - test_tick && git commit -m R1 && - - git checkout L1 && - git mv two three && - test_tick && git commit -m L1 && - - git checkout L1^0 && - test_tick && git merge -s ours R1 && - git tag L2 && - - git checkout R1^0 && - test_tick && git merge -s ours L1 && - git tag R2 + test_create_repo rename-modify && + ( + cd rename-modify && + + ten="0 1 2 3 4 5 6 7 8 9" && + for i in $ten + do + echo line $i in a sample file + done >one && + for i in $ten + do + echo line $i in another sample file + done >two && + git add one two && + test_tick && git commit -m initial && + + git branch L1 && + git checkout -b R1 && + git mv one three && + echo more >>two && + git add two && + test_tick && git commit -m R1 && + + git checkout L1 && + git mv two three && + test_tick && git commit -m L1 && + + git checkout L1^0 && + test_tick && git merge -s ours R1 && + git tag L2 && + + git checkout R1^0 && + test_tick && git merge -s ours L1 && + git tag R2 + ) ' test_expect_success 'merge criss-cross + rename merges with basic modification' ' - git reset --hard && - git checkout L2^0 && - - test_must_fail git merge -s recursive R2^0 && - - test 2 = $(git ls-files -s | wc -l) && - test 2 = $(git ls-files -u | wc -l) && - test 2 = $(git ls-files -o | wc -l) && - - test $(git rev-parse :2:three) = $(git rev-parse L2:three) && - test $(git rev-parse :3:three) = $(git rev-parse R2:three) && - - test $(git rev-parse L2:three) = $(git hash-object three~HEAD) && - test $(git rev-parse R2:three) = $(git hash-object three~R2^0) + ( + cd rename-modify && + + git checkout L2^0 && + + test_must_fail git merge -s recursive R2^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >expect \ + L2:three R2:three \ + L2:three R2:three && + git rev-parse >actual \ + :2:three :3:three && + git hash-object >>actual \ + three~HEAD three~R2^0 + test_cmp expect actual + ) ' # @@ -156,64 +174,74 @@ test_expect_success 'merge criss-cross + rename merges with basic modification' # test_expect_success 'setup differently handled merges of rename/add conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n" >a && - git add a && - test_tick && git commit -m A && - - git branch B && - git checkout -b C && - echo 10 >>a && - echo "other content" >>new_a && - git add a new_a && - test_tick && git commit -m C && - - git checkout B && - git mv a new_a && - test_tick && git commit -m B && - - git checkout B^0 && - test_must_fail git merge C && - git clean -f && - test_tick && git commit -m D && - git tag D && - - git checkout C^0 && - test_must_fail git merge B && - rm new_a~HEAD new_a && - printf "Incorrectly merged content" >>new_a && - git add -u && - test_tick && git commit -m E && - git tag E + test_create_repo rename-add && + ( + cd rename-add && + + printf "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n" >a && + git add a && + test_tick && git commit -m A && + + git branch B && + git checkout -b C && + echo 10 >>a && + echo "other content" >>new_a && + git add a new_a && + test_tick && git commit -m C && + + git checkout B && + git mv a new_a && + test_tick && git commit -m B && + + git checkout B^0 && + test_must_fail git merge C && + git clean -f && + test_tick && git commit -m D && + git tag D && + + git checkout C^0 && + test_must_fail git merge B && + rm new_a~HEAD new_a && + printf "Incorrectly merged content" >>new_a && + git add -u && + test_tick && git commit -m E && + git tag E + ) ' test_expect_success 'git detects differently handled merges conflict' ' - git reset --hard && - git checkout D^0 && - - test_must_fail git merge -s recursive E^0 && - - test 3 = $(git ls-files -s | wc -l) && - test 3 = $(git ls-files -u | wc -l) && - test 0 = $(git ls-files -o | wc -l) && - - test $(git rev-parse :2:new_a) = $(git rev-parse D:new_a) && - test $(git rev-parse :3:new_a) = $(git rev-parse E:new_a) && - - git cat-file -p B:new_a >>merged && - git cat-file -p C:new_a >>merge-me && - >empty && - test_must_fail git merge-file \ - -L "Temporary merge branch 2" \ - -L "" \ - -L "Temporary merge branch 1" \ - merged empty merge-me && - sed -e "s/^\([<=>]\)/\1\1\1/" merged >merged-internal && - test $(git rev-parse :1:new_a) = $(git hash-object merged-internal) + ( + cd rename-add && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + D:new_a E:new_a && + git rev-parse >actual \ + :2:new_a :3:new_a && + test_cmp expect actual + + git cat-file -p B:new_a >ours && + git cat-file -p C:new_a >theirs && + >empty && + test_must_fail git merge-file \ + -L "Temporary merge branch 2" \ + -L "" \ + -L "Temporary merge branch 1" \ + ours empty theirs && + sed -e "s/^\([<=>]\)/\1\1\1/" ours >expect && + git cat-file -p :1:new_a >actual && + test_cmp expect actual + ) ' # @@ -236,67 +264,85 @@ test_expect_success 'git detects differently handled merges conflict' ' # Merging commits D & E should result in modify/delete conflict. test_expect_success 'setup criss-cross + modify/delete resolved differently' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - echo A >file && - git add file && - test_tick && - git commit -m A && - - git branch B && - git checkout -b C && - git rm file && - test_tick && - git commit -m C && - - git checkout B && - echo B >file && - git add file && - test_tick && - git commit -m B && - - git checkout B^0 && - test_must_fail git merge C && - echo B >file && - git add file && - test_tick && - git commit -m D && - git tag D && - - git checkout C^0 && - test_must_fail git merge B && - git rm file && - test_tick && - git commit -m E && - git tag E + test_create_repo modify-delete && + ( + cd modify-delete && + + echo A >file && + git add file && + test_tick && + git commit -m A && + + git branch B && + git checkout -b C && + git rm file && + test_tick && + git commit -m C && + + git checkout B && + echo B >file && + git add file && + test_tick && + git commit -m B && + + git checkout B^0 && + test_must_fail git merge C && + echo B >file && + git add file && + test_tick && + git commit -m D && + git tag D && + + git checkout C^0 && + test_must_fail git merge B && + git rm file && + test_tick && + git commit -m E && + git tag E + ) ' test_expect_success 'git detects conflict merging criss-cross+modify/delete' ' - git checkout D^0 && + ( + cd modify-delete && + + git checkout D^0 && - test_must_fail git merge -s recursive E^0 && + test_must_fail git merge -s recursive E^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u | wc -l) && + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && - test $(git rev-parse :1:file) = $(git rev-parse master:file) && - test $(git rev-parse :2:file) = $(git rev-parse B:file) + git rev-parse >expect \ + master:file B:file && + git rev-parse >actual \ + :1:file :2:file && + test_cmp expect actual + ) ' test_expect_success 'git detects conflict merging criss-cross+modify/delete, reverse direction' ' - git reset --hard && - git checkout E^0 && + ( + cd modify-delete && - test_must_fail git merge -s recursive D^0 && + git reset --hard && + git checkout E^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u | wc -l) && + test_must_fail git merge -s recursive D^0 && - test $(git rev-parse :1:file) = $(git rev-parse master:file) && - test $(git rev-parse :3:file) = $(git rev-parse B:file) + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + + git rev-parse >expect \ + master:file B:file && + git rev-parse >actual \ + :1:file :3:file && + test_cmp expect actual + ) ' # @@ -336,120 +382,164 @@ test_expect_success 'git detects conflict merging criss-cross+modify/delete, rev # test_expect_success 'setup differently handled merges of directory/file conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - >ignore-me && - git add ignore-me && - test_tick && - git commit -m A && - git tag A && - - git branch B && - git checkout -b C && - mkdir a && - echo 10 >a/file && - git add a/file && - test_tick && - git commit -m C && - - git checkout B && - echo 5 >a && - git add a && - test_tick && - git commit -m B && - - git checkout B^0 && - test_must_fail git merge C && - git clean -f && - rm -rf a/ && - echo 5 >a && - git add a && - test_tick && - git commit -m D && - git tag D && - - git checkout C^0 && - test_must_fail git merge B && - git clean -f && - git rm --cached a && - echo 10 >a/file && - git add a/file && - test_tick && - git commit -m E1 && - git tag E1 && - - git checkout C^0 && - test_must_fail git merge B && - git clean -f && - git rm --cached a && - printf "10\n11\n" >a/file && - git add a/file && - test_tick && - git commit -m E2 && - git tag E2 + test_create_repo directory-file && + ( + cd directory-file && + + >ignore-me && + git add ignore-me && + test_tick && + git commit -m A && + git tag A && + + git branch B && + git checkout -b C && + mkdir a && + echo 10 >a/file && + git add a/file && + test_tick && + git commit -m C && + + git checkout B && + echo 5 >a && + git add a && + test_tick && + git commit -m B && + + git checkout B^0 && + test_must_fail git merge C && + git clean -f && + rm -rf a/ && + echo 5 >a && + git add a && + test_tick && + git commit -m D && + git tag D && + + git checkout C^0 && + test_must_fail git merge B && + git clean -f && + git rm --cached a && + echo 10 >a/file && + git add a/file && + test_tick && + git commit -m E1 && + git tag E1 && + + git checkout C^0 && + test_must_fail git merge B && + git clean -f && + git rm --cached a && + printf "10\n11\n" >a/file && + git add a/file && + test_tick && + git commit -m E2 && + git tag E2 + ) ' test_expect_success 'merge of D & E1 fails but has appropriate contents' ' - get_clean_checkout D^0 && - - test_must_fail git merge -s recursive E1^0 && - - test 2 -eq $(git ls-files -s | wc -l) && - test 1 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && - test $(git rev-parse :2:a) = $(git rev-parse B:a) + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout D^0 && + + test_must_fail git merge -s recursive E1^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:ignore-me B:a && + git rev-parse >actual \ + :0:ignore-me :2:a && + test_cmp expect actual + ) ' test_expect_success 'merge of E1 & D fails but has appropriate contents' ' - get_clean_checkout E1^0 && - - test_must_fail git merge -s recursive D^0 && - - test 2 -eq $(git ls-files -s | wc -l) && - test 1 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && - test $(git rev-parse :3:a) = $(git rev-parse B:a) + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout E1^0 && + + test_must_fail git merge -s recursive D^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:ignore-me B:a && + git rev-parse >actual \ + :0:ignore-me :3:a && + test_cmp expect actual + ) ' test_expect_success 'merge of D & E2 fails but has appropriate contents' ' - get_clean_checkout D^0 && - - test_must_fail git merge -s recursive E2^0 && - - test 4 -eq $(git ls-files -s | wc -l) && - test 3 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse :2:a) = $(git rev-parse B:a) && - test $(git rev-parse :3:a/file) = $(git rev-parse E2:a/file) && - test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) && - test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && - - test -f a~HEAD + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout D^0 && + + test_must_fail git merge -s recursive E2^0 && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 2 out && + + git rev-parse >expect \ + B:a E2:a/file c:a/file A:ignore-me && + git rev-parse >actual \ + :2:a :3:a/file :1:a/file :0:ignore-me && + test_cmp expect actual + + test_path_is_file a~HEAD + ) ' test_expect_success 'merge of E2 & D fails but has appropriate contents' ' - get_clean_checkout E2^0 && - - test_must_fail git merge -s recursive D^0 && - - test 4 -eq $(git ls-files -s | wc -l) && - test 3 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse :3:a) = $(git rev-parse B:a) && - test $(git rev-parse :2:a/file) = $(git rev-parse E2:a/file) && - test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) && - test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && - - test -f a~D^0 + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout E2^0 && + + test_must_fail git merge -s recursive D^0 && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 2 out && + + git rev-parse >expect \ + B:a E2:a/file c:a/file A:ignore-me && + git rev-parse >actual \ + :3:a :2:a/file :1:a/file :0:ignore-me && + test_cmp expect actual + + test_path_is_file a~D^0 + ) ' # @@ -492,52 +582,58 @@ test_expect_success 'merge of E2 & D fails but has appropriate contents' ' # but that may cancel out at the final merge stage". test_expect_success 'setup rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' ' - git reset --hard && - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n6\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - echo 7 >>b && - git add -u && - git commit -m B && - - git checkout -b C A && - git mv a c && - git commit -m C && - - git checkout -q B^0 && - git merge --no-commit -s ours C^0 && - git mv b newname && - git commit -m "Merge commit C^0 into HEAD" && - git tag D && - - git checkout -q C^0 && - git merge --no-commit -s ours B^0 && - git mv c newname && - printf "7\n8\n" >>newname && - git add -u && - git commit -m "Merge commit B^0 into HEAD" && - git tag E + test_create_repo rename-squared-squared && + ( + cd rename-squared-squared && + + printf "1\n2\n3\n4\n5\n6\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + echo 7 >>b && + git add -u && + git commit -m B && + + git checkout -b C A && + git mv a c && + git commit -m C && + + git checkout -q B^0 && + git merge --no-commit -s ours C^0 && + git mv b newname && + git commit -m "Merge commit C^0 into HEAD" && + git tag D && + + git checkout -q C^0 && + git merge --no-commit -s ours B^0 && + git mv c newname && + printf "7\n8\n" >>newname && + git add -u && + git commit -m "Merge commit B^0 into HEAD" && + git tag E + ) ' test_expect_success 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' ' - git checkout D^0 && + ( + cd rename-squared-squared && + + git checkout D^0 && - git merge -s recursive E^0 && + git merge -s recursive E^0 && - test 1 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git ls-files -s >out && + test_line_count = 1 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && - test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname) + test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname) + ) ' # @@ -562,59 +658,72 @@ test_expect_success 'handle rename/rename(1to2)/modify followed by what looks li # renaming carefully (both in the virtual merge base and later), and getting # content merge handled. -test_expect_success 'setup criss-cross + rename/rename/add + modify/modify' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "lots\nof\nwords\nand\ncontent\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - git commit -m B && - - git checkout -b C A && - git mv a c && - printf "2\n3\n4\n5\n6\n7\n" >a && - git add a && - git commit -m C && - - git checkout B^0 && - git merge --no-commit -s ours C^0 && - git checkout C -- a c && - mv a old_a && - echo 1 >a && - cat old_a >>a && - rm old_a && - git add -u && - git commit -m "Merge commit C^0 into HEAD" && - git tag D && - - git checkout C^0 && - git merge --no-commit -s ours B^0 && - git checkout B -- b && - echo 8 >>a && - git add -u && - git commit -m "Merge commit B^0 into HEAD" && - git tag E +test_expect_success 'setup criss-cross + rename/rename/add-source + modify/modify' ' + test_create_repo rename-rename-add-source && + ( + cd rename-rename-add-source && + + printf "lots\nof\nwords\nand\ncontent\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + git commit -m B && + + git checkout -b C A && + git mv a c && + printf "2\n3\n4\n5\n6\n7\n" >a && + git add a && + git commit -m C && + + git checkout B^0 && + git merge --no-commit -s ours C^0 && + git checkout C -- a c && + mv a old_a && + echo 1 >a && + cat old_a >>a && + rm old_a && + git add -u && + git commit -m "Merge commit C^0 into HEAD" && + git tag D && + + git checkout C^0 && + git merge --no-commit -s ours B^0 && + git checkout B -- b && + echo 8 >>a && + git add -u && + git commit -m "Merge commit B^0 into HEAD" && + git tag E + ) ' test_expect_failure 'detect rename/rename/add-source for virtual merge-base' ' - git checkout D^0 && - - git merge -s recursive E^0 && - - test 3 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse HEAD:b) = $(git rev-parse A:a) && - test $(git rev-parse HEAD:c) = $(git rev-parse A:a) && - test "$(cat a)" = "$(printf "1\n2\n3\n4\n5\n6\n7\n8\n")" + ( + cd rename-rename-add-source && + + git checkout D^0 && + + git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + printf "1\n2\n3\n4\n5\n6\n7\n8\n" >correct && + git rev-parse >expect \ + A:a A:a \ + correct && + git rev-parse >actual \ + :0:b :0:c && + git hash-object >>actual \ + a && + test_cmp expect actual + ) ' # @@ -638,52 +747,62 @@ test_expect_failure 'detect rename/rename/add-source for virtual merge-base' ' # base of B & C needs to not delete B:c for that to work, though... test_expect_success 'setup criss-cross+rename/rename/add-dest + simple modify' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - printf "1\n2\n3\n4\n5\n6\n7\n" >c && - git add c && - git commit -m B && - - git checkout -b C A && - git mv a c && - git commit -m C && - - git checkout B^0 && - git merge --no-commit -s ours C^0 && - git mv b a && - git commit -m "D is like B but renames b back to a" && - git tag D && - - git checkout B^0 && - git merge --no-commit -s ours C^0 && - git mv b a && - echo 8 >>c && - git add c && - git commit -m "E like D but has mod in c" && - git tag E + test_create_repo rename-rename-add-dest && + ( + cd rename-rename-add-dest && + + >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + printf "1\n2\n3\n4\n5\n6\n7\n" >c && + git add c && + git commit -m B && + + git checkout -b C A && + git mv a c && + git commit -m C && + + git checkout B^0 && + git merge --no-commit -s ours C^0 && + git mv b a && + git commit -m "D is like B but renames b back to a" && + git tag D && + + git checkout B^0 && + git merge --no-commit -s ours C^0 && + git mv b a && + echo 8 >>c && + git add c && + git commit -m "E like D but has mod in c" && + git tag E + ) ' test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' ' - git checkout D^0 && - - git merge -s recursive E^0 && - - test 2 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse HEAD:a) = $(git rev-parse A:a) && - test $(git rev-parse HEAD:c) = $(git rev-parse E:c) + ( + cd rename-rename-add-dest && + + git checkout D^0 && + + git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:a E:c && + git rev-parse >actual \ + :0:a :0:c && + test_cmp expect actual + ) ' test_done diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh index 411550d2b6..1cbd946fc2 100755 --- a/t/t6042-merge-rename-corner-cases.sh +++ b/t/t6042-merge-rename-corner-cases.sh @@ -6,31 +6,40 @@ test_description="recursive merge corner cases w/ renames but not criss-crosses" . ./test-lib.sh test_expect_success 'setup rename/delete + untracked file' ' - echo "A pretty inscription" >ring && - git add ring && - test_tick && - git commit -m beginning && - - git branch people && - git checkout -b rename-the-ring && - git mv ring one-ring-to-rule-them-all && - test_tick && - git commit -m fullname && - - git checkout people && - git rm ring && - echo gollum >owner && - git add owner && - test_tick && - git commit -m track-people-instead-of-objects && - echo "Myyy PRECIOUSSS" >ring + test_create_repo rename-delete-untracked && + ( + cd rename-delete-untracked && + + echo "A pretty inscription" >ring && + git add ring && + test_tick && + git commit -m beginning && + + git branch people && + git checkout -b rename-the-ring && + git mv ring one-ring-to-rule-them-all && + test_tick && + git commit -m fullname && + + git checkout people && + git rm ring && + echo gollum >owner && + git add owner && + test_tick && + git commit -m track-people-instead-of-objects && + echo "Myyy PRECIOUSSS" >ring + ) ' test_expect_success "Does git preserve Gollum's precious artifact?" ' - test_must_fail git merge -s recursive rename-the-ring && + ( + cd rename-delete-untracked && - # Make sure git did not delete an untracked file - test -f ring + test_must_fail git merge -s recursive rename-the-ring && + + # Make sure git did not delete an untracked file + test_path_is_file ring + ) ' # Testcase setup for rename/modify/add-source: @@ -41,96 +50,125 @@ test_expect_success "Does git preserve Gollum's precious artifact?" ' # We should be able to merge B & C cleanly test_expect_success 'setup rename/modify/add-source conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n6\n7\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - echo 8 >>a && - git add a && - git commit -m B && - - git checkout -b C A && - git mv a b && - echo something completely different >a && - git add a && - git commit -m C + test_create_repo rename-modify-add-source && + ( + cd rename-modify-add-source && + + printf "1\n2\n3\n4\n5\n6\n7\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + echo 8 >>a && + git add a && + git commit -m B && + + git checkout -b C A && + git mv a b && + echo something completely different >a && + git add a && + git commit -m C + ) ' test_expect_failure 'rename/modify/add-source conflict resolvable' ' - git checkout B^0 && + ( + cd rename-modify-add-source && + + git checkout B^0 && - git merge -s recursive C^0 && + git merge -s recursive C^0 && - test $(git rev-parse B:a) = $(git rev-parse b) && - test $(git rev-parse C:a) = $(git rev-parse a) + git rev-parse >expect \ + B:a C:a && + git rev-parse >actual \ + b c && + test_cmp expect actual + ) ' test_expect_success 'setup resolvable conflict missed if rename missed' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n" >a && - echo foo >b && - git add a b && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a c && - echo "Completely different content" >a && - git add a && - git commit -m B && - - git checkout -b C A && - echo 6 >>a && - git add a && - git commit -m C + test_create_repo break-detection-1 && + ( + cd break-detection-1 && + + printf "1\n2\n3\n4\n5\n" >a && + echo foo >b && + git add a b && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a c && + echo "Completely different content" >a && + git add a && + git commit -m B && + + git checkout -b C A && + echo 6 >>a && + git add a && + git commit -m C + ) ' test_expect_failure 'conflict caused if rename not detected' ' - git checkout -q C^0 && - git merge -s recursive B^0 && - - test 3 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test_line_count = 6 c && - test $(git rev-parse HEAD:a) = $(git rev-parse B:a) && - test $(git rev-parse HEAD:b) = $(git rev-parse A:b) + ( + cd break-detection-1 && + + git checkout -q C^0 && + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + test_line_count = 6 c && + git rev-parse >expect \ + B:a A:b && + git rev-parse >actual \ + :0:a :0:b && + test_cmp expect actual + ) ' test_expect_success 'setup conflict resolved wrong if rename missed' ' - git reset --hard && - git clean -f && - - git checkout -b D A && - echo 7 >>a && - git add a && - git mv a c && - echo "Completely different content" >a && - git add a && - git commit -m D && - - git checkout -b E A && - git rm a && - echo "Completely different content" >>a && - git add a && - git commit -m E + test_create_repo break-detection-2 && + ( + cd break-detection-2 && + + printf "1\n2\n3\n4\n5\n" >a && + echo foo >b && + git add a b && + git commit -m A && + git tag A && + + git checkout -b D A && + echo 7 >>a && + git add a && + git mv a c && + echo "Completely different content" >a && + git add a && + git commit -m D && + + git checkout -b E A && + git rm a && + echo "Completely different content" >>a && + git add a && + git commit -m E + ) ' test_expect_failure 'missed conflict if rename not detected' ' - git checkout -q E^0 && - test_must_fail git merge -s recursive D^0 + ( + cd break-detection-2 && + + git checkout -q E^0 && + test_must_fail git merge -s recursive D^0 + ) ' # Tests for undetected rename/add-source causing a file to erroneously be @@ -145,198 +183,233 @@ test_expect_failure 'missed conflict if rename not detected' ' # Commit C: rename a->b, add unrelated a test_expect_success 'setup undetected rename/add-source causes data loss' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - git commit -m B && - - git checkout -b C A && - git mv a b && - echo foobar >a && - git add a && - git commit -m C + test_create_repo break-detection-3 && + ( + cd break-detection-3 && + + printf "1\n2\n3\n4\n5\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + git commit -m B && + + git checkout -b C A && + git mv a b && + echo foobar >a && + git add a && + git commit -m C + ) ' test_expect_failure 'detect rename/add-source and preserve all data' ' - git checkout B^0 && + ( + cd break-detection-3 && - git merge -s recursive C^0 && + git checkout B^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git merge -s recursive C^0 && - test -f a && - test -f b && + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && - test $(git rev-parse HEAD:b) = $(git rev-parse A:a) && - test $(git rev-parse HEAD:a) = $(git rev-parse C:a) + test_path_is_file a && + test_path_is_file b && + + git rev-parse >expect \ + A:a C:a && + git rev-parse >actual \ + :0:b :0:a && + test_cmp expect actual + ) ' test_expect_failure 'detect rename/add-source and preserve all data, merge other way' ' - git checkout C^0 && + ( + cd break-detection-3 && + + git checkout C^0 && - git merge -s recursive B^0 && + git merge -s recursive B^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && - test -f a && - test -f b && + test_path_is_file a && + test_path_is_file b && - test $(git rev-parse HEAD:b) = $(git rev-parse A:a) && - test $(git rev-parse HEAD:a) = $(git rev-parse C:a) + git rev-parse >expect \ + A:a C:a && + git rev-parse >actual \ + :0:b :0:a && + test_cmp expect actual + ) ' test_expect_success 'setup content merge + rename/directory conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n6\n" >file && - git add file && - test_tick && - git commit -m base && - git tag base && - - git checkout -b right && - echo 7 >>file && - mkdir newfile && - echo junk >newfile/realfile && - git add file newfile/realfile && - test_tick && - git commit -m right && - - git checkout -b left-conflict base && - echo 8 >>file && - git add file && - git mv file newfile && - test_tick && - git commit -m left && - - git checkout -b left-clean base && - echo 0 >newfile && - cat file >>newfile && - git add newfile && - git rm file && - test_tick && - git commit -m left + test_create_repo rename-directory-1 && + ( + cd rename-directory-1 && + + printf "1\n2\n3\n4\n5\n6\n" >file && + git add file && + test_tick && + git commit -m base && + git tag base && + + git checkout -b right && + echo 7 >>file && + mkdir newfile && + echo junk >newfile/realfile && + git add file newfile/realfile && + test_tick && + git commit -m right && + + git checkout -b left-conflict base && + echo 8 >>file && + git add file && + git mv file newfile && + test_tick && + git commit -m left && + + git checkout -b left-clean base && + echo 0 >newfile && + cat file >>newfile && + git add newfile && + git rm file && + test_tick && + git commit -m left + ) ' test_expect_success 'rename/directory conflict + clean content merge' ' - git reset --hard && - git reset --hard && - git clean -fdqx && + ( + cd rename-directory-1 && - git checkout left-clean^0 && + git checkout left-clean^0 && - test_must_fail git merge -s recursive right^0 && + test_must_fail git merge -s recursive right^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 1 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 2 out && - echo 0 >expect && - git cat-file -p base:file >>expect && - echo 7 >>expect && - test_cmp expect newfile~HEAD && + echo 0 >expect && + git cat-file -p base:file >>expect && + echo 7 >>expect && + test_cmp expect newfile~HEAD && - test $(git rev-parse :2:newfile) = $(git hash-object expect) && + test $(git rev-parse :2:newfile) = $(git hash-object expect) && - test -f newfile/realfile && - test -f newfile~HEAD + test_path_is_file newfile/realfile && + test_path_is_file newfile~HEAD + ) ' test_expect_success 'rename/directory conflict + content merge conflict' ' - git reset --hard && - git reset --hard && - git clean -fdqx && - - git checkout left-conflict^0 && - - test_must_fail git merge -s recursive right^0 && - - test 4 -eq $(git ls-files -s | wc -l) && - test 3 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && - - git cat-file -p left-conflict:newfile >left && - git cat-file -p base:file >base && - git cat-file -p right:file >right && - test_must_fail git merge-file \ - -L "HEAD:newfile" \ - -L "" \ - -L "right^0:file" \ - left base right && - test_cmp left newfile~HEAD && - - test $(git rev-parse :1:newfile) = $(git rev-parse base:file) && - test $(git rev-parse :2:newfile) = $(git rev-parse left-conflict:newfile) && - test $(git rev-parse :3:newfile) = $(git rev-parse right:file) && - - test -f newfile/realfile && - test -f newfile~HEAD + ( + cd rename-directory-1 && + + git reset --hard && + git reset --hard && + git clean -fdqx && + + git checkout left-conflict^0 && + + test_must_fail git merge -s recursive right^0 && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 2 out && + + git cat-file -p left-conflict:newfile >left && + git cat-file -p base:file >base && + git cat-file -p right:file >right && + test_must_fail git merge-file \ + -L "HEAD:newfile" \ + -L "" \ + -L "right^0:file" \ + left base right && + test_cmp left newfile~HEAD && + + git rev-parse >expect \ + base:file left-conflict:newfile right:file && + git rev-parse >actual \ + :1:newfile :2:newfile :3:newfile && + test_cmp expect actual + + test_path_is_file newfile/realfile && + test_path_is_file newfile~HEAD + ) ' test_expect_success 'setup content merge + rename/directory conflict w/ disappearing dir' ' - git reset --hard && - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - mkdir sub && - printf "1\n2\n3\n4\n5\n6\n" >sub/file && - git add sub/file && - test_tick && - git commit -m base && - git tag base && - - git checkout -b right && - echo 7 >>sub/file && - git add sub/file && - test_tick && - git commit -m right && - - git checkout -b left base && - echo 0 >newfile && - cat sub/file >>newfile && - git rm sub/file && - mv newfile sub && - git add sub && - test_tick && - git commit -m left + test_create_repo rename-directory-2 && + ( + cd rename-directory-2 && + + mkdir sub && + printf "1\n2\n3\n4\n5\n6\n" >sub/file && + git add sub/file && + test_tick && + git commit -m base && + git tag base && + + git checkout -b right && + echo 7 >>sub/file && + git add sub/file && + test_tick && + git commit -m right && + + git checkout -b left base && + echo 0 >newfile && + cat sub/file >>newfile && + git rm sub/file && + mv newfile sub && + git add sub && + test_tick && + git commit -m left + ) ' test_expect_success 'disappearing dir in rename/directory conflict handled' ' - git reset --hard && - git clean -fdqx && + ( + cd rename-directory-2 && - git checkout left^0 && + git checkout left^0 && - git merge -s recursive right^0 && + git merge -s recursive right^0 && - test 1 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git ls-files -s >out && + test_line_count = 1 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && - echo 0 >expect && - git cat-file -p base:sub/file >>expect && - echo 7 >>expect && - test_cmp expect sub && + echo 0 >expect && + git cat-file -p base:sub/file >>expect && + echo 7 >>expect && + test_cmp expect sub && - test -f sub + test_path_is_file sub + ) ' # Test for all kinds of things that can go wrong with rename/rename (2to1): @@ -352,48 +425,59 @@ test_expect_success 'disappearing dir in rename/directory conflict handled' ' # * Nothing else should be present. Is anything? test_expect_success 'setup rename/rename (2to1) + modify/modify' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n" >a && - printf "5\n4\n3\n2\n1\n" >b && - git add a b && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a c && - echo 0 >>b && - git add b && - git commit -m B && - - git checkout -b C A && - git mv b c && - echo 6 >>a && - git add a && - git commit -m C + test_create_repo rename-rename-2to1 && + ( + cd rename-rename-2to1 && + + printf "1\n2\n3\n4\n5\n" >a && + printf "5\n4\n3\n2\n1\n" >b && + git add a b && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a c && + echo 0 >>b && + git add b && + git commit -m B && + + git checkout -b C A && + git mv b c && + echo 6 >>a && + git add a && + git commit -m C + ) ' test_expect_success 'handle rename/rename (2to1) conflict correctly' ' - git checkout B^0 && - - test_must_fail git merge -s recursive C^0 >out && - test_i18ngrep "CONFLICT (rename/rename)" out && - - test 2 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u | wc -l) && - test 2 -eq $(git ls-files -u c | wc -l) && - test 3 -eq $(git ls-files -o | wc -l) && - - test ! -f a && - test ! -f b && - test -f c~HEAD && - test -f c~C^0 && - - test $(git hash-object c~HEAD) = $(git rev-parse C:a) && - test $(git hash-object c~C^0) = $(git rev-parse B:b) + ( + cd rename-rename-2to1 && + + git checkout B^0 && + + test_must_fail git merge -s recursive C^0 >out && + test_i18ngrep "CONFLICT (rename/rename)" out && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -u c >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 3 out && + + test_path_is_missing a && + test_path_is_missing b && + test_path_is_file c~HEAD && + test_path_is_file c~C^0 && + + git rev-parse >expect \ + C:a B:b && + git hash-object >actual \ + c~HEAD c~C^0 && + test_cmp expect actual + ) ' # Testcase setup for simple rename/rename (1to2) conflict: @@ -401,44 +485,53 @@ test_expect_success 'handle rename/rename (2to1) conflict correctly' ' # Commit B: rename a->b # Commit C: rename a->c test_expect_success 'setup simple rename/rename (1to2) conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - echo stuff >a && - git add a && - test_tick && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - test_tick && - git commit -m B && - - git checkout -b C A && - git mv a c && - test_tick && - git commit -m C + test_create_repo rename-rename-1to2 && + ( + cd rename-rename-1to2 && + + echo stuff >a && + git add a && + test_tick && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + test_tick && + git commit -m B && + + git checkout -b C A && + git mv a c && + test_tick && + git commit -m C + ) ' test_expect_success 'merge has correct working tree contents' ' - git checkout C^0 && - - test_must_fail git merge -s recursive B^0 && - - test 3 -eq $(git ls-files -s | wc -l) && - test 3 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse :1:a) = $(git rev-parse A:a) && - test $(git rev-parse :3:b) = $(git rev-parse A:a) && - test $(git rev-parse :2:c) = $(git rev-parse A:a) && - - test ! -f a && - test $(git hash-object b) = $(git rev-parse A:a) && - test $(git hash-object c) = $(git rev-parse A:a) + ( + cd rename-rename-1to2 && + + git checkout C^0 && + + test_must_fail git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + test_path_is_missing a && + git rev-parse >expect \ + A:a A:a A:a \ + A:a A:a && + git rev-parse >actual \ + :1:a :3:b :2:c && + git hash-object >>actual \ + b c && + test_cmp expect actual + ) ' # Testcase setup for rename/rename(1to2)/add-source conflict: @@ -449,130 +542,155 @@ test_expect_success 'merge has correct working tree contents' ' # Merging of B & C should NOT be clean; there's a rename/rename conflict test_expect_success 'setup rename/rename(1to2)/add-source conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n6\n7\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - git commit -m B && - - git checkout -b C A && - git mv a c && - echo something completely different >a && - git add a && - git commit -m C + test_create_repo rename-rename-1to2-add-source-1 && + ( + cd rename-rename-1to2-add-source-1 && + + printf "1\n2\n3\n4\n5\n6\n7\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + git commit -m B && + + git checkout -b C A && + git mv a c && + echo something completely different >a && + git add a && + git commit -m C + ) ' test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' ' - git checkout B^0 && + ( + cd rename-rename-1to2-add-source-1 && + + git checkout B^0 && - test_must_fail git merge -s recursive C^0 && + test_must_fail git merge -s recursive C^0 && - test 4 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -o >out && + test_line_count = 1 out && - test $(git rev-parse 3:a) = $(git rev-parse C:a) && - test $(git rev-parse 1:a) = $(git rev-parse A:a) && - test $(git rev-parse 2:b) = $(git rev-parse B:b) && - test $(git rev-parse 3:c) = $(git rev-parse C:c) && + git rev-parse >expect \ + C:a A:a B:b C:C && + git rev-parse >actual \ + :3:a :1:a :2:b :3:c && + test_cmp expect actual - test -f a && - test -f b && - test -f c + test_path_is_file a && + test_path_is_file b && + test_path_is_file c + ) ' test_expect_success 'setup rename/rename(1to2)/add-source resolvable conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - >a && - git add a && - test_tick && - git commit -m base && - git tag A && - - git checkout -b B A && - git mv a b && - test_tick && - git commit -m one && - - git checkout -b C A && - git mv a b && - echo important-info >a && - git add a && - test_tick && - git commit -m two + test_create_repo rename-rename-1to2-add-source-2 && + ( + cd rename-rename-1to2-add-source-2 && + + >a && + git add a && + test_tick && + git commit -m base && + git tag A && + + git checkout -b B A && + git mv a b && + test_tick && + git commit -m one && + + git checkout -b C A && + git mv a b && + echo important-info >a && + git add a && + test_tick && + git commit -m two + ) ' test_expect_failure 'rename/rename/add-source still tracks new a file' ' - git checkout C^0 && - git merge -s recursive B^0 && - - test 2 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse HEAD:a) = $(git rev-parse C:a) && - test $(git rev-parse HEAD:b) = $(git rev-parse A:a) + ( + cd rename-rename-1to2-add-source-2 && + + git checkout C^0 && + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + C:a A:a && + git rev-parse >actual \ + :0:a :0:b && + test_cmp expect actual + ) ' test_expect_success 'setup rename/rename(1to2)/add-dest conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - echo stuff >a && - git add a && - test_tick && - git commit -m base && - git tag A && - - git checkout -b B A && - git mv a b && - echo precious-data >c && - git add c && - test_tick && - git commit -m one && - - git checkout -b C A && - git mv a c && - echo important-info >b && - git add b && - test_tick && - git commit -m two + test_create_repo rename-rename-1to2-add-dest && + ( + cd rename-rename-1to2-add-dest && + + echo stuff >a && + git add a && + test_tick && + git commit -m base && + git tag A && + + git checkout -b B A && + git mv a b && + echo precious-data >c && + git add c && + test_tick && + git commit -m one && + + git checkout -b C A && + git mv a c && + echo important-info >b && + git add b && + test_tick && + git commit -m two + ) ' test_expect_success 'rename/rename/add-dest merge still knows about conflicting file versions' ' - git checkout C^0 && - test_must_fail git merge -s recursive B^0 && - - test 5 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u b | wc -l) && - test 2 -eq $(git ls-files -u c | wc -l) && - test 4 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse :1:a) = $(git rev-parse A:a) && - test $(git rev-parse :2:b) = $(git rev-parse C:b) && - test $(git rev-parse :3:b) = $(git rev-parse B:b) && - test $(git rev-parse :2:c) = $(git rev-parse C:c) && - test $(git rev-parse :3:c) = $(git rev-parse B:c) && - - test $(git hash-object c~HEAD) = $(git rev-parse C:c) && - test $(git hash-object c~B\^0) = $(git rev-parse B:c) && - test $(git hash-object b~HEAD) = $(git rev-parse C:b) && - test $(git hash-object b~B\^0) = $(git rev-parse B:b) && - - test ! -f b && - test ! -f c + ( + cd rename-rename-1to2-add-dest && + + git checkout C^0 && + test_must_fail git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u b >out && + test_line_count = 2 out && + git ls-files -u c >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 5 out && + + git rev-parse >expect \ + A:a C:b B:b C:c B:c && + git rev-parse >actual \ + :1:a :2:b :3:b :2:c :3:c && + test_cmp expect actual + + git rev-parse >expect \ + C:c B:c C:b B:b && + git hash-object >actual \ + c~HEAD c~B\^0 b~HEAD b~B\^0 && + test_cmp expect actual + + test_path_is_missing b && + test_path_is_missing c + ) ' test_done diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 48379aa0ee..e0496da812 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -795,4 +795,15 @@ test_expect_success ':remotename and :remoteref' ' ) ' +test_expect_success 'for-each-ref --ignore-case ignores case' ' + >expect && + git for-each-ref --format="%(refname)" refs/heads/MASTER >actual && + test_cmp expect actual && + + echo refs/heads/master >expect && + git for-each-ref --format="%(refname)" --ignore-case \ + refs/heads/MASTER >actual && + test_cmp expect actual +' + test_done diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index ec4b160ddb..e23de7d0b5 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -107,6 +107,21 @@ test_expect_success 'test that the directory was renamed' ' test dir/D = "$(cat diroh/D.t)" ' +V=$(git rev-parse HEAD) + +test_expect_success 'populate --state-branch' ' + git filter-branch --state-branch state -f --tree-filter "touch file || :" HEAD +' + +W=$(git rev-parse HEAD) + +test_expect_success 'using --state-branch to skip already rewritten commits' ' + test_when_finished git reset --hard $V && + git reset --hard $V && + git filter-branch --state-branch state -f --tree-filter "touch file || :" HEAD && + test_cmp_rev $W HEAD +' + git tag oldD HEAD~4 test_expect_success 'rewrite one branch, keeping a side branch' ' git branch modD oldD && diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh index b4b49eeb08..291a1e2b07 100755 --- a/t/t7030-verify-tag.sh +++ b/t/t7030-verify-tag.sh @@ -74,7 +74,7 @@ test_expect_success GPG 'verify and show signatures' ' test_expect_success GPG 'detect fudged signature' ' git cat-file tag seventh-signed >raw && - sed -e "s/seventh/7th forged/" raw >forged1 && + sed -e "/^tag / s/seventh/7th forged/" raw >forged1 && git hash-object -w -t tag forged1 >forged1.tag && test_must_fail git verify-tag $(cat forged1.tag) 2>actual1 && grep "BAD signature from" actual1 && diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 812db137b8..48fd14fae6 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -993,6 +993,11 @@ test_expect_success 'submodule deinit should remove the whole submodule section rmdir init ' +test_expect_success 'submodule deinit should unset core.worktree' ' + test_path_is_file .git/modules/example/config && + test_must_fail git config -f .git/modules/example/config core.worktree +' + test_expect_success 'submodule deinit from subdirectory' ' git submodule update --init && git config submodule.example.foo bar && diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh index 6ba5daf42e..77729ac4aa 100755 --- a/t/t7407-submodule-foreach.sh +++ b/t/t7407-submodule-foreach.sh @@ -82,16 +82,16 @@ test_expect_success 'test basic "submodule foreach" usage' ' cat >expect <<EOF Entering '../sub1' -$pwd/clone-foo1-../sub1-$sub1sha1 +$pwd/clone-foo1-sub1-../sub1-$sub1sha1 Entering '../sub3' -$pwd/clone-foo3-../sub3-$sub3sha1 +$pwd/clone-foo3-sub3-../sub3-$sub3sha1 EOF test_expect_success 'test "submodule foreach" from subdirectory' ' mkdir clone/sub && ( cd clone/sub && - git submodule foreach "echo \$toplevel-\$name-\$sm_path-\$sha1" >../../actual + git submodule foreach "echo \$toplevel-\$name-\$sm_path-\$displaypath-\$sha1" >../../actual ) && test_i18ncmp expect actual ' @@ -196,6 +196,38 @@ test_expect_success 'test messages from "foreach --recursive" from subdirectory' ) && test_i18ncmp expect actual ' +sub1sha1=$(cd clone2/sub1 && git rev-parse HEAD) +sub2sha1=$(cd clone2/sub2 && git rev-parse HEAD) +sub3sha1=$(cd clone2/sub3 && git rev-parse HEAD) +nested1sha1=$(cd clone2/nested1 && git rev-parse HEAD) +nested2sha1=$(cd clone2/nested1/nested2 && git rev-parse HEAD) +nested3sha1=$(cd clone2/nested1/nested2/nested3 && git rev-parse HEAD) +submodulesha1=$(cd clone2/nested1/nested2/nested3/submodule && git rev-parse HEAD) + +cat >expect <<EOF +Entering '../nested1' +toplevel: $pwd/clone2 name: nested1 path: nested1 displaypath: ../nested1 hash: $nested1sha1 +Entering '../nested1/nested2' +toplevel: $pwd/clone2/nested1 name: nested2 path: nested2 displaypath: ../nested1/nested2 hash: $nested2sha1 +Entering '../nested1/nested2/nested3' +toplevel: $pwd/clone2/nested1/nested2 name: nested3 path: nested3 displaypath: ../nested1/nested2/nested3 hash: $nested3sha1 +Entering '../nested1/nested2/nested3/submodule' +toplevel: $pwd/clone2/nested1/nested2/nested3 name: submodule path: submodule displaypath: ../nested1/nested2/nested3/submodule hash: $submodulesha1 +Entering '../sub1' +toplevel: $pwd/clone2 name: foo1 path: sub1 displaypath: ../sub1 hash: $sub1sha1 +Entering '../sub2' +toplevel: $pwd/clone2 name: foo2 path: sub2 displaypath: ../sub2 hash: $sub2sha1 +Entering '../sub3' +toplevel: $pwd/clone2 name: foo3 path: sub3 displaypath: ../sub3 hash: $sub3sha1 +EOF + +test_expect_success 'test "submodule foreach --recursive" from subdirectory' ' + ( + cd clone2/untracked && + git submodule foreach --recursive "echo toplevel: \$toplevel name: \$name path: \$sm_path displaypath: \$displaypath hash: \$sha1" >../../actual + ) && + test_i18ncmp expect actual +' cat > expect <<EOF nested1-nested1 diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 18a40257fb..e1f11293e2 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -1099,6 +1099,7 @@ EOF ' test_expect_success POSIXPERM,SANITY 'status succeeds in a read-only repository' ' + test_when_finished "chmod 775 .git" && ( chmod a-w .git && # make dir1/tracked stat-dirty @@ -1108,9 +1109,6 @@ test_expect_success POSIXPERM,SANITY 'status succeeds in a read-only repository' # make sure "status" succeeded without writing index out git diff-files | grep dir1/tracked ) - status=$? - chmod 775 .git - (exit $status) ' (cd sm && echo > bar && git add bar && git commit -q -m 'Add bar') && git add sm diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index 762135adea..6e2015ed9a 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -142,10 +142,9 @@ test_expect_success GPG 'show signed commit with signature' ' test_expect_success GPG 'detect fudged signature' ' git cat-file commit seventh-signed >raw && - - sed -e "s/seventh/7th forged/" raw >forged1 && + sed -e "s/^seventh/7th forged/" raw >forged1 && git hash-object -w -t commit forged1 >forged1.commit && - ! git verify-commit $(cat forged1.commit) && + test_must_fail git verify-commit $(cat forged1.commit) && git show --pretty=short --show-signature $(cat forged1.commit) >actual1 && grep "BAD signature from" actual1 && ! grep "Good signature from" actual1 @@ -156,7 +155,7 @@ test_expect_success GPG 'detect fudged signature with NUL' ' cat raw >forged2 && echo Qwik | tr "Q" "\000" >>forged2 && git hash-object -w -t commit forged2 >forged2.commit && - ! git verify-commit $(cat forged2.commit) && + test_must_fail git verify-commit $(cat forged2.commit) && git show --pretty=short --show-signature $(cat forged2.commit) >actual2 && grep "BAD signature from" actual2 && ! grep "Good signature from" actual2 diff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh index e797c74112..e2b1df817a 100755 --- a/t/t7612-merge-verify-signatures.sh +++ b/t/t7612-merge-verify-signatures.sh @@ -23,7 +23,7 @@ test_expect_success GPG 'create signed commits' ' echo 3 >bar && git add bar && test_tick && git commit -S -m "bad on side" && git cat-file commit side-bad >raw && - sed -e "s/bad/forged bad/" raw >forged && + sed -e "s/^bad/forged bad/" raw >forged && git hash-object -w -t commit forged >forged.commit && git checkout initial && diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 1797f632a3..9312c8daf5 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -99,6 +99,101 @@ do test_cmp expected actual ' + test_expect_success "grep -w $L (with --column)" ' + { + echo ${HC}file:5:foo mmap bar + echo ${HC}file:14:foo_mmap bar mmap + echo ${HC}file:5:foo mmap bar_mmap + echo ${HC}file:14:foo_mmap bar mmap baz + } >expected && + git grep --column -w -e mmap $H >actual && + test_cmp expected actual + ' + + test_expect_success "grep -w $L (with --column, extended OR)" ' + { + echo ${HC}file:14:foo_mmap bar mmap + echo ${HC}file:19:foo_mmap bar mmap baz + } >expected && + git grep --column -w -e mmap$ --or -e baz $H >actual && + test_cmp expected actual + ' + + test_expect_success "grep -w $L (with --column, --invert)" ' + { + echo ${HC}file:1:foo mmap bar + echo ${HC}file:1:foo_mmap bar + echo ${HC}file:1:foo_mmap bar mmap + echo ${HC}file:1:foo mmap bar_mmap + } >expected && + git grep --column --invert -w -e baz $H -- file >actual && + test_cmp expected actual + ' + + test_expect_success "grep $L (with --column, --invert, extended OR)" ' + { + echo ${HC}hello_world:6:HeLLo_world + } >expected && + git grep --column --invert -e ll --or --not -e _ $H -- hello_world \ + >actual && + test_cmp expected actual + ' + + test_expect_success "grep $L (with --column, --invert, extended AND)" ' + { + echo ${HC}hello_world:3:Hello world + echo ${HC}hello_world:3:Hello_world + echo ${HC}hello_world:6:HeLLo_world + } >expected && + git grep --column --invert --not -e _ --and --not -e ll $H -- hello_world \ + >actual && + test_cmp expected actual + ' + + test_expect_success "grep $L (with --column, double-negation)" ' + { + echo ${HC}file:1:foo_mmap bar mmap baz + } >expected && + git grep --column --not \( --not -e foo --or --not -e baz \) $H -- file \ + >actual && + test_cmp expected actual + ' + + test_expect_success "grep -w $L (with --column, -C)" ' + { + echo ${HC}file:5:foo mmap bar + echo ${HC}file-foo_mmap bar + echo ${HC}file:14:foo_mmap bar mmap + echo ${HC}file:5:foo mmap bar_mmap + echo ${HC}file:14:foo_mmap bar mmap baz + } >expected && + git grep --column -w -C1 -e mmap $H >actual && + test_cmp expected actual + ' + + test_expect_success "grep -w $L (with --line-number, --column)" ' + { + echo ${HC}file:1:5:foo mmap bar + echo ${HC}file:3:14:foo_mmap bar mmap + echo ${HC}file:4:5:foo mmap bar_mmap + echo ${HC}file:5:14:foo_mmap bar mmap baz + } >expected && + git grep -n --column -w -e mmap $H >actual && + test_cmp expected actual + ' + + test_expect_success "grep -w $L (with non-extended patterns, --column)" ' + { + echo ${HC}file:5:foo mmap bar + echo ${HC}file:10:foo_mmap bar + echo ${HC}file:10:foo_mmap bar mmap + echo ${HC}file:5:foo mmap bar_mmap + echo ${HC}file:10:foo_mmap bar mmap baz + } >expected && + git grep --column -w -e bar -e mmap $H >actual && + test_cmp expected actual + ' + test_expect_success "grep -w $L" ' { echo ${HC}file:1:foo mmap bar diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 9c49b6c1fe..5e0ad19177 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -215,7 +215,9 @@ test_expect_success "multi-fetch continues to work" " " test_expect_success "multi-fetch works off a 'clean' repository" ' - rm -rf "$GIT_DIR/svn" "$GIT_DIR/refs/remotes" && + rm -rf "$GIT_DIR/svn" && + git for-each-ref --format="option no-deref%0adelete %(refname)" refs/remotes | + git update-ref --stdin && git reflog expire --all --expire=all && mkdir "$GIT_DIR/svn" && git svn multi-fetch diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index a28640ce1a..3b3a7b66e4 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -501,6 +501,42 @@ test_expect_success '__gitcomp - suffix' ' EOF ' +test_expect_success '__gitcomp - ignore optional negative options' ' + test_gitcomp "--" "--abc --def --no-one -- --no-two" <<-\EOF + --abc Z + --def Z + --no-one Z + --no-... Z + EOF +' + +test_expect_success '__gitcomp - ignore/narrow optional negative options' ' + test_gitcomp "--a" "--abc --abcdef --no-one -- --no-two" <<-\EOF + --abc Z + --abcdef Z + EOF +' + +test_expect_success '__gitcomp - ignore/narrow optional negative options' ' + test_gitcomp "--n" "--abc --def --no-one -- --no-two" <<-\EOF + --no-one Z + --no-... Z + EOF +' + +test_expect_success '__gitcomp - expand all negative options' ' + test_gitcomp "--no-" "--abc --def --no-one -- --no-two" <<-\EOF + --no-one Z + --no-two Z + EOF +' + +test_expect_success '__gitcomp - expand/narrow all negative options' ' + test_gitcomp "--no-o" "--abc --def --no-one -- --no-two" <<-\EOF + --no-one Z + EOF +' + test_expect_success '__gitcomp - doesnt fail because of invalid variable name' ' __gitcomp "$invalid_variable_name" ' @@ -1398,8 +1434,8 @@ test_expect_success 'double dash "git checkout"' ' --ignore-other-worktrees Z --recurse-submodules Z --progress Z - --no-track Z - --no-recurse-submodules Z + --no-quiet Z + --no-... Z EOF ' @@ -1607,6 +1643,7 @@ test_expect_success 'completion used <cmd> completion for alias: !f() { : git <c test_expect_success 'completion without explicit _git_xxx function' ' test_completion "git version --" <<-\EOF --build-options Z + --no-build-options Z EOF ' @@ -1,8 +1,10 @@ #include "cache.h" #include "tag.h" +#include "object-store.h" #include "commit.h" #include "tree.h" #include "blob.h" +#include "alloc.h" #include "gpg-interface.h" const char *tag_type = "tag"; @@ -93,7 +95,8 @@ struct tag *lookup_tag(const struct object_id *oid) { struct object *obj = lookup_object(oid->hash); if (!obj) - return create_object(oid->hash, alloc_tag_node()); + return create_object(the_repository, oid->hash, + alloc_tag_node(the_repository)); return object_as_type(obj, OBJ_TAG, 0); } @@ -114,6 +117,14 @@ static timestamp_t parse_tag_date(const char *buf, const char *tail) return parse_timestamp(dateptr, NULL, 10); } +void release_tag_memory(struct tag *t) +{ + free(t->tag); + t->tagged = NULL; + t->object.parsed = 0; + t->date = 0; +} + int parse_tag_buffer(struct tag *item, const void *data, unsigned long size) { struct object_id oid; @@ -15,6 +15,7 @@ struct tag { extern struct tag *lookup_tag(const struct object_id *oid); extern int parse_tag_buffer(struct tag *item, const void *data, unsigned long size); extern int parse_tag(struct tag *item); +extern void release_tag_memory(struct tag *t); extern struct object *deref_tag(struct object *, const char *, int); extern struct object *deref_tag_noverify(struct object *); extern int gpg_verify_tag(const struct object_id *oid, diff --git a/transport-helper.c b/transport-helper.c index 1f8ff7e942..8b5abca29f 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -651,14 +651,16 @@ static int connect_helper(struct transport *transport, const char *name, } static int fetch(struct transport *transport, - int nr_heads, struct ref **to_fetch) + int nr_heads, struct ref **to_fetch, + struct ref **fetched_refs) { struct helper_data *data = transport->data; int i, count; if (process_connect(transport, 0)) { do_take_over(transport); - return transport->vtable->fetch(transport, nr_heads, to_fetch); + return transport->vtable->fetch(transport, nr_heads, to_fetch, + fetched_refs); } count = 0; diff --git a/transport-internal.h b/transport-internal.h index 1cde6258a7..eeb6c340e5 100644 --- a/transport-internal.h +++ b/transport-internal.h @@ -36,11 +36,18 @@ struct transport_vtable { * Fetch the objects for the given refs. Note that this gets * an array, and should ignore the list structure. * + * The transport *may* provide, in fetched_refs, the list of refs that + * it fetched. If the transport knows anything about the fetched refs + * that the caller does not know (for example, shallow status), it + * should provide that list of refs and include that information in the + * list. + * * If the transport did not get hashes for refs in * get_refs_list(), it should set the old_sha1 fields in the * provided refs now. **/ - int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs); + int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs, + struct ref **fetched_refs); /** * Push the objects and refs. Send the necessary objects, and diff --git a/transport.c b/transport.c index a32da30dee..fdd813f684 100644 --- a/transport.c +++ b/transport.c @@ -151,7 +151,8 @@ static struct ref *get_refs_from_bundle(struct transport *transport, } static int fetch_refs_from_bundle(struct transport *transport, - int nr_heads, struct ref **to_fetch) + int nr_heads, struct ref **to_fetch, + struct ref **fetched_refs) { struct bundle_transport_data *data = transport->data; return unbundle(&data->header, data->fd, @@ -287,7 +288,8 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus } static int fetch_refs_via_pack(struct transport *transport, - int nr_heads, struct ref **to_fetch) + int nr_heads, struct ref **to_fetch, + struct ref **fetched_refs) { int ret = 0; struct git_transport_data *data = transport->data; @@ -348,14 +350,19 @@ static int fetch_refs_via_pack(struct transport *transport, data->got_remote_heads = 0; data->options.self_contained_and_connected = args.self_contained_and_connected; + data->options.connectivity_checked = args.connectivity_checked; if (refs == NULL) ret = -1; if (report_unmatched_refs(to_fetch, nr_heads)) ret = -1; + if (fetched_refs) + *fetched_refs = refs; + else + free_refs(refs); + free_refs(refs_tmp); - free_refs(refs); free(dest); return ret; } @@ -1215,19 +1222,31 @@ const struct ref *transport_get_remote_refs(struct transport *transport, return transport->remote_refs; } -int transport_fetch_refs(struct transport *transport, struct ref *refs) +int transport_fetch_refs(struct transport *transport, struct ref *refs, + struct ref **fetched_refs) { int rc; int nr_heads = 0, nr_alloc = 0, nr_refs = 0; struct ref **heads = NULL; + struct ref *nop_head = NULL, **nop_tail = &nop_head; struct ref *rm; for (rm = refs; rm; rm = rm->next) { nr_refs++; if (rm->peer_ref && !is_null_oid(&rm->old_oid) && - !oidcmp(&rm->peer_ref->old_oid, &rm->old_oid)) + !oidcmp(&rm->peer_ref->old_oid, &rm->old_oid)) { + /* + * These need to be reported as fetched, but we don't + * actually need to fetch them. + */ + if (fetched_refs) { + struct ref *nop_ref = copy_ref(rm); + *nop_tail = nop_ref; + nop_tail = &nop_ref->next; + } continue; + } ALLOC_GROW(heads, nr_heads + 1, nr_alloc); heads[nr_heads++] = rm; } @@ -1245,7 +1264,11 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs) heads[nr_heads++] = rm; } - rc = transport->vtable->fetch(transport, nr_heads, heads); + rc = transport->vtable->fetch(transport, nr_heads, heads, fetched_refs); + if (fetched_refs && nop_head) { + *nop_tail = *fetched_refs; + *fetched_refs = nop_head; + } free(heads); return rc; diff --git a/transport.h b/transport.h index 7792b08582..7a9a7fcaf3 100644 --- a/transport.h +++ b/transport.h @@ -18,6 +18,17 @@ struct git_transport_options { unsigned deepen_relative : 1; unsigned from_promisor : 1; unsigned no_dependents : 1; + + /* + * If this transport supports connect or stateless-connect, + * the corresponding field in struct fetch_pack_args is copied + * here after fetching. + * + * See the definition of connectivity_checked in struct + * fetch_pack_args for more information. + */ + unsigned connectivity_checked:1; + int depth; const char *deepen_since; const struct string_list *deepen_not; @@ -218,7 +229,8 @@ int transport_push(struct transport *connection, const struct ref *transport_get_remote_refs(struct transport *transport, const struct argv_array *ref_prefixes); -int transport_fetch_refs(struct transport *transport, struct ref *refs); +int transport_fetch_refs(struct transport *transport, struct ref *refs, + struct ref **fetched_refs); void transport_unlock_pack(struct transport *transport); int transport_disconnect(struct transport *transport); char *transport_anonymize_url(const char *url); diff --git a/tree-walk.c b/tree-walk.c index 8f5090862b..ecd6e53b85 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -2,6 +2,7 @@ #include "tree-walk.h" #include "unpack-trees.h" #include "dir.h" +#include "object-store.h" #include "tree.h" #include "pathspec.h" @@ -2,9 +2,11 @@ #include "cache.h" #include "cache-tree.h" #include "tree.h" +#include "object-store.h" #include "blob.h" #include "commit.h" #include "tag.h" +#include "alloc.h" #include "tree-walk.h" const char *tree_type = "tree"; @@ -196,7 +198,8 @@ struct tree *lookup_tree(const struct object_id *oid) { struct object *obj = lookup_object(oid->hash); if (!obj) - return create_object(oid->hash, alloc_tree_node()); + return create_object(the_repository, oid->hash, + alloc_tree_node(the_repository)); return object_as_type(obj, OBJ_TREE, 0); } diff --git a/unpack-trees.c b/unpack-trees.c index 3a85a02a77..66741130ae 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -16,6 +16,7 @@ #include "submodule.h" #include "submodule-config.h" #include "fsmonitor.h" +#include "object-store.h" #include "fetch-object.h" /* diff --git a/upload-pack.c b/upload-pack.c index 87c6722ea5..de6106a9dd 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -3,6 +3,7 @@ #include "refs.h" #include "pkt-line.h" #include "sideband.h" +#include "object-store.h" #include "tag.h" #include "object.h" #include "commit.h" @@ -64,6 +65,7 @@ static const char *pack_objects_hook; static int filter_capability_requested; static int allow_filter; +static int allow_ref_in_want; static struct list_objects_filter_options filter_options; static void reset_timeout(void) @@ -658,7 +660,7 @@ static void send_shallow(struct commit_list *result) if (!(object->flags & (CLIENT_SHALLOW|NOT_SHALLOW))) { packet_write_fmt(1, "shallow %s", oid_to_hex(&object->oid)); - register_shallow(&object->oid); + register_shallow(the_repository, &object->oid); shallow_nr++; } result = result->next; @@ -695,14 +697,14 @@ static void send_unshallow(const struct object_array *shallows) add_object_array(object, NULL, &extra_edge_obj); } /* make sure commit traversal conforms to client */ - register_shallow(&object->oid); + register_shallow(the_repository, &object->oid); } } static void deepen(int depth, int deepen_relative, struct object_array *shallows) { - if (depth == INFINITE_DEPTH && !is_repository_shallow()) { + if (depth == INFINITE_DEPTH && !is_repository_shallow(the_repository)) { int i; for (i = 0; i < shallows->nr; i++) { @@ -782,7 +784,8 @@ static int send_shallow_list(int depth, int deepen_rev_list, if (shallows->nr > 0) { int i; for (i = 0; i < shallows->nr; i++) - register_shallow(&shallows->objects[i].item->oid); + register_shallow(the_repository, + &shallows->objects[i].item->oid); } } @@ -1075,6 +1078,8 @@ static int upload_pack_config(const char *var, const char *value, void *unused) return git_config_string(&pack_objects_hook, var, value); } else if (!strcmp("uploadpack.allowfilter", var)) { allow_filter = git_config_bool(var, value); + } else if (!strcmp("uploadpack.allowrefinwant", var)) { + allow_ref_in_want = git_config_bool(var, value); } return parse_hide_refs_config(var, value, "uploadpack"); } @@ -1114,6 +1119,7 @@ void upload_pack(struct upload_pack_options *options) struct upload_pack_data { struct object_array wants; + struct string_list wanted_refs; struct oid_array haves; struct object_array shallows; @@ -1135,12 +1141,14 @@ struct upload_pack_data { static void upload_pack_data_init(struct upload_pack_data *data) { struct object_array wants = OBJECT_ARRAY_INIT; + struct string_list wanted_refs = STRING_LIST_INIT_DUP; struct oid_array haves = OID_ARRAY_INIT; struct object_array shallows = OBJECT_ARRAY_INIT; struct string_list deepen_not = STRING_LIST_INIT_DUP; memset(data, 0, sizeof(*data)); data->wants = wants; + data->wanted_refs = wanted_refs; data->haves = haves; data->shallows = shallows; data->deepen_not = deepen_not; @@ -1149,6 +1157,7 @@ static void upload_pack_data_init(struct upload_pack_data *data) static void upload_pack_data_clear(struct upload_pack_data *data) { object_array_clear(&data->wants); + string_list_clear(&data->wanted_refs, 1); oid_array_clear(&data->haves); object_array_clear(&data->shallows); string_list_clear(&data->deepen_not, 0); @@ -1185,6 +1194,34 @@ static int parse_want(const char *line) return 0; } +static int parse_want_ref(const char *line, struct string_list *wanted_refs) +{ + const char *arg; + if (skip_prefix(line, "want-ref ", &arg)) { + struct object_id oid; + struct string_list_item *item; + struct object *o; + + if (read_ref(arg, &oid)) { + packet_write_fmt(1, "ERR unknown ref %s", arg); + die("unknown ref %s", arg); + } + + item = string_list_append(wanted_refs, arg); + item->util = oiddup(&oid); + + o = parse_object_or_die(&oid, arg); + if (!(o->flags & WANTED)) { + o->flags |= WANTED; + add_object_array(o, NULL, &want_obj); + } + + return 1; + } + + return 0; +} + static int parse_have(const char *line, struct oid_array *haves) { const char *arg; @@ -1210,6 +1247,8 @@ static void process_args(struct packet_reader *request, /* process want */ if (parse_want(arg)) continue; + if (allow_ref_in_want && parse_want_ref(arg, &data->wanted_refs)) + continue; /* process have line */ if (parse_have(arg, &data->haves)) continue; @@ -1352,18 +1391,37 @@ static int process_haves_and_send_acks(struct upload_pack_data *data) return ret; } +static void send_wanted_ref_info(struct upload_pack_data *data) +{ + const struct string_list_item *item; + + if (!data->wanted_refs.nr) + return; + + packet_write_fmt(1, "wanted-refs\n"); + + for_each_string_list_item(item, &data->wanted_refs) { + packet_write_fmt(1, "%s %s\n", + oid_to_hex(item->util), + item->string); + } + + packet_delim(1); +} + static void send_shallow_info(struct upload_pack_data *data) { /* No shallow info needs to be sent */ if (!data->depth && !data->deepen_rev_list && !data->shallows.nr && - !is_repository_shallow()) + !is_repository_shallow(the_repository)) return; packet_write_fmt(1, "shallow-info\n"); if (!send_shallow_list(data->depth, data->deepen_rev_list, data->deepen_since, &data->deepen_not, - &data->shallows) && is_repository_shallow()) + &data->shallows) && + is_repository_shallow(the_repository)) deepen(INFINITE_DEPTH, data->deepen_relative, &data->shallows); packet_delim(1); @@ -1418,6 +1476,7 @@ int upload_pack_v2(struct repository *r, struct argv_array *keys, state = FETCH_DONE; break; case FETCH_SEND_PACK: + send_wanted_ref_info(&data); send_shallow_info(&data); packet_write_fmt(1, "packfile\n"); @@ -1438,12 +1497,22 @@ int upload_pack_advertise(struct repository *r, { if (value) { int allow_filter_value; + int allow_ref_in_want; + strbuf_addstr(value, "shallow"); + if (!repo_config_get_bool(the_repository, "uploadpack.allowfilter", &allow_filter_value) && allow_filter_value) strbuf_addstr(value, " filter"); + + if (!repo_config_get_bool(the_repository, + "uploadpack.allowrefinwant", + &allow_ref_in_want) && + allow_ref_in_want) + strbuf_addstr(value, " ref-in-want"); } + return 1; } diff --git a/userdiff.c b/userdiff.c index a69241b25d..36af25e7f9 100644 --- a/userdiff.c +++ b/userdiff.c @@ -114,7 +114,7 @@ PATTERNS("perl", "|<<|<>|<=>|>>"), PATTERNS("php", "^[\t ]*(((public|protected|private|static)[\t ]+)*function.*)$\n" - "^[\t ]*(class.*)$", + "^[\t ]*((((final|abstract)[\t ]+)?class|interface|trait).*)$", /* -- */ "[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+" @@ -1,5 +1,6 @@ #include "cache.h" #include "walker.h" +#include "object-store.h" #include "commit.h" #include "tree.h" #include "tree-walk.h" diff --git a/wt-status.c b/wt-status.c index d1c05145a4..8827a256d3 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1317,7 +1317,7 @@ static void show_rebase_in_progress(struct wt_status *s, status_printf_ln(s, color, _(" (use \"git rebase --abort\" to check out the original branch)")); } - } else if (state->rebase_in_progress || !stat(git_path_merge_msg(), &st)) { + } else if (state->rebase_in_progress || !stat(git_path_merge_msg(the_repository), &st)) { print_rebase_state(s, state, color); if (s->hints) status_printf_ln(s, color, @@ -1552,17 +1552,17 @@ void wt_status_get_state(struct wt_status_state *state, struct stat st; struct object_id oid; - if (!stat(git_path_merge_head(), &st)) { + if (!stat(git_path_merge_head(the_repository), &st)) { state->merge_in_progress = 1; } else if (wt_status_check_rebase(NULL, state)) { ; /* all set */ - } else if (!stat(git_path_cherry_pick_head(), &st) && + } else if (!stat(git_path_cherry_pick_head(the_repository), &st) && !get_oid("CHERRY_PICK_HEAD", &oid)) { state->cherry_pick_in_progress = 1; oidcpy(&state->cherry_pick_head_oid, &oid); } wt_status_check_bisect(NULL, state); - if (!stat(git_path_revert_head(), &st) && + if (!stat(git_path_revert_head(the_repository), &st) && !get_oid("REVERT_HEAD", &oid)) { state->revert_in_progress = 1; oidcpy(&state->revert_head_oid, &oid); diff --git a/xdiff-interface.c b/xdiff-interface.c index 9315bc0ede..ec6e574e4a 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -1,5 +1,6 @@ #include "cache.h" #include "config.h" +#include "object-store.h" #include "xdiff-interface.h" #include "xdiff/xtypes.h" #include "xdiff/xdiffi.h" |