* Re: [PATCH] object-name: reject too-deep recursive ancestor queries
From: Patrick Steinhardt @ 2023-11-24 10:11 UTC (permalink / raw)
To: Junio C Hamano
Cc: Taylor Blau, git, Jeff King,
Carlos Andrés Ramírez Cataño
In-Reply-To: <xmqqy1en7af2.fsf@gitster.g>
[-- Attachment #1: Type: text/plain, Size: 1291 bytes --]
On Fri, Nov 24, 2023 at 06:44:33PM +0900, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
>
> > I have to wonder whether we should tighten restrictions even further:
> > instead of manually keeping track of how deep in the stack we are, we
> > limit the length of revisions to at most 1MB. I would claim that this
> > limit is sufficiently large to never be a problem in practice.
>
> Tempting.
>
> > Revisions
> > are limited to 4kB on most platforms anyway due to the maximum path
> > length.
>
> I do not quite get this part, though.
>
> When we get "HEAD~~~~~~~~~^2~~~~~~" from the user, do we somehow try
> to create a file or a directory with that name and fail due to
> ENAMETOOLONG?
Sorry, this was a typo on my part. I didn't mean "revision", I meant
"reference" here. References are limited to at most 4kB on most
platforms due to filesystem limitations, whereas revisions currently
have no limits in place.
Patrick
> There are ways like "git rev-list --stdin" to cause Git read input
> lines of arbitrary length, so I do not think the command line length
> limit does not come into the picture, either.
>
> But I do agree that the only useful use of such a revision string
> that is longer than 1MB would be to attack.
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [PATCH v2] commit-graph: disable GIT_COMMIT_GRAPH_PARANOIA by default
From: Patrick Steinhardt @ 2023-11-24 11:07 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Taylor Blau, Jeff King
In-Reply-To: <xmqq7cm8bsny.fsf@gitster.g>
[-- Attachment #1: Type: text/plain, Size: 1565 bytes --]
On Thu, Nov 23, 2023 at 08:44:33PM +0900, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
>
> > Note that this requires us to amend some tests to manually turn on the
> > paranoid checks again. This is because we cause repository corruption by
> > manually deleting objects which are part of the commit graph already.
> > These circumstances shouldn't usually happen in repositories.
> > ...
> > diff --git a/t/t6022-rev-list-missing.sh b/t/t6022-rev-list-missing.sh
> > index 40265a4f66..1ca4eb5a36 100755
> > --- a/t/t6022-rev-list-missing.sh
> > +++ b/t/t6022-rev-list-missing.sh
> > @@ -13,6 +13,11 @@ test_expect_success 'create repository and alternate directory' '
> > test_commit 3
> > '
> >
> > +# We manually corrupt the repository, which means that the commit-graph may
> > +# contain references to already-deleted objects. We thus need to enable
> > +# commit-graph paranoia to not returned these deleted commits from the graph.
> > +export GIT_COMMIT_GRAPH_PARANOIA=true
>
> test-lint-shell-syntax is a bit overly strict and complains against
> this line, so until it is loosened, I'd suggest to do
>
> GIT_COMMIT_GRAPH_PARANOIA=true
> export GIT_COMMIT_GRAPH_PARANOIA
>
> instead here.
Fair. I was pondering whether to do this when writing this line, but
remembering the recent discussion about it being in POSIX [1] I didn't.
Didn't know though we had a linting rule for this, so I'll send a v3 to
split up the statement.
Patrick
[1]: <87430c6c-91c0-4be1-b89d-bf442b3f018b@gmail.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* [PATCH v3] commit-graph: disable GIT_COMMIT_GRAPH_PARANOIA by default
From: Patrick Steinhardt @ 2023-11-24 11:08 UTC (permalink / raw)
To: git; +Cc: Taylor Blau, Jeff King, Junio C Hamano
In-Reply-To: <7e2d300c4af9a7853201121d66f982afa421bbba.1699957350.git.ps@pks.im>
[-- Attachment #1: Type: text/plain, Size: 8404 bytes --]
In 7a5d604443 (commit: detect commits that exist in commit-graph but not
in the ODB, 2023-10-31), we have introduced a new object existence check
into `repo_parse_commit_internal()` so that we do not parse commits via
the commit-graph that don't have a corresponding object in the object
database. This new check of course comes with a performance penalty,
which the commit put at around 30% for `git rev-list --topo-order`. But
there are in fact scenarios where the performance regression is even
higher. The following benchmark against linux.git with a fully-build
commit-graph:
Benchmark 1: git.v2.42.1 rev-list --count HEAD
Time (mean ± σ): 658.0 ms ± 5.2 ms [User: 613.5 ms, System: 44.4 ms]
Range (min … max): 650.2 ms … 666.0 ms 10 runs
Benchmark 2: git.v2.43.0-rc1 rev-list --count HEAD
Time (mean ± σ): 1.333 s ± 0.019 s [User: 1.263 s, System: 0.069 s]
Range (min … max): 1.302 s … 1.361 s 10 runs
Summary
git.v2.42.1 rev-list --count HEAD ran
2.03 ± 0.03 times faster than git.v2.43.0-rc1 rev-list --count HEAD
While it's a noble goal to ensure that results are the same regardless
of whether or not we have a potentially stale commit-graph, taking twice
as much time is a tough sell. Furthermore, we can generally assume that
the commit-graph will be updated by git-gc(1) or git-maintenance(1) as
required so that the case where the commit-graph is stale should not at
all be common.
With that in mind, default-disable GIT_COMMIT_GRAPH_PARANOIA and restore
the behaviour and thus performance previous to the mentioned commit. In
order to not be inconsistent, also disable this behaviour by default in
`lookup_commit_in_graph()`, where the object existence check has been
introduced right at its inception via f559d6d45e (revision: avoid
hitting packfiles when commits are in commit-graph, 2021-08-09).
This results in another speedup in commands that end up calling this
function, even though it's less pronounced compared to the above
benchmark. The following has been executed in linux.git with ~1.2
million references:
Benchmark 1: GIT_COMMIT_GRAPH_PARANOIA=true git rev-list --all --no-walk=unsorted
Time (mean ± σ): 2.947 s ± 0.003 s [User: 2.412 s, System: 0.534 s]
Range (min … max): 2.943 s … 2.949 s 3 runs
Benchmark 2: GIT_COMMIT_GRAPH_PARANOIA=false git rev-list --all --no-walk=unsorted
Time (mean ± σ): 2.724 s ± 0.030 s [User: 2.207 s, System: 0.514 s]
Range (min … max): 2.704 s … 2.759 s 3 runs
Summary
GIT_COMMIT_GRAPH_PARANOIA=false git rev-list --all --no-walk=unsorted ran
1.08 ± 0.01 times faster than GIT_COMMIT_GRAPH_PARANOIA=true git rev-list --all --no-walk=unsorted
So whereas 7a5d604443 initially introduced the logic to start doing an
object existence check in `repo_parse_commit_internal()` by default, the
updated logic will now instead cause `lookup_commit_in_graph()` to stop
doing the check by default. This behaviour continues to be tweakable by
the user via the GIT_COMMIT_GRAPH_PARANOIA environment variable.
Note that this requires us to amend some tests to manually turn on the
paranoid checks again. This is because we cause repository corruption by
manually deleting objects which are part of the commit graph already.
These circumstances shouldn't usually happen in repositories.
Reported-by: Jeff King <peff@peff.net>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
The only change compared to v2 is that I've split up the `export
GIT_COMMIT_GRAPH_PARANOIA=true` line into two lines as suggested by
Junio.
Documentation/git.txt | 6 +++---
commit-graph.c | 2 +-
commit.c | 2 +-
t/t5318-commit-graph.sh | 8 ++++----
t/t6022-rev-list-missing.sh | 6 ++++++
t/t7700-repack.sh | 2 +-
6 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 2535a30194..6c19fd1d76 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -917,9 +917,9 @@ for full details.
avoid issues with stale commit-graphs that contain references to
already-deleted commits, but comes with a performance penalty.
+
-The default is "true", which enables the aforementioned behavior.
-Setting this to "false" disables the existence check. This can lead to
-a performance improvement at the cost of consistency.
+The default is "false", which disables the aforementioned behavior.
+Setting this to "true" enables the existence check so that stale commits
+will never be returned from the commit-graph at the cost of performance.
`GIT_ALLOW_PROTOCOL`::
If set to a colon-separated list of protocols, behave as if
diff --git a/commit-graph.c b/commit-graph.c
index acac9bf6e1..6fad9d195d 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -1005,7 +1005,7 @@ struct commit *lookup_commit_in_graph(struct repository *repo, const struct obje
uint32_t pos;
if (commit_graph_paranoia == -1)
- commit_graph_paranoia = git_env_bool(GIT_COMMIT_GRAPH_PARANOIA, 1);
+ commit_graph_paranoia = git_env_bool(GIT_COMMIT_GRAPH_PARANOIA, 0);
if (!prepare_commit_graph(repo))
return NULL;
diff --git a/commit.c b/commit.c
index 8405d7c3fc..37956b836c 100644
--- a/commit.c
+++ b/commit.c
@@ -577,7 +577,7 @@ int repo_parse_commit_internal(struct repository *r,
static int commit_graph_paranoia = -1;
if (commit_graph_paranoia == -1)
- commit_graph_paranoia = git_env_bool(GIT_COMMIT_GRAPH_PARANOIA, 1);
+ commit_graph_paranoia = git_env_bool(GIT_COMMIT_GRAPH_PARANOIA, 0);
if (commit_graph_paranoia && !has_object(r, &item->object.oid, 0)) {
unparse_commit(r, &item->object.oid);
diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
index 7fe7c72a87..a2b4442660 100755
--- a/t/t5318-commit-graph.sh
+++ b/t/t5318-commit-graph.sh
@@ -911,10 +911,10 @@ test_expect_success 'stale commit cannot be parsed when given directly' '
# Verify that it is possible to read the commit from the
# commit graph when not being paranoid, ...
- GIT_COMMIT_GRAPH_PARANOIA=false git rev-list B &&
+ git rev-list B &&
# ... but parsing the commit when double checking that
# it actually exists in the object database should fail.
- test_must_fail git rev-list -1 B
+ test_must_fail env GIT_COMMIT_GRAPH_PARANOIA=true git rev-list -1 B
)
'
@@ -938,9 +938,9 @@ test_expect_success 'stale commit cannot be parsed when traversing graph' '
# Again, we should be able to parse the commit when not
# being paranoid about commit graph staleness...
- GIT_COMMIT_GRAPH_PARANOIA=false git rev-parse HEAD~2 &&
+ git rev-parse HEAD~2 &&
# ... but fail when we are paranoid.
- test_must_fail git rev-parse HEAD~2 2>error &&
+ test_must_fail env GIT_COMMIT_GRAPH_PARANOIA=true git rev-parse HEAD~2 2>error &&
grep "error: commit $oid exists in commit-graph but not in the object database" error
)
'
diff --git a/t/t6022-rev-list-missing.sh b/t/t6022-rev-list-missing.sh
index 40265a4f66..211672759a 100755
--- a/t/t6022-rev-list-missing.sh
+++ b/t/t6022-rev-list-missing.sh
@@ -13,6 +13,12 @@ test_expect_success 'create repository and alternate directory' '
test_commit 3
'
+# We manually corrupt the repository, which means that the commit-graph may
+# contain references to already-deleted objects. We thus need to enable
+# commit-graph paranoia to not returned these deleted commits from the graph.
+GIT_COMMIT_GRAPH_PARANOIA=true
+export GIT_COMMIT_GRAPH_PARANOIA
+
for obj in "HEAD~1" "HEAD~1^{tree}" "HEAD:1.t"
do
test_expect_success "rev-list --missing=error fails with missing object $obj" '
diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh
index d2975e6c93..94f9f4a1da 100755
--- a/t/t7700-repack.sh
+++ b/t/t7700-repack.sh
@@ -271,7 +271,7 @@ test_expect_success 'repacking fails when missing .pack actually means missing o
ls .git/objects/pack/*.pack >before-pack-dir &&
test_must_fail git fsck &&
- test_must_fail git repack --cruft -d 2>err &&
+ test_must_fail env GIT_COMMIT_GRAPH_PARANOIA=true git repack --cruft -d 2>err &&
grep "bad object" err &&
# Before failing, the repack did not modify the
--
2.43.0
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related
* [PATCH v8 00/14] Introduce new `git replay` command
From: Christian Couder @ 2023-11-24 11:10 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Patrick Steinhardt, Johannes Schindelin,
Elijah Newren, John Cai, Derrick Stolee, Phillip Wood, Calvin Wan,
Toon Claes, Dragan Simic, Linus Arver, Christian Couder
In-Reply-To: <20231115143327.2441397-1-christian.couder@gmail.com>
# Intro
`git replay` has initially been developed entirely by Elijah Newren
mostly last year (2022) at:
https://github.com/newren/git/commits/replay
I took over this year to polish and upstream it as GitLab is
interested in replacing libgit2, and for that purpose needs a command
to do server side (so without using a worktree) rebases, cherry-picks
and reverts.
I reduced the number of commits and features in this patch series,
compared to what Elijah already developed. Especially I stopped short
of replaying merge commits and replaying interactively. These and
other features might be upstreamed in the future after this patch
series has graduated.
The focus in this series is to make it a good plumbing command that
can already be used server side and that replaces the "fast-rebase"
test-tool command. So things to make it easier to use on the command
line, and more advanced features (like replaying merges) are left out.
It looks like GitHub has actually already been using version 3 of this
patch series in production with good results. See:
https://github.blog/2023-07-27-scaling-merge-ort-across-github/
https://lore.kernel.org/git/304f2a49-5e05-7655-9f87-2011606df5db@gmx.de/
# Content of this cover letter
The "Quick Overview" and "Reasons for diverging from cherry-pick &
rebase" sections just below are describing the purpose of the new
command in the big scheme of things. They are taken from Elijah's
design notes
(https://github.com/newren/git/blob/replay/replay-design-notes.txt)
and describe what we want this command to become and the reasons for
that, not what the command is after only this patch series. Also these
design notes were written at least one year ago, so parts of those 2
sections are not true anymore. I have added Phillip Wood's or Felipe
Contreras' notes (thanks to them) where that's the case, but some now
flawed parts may have missed.
After these two sections, starting with the "Important limitations"
section, you will find sections describing what is actually in this
patch series.
More interesting material is available in Elijah's design notes like
an "Intro via examples"
(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L37-L132),
a discussion about "Preserving topology, replaying merges"
(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L264-L341)
and a "Current status" section describing Elijah's work
(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L344-L392)
before I started working on upstreaming it.
I have not included this material here though, as the documentation
added by this patch series for the `git replay` command already
includes an "EXAMPLES" section, and other sections of Elijah's design
notes might not be interesting for now. Also this cover letter is
already pretty long. But reviewers can refer to the links above if
they think it can help.
# Quick Overview (from Elijah's design notes)
`git replay`, at a basic level, can perhaps be thought of as a
"default-to-dry-run rebase" -- meaning no updates to the working tree,
or to the index, or to any references. However, it differs from
rebase in that it:
* Works for branches that aren't checked out
* Works in a bare repository
* Can replay multiple branches simultaneously (with or without common
history in the range being replayed)
* Preserves relative topology by default (merges are replayed too in
Elijah's original work, not in this series)
* Focuses on performance
* Has several altered defaults as a result of the above
I sometimes think of `git replay` as "fast-replay", a patch-based
analogue to the snapshot-based fast-export & fast-import tools.
# Reasons for diverging from cherry-pick & rebase (from Elijah's
design notes)
There are multiple reasons to diverge from the defaults in cherry-pick and
rebase.
* Server side needs
* Both cherry-pick and rebase, via the sequencer, are heavily tied
to updating the working tree, index, some refs, and a lot of
control files with every commit replayed, and invoke a mess of
hooks[1] that might be hard to avoid for backward compatibility
reasons (at least, that's been brought up a few times on the
list).
* cherry-pick and rebase both fork various subprocesses
unnecessarily, but somewhat intrinsically in part to ensure the
same hooks are called that old scripted implementations would have
called.
Note: since 356ee4659bb (sequencer: try to commit without forking
'git commit', 2017-11-24) cherry-pick and rebase do not fork
subprocesses other than hooks for the cases covered by this patch
series (i.e. they do not fork "git commit" for simple picks).
* "Dry run" behavior, where there are no updates to worktree, index,
or even refs might be important.
* Should not assume users only want to operate on HEAD (see next
section)
* Decapitate HEAD-centric assumptions
* cherry-pick forces commits to be played on top of HEAD;
inflexible.
* rebase assumes the range of commits to be replayed is
upstream..HEAD by default, though it allows one to replay
upstream..otherbranch -- but it still forcibly and needlessly
checks out 'otherbranch' before starting to replay things.
Note: since 767a9c417eb (rebase -i: stop checking out the tip of
the branch to rebase, 2020-01-24) it's not true that rebase
forcibly and needlessly checks out 'otherbranch'.
* Assuming HEAD is involved severely limits replaying multiple
(possibly divergent) branches.
Note: since 89fc0b53fdb (rebase: update refs from 'update-ref'
commands, 2022-07-19) the sequencer can update multiple
branches. The issue with divergent branch is with command line
arguments and the todo list generation rather than the
capabilities of the sequencer.
* Once you stop assuming HEAD has a certain meaning, there's not
much reason to have two separate commands anymore (except for the
funny extra not-necessarily-compatible options both have gained
over time).
* (Micro issue: Assuming HEAD is involved also makes it harder for
new users to learn what rebase means and does; it makes command
lines hard to parse. Not sure I want to harp on this too much, as
I have a suspicion I might be creating a tool for experts with
complicated use cases, but it's a minor quibble.)
* Performance
* jj is slaughtering us on rebase speed[2]. I would like us to become
competitive. (I dropped a few comments in the link at [2] about why
git is currently so bad.)
* From [3], there was a simple 4-patch series in linux.git that took
53 seconds to rebase. Switching to ort dropped it to 16 seconds.
While that sounds great, only 11 *milliseconds* were needed to do
the actual merges. That means almost *all* the time (>99%) was
overhead! Big offenders:
* --reapply-cherry-picks should be the default
* can_fast_forward() should be ripped out, and perhaps other extraneous
revision walks
Note: d42c9ffa0f (rebase: factor out branch_base calculation,
2022-10-17) might already deal with that (according to Felipe
Contreras).
* avoid updating working tree, index, refs, reflogs, and control
structures except when needed (e.g. hitting a conflict, or operation
finished)
* Other performance ideas (mostly for future work, not in this
series)
* single-file control structures instead of directory of files
(when doing interactive things which is in Elijah's original
work, but not in this series)
* avoid forking subprocesses unless explicitly requested (e.g.
--exec, --strategy, --run-hooks). For example, definitely do not
invoke `git commit` or `git merge`.
* Sanitize hooks:
* dispense with all per-commit hooks for sure (pre-commit,
post-commit, post-checkout).
* pre-rebase also seems to assume exactly 1 ref is written, and
invoking it repeatedly would be stupid. Plus, it's specific
to "rebase". So...ignore? (Stolee's --ref-update option for
rebase probably broke the pre-rebase assumptions already...)
* post-rewrite hook might make sense, but fast-import got
exempted, and I think of replay like a patch-based analogue
to the snapshot-based fast-import.
* When not running server side, resolve conflicts in a sparse-cone
sparse-index worktree to reduce number of files written to a
working tree. (See below as well.)
* [High risk of possible premature optimization] Avoid large
numbers of newly created loose objects, when replaying large
numbers of commits. Two possibilities: (1) Consider using
tmp-objdir and pack objects from the tmp-objdir at end of
exercise, (2) Lift code from git-fast-import to immediately
stuff new objects into a pack?
* Multiple branches and non-checked out branches
* The ability to operate on non-checked out branches also implies
that we should generally be able to replay when in a dirty working
tree (exception being when we expect to update HEAD and any of the
dirty files is one that needs to be updated by the replay).
* Also, if we are operating locally on a non-checked out branch and
hit a conflict, we should have a way to resolve the conflict
without messing with the user's work on their current
branch. (This is not is this patch series though.)
* Idea: new worktree with sparse cone + sparse index checkout,
containing only files in the root directory, and whatever is
necessary to get the conflicts
* Companion to above idea: control structures should be written to
$GIT_COMMON_DIR/replay-${worktree}, so users can have multiple
replay sessions, and so we know which worktrees are associated
with which replay operations.
- [1] https://lore.kernel.org/git/pull.749.v3.git.git.1586044818132.gitgitgadget@gmail.com/
- [2] https://github.com/martinvonz/jj/discussions/49
- [3] https://lore.kernel.org/git/CABPp-BE48=97k_3tnNqXPjSEfA163F8hoE+HY0Zvz1SWB2B8EA@mail.gmail.com/
# Important limitations
* The code exits with code 1 if there are any conflict. No
resumability. No nice output. No interactivity. No special exit code
depending on the reason.
* When a commit becomes empty as it is replayed, it is still replayed
as an empty commit, instead of being dropped.
* No replaying merges, nor root commits. Only regular commits.
* Signed commits are not properly handled. It's not clear what to do
to such commits when replaying on the server side.
* Notes associated with replayed commits are not updated nor
duplicated. (Thanks to Phillip Wood for noticing.)
# Commit overview
* 1/14 t6429: remove switching aspects of fast-rebase
Preparatory commit to make it easier to later replace the
fast-rebase test-tool by `git replay` without breaking existing
tests.
* 2/14 replay: introduce new builtin
This creates a minimal `git replay` command by moving the code
from the `fast-rebase` test helper from `t/helper/` into
`builtin/` and doing some renames and a few other needed changes.
Since v7, there is only a synopsys change in the doc, and the
corresponding usage message change, as suggested by Dscho.
* - 3/14 replay: start using parse_options API
- 4/14 replay: die() instead of failing assert()
- 5/14 replay: introduce pick_regular_commit()
- 6/14 replay: change rev walking options
- 7/14 replay: add an important FIXME comment about gpg signing
- 8/14 replay: remove progress and info output
- 9/14 replay: remove HEAD related sanity check
These slowly change the command to make it behave more like
regular commands and to start cleaning up its output.
* 10/14 replay: make it a minimal server side command
After the cleaning up in previous commits, it's now time to
radically change the way it works by stopping it to do ref
updates, to update the index and worktree, to consider HEAD as
special. Instead just make it output commands that should be
passed to `git update-ref --stdin`.
* 11/14 replay: use standard revision ranges
Start adding new interesting features and also documentation and
tests, as the interface of the command is cristalizing into its
final form.
* - 12/14 replay: add --advance or 'cherry-pick' mode
- 13/14 replay: add --contained to rebase contained branches
Add new options and features to the command.
* 14/14 replay: stop assuming replayed branches do not diverge
This adds another interesting feature, as well as related
documentation and tests.
# Notes about `fast-rebase`, tests and documentation
The `fast-rebase` test-tool helper was developed by Elijah to
experiment with a rebasing tool that would be developed from scratch
based on his merge-ort work, could be used to test that merge-ort
work, and would not have the speed and interface limitations of `git
rebase` or `git cherry-pick`.
This `fast-rebase` helper was used before this series in:
t6429-merge-sequence-rename-caching.sh
So when `git replay` is created from `fast-rebase` in patch 2/14, the
t6429 test script is also converted to use `git replay`. This ensures
that `git replay` doesn't break too badly during the first 10 patches
in this patch series.
Tests and documentation are introduced specifically for `git replay`
as soon as patch 2/14, but they are not much improved since around
11/14 as it doesn't make much sense to document and test behavior that
we know is going to change soon.
# Possibly controversial issues
* bare or not bare: this series works towards a plumbing command with
the end goal of it being usable and used first on bare repos,
contrary to existing commands like `git rebase` and `git
cherry-pick`. The tests check that the command works on both bare
and non-bare repo though.
* exit status: a successful, non-conflicted replay exits with code
0. When the replay has conflicts, the exit status is 1. If the
replay is not able to complete (or start) due to some kind of error,
the exit status is something other than 0 or 1. There are a few
tests checking that. It has been suggested in an internal review
that conflicts might want to get a more specific error code as an
error code of 1 might be quite easy to return by accident. It
doesn't seem to me from their docs (which might want to be improved,
I didn't look at the code) that other commands like `git merge` and
`git rebase` exit with a special error code in case of conflict.
* make worktree and index changes optional: commit 10/14 stops
updating the index and worktree, but it might be better especially
for cli users to make that optional. The issue is that this would
make the command more complex while we are developing a number of
important features so that the command can be used on bare repos. It
seems that this should rather be done in an iterative improvement
after the important features have landed.
* --advance and --contained: these two advanced options might not
belong to this first series and could perhaps be added in a followup
series in separate commits. On the other hand the code for
--contained seems involved with the code of --advance and it's nice
to see soon that git replay can indeed do cherry-picking and rebase
many refs at once, and this way fullfil these parts of its promise.
* replaying diverging branches: 14/14 the last patch in the series,
which allow replaying diverging branches, can be seen as a
fundamental fix or alternatively as adding an interesting
feature. So it's debatable if it should be in its own patch along
with its own tests as in this series, or if it should be merged into
a previous patch and which one.
* only 2 patches: this patch series can be seen from a high level
point of view as 1) introducing the new `git replay` command, and 2)
using `git replay` to replace, and get rid of, the fast-rebase
test-tool command. The fact that not much of the original
fast-rebase code and interface is left would agree with that point
of view. On the other hand, fast-rebase can also be seen as a first
iteration towards `git replay`. So it can also make sense to see how
`git replay` evolved from it.
# Changes between v7 and v8
Thanks to Dscho, Linus Arver, Dragan Simic, Elijah, Junio, Derrick
Stolee, Phillip Wood, Calvin Wan and Toon Claes for their suggestions
on the previous versions! The only few changes compared to v7 are:
* The patch series was rebased onto master at 564d0252ca (Git 2.43,
2023-11-20). This is to make it stand on a stable base.
* In patch 2/14 (replay: introduce new builtin), there is a synopsys
change in the doc, and the corresponding usage message change as
suggested by Dscho. This is just about replacing " # EXPERIMENTAL"
at the end of both the synopsys and usage message with
"(EXPERIMENTAL!) " at the beginning of them.
CI tests seem to pass according to:
https://github.com/chriscool/git/actions/runs/6979770154
(Sorry I am not waiting more than 20 minutes for the 3 last ones to
finish.)
# Range-diff between v7 and v8
(A single change was made in patch 2/14, but unfortunately as the
lines changed in that patch are also changed by other patches later,
it looks like there are more changes in subsequent patches.)
1: cddcd967b2 = 1: 18fd9b0d5d t6429: remove switching aspects of fast-rebase
2: c8476fb093 ! 2: fc6bdf4de4 replay: introduce new builtin
@@ Documentation/git-replay.txt (new)
+SYNOPSIS
+--------
+[verse]
-+'git replay' --onto <newbase> <oldbase> <branch> # EXPERIMENTAL
++(EXPERIMENTAL!) 'git replay' --onto <newbase> <oldbase> <branch>
+
+DESCRIPTION
+-----------
@@ builtin/replay.c: int cmd__fast_rebase(int argc, const char **argv)
-
if (argc == 2 && !strcmp(argv[1], "-h")) {
- printf("Sorry, I am not a psychiatrist; I can not give you the help you need. Oh, you meant usage...\n");
-+ printf("git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\n");
++ printf("usage: (EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\n");
exit(129);
}
3: 43322abd1e ! 3: e96a66c352 replay: start using parse_options API
@@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix
int ret = 0;
- if (argc == 2 && !strcmp(argv[1], "-h")) {
-- printf("git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\n");
+- printf("usage: (EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\n");
- exit(129);
+ const char * const replay_usage[] = {
-+ N_("git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL"),
++ N_("(EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>"),
+ NULL
+ };
+ struct option replay_options[] = {
4: 6524c7f045 = 4: f819d283d9 replay: die() instead of failing assert()
5: 05d0efa3cb = 5: 68bbcf9492 replay: introduce pick_regular_commit()
6: c7a5aad3d6 = 6: 72221c647e replay: change rev walking options
7: 01f35f924b = 7: f54d8fce22 replay: add an important FIXME comment about gpg signing
8: 1498b24bad = 8: e50cc22522 replay: remove progress and info output
9: 6786fc147b = 9: 0c5ea3d18e replay: remove HEAD related sanity check
10: 9a24dbb530 = 10: 9fc636fc3d replay: make it a minimal server side command
11: ad6ca2fbef ! 11: 2096bcad79 replay: use standard revision ranges
@@ Documentation/git-replay.txt: git-replay - EXPERIMENTAL: Replay commits on a new
SYNOPSIS
--------
[verse]
--'git replay' --onto <newbase> <oldbase> <branch> # EXPERIMENTAL
-+'git replay' --onto <newbase> <revision-range>... # EXPERIMENTAL
+-(EXPERIMENTAL!) 'git replay' --onto <newbase> <oldbase> <branch>
++(EXPERIMENTAL!) 'git replay' --onto <newbase> <revision-range>...
DESCRIPTION
-----------
@@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix
int ret = 0;
const char * const replay_usage[] = {
-- N_("git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL"),
-+ N_("git replay --onto <newbase> <revision-range>... # EXPERIMENTAL"),
+- N_("(EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>"),
++ N_("(EXPERIMENTAL!) git replay --onto <newbase> <revision-range>..."),
NULL
};
struct option replay_options[] = {
12: 081864ed5f ! 12: d5414806ef replay: add --advance or 'cherry-pick' mode
@@ Documentation/git-replay.txt: git-replay - EXPERIMENTAL: Replay commits on a new
SYNOPSIS
--------
[verse]
--'git replay' --onto <newbase> <revision-range>... # EXPERIMENTAL
-+'git replay' (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL
+-(EXPERIMENTAL!) 'git replay' --onto <newbase> <revision-range>...
++(EXPERIMENTAL!) 'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...
DESCRIPTION
-----------
@@ builtin/replay.c: static struct commit *pick_regular_commit(struct commit *pickm
int ret = 0;
const char * const replay_usage[] = {
-- N_("git replay --onto <newbase> <revision-range>... # EXPERIMENTAL"),
-+ N_("git replay (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL"),
+- N_("(EXPERIMENTAL!) git replay --onto <newbase> <revision-range>..."),
++ N_("(EXPERIMENTAL!) git replay (--onto <newbase> | --advance <branch>) <revision-range>..."),
NULL
};
struct option replay_options[] = {
13: 19c4016c7c ! 13: 2a3e521c13 replay: add --contained to rebase contained branches
@@ Documentation/git-replay.txt: git-replay - EXPERIMENTAL: Replay commits on a new
SYNOPSIS
--------
[verse]
--'git replay' (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL
-+'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL
+-(EXPERIMENTAL!) 'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...
++(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...
DESCRIPTION
-----------
@@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix
int ret = 0;
const char * const replay_usage[] = {
-- N_("git replay (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL"),
-+ N_("git replay ([--contained] --onto <newbase> | --advance <branch>) "
-+ "<revision-range>... # EXPERIMENTAL"),
+- N_("(EXPERIMENTAL!) git replay (--onto <newbase> | --advance <branch>) <revision-range>..."),
++ N_("(EXPERIMENTAL!) git replay "
++ "([--contained] --onto <newbase> | --advance <branch>) "
++ "<revision-range>..."),
NULL
};
struct option replay_options[] = {
14: 29556bcc86 = 14: 93e034faee replay: stop assuming replayed branches do not diverge
Elijah Newren (14):
t6429: remove switching aspects of fast-rebase
replay: introduce new builtin
replay: start using parse_options API
replay: die() instead of failing assert()
replay: introduce pick_regular_commit()
replay: change rev walking options
replay: add an important FIXME comment about gpg signing
replay: remove progress and info output
replay: remove HEAD related sanity check
replay: make it a minimal server side command
replay: use standard revision ranges
replay: add --advance or 'cherry-pick' mode
replay: add --contained to rebase contained branches
replay: stop assuming replayed branches do not diverge
.gitignore | 1 +
Documentation/git-replay.txt | 127 +++++++
Makefile | 2 +-
builtin.h | 1 +
builtin/replay.c | 446 +++++++++++++++++++++++
command-list.txt | 1 +
git.c | 1 +
t/helper/test-fast-rebase.c | 241 ------------
t/helper/test-tool.c | 1 -
t/helper/test-tool.h | 1 -
t/t3650-replay-basics.sh | 198 ++++++++++
t/t6429-merge-sequence-rename-caching.sh | 45 ++-
12 files changed, 801 insertions(+), 264 deletions(-)
create mode 100644 Documentation/git-replay.txt
create mode 100644 builtin/replay.c
delete mode 100644 t/helper/test-fast-rebase.c
create mode 100755 t/t3650-replay-basics.sh
base-commit: 564d0252ca632e0264ed670534a51d18a689ef5d
--
2.43.0.14.g93e034faee
^ permalink raw reply
* [PATCH v8 01/14] t6429: remove switching aspects of fast-rebase
From: Christian Couder @ 2023-11-24 11:10 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Patrick Steinhardt, Johannes Schindelin,
Elijah Newren, John Cai, Derrick Stolee, Phillip Wood, Calvin Wan,
Toon Claes, Dragan Simic, Linus Arver, Christian Couder
In-Reply-To: <20231124111044.3426007-1-christian.couder@gmail.com>
From: Elijah Newren <newren@gmail.com>
At the time t6429 was written, merge-ort was still under development,
did not have quite as many tests, and certainly was not widely deployed.
Since t6429 was exercising some codepaths just a little differently, we
thought having them also test the "merge_switch_to_result()" bits of
merge-ort was useful even though they weren't intrinsic to the real
point of these tests.
However, the value provided by doing extra testing of the
"merge_switch_to_result()" bits has decreased a bit over time, and it's
actively making it harder to refactor `test-tool fast-rebase` into `git
replay`, which we are going to do in following commits. Dispense with
these bits.
Co-authored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
t/helper/test-fast-rebase.c | 9 +--------
t/t6429-merge-sequence-rename-caching.sh | 9 +++++++--
2 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/t/helper/test-fast-rebase.c b/t/helper/test-fast-rebase.c
index cac20a72b3..2bfab66b1b 100644
--- a/t/helper/test-fast-rebase.c
+++ b/t/helper/test-fast-rebase.c
@@ -194,7 +194,7 @@ int cmd__fast_rebase(int argc, const char **argv)
last_commit = create_commit(result.tree, commit, last_commit);
}
- merge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);
+ merge_finalize(&merge_opt, &result);
if (result.clean < 0)
exit(128);
@@ -213,9 +213,6 @@ int cmd__fast_rebase(int argc, const char **argv)
}
if (create_symref("HEAD", branch_name.buf, reflog_msg.buf) < 0)
die(_("unable to update HEAD"));
-
- prime_cache_tree(the_repository, the_repository->index,
- result.tree);
} else {
fprintf(stderr, "\nAborting: Hit a conflict.\n");
strbuf_addf(&reflog_msg, "rebase progress up to %s",
@@ -228,10 +225,6 @@ int cmd__fast_rebase(int argc, const char **argv)
die("Failed to update %s", argv[4]);
}
}
- if (write_locked_index(&the_index, &lock,
- COMMIT_LOCK | SKIP_IF_UNCHANGED))
- die(_("unable to write %s"), get_index_file());
-
ret = (result.clean == 0);
cleanup:
strbuf_release(&reflog_msg);
diff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh
index d02fa16614..75d3fd2dba 100755
--- a/t/t6429-merge-sequence-rename-caching.sh
+++ b/t/t6429-merge-sequence-rename-caching.sh
@@ -72,6 +72,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '
git switch upstream &&
test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ git reset --hard topic &&
#git cherry-pick upstream~1..topic
git ls-files >tracked-files &&
@@ -200,6 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '
export GIT_TRACE2_PERF &&
test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ git reset --hard topic &&
#git cherry-pick upstream~1..topic &&
git ls-files >tracked &&
@@ -277,6 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '
export GIT_TRACE2_PERF &&
test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ git reset --hard topic &&
#git cherry-pick upstream~1..topic &&
git ls-files >tracked &&
@@ -356,8 +359,6 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'
test_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&
#git cherry-pick upstream..topic &&
- grep CONFLICT..rename/rename output &&
-
grep region_enter.*diffcore_rename trace.output >calls &&
test_line_count = 2 calls
)
@@ -456,6 +457,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '
export GIT_TRACE2_PERF &&
test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ git reset --hard topic &&
#git cherry-pick upstream..topic &&
grep region_enter.*diffcore_rename trace.output >calls &&
@@ -522,6 +524,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir
export GIT_TRACE2_PERF &&
test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ git reset --hard topic &&
#git cherry-pick upstream..topic &&
grep region_enter.*diffcore_rename trace.output >calls &&
@@ -624,6 +627,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '
export GIT_TRACE2_PERF &&
test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ git reset --hard topic &&
#git cherry-pick upstream..topic &&
grep region_enter.*diffcore_rename trace.output >calls &&
@@ -682,6 +686,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '
export GIT_TRACE2_PERF &&
test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ git reset --hard topic &&
#git cherry-pick upstream..topic &&
grep region_enter.*diffcore_rename trace.output >calls &&
--
2.43.0.14.g93e034faee
^ permalink raw reply related
* [PATCH v8 02/14] replay: introduce new builtin
From: Christian Couder @ 2023-11-24 11:10 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Patrick Steinhardt, Johannes Schindelin,
Elijah Newren, John Cai, Derrick Stolee, Phillip Wood, Calvin Wan,
Toon Claes, Dragan Simic, Linus Arver, Christian Couder
In-Reply-To: <20231124111044.3426007-1-christian.couder@gmail.com>
From: Elijah Newren <newren@gmail.com>
For now, this is just a rename from `t/helper/test-fast-rebase.c` into
`builtin/replay.c` with minimal changes to make it build appropriately.
Let's add a stub documentation and a stub test script though.
Subsequent commits will flesh out the capabilities of the new command
and make it a more standard regular builtin.
Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Co-authored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
.gitignore | 1 +
Documentation/git-replay.txt | 39 ++++++++++++
Makefile | 2 +-
builtin.h | 1 +
.../test-fast-rebase.c => builtin/replay.c | 29 +++------
command-list.txt | 1 +
git.c | 1 +
t/helper/test-tool.c | 1 -
t/helper/test-tool.h | 1 -
t/t3650-replay-basics.sh | 60 +++++++++++++++++++
t/t6429-merge-sequence-rename-caching.sh | 27 +++------
11 files changed, 122 insertions(+), 41 deletions(-)
create mode 100644 Documentation/git-replay.txt
rename t/helper/test-fast-rebase.c => builtin/replay.c (87%)
create mode 100755 t/t3650-replay-basics.sh
diff --git a/.gitignore b/.gitignore
index 5e56e471b3..612c0f6a0f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -135,6 +135,7 @@
/git-remote-ext
/git-repack
/git-replace
+/git-replay
/git-request-pull
/git-rerere
/git-reset
diff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt
new file mode 100644
index 0000000000..2ca7ca5fd8
--- /dev/null
+++ b/Documentation/git-replay.txt
@@ -0,0 +1,39 @@
+git-replay(1)
+=============
+
+NAME
+----
+git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos too
+
+
+SYNOPSIS
+--------
+[verse]
+(EXPERIMENTAL!) 'git replay' --onto <newbase> <oldbase> <branch>
+
+DESCRIPTION
+-----------
+
+Takes a range of commits, specified by <oldbase> and <branch>, and
+replays them onto a new location (see `--onto` option below).
+
+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
+
+OPTIONS
+-------
+
+--onto <newbase>::
+ Starting point at which to create the new commits. May be any
+ valid commit, and not just an existing branch name.
+
+EXIT STATUS
+-----------
+
+For a successful, non-conflicted replay, the exit status is 0. When
+the replay has conflicts, the exit status is 1. If the replay is not
+able to complete (or start) due to some kind of error, the exit status
+is something other than 0 or 1.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index 03adcb5a48..3834bc1544 100644
--- a/Makefile
+++ b/Makefile
@@ -799,7 +799,6 @@ TEST_BUILTINS_OBJS += test-dump-split-index.o
TEST_BUILTINS_OBJS += test-dump-untracked-cache.o
TEST_BUILTINS_OBJS += test-env-helper.o
TEST_BUILTINS_OBJS += test-example-decorate.o
-TEST_BUILTINS_OBJS += test-fast-rebase.o
TEST_BUILTINS_OBJS += test-find-pack.o
TEST_BUILTINS_OBJS += test-fsmonitor-client.o
TEST_BUILTINS_OBJS += test-genrandom.o
@@ -1290,6 +1289,7 @@ BUILTIN_OBJS += builtin/remote-fd.o
BUILTIN_OBJS += builtin/remote.o
BUILTIN_OBJS += builtin/repack.o
BUILTIN_OBJS += builtin/replace.o
+BUILTIN_OBJS += builtin/replay.o
BUILTIN_OBJS += builtin/rerere.o
BUILTIN_OBJS += builtin/reset.o
BUILTIN_OBJS += builtin/rev-list.o
diff --git a/builtin.h b/builtin.h
index d560baa661..28280636da 100644
--- a/builtin.h
+++ b/builtin.h
@@ -211,6 +211,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix);
int cmd_remote_ext(int argc, const char **argv, const char *prefix);
int cmd_remote_fd(int argc, const char **argv, const char *prefix);
int cmd_repack(int argc, const char **argv, const char *prefix);
+int cmd_replay(int argc, const char **argv, const char *prefix);
int cmd_rerere(int argc, const char **argv, const char *prefix);
int cmd_reset(int argc, const char **argv, const char *prefix);
int cmd_restore(int argc, const char **argv, const char *prefix);
diff --git a/t/helper/test-fast-rebase.c b/builtin/replay.c
similarity index 87%
rename from t/helper/test-fast-rebase.c
rename to builtin/replay.c
index 2bfab66b1b..1998134683 100644
--- a/t/helper/test-fast-rebase.c
+++ b/builtin/replay.c
@@ -1,17 +1,11 @@
/*
- * "git fast-rebase" builtin command
- *
- * FAST: Forking Any Subprocesses (is) Taboo
- *
- * This is meant SOLELY as a demo of what is possible. sequencer.c and
- * rebase.c should be refactored to use the ideas here, rather than attempting
- * to extend this file to replace those (unless Phillip or Dscho say that
- * refactoring is too hard and we need a clean slate, but I'm guessing that
- * refactoring is the better route).
+ * "git replay" builtin command
*/
#define USE_THE_INDEX_VARIABLE
-#include "test-tool.h"
+#include "git-compat-util.h"
+
+#include "builtin.h"
#include "cache-tree.h"
#include "commit.h"
#include "environment.h"
@@ -27,7 +21,8 @@
#include "sequencer.h"
#include "setup.h"
#include "strvec.h"
-#include "tree.h"
+#include <oidset.h>
+#include <tree.h>
static const char *short_commit_name(struct commit *commit)
{
@@ -94,7 +89,7 @@ static struct commit *create_commit(struct tree *tree,
return (struct commit *)obj;
}
-int cmd__fast_rebase(int argc, const char **argv)
+int cmd_replay(int argc, const char **argv, const char *prefix)
{
struct commit *onto;
struct commit *last_commit = NULL, *last_picked_commit = NULL;
@@ -110,14 +105,8 @@ int cmd__fast_rebase(int argc, const char **argv)
struct strbuf branch_name = STRBUF_INIT;
int ret = 0;
- /*
- * test-tool stuff doesn't set up the git directory by default; need to
- * do that manually.
- */
- setup_git_directory();
-
if (argc == 2 && !strcmp(argv[1], "-h")) {
- printf("Sorry, I am not a psychiatrist; I can not give you the help you need. Oh, you meant usage...\n");
+ printf("usage: (EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\n");
exit(129);
}
@@ -136,7 +125,7 @@ int cmd__fast_rebase(int argc, const char **argv)
if (repo_read_index(the_repository) < 0)
BUG("Could not read index");
- repo_init_revisions(the_repository, &revs, NULL);
+ repo_init_revisions(the_repository, &revs, prefix);
revs.verbose_header = 1;
revs.max_parents = 1;
revs.cherry_mark = 1;
diff --git a/command-list.txt b/command-list.txt
index 54b2a50f5f..c4cd0f352b 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -160,6 +160,7 @@ git-reflog ancillarymanipulators complete
git-remote ancillarymanipulators complete
git-repack ancillarymanipulators complete
git-replace ancillarymanipulators complete
+git-replay plumbingmanipulators
git-request-pull foreignscminterface complete
git-rerere ancillaryinterrogators
git-reset mainporcelain history
diff --git a/git.c b/git.c
index c67e44dd82..7068a184b0 100644
--- a/git.c
+++ b/git.c
@@ -594,6 +594,7 @@ static struct cmd_struct commands[] = {
{ "remote-fd", cmd_remote_fd, NO_PARSEOPT },
{ "repack", cmd_repack, RUN_SETUP },
{ "replace", cmd_replace, RUN_SETUP },
+ { "replay", cmd_replay, RUN_SETUP },
{ "rerere", cmd_rerere, RUN_SETUP },
{ "reset", cmd_reset, RUN_SETUP },
{ "restore", cmd_restore, RUN_SETUP | NEED_WORK_TREE },
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index 876cd2dc31..37ba996539 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -30,7 +30,6 @@ static struct test_cmd cmds[] = {
{ "dump-untracked-cache", cmd__dump_untracked_cache },
{ "env-helper", cmd__env_helper },
{ "example-decorate", cmd__example_decorate },
- { "fast-rebase", cmd__fast_rebase },
{ "find-pack", cmd__find_pack },
{ "fsmonitor-client", cmd__fsmonitor_client },
{ "genrandom", cmd__genrandom },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index 70dd4eba11..8a1a7c63da 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -24,7 +24,6 @@ int cmd__dump_untracked_cache(int argc, const char **argv);
int cmd__dump_reftable(int argc, const char **argv);
int cmd__env_helper(int argc, const char **argv);
int cmd__example_decorate(int argc, const char **argv);
-int cmd__fast_rebase(int argc, const char **argv);
int cmd__find_pack(int argc, const char **argv);
int cmd__fsmonitor_client(int argc, const char **argv);
int cmd__genrandom(int argc, const char **argv);
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
new file mode 100755
index 0000000000..36c1b5082a
--- /dev/null
+++ b/t/t3650-replay-basics.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+test_description='basic git replay tests'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+
+GIT_AUTHOR_NAME=author@name
+GIT_AUTHOR_EMAIL=bogus@email@address
+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
+
+test_expect_success 'setup' '
+ test_commit A &&
+ test_commit B &&
+
+ git switch -c topic1 &&
+ test_commit C &&
+ git switch -c topic2 &&
+ test_commit D &&
+ test_commit E &&
+ git switch topic1 &&
+ test_commit F &&
+ git switch -c topic3 &&
+ test_commit G &&
+ test_commit H &&
+ git switch -c topic4 main &&
+ test_commit I &&
+ test_commit J &&
+
+ git switch -c next main &&
+ test_commit K &&
+ git merge -m "Merge topic1" topic1 &&
+ git merge -m "Merge topic2" topic2 &&
+ git merge -m "Merge topic3" topic3 &&
+ >evil &&
+ git add evil &&
+ git commit --amend &&
+ git merge -m "Merge topic4" topic4 &&
+
+ git switch main &&
+ test_commit L &&
+ test_commit M &&
+
+ git switch -c conflict B &&
+ test_commit C.conflict C.t conflict
+'
+
+test_expect_success 'using replay to rebase two branches, one on top of other' '
+ git switch main &&
+
+ git replay --onto main topic1 topic2 >result &&
+
+ git log --format=%s $(cut -f 3 -d " " result) >actual &&
+ test_write_lines E D M L B A >expect &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh
index 75d3fd2dba..7670b72008 100755
--- a/t/t6429-merge-sequence-rename-caching.sh
+++ b/t/t6429-merge-sequence-rename-caching.sh
@@ -71,9 +71,8 @@ test_expect_success 'caching renames does not preclude finding new ones' '
git switch upstream &&
- test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ git replay --onto HEAD upstream~1 topic &&
git reset --hard topic &&
- #git cherry-pick upstream~1..topic
git ls-files >tracked-files &&
test_line_count = 2 tracked-files &&
@@ -141,8 +140,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- test-tool fast-rebase --onto HEAD upstream~1 topic &&
- #git cherry-pick upstream~1..topic &&
+ git replay --onto HEAD upstream~1 topic &&
grep region_enter.*diffcore_rename trace.output >calls &&
test_line_count = 1 calls
@@ -200,9 +198,8 @@ test_expect_success 'rename same file identically, then reintroduce it' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ git replay --onto HEAD upstream~1 topic &&
git reset --hard topic &&
- #git cherry-pick upstream~1..topic &&
git ls-files >tracked &&
test_line_count = 2 tracked &&
@@ -278,9 +275,8 @@ test_expect_success 'rename same file identically, then add file to old dir' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ git replay --onto HEAD upstream~1 topic &&
git reset --hard topic &&
- #git cherry-pick upstream~1..topic &&
git ls-files >tracked &&
test_line_count = 4 tracked &&
@@ -356,8 +352,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- test_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&
- #git cherry-pick upstream..topic &&
+ test_must_fail git replay --onto HEAD upstream~1 topic >output &&
grep region_enter.*diffcore_rename trace.output >calls &&
test_line_count = 2 calls
@@ -456,9 +451,8 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ git replay --onto HEAD upstream~1 topic &&
git reset --hard topic &&
- #git cherry-pick upstream..topic &&
grep region_enter.*diffcore_rename trace.output >calls &&
test_line_count = 2 calls &&
@@ -523,9 +517,8 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ git replay --onto HEAD upstream~1 topic &&
git reset --hard topic &&
- #git cherry-pick upstream..topic &&
grep region_enter.*diffcore_rename trace.output >calls &&
test_line_count = 3 calls &&
@@ -626,9 +619,8 @@ test_expect_success 'caching renames only on upstream side, part 1' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ git replay --onto HEAD upstream~1 topic &&
git reset --hard topic &&
- #git cherry-pick upstream..topic &&
grep region_enter.*diffcore_rename trace.output >calls &&
test_line_count = 1 calls &&
@@ -685,9 +677,8 @@ test_expect_success 'caching renames only on upstream side, part 2' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- test-tool fast-rebase --onto HEAD upstream~1 topic &&
+ git replay --onto HEAD upstream~1 topic &&
git reset --hard topic &&
- #git cherry-pick upstream..topic &&
grep region_enter.*diffcore_rename trace.output >calls &&
test_line_count = 2 calls &&
--
2.43.0.14.g93e034faee
^ permalink raw reply related
* [PATCH v8 03/14] replay: start using parse_options API
From: Christian Couder @ 2023-11-24 11:10 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Patrick Steinhardt, Johannes Schindelin,
Elijah Newren, John Cai, Derrick Stolee, Phillip Wood, Calvin Wan,
Toon Claes, Dragan Simic, Linus Arver, Christian Couder
In-Reply-To: <20231124111044.3426007-1-christian.couder@gmail.com>
From: Elijah Newren <newren@gmail.com>
Instead of manually parsing arguments, let's start using the parse_options
API. This way this new builtin will look more standard, and in some
upcoming commits will more easily be able to handle more command line
options.
Note that we plan to later use standard revision ranges instead of
hardcoded "<oldbase> <branch>" arguments. When we will use standard
revision ranges, it will be easier to check if there are no spurious
arguments if we keep ARGV[0], so let's call parse_options() with
PARSE_OPT_KEEP_ARGV0 even if we don't need ARGV[0] right now to avoid
some useless code churn.
Co-authored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
builtin/replay.c | 45 ++++++++++++++++++++++++++++++++-------------
1 file changed, 32 insertions(+), 13 deletions(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index 1998134683..7998f6ed04 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -15,7 +15,7 @@
#include "lockfile.h"
#include "merge-ort.h"
#include "object-name.h"
-#include "read-cache-ll.h"
+#include "parse-options.h"
#include "refs.h"
#include "revision.h"
#include "sequencer.h"
@@ -92,6 +92,7 @@ static struct commit *create_commit(struct tree *tree,
int cmd_replay(int argc, const char **argv, const char *prefix)
{
struct commit *onto;
+ const char *onto_name = NULL;
struct commit *last_commit = NULL, *last_picked_commit = NULL;
struct object_id head;
struct lock_file lock = LOCK_INIT;
@@ -105,16 +106,32 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
struct strbuf branch_name = STRBUF_INIT;
int ret = 0;
- if (argc == 2 && !strcmp(argv[1], "-h")) {
- printf("usage: (EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\n");
- exit(129);
+ const char * const replay_usage[] = {
+ N_("(EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>"),
+ NULL
+ };
+ struct option replay_options[] = {
+ OPT_STRING(0, "onto", &onto_name,
+ N_("revision"),
+ N_("replay onto given commit")),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, replay_options, replay_usage,
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
+
+ if (!onto_name) {
+ error(_("option --onto is mandatory"));
+ usage_with_options(replay_usage, replay_options);
}
- if (argc != 5 || strcmp(argv[1], "--onto"))
- die("usage: read the code, figure out how to use it, then do so");
+ if (argc != 3) {
+ error(_("bad number of arguments"));
+ usage_with_options(replay_usage, replay_options);
+ }
- onto = peel_committish(argv[2]);
- strbuf_addf(&branch_name, "refs/heads/%s", argv[4]);
+ onto = peel_committish(onto_name);
+ strbuf_addf(&branch_name, "refs/heads/%s", argv[2]);
/* Sanity check */
if (repo_get_oid(the_repository, "HEAD", &head))
@@ -126,6 +143,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
BUG("Could not read index");
repo_init_revisions(the_repository, &revs, prefix);
+
revs.verbose_header = 1;
revs.max_parents = 1;
revs.cherry_mark = 1;
@@ -134,7 +152,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
revs.right_only = 1;
revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
revs.topo_order = 1;
- strvec_pushl(&rev_walk_args, "", argv[4], "--not", argv[3], NULL);
+
+ strvec_pushl(&rev_walk_args, "", argv[2], "--not", argv[1], NULL);
if (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {
ret = error(_("unhandled options"));
@@ -197,8 +216,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
&last_commit->object.oid,
&last_picked_commit->object.oid,
REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
- error(_("could not update %s"), argv[4]);
- die("Failed to update %s", argv[4]);
+ error(_("could not update %s"), argv[2]);
+ die("Failed to update %s", argv[2]);
}
if (create_symref("HEAD", branch_name.buf, reflog_msg.buf) < 0)
die(_("unable to update HEAD"));
@@ -210,8 +229,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
&last_commit->object.oid,
&head,
REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
- error(_("could not update %s"), argv[4]);
- die("Failed to update %s", argv[4]);
+ error(_("could not update %s"), argv[2]);
+ die("Failed to update %s", argv[2]);
}
}
ret = (result.clean == 0);
--
2.43.0.14.g93e034faee
^ permalink raw reply related
* [PATCH v8 04/14] replay: die() instead of failing assert()
From: Christian Couder @ 2023-11-24 11:10 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Patrick Steinhardt, Johannes Schindelin,
Elijah Newren, John Cai, Derrick Stolee, Phillip Wood, Calvin Wan,
Toon Claes, Dragan Simic, Linus Arver, Christian Couder
In-Reply-To: <20231124111044.3426007-1-christian.couder@gmail.com>
From: Elijah Newren <newren@gmail.com>
It's not a good idea for regular Git commands to use an assert() to
check for things that could happen but are not supported.
Let's die() with an explanation of the issue instead.
Co-authored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
builtin/replay.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index 7998f6ed04..f48c5ed255 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -179,7 +179,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
fprintf(stderr, "Rebasing %s...\r",
oid_to_hex(&commit->object.oid));
- assert(commit->parents && !commit->parents->next);
+
+ if (!commit->parents)
+ die(_("replaying down to root commit is not supported yet!"));
+ if (commit->parents->next)
+ die(_("replaying merge commits is not supported yet!"));
+
base = commit->parents->item;
next_tree = repo_get_commit_tree(the_repository, commit);
--
2.43.0.14.g93e034faee
^ permalink raw reply related
* [PATCH v8 05/14] replay: introduce pick_regular_commit()
From: Christian Couder @ 2023-11-24 11:10 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Patrick Steinhardt, Johannes Schindelin,
Elijah Newren, John Cai, Derrick Stolee, Phillip Wood, Calvin Wan,
Toon Claes, Dragan Simic, Linus Arver, Christian Couder
In-Reply-To: <20231124111044.3426007-1-christian.couder@gmail.com>
From: Elijah Newren <newren@gmail.com>
Let's refactor the code to handle a regular commit (a commit that is
neither a root commit nor a merge commit) into a single function instead
of keeping it inside cmd_replay().
This is good for separation of concerns, and this will help further work
in the future to replay merge commits.
Co-authored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
builtin/replay.c | 54 ++++++++++++++++++++++++++++++------------------
1 file changed, 34 insertions(+), 20 deletions(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index f48c5ed255..d039467cd4 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -89,6 +89,35 @@ static struct commit *create_commit(struct tree *tree,
return (struct commit *)obj;
}
+static struct commit *pick_regular_commit(struct commit *pickme,
+ struct commit *last_commit,
+ struct merge_options *merge_opt,
+ struct merge_result *result)
+{
+ struct commit *base;
+ struct tree *pickme_tree, *base_tree;
+
+ base = pickme->parents->item;
+
+ pickme_tree = repo_get_commit_tree(the_repository, pickme);
+ base_tree = repo_get_commit_tree(the_repository, base);
+
+ merge_opt->branch2 = short_commit_name(pickme);
+ merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
+
+ merge_incore_nonrecursive(merge_opt,
+ base_tree,
+ result->tree,
+ pickme_tree,
+ result);
+
+ free((char*)merge_opt->ancestor);
+ merge_opt->ancestor = NULL;
+ if (!result->clean)
+ return NULL;
+ return create_commit(result->tree, pickme, last_commit);
+}
+
int cmd_replay(int argc, const char **argv, const char *prefix)
{
struct commit *onto;
@@ -100,7 +129,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
struct rev_info revs;
struct commit *commit;
struct merge_options merge_opt;
- struct tree *next_tree, *base_tree, *head_tree;
+ struct tree *head_tree;
struct merge_result result;
struct strbuf reflog_msg = STRBUF_INIT;
struct strbuf branch_name = STRBUF_INIT;
@@ -175,7 +204,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
result.tree = head_tree;
last_commit = onto;
while ((commit = get_revision(&revs))) {
- struct commit *base;
+ struct commit *pick;
fprintf(stderr, "Rebasing %s...\r",
oid_to_hex(&commit->object.oid));
@@ -185,26 +214,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
if (commit->parents->next)
die(_("replaying merge commits is not supported yet!"));
- base = commit->parents->item;
-
- next_tree = repo_get_commit_tree(the_repository, commit);
- base_tree = repo_get_commit_tree(the_repository, base);
-
- merge_opt.branch2 = short_commit_name(commit);
- merge_opt.ancestor = xstrfmt("parent of %s", merge_opt.branch2);
-
- merge_incore_nonrecursive(&merge_opt,
- base_tree,
- result.tree,
- next_tree,
- &result);
-
- free((char*)merge_opt.ancestor);
- merge_opt.ancestor = NULL;
- if (!result.clean)
+ pick = pick_regular_commit(commit, last_commit, &merge_opt, &result);
+ if (!pick)
break;
+ last_commit = pick;
last_picked_commit = commit;
- last_commit = create_commit(result.tree, commit, last_commit);
}
merge_finalize(&merge_opt, &result);
--
2.43.0.14.g93e034faee
^ permalink raw reply related
* [PATCH v8 06/14] replay: change rev walking options
From: Christian Couder @ 2023-11-24 11:10 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Patrick Steinhardt, Johannes Schindelin,
Elijah Newren, John Cai, Derrick Stolee, Phillip Wood, Calvin Wan,
Toon Claes, Dragan Simic, Linus Arver, Christian Couder
In-Reply-To: <20231124111044.3426007-1-christian.couder@gmail.com>
From: Elijah Newren <newren@gmail.com>
Let's force the rev walking options we need after calling
setup_revisions() instead of before.
This might override some user supplied rev walking command line options
though. So let's detect that and warn users by:
a) setting the desired values, before setup_revisions(),
b) checking after setup_revisions() whether these values differ from
the desired values,
c) if so throwing a warning and setting the desired values again.
We want the command to work from older commits to newer ones by default.
Also we don't want history simplification, as we want to deal with all
the commits in the affected range.
Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Co-authored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
builtin/replay.c | 48 +++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 41 insertions(+), 7 deletions(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index d039467cd4..2f664218be 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -173,22 +173,56 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
repo_init_revisions(the_repository, &revs, prefix);
- revs.verbose_header = 1;
- revs.max_parents = 1;
- revs.cherry_mark = 1;
- revs.limited = 1;
+ strvec_pushl(&rev_walk_args, "", argv[2], "--not", argv[1], NULL);
+
+ /*
+ * Set desired values for rev walking options here. If they
+ * are changed by some user specified option in setup_revisions()
+ * below, we will detect that below and then warn.
+ *
+ * TODO: In the future we might want to either die(), or allow
+ * some options changing these values if we think they could
+ * be useful.
+ */
revs.reverse = 1;
- revs.right_only = 1;
revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
revs.topo_order = 1;
-
- strvec_pushl(&rev_walk_args, "", argv[2], "--not", argv[1], NULL);
+ revs.simplify_history = 0;
if (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {
ret = error(_("unhandled options"));
goto cleanup;
}
+ /*
+ * Detect and warn if we override some user specified rev
+ * walking options.
+ */
+ if (revs.reverse != 1) {
+ warning(_("some rev walking options will be overridden as "
+ "'%s' bit in 'struct rev_info' will be forced"),
+ "reverse");
+ revs.reverse = 1;
+ }
+ if (revs.sort_order != REV_SORT_IN_GRAPH_ORDER) {
+ warning(_("some rev walking options will be overridden as "
+ "'%s' bit in 'struct rev_info' will be forced"),
+ "sort_order");
+ revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
+ }
+ if (revs.topo_order != 1) {
+ warning(_("some rev walking options will be overridden as "
+ "'%s' bit in 'struct rev_info' will be forced"),
+ "topo_order");
+ revs.topo_order = 1;
+ }
+ if (revs.simplify_history != 0) {
+ warning(_("some rev walking options will be overridden as "
+ "'%s' bit in 'struct rev_info' will be forced"),
+ "simplify_history");
+ revs.simplify_history = 0;
+ }
+
strvec_clear(&rev_walk_args);
if (prepare_revision_walk(&revs) < 0) {
--
2.43.0.14.g93e034faee
^ permalink raw reply related
* [PATCH v8 07/14] replay: add an important FIXME comment about gpg signing
From: Christian Couder @ 2023-11-24 11:10 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Patrick Steinhardt, Johannes Schindelin,
Elijah Newren, John Cai, Derrick Stolee, Phillip Wood, Calvin Wan,
Toon Claes, Dragan Simic, Linus Arver, Christian Couder
In-Reply-To: <20231124111044.3426007-1-christian.couder@gmail.com>
From: Elijah Newren <newren@gmail.com>
We want to be able to handle signed commits in some way in the future,
but we are not ready to do it now. So for the time being let's just add
a FIXME comment to remind us about it.
These are different ways we could handle them:
- in case of a cli user and if there was an interactive mode, we could
perhaps ask if the user wants to sign again
- we could add an option to just fail if there are signed commits
Co-authored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
builtin/replay.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index 2f664218be..384bb4ddd3 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -62,7 +62,7 @@ static struct commit *create_commit(struct tree *tree,
struct object *obj;
struct commit_list *parents = NULL;
char *author;
- char *sign_commit = NULL;
+ char *sign_commit = NULL; /* FIXME: cli users might want to sign again */
struct commit_extra_header *extra;
struct strbuf msg = STRBUF_INIT;
const char *out_enc = get_commit_output_encoding();
--
2.43.0.14.g93e034faee
^ permalink raw reply related
* [PATCH v8 08/14] replay: remove progress and info output
From: Christian Couder @ 2023-11-24 11:10 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Patrick Steinhardt, Johannes Schindelin,
Elijah Newren, John Cai, Derrick Stolee, Phillip Wood, Calvin Wan,
Toon Claes, Dragan Simic, Linus Arver, Christian Couder
In-Reply-To: <20231124111044.3426007-1-christian.couder@gmail.com>
From: Elijah Newren <newren@gmail.com>
The replay command will be changed in a follow up commit, so that it
will not update refs directly, but instead it will print on stdout a
list of commands that can be consumed by `git update-ref --stdin`.
We don't want this output to be polluted by its current low value
output, so let's just remove the latter.
In the future, when the command gets an option to update refs by
itself, it will make a lot of sense to display a progress meter, but
we are not there yet.
Co-authored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
builtin/replay.c | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index 384bb4ddd3..ca3867dc57 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -232,7 +232,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
init_merge_options(&merge_opt, the_repository);
memset(&result, 0, sizeof(result));
- merge_opt.show_rename_progress = 1;
+ merge_opt.show_rename_progress = 0;
merge_opt.branch1 = "HEAD";
head_tree = repo_get_commit_tree(the_repository, onto);
result.tree = head_tree;
@@ -240,9 +240,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
while ((commit = get_revision(&revs))) {
struct commit *pick;
- fprintf(stderr, "Rebasing %s...\r",
- oid_to_hex(&commit->object.oid));
-
if (!commit->parents)
die(_("replaying down to root commit is not supported yet!"));
if (commit->parents->next)
@@ -261,7 +258,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
exit(128);
if (result.clean) {
- fprintf(stderr, "\nDone.\n");
strbuf_addf(&reflog_msg, "finish rebase %s onto %s",
oid_to_hex(&last_picked_commit->object.oid),
oid_to_hex(&last_commit->object.oid));
@@ -275,7 +271,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
if (create_symref("HEAD", branch_name.buf, reflog_msg.buf) < 0)
die(_("unable to update HEAD"));
} else {
- fprintf(stderr, "\nAborting: Hit a conflict.\n");
strbuf_addf(&reflog_msg, "rebase progress up to %s",
oid_to_hex(&last_picked_commit->object.oid));
if (update_ref(reflog_msg.buf, "HEAD",
--
2.43.0.14.g93e034faee
^ permalink raw reply related
* [PATCH v8 09/14] replay: remove HEAD related sanity check
From: Christian Couder @ 2023-11-24 11:10 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Patrick Steinhardt, Johannes Schindelin,
Elijah Newren, John Cai, Derrick Stolee, Phillip Wood, Calvin Wan,
Toon Claes, Dragan Simic, Linus Arver, Christian Couder
In-Reply-To: <20231124111044.3426007-1-christian.couder@gmail.com>
From: Elijah Newren <newren@gmail.com>
We want replay to be a command that can be used on the server side on
any branch, not just the current one, so we are going to stop updating
HEAD in a future commit.
A "sanity check" that makes sure we are replaying the current branch
doesn't make sense anymore. Let's remove it.
Co-authored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
builtin/replay.c | 8 +-------
t/t3650-replay-basics.sh | 2 --
2 files changed, 1 insertion(+), 9 deletions(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index ca3867dc57..bdec2f2b97 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -123,7 +123,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
struct commit *onto;
const char *onto_name = NULL;
struct commit *last_commit = NULL, *last_picked_commit = NULL;
- struct object_id head;
struct lock_file lock = LOCK_INIT;
struct strvec rev_walk_args = STRVEC_INIT;
struct rev_info revs;
@@ -162,11 +161,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
onto = peel_committish(onto_name);
strbuf_addf(&branch_name, "refs/heads/%s", argv[2]);
- /* Sanity check */
- if (repo_get_oid(the_repository, "HEAD", &head))
- die(_("Cannot read HEAD"));
- assert(oideq(&onto->object.oid, &head));
-
repo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);
if (repo_read_index(the_repository) < 0)
BUG("Could not read index");
@@ -275,7 +269,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
oid_to_hex(&last_picked_commit->object.oid));
if (update_ref(reflog_msg.buf, "HEAD",
&last_commit->object.oid,
- &head,
+ &onto->object.oid,
REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
error(_("could not update %s"), argv[2]);
die("Failed to update %s", argv[2]);
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index 36c1b5082a..b5b9f9ade2 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -48,8 +48,6 @@ test_expect_success 'setup' '
'
test_expect_success 'using replay to rebase two branches, one on top of other' '
- git switch main &&
-
git replay --onto main topic1 topic2 >result &&
git log --format=%s $(cut -f 3 -d " " result) >actual &&
--
2.43.0.14.g93e034faee
^ permalink raw reply related
* [PATCH v8 10/14] replay: make it a minimal server side command
From: Christian Couder @ 2023-11-24 11:10 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Patrick Steinhardt, Johannes Schindelin,
Elijah Newren, John Cai, Derrick Stolee, Phillip Wood, Calvin Wan,
Toon Claes, Dragan Simic, Linus Arver, Christian Couder
In-Reply-To: <20231124111044.3426007-1-christian.couder@gmail.com>
From: Elijah Newren <newren@gmail.com>
We want this command to be a minimal command that just does server side
picking of commits, displaying the results on stdout for higher level
scripts to consume.
So let's simplify it:
* remove the worktree and index reading/writing,
* remove the ref (and reflog) updating,
* remove the assumptions tying us to HEAD, since (a) this is not a
rebase and (b) we want to be able to pick commits in a bare repo,
i.e. to/from branches that are not checked out and not the main
branch,
* remove unneeded includes,
* handle rebasing multiple branches by printing on stdout the update
ref commands that should be performed.
The output can be piped into `git update-ref --stdin` for the ref
updates to happen.
In the future to make it easier for users to use this command
directly maybe an option can be added to automatically pipe its output
into `git update-ref`.
Co-authored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
Documentation/git-replay.txt | 5 +-
builtin/replay.c | 78 ++++++++----------------
t/t3650-replay-basics.sh | 19 +++++-
t/t6429-merge-sequence-rename-caching.sh | 39 +++++++-----
4 files changed, 72 insertions(+), 69 deletions(-)
diff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt
index 2ca7ca5fd8..267282d92a 100644
--- a/Documentation/git-replay.txt
+++ b/Documentation/git-replay.txt
@@ -15,7 +15,10 @@ DESCRIPTION
-----------
Takes a range of commits, specified by <oldbase> and <branch>, and
-replays them onto a new location (see `--onto` option below).
+replays them onto a new location (see `--onto` option below). Leaves
+the working tree and the index untouched, and updates no references.
+The output of this command is meant to be used as input to
+`git update-ref --stdin`, which would update the relevant branches.
THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
diff --git a/builtin/replay.c b/builtin/replay.c
index bdec2f2b97..bfccbbbfea 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -6,11 +6,7 @@
#include "git-compat-util.h"
#include "builtin.h"
-#include "cache-tree.h"
-#include "commit.h"
#include "environment.h"
-#include "gettext.h"
-#include "hash.h"
#include "hex.h"
#include "lockfile.h"
#include "merge-ort.h"
@@ -18,8 +14,6 @@
#include "parse-options.h"
#include "refs.h"
#include "revision.h"
-#include "sequencer.h"
-#include "setup.h"
#include "strvec.h"
#include <oidset.h>
#include <tree.h>
@@ -102,6 +96,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,
pickme_tree = repo_get_commit_tree(the_repository, pickme);
base_tree = repo_get_commit_tree(the_repository, base);
+ merge_opt->branch1 = short_commit_name(last_commit);
merge_opt->branch2 = short_commit_name(pickme);
merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
@@ -122,15 +117,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
{
struct commit *onto;
const char *onto_name = NULL;
- struct commit *last_commit = NULL, *last_picked_commit = NULL;
- struct lock_file lock = LOCK_INIT;
+ struct commit *last_commit = NULL;
struct strvec rev_walk_args = STRVEC_INIT;
struct rev_info revs;
struct commit *commit;
struct merge_options merge_opt;
- struct tree *head_tree;
struct merge_result result;
- struct strbuf reflog_msg = STRBUF_INIT;
struct strbuf branch_name = STRBUF_INIT;
int ret = 0;
@@ -161,10 +153,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
onto = peel_committish(onto_name);
strbuf_addf(&branch_name, "refs/heads/%s", argv[2]);
- repo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);
- if (repo_read_index(the_repository) < 0)
- BUG("Could not read index");
-
repo_init_revisions(the_repository, &revs, prefix);
strvec_pushl(&rev_walk_args, "", argv[2], "--not", argv[1], NULL);
@@ -227,58 +215,44 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
init_merge_options(&merge_opt, the_repository);
memset(&result, 0, sizeof(result));
merge_opt.show_rename_progress = 0;
- merge_opt.branch1 = "HEAD";
- head_tree = repo_get_commit_tree(the_repository, onto);
- result.tree = head_tree;
+ result.tree = repo_get_commit_tree(the_repository, onto);
last_commit = onto;
while ((commit = get_revision(&revs))) {
- struct commit *pick;
+ const struct name_decoration *decoration;
if (!commit->parents)
die(_("replaying down to root commit is not supported yet!"));
if (commit->parents->next)
die(_("replaying merge commits is not supported yet!"));
- pick = pick_regular_commit(commit, last_commit, &merge_opt, &result);
- if (!pick)
+ last_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);
+ if (!last_commit)
break;
- last_commit = pick;
- last_picked_commit = commit;
+
+ decoration = get_name_decoration(&commit->object);
+ if (!decoration)
+ continue;
+
+ while (decoration) {
+ if (decoration->type == DECORATION_REF_LOCAL) {
+ printf("update %s %s %s\n",
+ decoration->name,
+ oid_to_hex(&last_commit->object.oid),
+ oid_to_hex(&commit->object.oid));
+ }
+ decoration = decoration->next;
+ }
}
merge_finalize(&merge_opt, &result);
+ ret = result.clean;
- if (result.clean < 0)
- exit(128);
-
- if (result.clean) {
- strbuf_addf(&reflog_msg, "finish rebase %s onto %s",
- oid_to_hex(&last_picked_commit->object.oid),
- oid_to_hex(&last_commit->object.oid));
- if (update_ref(reflog_msg.buf, branch_name.buf,
- &last_commit->object.oid,
- &last_picked_commit->object.oid,
- REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
- error(_("could not update %s"), argv[2]);
- die("Failed to update %s", argv[2]);
- }
- if (create_symref("HEAD", branch_name.buf, reflog_msg.buf) < 0)
- die(_("unable to update HEAD"));
- } else {
- strbuf_addf(&reflog_msg, "rebase progress up to %s",
- oid_to_hex(&last_picked_commit->object.oid));
- if (update_ref(reflog_msg.buf, "HEAD",
- &last_commit->object.oid,
- &onto->object.oid,
- REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
- error(_("could not update %s"), argv[2]);
- die("Failed to update %s", argv[2]);
- }
- }
- ret = (result.clean == 0);
cleanup:
- strbuf_release(&reflog_msg);
strbuf_release(&branch_name);
release_revisions(&revs);
- return ret;
+
+ /* Return */
+ if (ret < 0)
+ exit(128);
+ return ret ? 0 : 1;
}
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index b5b9f9ade2..3567c98362 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -47,12 +47,29 @@ test_expect_success 'setup' '
test_commit C.conflict C.t conflict
'
+test_expect_success 'setup bare' '
+ git clone --bare . bare
+'
+
test_expect_success 'using replay to rebase two branches, one on top of other' '
git replay --onto main topic1 topic2 >result &&
+ test_line_count = 1 result &&
+
git log --format=%s $(cut -f 3 -d " " result) >actual &&
test_write_lines E D M L B A >expect &&
- test_cmp expect actual
+ test_cmp expect actual &&
+
+ printf "update refs/heads/topic2 " >expect &&
+ printf "%s " $(cut -f 3 -d " " result) >>expect &&
+ git rev-parse topic2 >>expect &&
+
+ test_cmp expect result
+'
+
+test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '
+ git -C bare replay --onto main topic1 topic2 >result-bare &&
+ test_cmp expect result-bare
'
test_done
diff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh
index 7670b72008..099aefeffc 100755
--- a/t/t6429-merge-sequence-rename-caching.sh
+++ b/t/t6429-merge-sequence-rename-caching.sh
@@ -71,8 +71,9 @@ test_expect_success 'caching renames does not preclude finding new ones' '
git switch upstream &&
- git replay --onto HEAD upstream~1 topic &&
- git reset --hard topic &&
+ git replay --onto HEAD upstream~1 topic >out &&
+ git update-ref --stdin <out &&
+ git checkout topic &&
git ls-files >tracked-files &&
test_line_count = 2 tracked-files &&
@@ -140,7 +141,9 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- git replay --onto HEAD upstream~1 topic &&
+ git replay --onto HEAD upstream~1 topic >out &&
+ git update-ref --stdin <out &&
+ git checkout topic &&
grep region_enter.*diffcore_rename trace.output >calls &&
test_line_count = 1 calls
@@ -198,8 +201,9 @@ test_expect_success 'rename same file identically, then reintroduce it' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- git replay --onto HEAD upstream~1 topic &&
- git reset --hard topic &&
+ git replay --onto HEAD upstream~1 topic >out &&
+ git update-ref --stdin <out &&
+ git checkout topic &&
git ls-files >tracked &&
test_line_count = 2 tracked &&
@@ -275,8 +279,9 @@ test_expect_success 'rename same file identically, then add file to old dir' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- git replay --onto HEAD upstream~1 topic &&
- git reset --hard topic &&
+ git replay --onto HEAD upstream~1 topic >out &&
+ git update-ref --stdin <out &&
+ git checkout topic &&
git ls-files >tracked &&
test_line_count = 4 tracked &&
@@ -451,8 +456,9 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- git replay --onto HEAD upstream~1 topic &&
- git reset --hard topic &&
+ git replay --onto HEAD upstream~1 topic >out &&
+ git update-ref --stdin <out &&
+ git checkout topic &&
grep region_enter.*diffcore_rename trace.output >calls &&
test_line_count = 2 calls &&
@@ -517,8 +523,9 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- git replay --onto HEAD upstream~1 topic &&
- git reset --hard topic &&
+ git replay --onto HEAD upstream~1 topic >out &&
+ git update-ref --stdin <out &&
+ git checkout topic &&
grep region_enter.*diffcore_rename trace.output >calls &&
test_line_count = 3 calls &&
@@ -619,8 +626,9 @@ test_expect_success 'caching renames only on upstream side, part 1' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- git replay --onto HEAD upstream~1 topic &&
- git reset --hard topic &&
+ git replay --onto HEAD upstream~1 topic >out &&
+ git update-ref --stdin <out &&
+ git checkout topic &&
grep region_enter.*diffcore_rename trace.output >calls &&
test_line_count = 1 calls &&
@@ -677,8 +685,9 @@ test_expect_success 'caching renames only on upstream side, part 2' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- git replay --onto HEAD upstream~1 topic &&
- git reset --hard topic &&
+ git replay --onto HEAD upstream~1 topic >out &&
+ git update-ref --stdin <out &&
+ git checkout topic &&
grep region_enter.*diffcore_rename trace.output >calls &&
test_line_count = 2 calls &&
--
2.43.0.14.g93e034faee
^ permalink raw reply related
* [PATCH v8 11/14] replay: use standard revision ranges
From: Christian Couder @ 2023-11-24 11:10 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Patrick Steinhardt, Johannes Schindelin,
Elijah Newren, John Cai, Derrick Stolee, Phillip Wood, Calvin Wan,
Toon Claes, Dragan Simic, Linus Arver, Christian Couder
In-Reply-To: <20231124111044.3426007-1-christian.couder@gmail.com>
From: Elijah Newren <newren@gmail.com>
Instead of the fixed "<oldbase> <branch>" arguments, the replay
command now accepts "<revision-range>..." arguments in a similar
way as many other Git commands. This makes its interface more
standard and more flexible.
This also enables many revision related options accepted and
eaten by setup_revisions(). If the replay command was a high level
one or had a high level mode, it would make sense to restrict some
of the possible options, like those generating non-contiguous
history, as they could be confusing for most users.
Also as the interface of the command is now mostly finalized,
we can add more documentation and more testcases to make sure
the command will continue to work as designed in the future.
We only document the rev-list related options among all the
revision related options that are now accepted, as the rev-list
related ones are probably the most useful for now.
Helped-by: Dragan Simic <dsimic@manjaro.org>
Helped-by: Linus Arver <linusa@google.com>
Co-authored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
Documentation/git-replay.txt | 58 ++++++++++++++++++++++--
builtin/replay.c | 21 ++-------
t/t3650-replay-basics.sh | 12 ++++-
t/t6429-merge-sequence-rename-caching.sh | 18 ++++----
4 files changed, 77 insertions(+), 32 deletions(-)
diff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt
index 267282d92a..f7b232caa2 100644
--- a/Documentation/git-replay.txt
+++ b/Documentation/git-replay.txt
@@ -9,16 +9,16 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t
SYNOPSIS
--------
[verse]
-(EXPERIMENTAL!) 'git replay' --onto <newbase> <oldbase> <branch>
+(EXPERIMENTAL!) 'git replay' --onto <newbase> <revision-range>...
DESCRIPTION
-----------
-Takes a range of commits, specified by <oldbase> and <branch>, and
-replays them onto a new location (see `--onto` option below). Leaves
+Takes ranges of commits and replays them onto a new location. Leaves
the working tree and the index untouched, and updates no references.
The output of this command is meant to be used as input to
-`git update-ref --stdin`, which would update the relevant branches.
+`git update-ref --stdin`, which would update the relevant branches
+(see the OUTPUT section below).
THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
@@ -28,6 +28,30 @@ OPTIONS
--onto <newbase>::
Starting point at which to create the new commits. May be any
valid commit, and not just an existing branch name.
++
+The update-ref command(s) in the output will update the branch(es) in
+the revision range to point at the new commits, similar to the way how
+`git rebase --update-refs` updates multiple branches in the affected
+range.
+
+<revision-range>::
+ Range of commits to replay; see "Specifying Ranges" in
+ linkgit:git-rev-parse and the "Commit Limiting" options below.
+
+include::rev-list-options.txt[]
+
+OUTPUT
+------
+
+When there are no conflicts, the output of this command is usable as
+input to `git update-ref --stdin`. It is of the form:
+
+ update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}
+ update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}
+ update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}
+
+where the number of refs updated depends on the arguments passed and
+the shape of the history being replayed.
EXIT STATUS
-----------
@@ -37,6 +61,32 @@ the replay has conflicts, the exit status is 1. If the replay is not
able to complete (or start) due to some kind of error, the exit status
is something other than 0 or 1.
+EXAMPLES
+--------
+
+To simply rebase `mybranch` onto `target`:
+
+------------
+$ git replay --onto target origin/main..mybranch
+update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}
+------------
+
+When calling `git replay`, one does not need to specify a range of
+commits to replay using the syntax `A..B`; any range expression will
+do:
+
+------------
+$ git replay --onto origin/main ^base branch1 branch2 branch3
+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}
+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}
+update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}
+------------
+
+This will simultaneously rebase `branch1`, `branch2`, and `branch3`,
+all commits they have since `base`, playing them on top of
+`origin/main`. These three branches may have commits on top of `base`
+that they have in common, but that does not need to be the case.
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/builtin/replay.c b/builtin/replay.c
index bfccbbbfea..3d5e00147b 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -14,7 +14,6 @@
#include "parse-options.h"
#include "refs.h"
#include "revision.h"
-#include "strvec.h"
#include <oidset.h>
#include <tree.h>
@@ -118,16 +117,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
struct commit *onto;
const char *onto_name = NULL;
struct commit *last_commit = NULL;
- struct strvec rev_walk_args = STRVEC_INIT;
struct rev_info revs;
struct commit *commit;
struct merge_options merge_opt;
struct merge_result result;
- struct strbuf branch_name = STRBUF_INIT;
int ret = 0;
const char * const replay_usage[] = {
- N_("(EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>"),
+ N_("(EXPERIMENTAL!) git replay --onto <newbase> <revision-range>..."),
NULL
};
struct option replay_options[] = {
@@ -145,18 +142,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
usage_with_options(replay_usage, replay_options);
}
- if (argc != 3) {
- error(_("bad number of arguments"));
- usage_with_options(replay_usage, replay_options);
- }
-
onto = peel_committish(onto_name);
- strbuf_addf(&branch_name, "refs/heads/%s", argv[2]);
repo_init_revisions(the_repository, &revs, prefix);
- strvec_pushl(&rev_walk_args, "", argv[2], "--not", argv[1], NULL);
-
/*
* Set desired values for rev walking options here. If they
* are changed by some user specified option in setup_revisions()
@@ -171,8 +160,9 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
revs.topo_order = 1;
revs.simplify_history = 0;
- if (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {
- ret = error(_("unhandled options"));
+ argc = setup_revisions(argc, argv, &revs, NULL);
+ if (argc > 1) {
+ ret = error(_("unrecognized argument: %s"), argv[1]);
goto cleanup;
}
@@ -205,8 +195,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
revs.simplify_history = 0;
}
- strvec_clear(&rev_walk_args);
-
if (prepare_revision_walk(&revs) < 0) {
ret = error(_("error preparing revisions"));
goto cleanup;
@@ -248,7 +236,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
ret = result.clean;
cleanup:
- strbuf_release(&branch_name);
release_revisions(&revs);
/* Return */
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index 3567c98362..a1da4f9ef9 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -52,7 +52,7 @@ test_expect_success 'setup bare' '
'
test_expect_success 'using replay to rebase two branches, one on top of other' '
- git replay --onto main topic1 topic2 >result &&
+ git replay --onto main topic1..topic2 >result &&
test_line_count = 1 result &&
@@ -68,8 +68,16 @@ test_expect_success 'using replay to rebase two branches, one on top of other' '
'
test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '
- git -C bare replay --onto main topic1 topic2 >result-bare &&
+ git -C bare replay --onto main topic1..topic2 >result-bare &&
test_cmp expect result-bare
'
+test_expect_success 'using replay to rebase with a conflict' '
+ test_expect_code 1 git replay --onto topic1 B..conflict
+'
+
+test_expect_success 'using replay on bare repo to rebase with a conflict' '
+ test_expect_code 1 git -C bare replay --onto topic1 B..conflict
+'
+
test_done
diff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh
index 099aefeffc..0f39ed0d08 100755
--- a/t/t6429-merge-sequence-rename-caching.sh
+++ b/t/t6429-merge-sequence-rename-caching.sh
@@ -71,7 +71,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '
git switch upstream &&
- git replay --onto HEAD upstream~1 topic >out &&
+ git replay --onto HEAD upstream~1..topic >out &&
git update-ref --stdin <out &&
git checkout topic &&
@@ -141,7 +141,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- git replay --onto HEAD upstream~1 topic >out &&
+ git replay --onto HEAD upstream~1..topic >out &&
git update-ref --stdin <out &&
git checkout topic &&
@@ -201,7 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- git replay --onto HEAD upstream~1 topic >out &&
+ git replay --onto HEAD upstream~1..topic >out &&
git update-ref --stdin <out &&
git checkout topic &&
@@ -279,7 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- git replay --onto HEAD upstream~1 topic >out &&
+ git replay --onto HEAD upstream~1..topic >out &&
git update-ref --stdin <out &&
git checkout topic &&
@@ -357,7 +357,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- test_must_fail git replay --onto HEAD upstream~1 topic >output &&
+ test_must_fail git replay --onto HEAD upstream~1..topic >output &&
grep region_enter.*diffcore_rename trace.output >calls &&
test_line_count = 2 calls
@@ -456,7 +456,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- git replay --onto HEAD upstream~1 topic >out &&
+ git replay --onto HEAD upstream~1..topic >out &&
git update-ref --stdin <out &&
git checkout topic &&
@@ -523,7 +523,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- git replay --onto HEAD upstream~1 topic >out &&
+ git replay --onto HEAD upstream~1..topic >out &&
git update-ref --stdin <out &&
git checkout topic &&
@@ -626,7 +626,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- git replay --onto HEAD upstream~1 topic >out &&
+ git replay --onto HEAD upstream~1..topic >out &&
git update-ref --stdin <out &&
git checkout topic &&
@@ -685,7 +685,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '
GIT_TRACE2_PERF="$(pwd)/trace.output" &&
export GIT_TRACE2_PERF &&
- git replay --onto HEAD upstream~1 topic >out &&
+ git replay --onto HEAD upstream~1..topic >out &&
git update-ref --stdin <out &&
git checkout topic &&
--
2.43.0.14.g93e034faee
^ permalink raw reply related
* [PATCH v8 12/14] replay: add --advance or 'cherry-pick' mode
From: Christian Couder @ 2023-11-24 11:10 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Patrick Steinhardt, Johannes Schindelin,
Elijah Newren, John Cai, Derrick Stolee, Phillip Wood, Calvin Wan,
Toon Claes, Dragan Simic, Linus Arver, Christian Couder
In-Reply-To: <20231124111044.3426007-1-christian.couder@gmail.com>
From: Elijah Newren <newren@gmail.com>
There is already a 'rebase' mode with `--onto`. Let's add an 'advance' or
'cherry-pick' mode with `--advance`. This new mode will make the target
branch advance as we replay commits onto it.
The replayed commits should have a single tip, so that it's clear where
the target branch should be advanced. If they have more than one tip,
this new mode will error out.
Co-authored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
Documentation/git-replay.txt | 41 ++++++--
builtin/replay.c | 185 +++++++++++++++++++++++++++++++++--
t/t3650-replay-basics.sh | 34 +++++++
3 files changed, 243 insertions(+), 17 deletions(-)
diff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt
index f7b232caa2..c4c64f955a 100644
--- a/Documentation/git-replay.txt
+++ b/Documentation/git-replay.txt
@@ -9,7 +9,7 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t
SYNOPSIS
--------
[verse]
-(EXPERIMENTAL!) 'git replay' --onto <newbase> <revision-range>...
+(EXPERIMENTAL!) 'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...
DESCRIPTION
-----------
@@ -29,14 +29,25 @@ OPTIONS
Starting point at which to create the new commits. May be any
valid commit, and not just an existing branch name.
+
-The update-ref command(s) in the output will update the branch(es) in
-the revision range to point at the new commits, similar to the way how
-`git rebase --update-refs` updates multiple branches in the affected
-range.
+When `--onto` is specified, the update-ref command(s) in the output will
+update the branch(es) in the revision range to point at the new
+commits, similar to the way how `git rebase --update-refs` updates
+multiple branches in the affected range.
+
+--advance <branch>::
+ Starting point at which to create the new commits; must be a
+ branch name.
++
+When `--advance` is specified, the update-ref command(s) in the output
+will update the branch passed as an argument to `--advance` to point at
+the new commits (in other words, this mimics a cherry-pick operation).
<revision-range>::
- Range of commits to replay; see "Specifying Ranges" in
- linkgit:git-rev-parse and the "Commit Limiting" options below.
+ Range of commits to replay. More than one <revision-range> can
+ be passed, but in `--advance <branch>` mode, they should have
+ a single tip, so that it's clear where <branch> should point
+ to. See "Specifying Ranges" in linkgit:git-rev-parse and the
+ "Commit Limiting" options below.
include::rev-list-options.txt[]
@@ -51,7 +62,9 @@ input to `git update-ref --stdin`. It is of the form:
update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}
where the number of refs updated depends on the arguments passed and
-the shape of the history being replayed.
+the shape of the history being replayed. When using `--advance`, the
+number of refs updated is always one, but for `--onto`, it can be one
+or more (rebasing multiple branches simultaneously is supported).
EXIT STATUS
-----------
@@ -71,6 +84,18 @@ $ git replay --onto target origin/main..mybranch
update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}
------------
+To cherry-pick the commits from mybranch onto target:
+
+------------
+$ git replay --advance target origin/main..mybranch
+update refs/heads/target ${NEW_target_HASH} ${OLD_target_HASH}
+------------
+
+Note that the first two examples replay the exact same commits and on
+top of the exact same new base, they only differ in that the first
+provides instructions to make mybranch point at the new commits and
+the second provides instructions to make target point at them.
+
When calling `git replay`, one does not need to specify a range of
commits to replay using the syntax `A..B`; any range expression will
do:
diff --git a/builtin/replay.c b/builtin/replay.c
index 3d5e00147b..f26806d7e2 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -14,6 +14,7 @@
#include "parse-options.h"
#include "refs.h"
#include "revision.h"
+#include "strmap.h"
#include <oidset.h>
#include <tree.h>
@@ -82,6 +83,146 @@ static struct commit *create_commit(struct tree *tree,
return (struct commit *)obj;
}
+struct ref_info {
+ struct commit *onto;
+ struct strset positive_refs;
+ struct strset negative_refs;
+ int positive_refexprs;
+ int negative_refexprs;
+};
+
+static void get_ref_information(struct rev_cmdline_info *cmd_info,
+ struct ref_info *ref_info)
+{
+ int i;
+
+ ref_info->onto = NULL;
+ strset_init(&ref_info->positive_refs);
+ strset_init(&ref_info->negative_refs);
+ ref_info->positive_refexprs = 0;
+ ref_info->negative_refexprs = 0;
+
+ /*
+ * When the user specifies e.g.
+ * git replay origin/main..mybranch
+ * git replay ^origin/next mybranch1 mybranch2
+ * we want to be able to determine where to replay the commits. In
+ * these examples, the branches are probably based on an old version
+ * of either origin/main or origin/next, so we want to replay on the
+ * newest version of that branch. In contrast we would want to error
+ * out if they ran
+ * git replay ^origin/master ^origin/next mybranch
+ * git replay mybranch~2..mybranch
+ * the first of those because there's no unique base to choose, and
+ * the second because they'd likely just be replaying commits on top
+ * of the same commit and not making any difference.
+ */
+ for (i = 0; i < cmd_info->nr; i++) {
+ struct rev_cmdline_entry *e = cmd_info->rev + i;
+ struct object_id oid;
+ const char *refexpr = e->name;
+ char *fullname = NULL;
+ int can_uniquely_dwim = 1;
+
+ if (*refexpr == '^')
+ refexpr++;
+ if (repo_dwim_ref(the_repository, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1)
+ can_uniquely_dwim = 0;
+
+ if (e->flags & BOTTOM) {
+ if (can_uniquely_dwim)
+ strset_add(&ref_info->negative_refs, fullname);
+ if (!ref_info->negative_refexprs)
+ ref_info->onto = lookup_commit_reference_gently(the_repository,
+ &e->item->oid, 1);
+ ref_info->negative_refexprs++;
+ } else {
+ if (can_uniquely_dwim)
+ strset_add(&ref_info->positive_refs, fullname);
+ ref_info->positive_refexprs++;
+ }
+
+ free(fullname);
+ }
+}
+
+static void determine_replay_mode(struct rev_cmdline_info *cmd_info,
+ const char *onto_name,
+ const char **advance_name,
+ struct commit **onto,
+ struct strset **update_refs)
+{
+ struct ref_info rinfo;
+
+ get_ref_information(cmd_info, &rinfo);
+ if (!rinfo.positive_refexprs)
+ die(_("need some commits to replay"));
+ if (onto_name && *advance_name)
+ die(_("--onto and --advance are incompatible"));
+ else if (onto_name) {
+ *onto = peel_committish(onto_name);
+ if (rinfo.positive_refexprs <
+ strset_get_size(&rinfo.positive_refs))
+ die(_("all positive revisions given must be references"));
+ } else if (*advance_name) {
+ struct object_id oid;
+ char *fullname = NULL;
+
+ *onto = peel_committish(*advance_name);
+ if (repo_dwim_ref(the_repository, *advance_name, strlen(*advance_name),
+ &oid, &fullname, 0) == 1) {
+ *advance_name = fullname;
+ } else {
+ die(_("argument to --advance must be a reference"));
+ }
+ if (rinfo.positive_refexprs > 1)
+ die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
+ } else {
+ int positive_refs_complete = (
+ rinfo.positive_refexprs ==
+ strset_get_size(&rinfo.positive_refs));
+ int negative_refs_complete = (
+ rinfo.negative_refexprs ==
+ strset_get_size(&rinfo.negative_refs));
+ /*
+ * We need either positive_refs_complete or
+ * negative_refs_complete, but not both.
+ */
+ if (rinfo.negative_refexprs > 0 &&
+ positive_refs_complete == negative_refs_complete)
+ die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
+ if (negative_refs_complete) {
+ struct hashmap_iter iter;
+ struct strmap_entry *entry;
+
+ if (rinfo.negative_refexprs == 0)
+ die(_("all positive revisions given must be references"));
+ else if (rinfo.negative_refexprs > 1)
+ die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
+ else if (rinfo.positive_refexprs > 1)
+ die(_("cannot advance target with multiple source branches because ordering would be ill-defined"));
+
+ /* Only one entry, but we have to loop to get it */
+ strset_for_each_entry(&rinfo.negative_refs,
+ &iter, entry) {
+ *advance_name = entry->key;
+ }
+ } else { /* positive_refs_complete */
+ if (rinfo.negative_refexprs > 1)
+ die(_("cannot implicitly determine correct base for --onto"));
+ if (rinfo.negative_refexprs == 1)
+ *onto = rinfo.onto;
+ }
+ }
+ if (!*advance_name) {
+ *update_refs = xcalloc(1, sizeof(**update_refs));
+ **update_refs = rinfo.positive_refs;
+ memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
+ }
+ strset_clear(&rinfo.negative_refs);
+ strset_clear(&rinfo.positive_refs);
+}
+
static struct commit *pick_regular_commit(struct commit *pickme,
struct commit *last_commit,
struct merge_options *merge_opt,
@@ -114,20 +255,26 @@ static struct commit *pick_regular_commit(struct commit *pickme,
int cmd_replay(int argc, const char **argv, const char *prefix)
{
- struct commit *onto;
+ const char *advance_name = NULL;
+ struct commit *onto = NULL;
const char *onto_name = NULL;
- struct commit *last_commit = NULL;
+
struct rev_info revs;
+ struct commit *last_commit = NULL;
struct commit *commit;
struct merge_options merge_opt;
struct merge_result result;
+ struct strset *update_refs = NULL;
int ret = 0;
const char * const replay_usage[] = {
- N_("(EXPERIMENTAL!) git replay --onto <newbase> <revision-range>..."),
+ N_("(EXPERIMENTAL!) git replay (--onto <newbase> | --advance <branch>) <revision-range>..."),
NULL
};
struct option replay_options[] = {
+ OPT_STRING(0, "advance", &advance_name,
+ N_("branch"),
+ N_("make replay advance given branch")),
OPT_STRING(0, "onto", &onto_name,
N_("revision"),
N_("replay onto given commit")),
@@ -137,13 +284,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, replay_options, replay_usage,
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
- if (!onto_name) {
- error(_("option --onto is mandatory"));
+ if (!onto_name && !advance_name) {
+ error(_("option --onto or --advance is mandatory"));
usage_with_options(replay_usage, replay_options);
}
- onto = peel_committish(onto_name);
-
repo_init_revisions(the_repository, &revs, prefix);
/*
@@ -195,6 +340,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
revs.simplify_history = 0;
}
+ determine_replay_mode(&revs.cmdline, onto_name, &advance_name,
+ &onto, &update_refs);
+
+ if (!onto) /* FIXME: Should handle replaying down to root commit */
+ die("Replaying down to root commit is not supported yet!");
+
if (prepare_revision_walk(&revs) < 0) {
ret = error(_("error preparing revisions"));
goto cleanup;
@@ -203,6 +354,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
init_merge_options(&merge_opt, the_repository);
memset(&result, 0, sizeof(result));
merge_opt.show_rename_progress = 0;
+
result.tree = repo_get_commit_tree(the_repository, onto);
last_commit = onto;
while ((commit = get_revision(&revs))) {
@@ -217,12 +369,15 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
if (!last_commit)
break;
+ /* Update any necessary branches */
+ if (advance_name)
+ continue;
decoration = get_name_decoration(&commit->object);
if (!decoration)
continue;
-
while (decoration) {
- if (decoration->type == DECORATION_REF_LOCAL) {
+ if (decoration->type == DECORATION_REF_LOCAL &&
+ strset_contains(update_refs, decoration->name)) {
printf("update %s %s %s\n",
decoration->name,
oid_to_hex(&last_commit->object.oid),
@@ -232,10 +387,22 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
}
}
+ /* In --advance mode, advance the target ref */
+ if (result.clean == 1 && advance_name) {
+ printf("update %s %s %s\n",
+ advance_name,
+ oid_to_hex(&last_commit->object.oid),
+ oid_to_hex(&onto->object.oid));
+ }
+
merge_finalize(&merge_opt, &result);
ret = result.clean;
cleanup:
+ if (update_refs) {
+ strset_clear(update_refs);
+ free(update_refs);
+ }
release_revisions(&revs);
/* Return */
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index a1da4f9ef9..68a87e7803 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -80,4 +80,38 @@ test_expect_success 'using replay on bare repo to rebase with a conflict' '
test_expect_code 1 git -C bare replay --onto topic1 B..conflict
'
+test_expect_success 'using replay to perform basic cherry-pick' '
+ # The differences between this test and previous ones are:
+ # --advance vs --onto
+ # 2nd field of result is refs/heads/main vs. refs/heads/topic2
+ # 4th field of result is hash for main instead of hash for topic2
+
+ git replay --advance main topic1..topic2 >result &&
+
+ test_line_count = 1 result &&
+
+ git log --format=%s $(cut -f 3 -d " " result) >actual &&
+ test_write_lines E D M L B A >expect &&
+ test_cmp expect actual &&
+
+ printf "update refs/heads/main " >expect &&
+ printf "%s " $(cut -f 3 -d " " result) >>expect &&
+ git rev-parse main >>expect &&
+
+ test_cmp expect result
+'
+
+test_expect_success 'using replay on bare repo to perform basic cherry-pick' '
+ git -C bare replay --advance main topic1..topic2 >result-bare &&
+ test_cmp expect result-bare
+'
+
+test_expect_success 'replay on bare repo fails with both --advance and --onto' '
+ test_must_fail git -C bare replay --advance main --onto main topic1..topic2 >result-bare
+'
+
+test_expect_success 'replay fails when both --advance and --onto are omitted' '
+ test_must_fail git replay topic1..topic2 >result
+'
+
test_done
--
2.43.0.14.g93e034faee
^ permalink raw reply related
* [PATCH v8 13/14] replay: add --contained to rebase contained branches
From: Christian Couder @ 2023-11-24 11:10 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Patrick Steinhardt, Johannes Schindelin,
Elijah Newren, John Cai, Derrick Stolee, Phillip Wood, Calvin Wan,
Toon Claes, Dragan Simic, Linus Arver, Christian Couder
In-Reply-To: <20231124111044.3426007-1-christian.couder@gmail.com>
From: Elijah Newren <newren@gmail.com>
Let's add a `--contained` option that can be used along with
`--onto` to rebase all the branches contained in the <revision-range>
argument.
Co-authored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
Documentation/git-replay.txt | 12 +++++++++++-
builtin/replay.c | 14 ++++++++++++--
t/t3650-replay-basics.sh | 29 +++++++++++++++++++++++++++++
3 files changed, 52 insertions(+), 3 deletions(-)
diff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt
index c4c64f955a..f6c269c62d 100644
--- a/Documentation/git-replay.txt
+++ b/Documentation/git-replay.txt
@@ -9,7 +9,7 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t
SYNOPSIS
--------
[verse]
-(EXPERIMENTAL!) 'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...
+(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...
DESCRIPTION
-----------
@@ -96,6 +96,16 @@ top of the exact same new base, they only differ in that the first
provides instructions to make mybranch point at the new commits and
the second provides instructions to make target point at them.
+What if you have a stack of branches, one depending upon another, and
+you'd really like to rebase the whole set?
+
+------------
+$ git replay --contained --onto origin/main origin/main..tipbranch
+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}
+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}
+update refs/heads/tipbranch ${NEW_tipbranch_HASH} ${OLD_tipbranch_HASH}
+------------
+
When calling `git replay`, one does not need to specify a range of
commits to replay using the syntax `A..B`; any range expression will
do:
diff --git a/builtin/replay.c b/builtin/replay.c
index f26806d7e2..df14657e2f 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -258,6 +258,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
const char *advance_name = NULL;
struct commit *onto = NULL;
const char *onto_name = NULL;
+ int contained = 0;
struct rev_info revs;
struct commit *last_commit = NULL;
@@ -268,7 +269,9 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
int ret = 0;
const char * const replay_usage[] = {
- N_("(EXPERIMENTAL!) git replay (--onto <newbase> | --advance <branch>) <revision-range>..."),
+ N_("(EXPERIMENTAL!) git replay "
+ "([--contained] --onto <newbase> | --advance <branch>) "
+ "<revision-range>..."),
NULL
};
struct option replay_options[] = {
@@ -278,6 +281,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
OPT_STRING(0, "onto", &onto_name,
N_("revision"),
N_("replay onto given commit")),
+ OPT_BOOL(0, "contained", &contained,
+ N_("advance all branches contained in revision-range")),
OPT_END()
};
@@ -289,6 +294,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
usage_with_options(replay_usage, replay_options);
}
+ if (advance_name && contained)
+ die(_("options '%s' and '%s' cannot be used together"),
+ "--advance", "--contained");
+
repo_init_revisions(the_repository, &revs, prefix);
/*
@@ -377,7 +386,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
continue;
while (decoration) {
if (decoration->type == DECORATION_REF_LOCAL &&
- strset_contains(update_refs, decoration->name)) {
+ (contained || strset_contains(update_refs,
+ decoration->name))) {
printf("update %s %s %s\n",
decoration->name,
oid_to_hex(&last_commit->object.oid),
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index 68a87e7803..d6286f9580 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -114,4 +114,33 @@ test_expect_success 'replay fails when both --advance and --onto are omitted' '
test_must_fail git replay topic1..topic2 >result
'
+test_expect_success 'using replay to also rebase a contained branch' '
+ git replay --contained --onto main main..topic3 >result &&
+
+ test_line_count = 2 result &&
+ cut -f 3 -d " " result >new-branch-tips &&
+
+ git log --format=%s $(head -n 1 new-branch-tips) >actual &&
+ test_write_lines F C M L B A >expect &&
+ test_cmp expect actual &&
+
+ git log --format=%s $(tail -n 1 new-branch-tips) >actual &&
+ test_write_lines H G F C M L B A >expect &&
+ test_cmp expect actual &&
+
+ printf "update refs/heads/topic1 " >expect &&
+ printf "%s " $(head -n 1 new-branch-tips) >>expect &&
+ git rev-parse topic1 >>expect &&
+ printf "update refs/heads/topic3 " >>expect &&
+ printf "%s " $(tail -n 1 new-branch-tips) >>expect &&
+ git rev-parse topic3 >>expect &&
+
+ test_cmp expect result
+'
+
+test_expect_success 'using replay on bare repo to also rebase a contained branch' '
+ git -C bare replay --contained --onto main main..topic3 >result-bare &&
+ test_cmp expect result-bare
+'
+
test_done
--
2.43.0.14.g93e034faee
^ permalink raw reply related
* [PATCH v8 14/14] replay: stop assuming replayed branches do not diverge
From: Christian Couder @ 2023-11-24 11:10 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Patrick Steinhardt, Johannes Schindelin,
Elijah Newren, John Cai, Derrick Stolee, Phillip Wood, Calvin Wan,
Toon Claes, Dragan Simic, Linus Arver, Christian Couder
In-Reply-To: <20231124111044.3426007-1-christian.couder@gmail.com>
From: Elijah Newren <newren@gmail.com>
The replay command is able to replay multiple branches but when some of
them are based on other replayed branches, their commit should be
replayed onto already replayed commits.
For this purpose, let's store the replayed commit and its original
commit in a key value store, so that we can easily find and reuse a
replayed commit instead of the original one.
Co-authored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
builtin/replay.c | 44 ++++++++++++++++++++++++++--------
t/t3650-replay-basics.sh | 52 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 86 insertions(+), 10 deletions(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index df14657e2f..6bc4b47f09 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -223,20 +223,33 @@ static void determine_replay_mode(struct rev_cmdline_info *cmd_info,
strset_clear(&rinfo.positive_refs);
}
+static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,
+ struct commit *commit,
+ struct commit *fallback)
+{
+ khint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);
+ if (pos == kh_end(replayed_commits))
+ return fallback;
+ return kh_value(replayed_commits, pos);
+}
+
static struct commit *pick_regular_commit(struct commit *pickme,
- struct commit *last_commit,
+ kh_oid_map_t *replayed_commits,
+ struct commit *onto,
struct merge_options *merge_opt,
struct merge_result *result)
{
- struct commit *base;
+ struct commit *base, *replayed_base;
struct tree *pickme_tree, *base_tree;
base = pickme->parents->item;
+ replayed_base = mapped_commit(replayed_commits, base, onto);
+ result->tree = repo_get_commit_tree(the_repository, replayed_base);
pickme_tree = repo_get_commit_tree(the_repository, pickme);
base_tree = repo_get_commit_tree(the_repository, base);
- merge_opt->branch1 = short_commit_name(last_commit);
+ merge_opt->branch1 = short_commit_name(replayed_base);
merge_opt->branch2 = short_commit_name(pickme);
merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
@@ -250,7 +263,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,
merge_opt->ancestor = NULL;
if (!result->clean)
return NULL;
- return create_commit(result->tree, pickme, last_commit);
+ return create_commit(result->tree, pickme, replayed_base);
}
int cmd_replay(int argc, const char **argv, const char *prefix)
@@ -266,6 +279,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
struct merge_options merge_opt;
struct merge_result result;
struct strset *update_refs = NULL;
+ kh_oid_map_t *replayed_commits;
int ret = 0;
const char * const replay_usage[] = {
@@ -363,21 +377,30 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
init_merge_options(&merge_opt, the_repository);
memset(&result, 0, sizeof(result));
merge_opt.show_rename_progress = 0;
-
- result.tree = repo_get_commit_tree(the_repository, onto);
last_commit = onto;
+ replayed_commits = kh_init_oid_map();
while ((commit = get_revision(&revs))) {
const struct name_decoration *decoration;
+ khint_t pos;
+ int hr;
if (!commit->parents)
die(_("replaying down to root commit is not supported yet!"));
if (commit->parents->next)
die(_("replaying merge commits is not supported yet!"));
- last_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);
+ last_commit = pick_regular_commit(commit, replayed_commits, onto,
+ &merge_opt, &result);
if (!last_commit)
break;
+ /* Record commit -> last_commit mapping */
+ pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);
+ if (hr == 0)
+ BUG("Duplicate rewritten commit: %s\n",
+ oid_to_hex(&commit->object.oid));
+ kh_value(replayed_commits, pos) = last_commit;
+
/* Update any necessary branches */
if (advance_name)
continue;
@@ -406,13 +429,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
}
merge_finalize(&merge_opt, &result);
- ret = result.clean;
-
-cleanup:
+ kh_destroy_oid_map(replayed_commits);
if (update_refs) {
strset_clear(update_refs);
free(update_refs);
}
+ ret = result.clean;
+
+cleanup:
release_revisions(&revs);
/* Return */
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index d6286f9580..389670262e 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -143,4 +143,56 @@ test_expect_success 'using replay on bare repo to also rebase a contained branch
test_cmp expect result-bare
'
+test_expect_success 'using replay to rebase multiple divergent branches' '
+ git replay --onto main ^topic1 topic2 topic4 >result &&
+
+ test_line_count = 2 result &&
+ cut -f 3 -d " " result >new-branch-tips &&
+
+ git log --format=%s $(head -n 1 new-branch-tips) >actual &&
+ test_write_lines E D M L B A >expect &&
+ test_cmp expect actual &&
+
+ git log --format=%s $(tail -n 1 new-branch-tips) >actual &&
+ test_write_lines J I M L B A >expect &&
+ test_cmp expect actual &&
+
+ printf "update refs/heads/topic2 " >expect &&
+ printf "%s " $(head -n 1 new-branch-tips) >>expect &&
+ git rev-parse topic2 >>expect &&
+ printf "update refs/heads/topic4 " >>expect &&
+ printf "%s " $(tail -n 1 new-branch-tips) >>expect &&
+ git rev-parse topic4 >>expect &&
+
+ test_cmp expect result
+'
+
+test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' '
+ git -C bare replay --contained --onto main ^main topic2 topic3 topic4 >result &&
+
+ test_line_count = 4 result &&
+ cut -f 3 -d " " result >new-branch-tips &&
+
+ >expect &&
+ for i in 2 1 3 4
+ do
+ printf "update refs/heads/topic$i " >>expect &&
+ printf "%s " $(grep topic$i result | cut -f 3 -d " ") >>expect &&
+ git -C bare rev-parse topic$i >>expect || return 1
+ done &&
+
+ test_cmp expect result &&
+
+ test_write_lines F C M L B A >expect1 &&
+ test_write_lines E D C M L B A >expect2 &&
+ test_write_lines H G F C M L B A >expect3 &&
+ test_write_lines J I M L B A >expect4 &&
+
+ for i in 1 2 3 4
+ do
+ git -C bare log --format=%s $(grep topic$i result | cut -f 3 -d " ") >actual &&
+ test_cmp expect$i actual || return 1
+ done
+'
+
test_done
--
2.43.0.14.g93e034faee
^ permalink raw reply related
* [PATCH] git-prompt: stop manually parsing HEAD
From: Patrick Steinhardt @ 2023-11-24 11:37 UTC (permalink / raw)
To: git
[-- Attachment #1: Type: text/plain, Size: 1675 bytes --]
We're manually parsing the HEAD reference in git-prompt to figure out
whether it is a symbolic or direct reference. This makes it intimately
tied to the on-disk format we use to store references and will stop
working once we gain additional reference backends in the Git project.
Refactor the code to always use git-symbolic-ref(1) to read HEAD, which
is both simpler and compatible with alternate reference backends.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
contrib/completion/git-prompt.sh | 22 ++++++++++------------
1 file changed, 10 insertions(+), 12 deletions(-)
diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 2c030050ae..05de540e13 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -474,17 +474,10 @@ __git_ps1 ()
if [ -n "$b" ]; then
:
- elif [ -h "$g/HEAD" ]; then
- # symlink symbolic ref
- b="$(git symbolic-ref HEAD 2>/dev/null)"
else
- local head=""
- if ! __git_eread "$g/HEAD" head; then
- return $exit
- fi
- # is it a symbolic ref?
- b="${head#ref: }"
- if [ "$head" = "$b" ]; then
+ b="$(git symbolic-ref HEAD 2>/dev/null)"
+
+ if test -z "$b"; then
detached=yes
b="$(
case "${GIT_PS1_DESCRIBE_STYLE-}" in
@@ -498,9 +491,14 @@ __git_ps1 ()
git describe HEAD ;;
(* | default)
git describe --tags --exact-match HEAD ;;
- esac 2>/dev/null)" ||
+ esac 2>/dev/null)"
+
+ if test -z "$b"; then
+ test -z "$short_sha" && return $exit
+
+ b="$short_sha..."
+ fi
- b="$short_sha..."
b="($b)"
fi
fi
--
2.43.0
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related
* Running git(1) from within hooks/post-update
From: Alejandro Colomar @ 2023-11-24 14:00 UTC (permalink / raw)
To: git
[-- Attachment #1: Type: text/plain, Size: 1429 bytes --]
Hi,
I'm trying to set up a post-update hook to produce a build of the Linux
man-pages PDF book after every push to my personal server to the 'main'
branch, so that I can serve man-pages-HEAD.pdf at some URL, for users to
be able to easily check the manual at git HEAD without having to clone
the repo.
I thought of having a git non-bare repo where I build it, so the script
would be the following (the paths are tmp, because I'm still testing).
$ cat post-update
#!/bin/sh
test "$1" = "refs/heads/main" || exit 0;
cd ~/tmp/man-pages/;
whoami; pwd; ls -ld .git/; # This is for debugging.
git fetch srv #>/dev/null 2>&1;
git reset srv/main --hard #>/dev/null 2>&1;
git clean -dffx #>/dev/null 2>&1;
scripts/LinuxManBook/build.sh >~/tmp/LMB-HEAD.pdf &
But it's not working. The git(1) calls are failing, saying it's not a
git repo, but it clearly is, and I have permissions, so I don't
understand what's going wrong. Here's the 'remote:' output of a push to
that testing server:
remote: alx
remote: /home/alx/tmp/man-pages
remote: drwxr-xr-x 8 alx alx 4096 Nov 24 14:41 .git/
remote: fatal: not a git repository: '.'
remote: fatal: not a git repository: '.'
remote: fatal: not a git repository: '.'
remote: hooks/post-update: 12: ./scripts/LinuxManBook/build.sh: not found
Can you please help? :)
Thanks,
Alex
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: Unable to split then edit hunk in git interactive add
From: Phillip Wood @ 2023-11-24 15:19 UTC (permalink / raw)
To: scarf, git
In-Reply-To: <8d7c8313-0f02-4218-ba2a-1716ada7956f@gmail.com>
On 24/11/2023 08:16, scarf wrote:
> |Thank you for filling out a Git bug report!
> Please answer the following questions to help us understand your issue.
>
> What did you do before the bug happened? (Steps to reproduce your issue)
Thanks for taking the time to report this. While it is a known
limitation of "git add -p" that editing a hunk after splitting it can
cause problems (see below) I am unable to reproduce this particular bug.
When a hunk is split the resulting hunks share the context lines around
the split. The trailing context lines of the first hunk are the same
lines as the leading context of the second hunk. If the shared context
is edited then the lines no-longer match between the hunks which means
they cannot be applied. In general it is better to avoid splitting a
hunk if you plan to edit it.
Best Wishes
Phillip
^ permalink raw reply
* Re: [PATCH 4/4] completion: avoid user confusion in non-cone mode
From: Elijah Newren @ 2023-11-24 15:28 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Elijah Newren via GitGitGadget, git
In-Reply-To: <xmqqo7fk9cdt.fsf@gitster.g>
On Thu, Nov 23, 2023 at 5:19 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> "Elijah Newren via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > if [[ "$using_cone" == "true" ]]; then
> > __gitcomp_directories
>
> Hmph, doesn't "Providing the files and directories currently present
> is thus always wrong." apply equally to cone mode?
Absolutely, it definitely applies to cone mode. We (mostly) fixed
that a long time ago, making it not complete on the files &
directories currently present. In particular, the
__gitcomp_directories() function highlighted here completes on the
output of `git ls-tree -z -d --name-only HEAD`.
However, before this series, there was a problem when
__gitcomp_directories() finds no possible completions. In that case,
the code would fall back to bash-completion's default of completing on
all files and directories currently present. But that was fixed in
patch 3 of this series to avoid that fallback.
This patch, though, isn't about cone mode. It's about fixing (or at
least improving) non-cone mode.
> > + else
> > + # NEEDSWORK: It might be useful to provide a
> > + # completion function which:
> > + #
> > + # 1. Provides completions based on
> > + # files/directories that exist in HEAD, not
> > + # just those currently present in the working
> > + # tree.
>
> This makes a lot of sense. May make even more sense with
> s/HEAD/index/, though.
Ooh, interesting. That wouldn't work with the sparse index (where
paths we want to complete on are currently missing from the index
too), but sparse index is restricted to cone mode, and we're
discussing non-cone-mode here. So, this might be a basis for a good
alternative.
> > + # 4. Provides no completions when run from a
> > + # subdirectory of the repository root. (If we
> > + # did provide file/directory completions, the
> > + # user would just get a "please run from the
> > + # toplevel directory" error message when they
> > + # ran it. *Further*, if the user did rerun
> > + # the command from the toplevel, the
> > + # completions we previously provided would
> > + # likely be wrong as they'd be relative to the
> > + # subdirectory rather than the repository
> > + # root. That could lead to users getting a
> > + # nasty surprise based on trying to use a
> > + # command we helped them create.)
>
> Hmph, would an obvious alternative to (1) check against the HEAD (or
> the index) to see if the prefix string matches an entity at the
> current directory level, and then (2) to prefix the result of the
> previous step with "/$(git rev-parse --show-prefix)" work? That is
> something like this:
>
> $ cd t
> $ git sparse-checkout add help<TAB>
> ->
> $ git sparse-checkout add /t/helper/
I thought bash-completion was only for completions, not for startings
as well. Was I mistaken?
> and when the user gave the full path from the root level, do the
> obvious:
>
> $ cd t
> $ git sparse-checkout add /t/help<TAB>
> ->
> $ git sparse-checkout add /t/helper/
>
> Another more fundamental approach to avoid "confusion" this bullet
> item tries to side step might be to *fix* the command that gets
> completed. As "git sparse-checkout --help" is marked as
> EXPERIMENTAL in capital letters, we should be able to say "what was
> traditionally known as 'add' is from now on called 'add-pattern' and
> command line completion would not get in the way; the 'add'
> subcommand now takes only literal paths, not patterns, that are
> relative to the current directory" if we wanted to.
That's interesting...but it opens up a new can of worms:
* Would we also need both `set-patterns` and `set`, in addition to
`add-patterns` and `add`?
* In cone mode, the paths passed are literal directories (and only
directories; no individual files), but the thing added is a
telescoping "cone" of leading directories as well. Does this make it
potentially confusing to users to say that `add` only takes literal
paths?
* In cone mode (the default), should `add-patterns` just be an
error, since no pattern specification is allowed?
* In the git-sparse-checkout manual, for performance reasons, we
recommend users _not_ specify individual paths in non-cone mode.
Would our recommendation then be to just not use `add` or `set` and
only use `add-patterns` and `set-patterns`? If so, what have we
accomplished by adding the new names?
Maybe I'm missing something about your suggestion, but this seems much
more complex than the simple solution we implemented in bb8b5e9a90d
("sparse-checkout: pay attention to prefix for {set, add}",
2022-02-19) for the !core_sparse_checkout_cone case. I like the
simple solution there, though that simple solution omitted modifying
the completion rules in a way that was consistent (i.e. that returns
nothing when the user is running from a subdirectory).
> > + # 5. Provides escaped completions for any paths
> > + # containing a '*', '?', '\', '[', ']', or
> > + # leading '#' or '!'. (These characters might
> > + # already be escaped to protect from the
> > + # shell, but they need an *extra* layer of
> > + # escaping to prevent the pattern parsing in
> > + # Git from seeing them as special characters.)
> > + #
> > + # Of course, this would be a lot of work, so for now,
> > + # just avoid the many forms of user confusion that
> > + # could be caused by providing bad completions by
> > + # providing a fake completion to avoid falling back to
> > + # bash's normal file and directory completion.
>
> > + COMPREPLY=( "" )
> > fi
> > esac
> > }
^ permalink raw reply
* Re: [PATCH v2 4/4] doc: refer to internet archive
From: Elijah Newren @ 2023-11-24 15:32 UTC (permalink / raw)
To: Josh Soref; +Cc: Josh Soref via GitGitGadget, git
In-Reply-To: <CACZqfqCksg=uMh=URh-0KEoPA9cvjgDAKPhn+0WZti-y2A540Q@mail.gmail.com>
On Thu, Nov 23, 2023 at 7:24 PM Josh Soref <jsoref@gmail.com> wrote:
>
> Elijah Newren wrote:
> > Thanks, these all look good, except on of the old links works for me.
> > Maybe it was just down the day you checked?
>
> > More comments on that below...
>
> > > - # Atom: http://www.atomenabled.org/developers/syndication/
> > > - # RSS: http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
> > > + # Atom: https://web.archive.org/web/20230815171113/https://www.atomenabled.org/developers/syndication/
> > > + # RSS: https://web.archive.org/web/20030729001534/http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
> >
> > The original www.atomenabled.org link works for me.
>
> That's odd. As you can see based on my archive.org link, at some point
> they had `https` support (their certificate expired Feb 7, 2021 UTC).
>
> Personally, as a former web browser developer, I'd rather people rely
> on archived authenticated content than content that could be rewritten
> in transit.
I think that'd be perfectly reasonable, I was just thrown because the
justification was that the links no longer worked. Mentioning why the
changes are being made in the commit message, and in particular that
there are two reasons for the changes, would have been sufficient.
> That said, I'm going to drop this change.
Your call.
^ permalink raw reply
* Re: [PATCH v2 1/4] doc: switch links to https
From: Elijah Newren @ 2023-11-24 15:33 UTC (permalink / raw)
To: Josh Soref; +Cc: Josh Soref via GitGitGadget, git
In-Reply-To: <CACZqfqD-M3NAb3=Y-StWfAk50r-XPkFRRLLJPe9RUek21+Lnsg@mail.gmail.com>
On Thu, Nov 23, 2023 at 7:13 PM Josh Soref <jsoref@gmail.com> wrote:
>
> > > -See http://repo.or.cz/w/git.git/tree/HEAD:/gitweb/[] for gitweb source code,
> > > +See https://repo.or.cz/w/git.git/tree/HEAD:/gitweb/[] for gitweb source code,
> > > browsed using gitweb itself.
> >
> > The suggested link gives a "404 - No such tree". Granted, the http:
> > link also does that, but it'd be nicer to provide a non-broken link,
> > which you can do by stripping the '/[]' from the end of the URL.
>
> The `[]` is part of AsciiDoc's [1] notation. I tripped on this when I
> first looked into
> this series (as I'm much more familiar w/ Markdown and Restructured Text).
Ah, thanks for the explanation.
>
> [1] https://docs.asciidoctor.org/asciidoc/latest/syntax-quick-reference/
^ permalink raw reply
* Re: [PATCH v3 0/4] Switch links to https
From: Elijah Newren @ 2023-11-24 15:36 UTC (permalink / raw)
To: Josh Soref via GitGitGadget; +Cc: git, Eric Sunshine, Josh Soref
In-Reply-To: <pull.1589.v3.git.1700796916.gitgitgadget@gmail.com>
On Thu, Nov 23, 2023 at 7:35 PM Josh Soref via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>
> There are a couple of categories of http links...
>
> There are four categories worth changing:
>
> * pages that have jittered a bit but are now available as https:
> * pages which exist at both http: and https: and can be safely switched
> * pages that have jittered a bit and are not available over https:
> * pages that are gone and for which the best source is
> https://web.archive.org
>
> And some categories that aren't being changed:
>
> * links that are required to be http: because they're copied from something
> that mandates it (the apache license, xml namespaces, xsl docbook
> things?)
> * urls that were imaginary (e.g. http://example.com/repo.git)
> * links in borrowed code where the http: form still works
>
> In order:
>
> * doc: update links to current pages -- I found the current pages for
> these, it should be easy enough to verify these / reject them
> * doc: switch links to https -- the simplest
> * doc: update links for andre-simon.de -- I've split this out, I don't like
> the idea of having to download binaries over http. If this were my
> project, I'd be tempted to remove the feature or self-host w/ https...
> * doc: refer to internet archive -- the original urls are dead, I've found
> internet archive date links for them. (There are some in git already, so
> this seemed like a very reasonable choice.)
>
> Changes from v1:
>
> * Commit messages have been adjusted since v1
> * files were dropped based on feedback from Junio
>
> Changes from v2:
>
> * The first two commits have been swapped (favoring more complicated urls
> over simply switching to https)
> * The archive.org link for atomenabled.org has been dropped, we'll risk
> users getting hacked content from an arbitrary MITM instead of taking
> archived authenticated content based on the last time their web site was
> properly maintained.
As stated elsewhere, I'd be fine with using the archived link if the
justification presented in the series for using archived links was
consistent and mentioned both reasons for changes. But, I think this
series is fine to merge down as-is if you don't want to go through the
trouble. Especially given how long you've waited.
Anyway, I checked through every link in this series; it all looks good to me.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox