Git commits rebasing automation

Just wanted to share some ideas on the automation of such a boring recurrent manual work in programming as “rebasing”. Be prepared for some dense theory with examples.

The general idea

So, some time ago I was asked to figure out a rebase automation to reduce amount of outdated forks and downstream branches in company by continuously rebasing them on top of the newest upstream. The idea was simple: add an automation that will try to rebase on top of the upstream periodically (like once per week, or once per day in case of some really active upstream branches) so the company will reduce one of the reasons of the outdated changes: when the fork developers forget or do not have time to even check whether the upstream has updates and whether rebasing on top of these updates will introduce any conflicts.

So, the automation should have the following inputs:

  1. The downstream repository (supposed but not required to be a fork of the upstream repository).
  2. The downstream branch in the downstream repository that should be rebased.
  3. The upstream repository.
  4. The upstream branch in the upstream repository on top of which the downstream changes should be rebased on.

And the automation should produce the following outputs:

  1. A rebased on top of the upstream branch downstream branch in case there is no conflicts.
  2. A set of data about the conflict in case the automated rebase meets a conflict.

As you can see, the automation should not try to resolve the conflict by itself. Instead it should stop and provide a sufficient set of data for a human or another automation to resolve the conflict. So, the logic can be described by the following diagram:

general-idea The general idea diagram

The additional requirements

I had the following additional requirements:

  1. The solution must not depend on CI&CD technologies (e.g., GitHub Actions, Woodpecker, etc.) so it will be possible to use it outside of the CI&CDs.
  2. The solution should not depend on tools and projects that have huge codebase, or are unstable in terms of maintenance and license conditions.

For more about these requirements I strongly recomend you to read the Stop rewriting your pipelines - achieving CI portability with Docker and Taskfile blog post posted by 3mdeb and Maciej Pijanowski.

The existing solutions

Lets take a brief look on what I have found during my quick research.

So it seems, out of everything I have found so far, the rebasebot is the only tool that has a potential to be used in the planned flow.

But we have switched from a high-level flow right to the specific implementations without analysing the implementation problems that need to be solved by the tools. So lets analyze how the implementation should look like, and what are the main implementation challenges here. Then we will check whether the rebasebot solves the challenges.

The implementation challenges

Lets discuss the challenges by going through every component from the “The general idea diagram.” I presented in the chapter above. I will reference the components by their IDs from the diagram (e.g., the S1, or D1).

I want to go through the S4.1 and S4.2 before touching the S3, as the way the results of the automatic rebase attempt are stored will effect the S3.

S1 and D1

The D1 component represents the format and place where the downstream changes are stored. For my case it was git commits and git repository. But for the cases when the automation will be launched in CI/CDs it might be useful to add support for both: local and remote repositories (e.g., GitHub, Gitea). This would make the automation more self-contained and will satisfy the first requirement from the The additional requirements chapter.

So, the activity diagram should be the following:

fetching and preparation S1 action diagram

S2 and D2

These are actually similar to the S1 and the D1:

fetching and preparation S2 action diagram

The adding the upstream repository as a remote is needed to use git rebase command.

S4.1 and D3.1

As I have mentioned for the S1 and S2 - the changes will be prepared for the rebase as git commits and git repositories. So the outputs should adhere to the same format so the automation could be called in loop without surplus conversion between the inputs and outputs format or some other automation that understands the git could, without any conversions, consume the result of this automation. Why the loop?

The question is, how to store the data from the D3.1 block using only the git refs? For the D3.1 the solution is simple and is the result of the following facts:

  1. We are rebasing git commits.
  2. git commits must be assigned to some git ref. to be able to exist in git tree.
  3. The git ref. can be easily pushed to remote later.

Because we are rebasing the branches - it would be logical to create a new branch with rebased commits. Then we can push the new branch to remote if needed.

I am highlighting the “a new” here, because for me it is important the rebase automation should never force push (hence, overwrite) somebody’s commits. The reason: even if the rebase has completed without conflicts - it does not mean the result will work (will build, launch, pass tests, etc.) as expected, because most of the tools used for rebase (e.g., git) do not understand the semantics of the code they operate on and work mostly on such categories as filesystem structures (e.g., the placement of file and directories in a repository), the files contents, and the differences between two filesystem structures and files contents.

Well, unless you will find a semantic version control system that will do the automatic rebase - the entity, that should decide whether the rebase was successful should be either a human or another automation that either understand the semantics somehow or will confirm the correctness of the rebase via some empirical experiment (e.g., a set of automatic tests performed on the target system using the rebased software).

So, the S4.1 should look like this:

fetching and preparation S4.1 action diagram

Note, that you can encode whatever you want into the name of the branch that will contain the rebased commits. For my needs the name of the downstream branch was enough. And the -rebased postfix is for identification purposes. E.g., you can consider encoding the hash of the top commit of the new base, if it is useful for you.

The policy for not using the force pushes will not only protect the original downstream branch but the branches with the same name as the newly created branch that were created by somebody else or by the previous automated rebase attempt. So it is a win-win design.

S4.2 and D3.2

Before diving into the logic for the S4.2 we need to answer the question what is the “Conflict data” that should be stored in D3.2? The are two answers depending on which abstraction level one wants to look:

  1. The rebased code level. This means this is the data that describes which line in which file caused the conflict. E.g., when git rebase faces a conflict and leaves the repository in the state when some file contains the git conflict markers, for example:

     feature 1
     <<<<<<< feature-2
     feature 2
     =======
     feature 1.2
     >>>>>>> feature-1.2
    

    This data describes the actual conflict, but does not provide the information which commit and when caused the conflict. This would be usable as a direct inptu to some conflict resolution tool.

  2. The git refs and commits data level. E.g., when git reports you that it faced a conflict, for example:

     λ git rebase feature-1.2 feature-2
     Auto-merging feature-file
     CONFLICT (content): Merge conflict in feature-file
     error: could not apply b175ce38688b... feature 2 commit
     hint: Resolve all conflicts manually, mark them as resolved with
     hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
     hint: You can instead skip this commit: run "git rebase --skip".
     hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
     hint: Disable this message with "git config set advice.mergeConflict false"
     Could not apply b175ce38688b... # feature 2 commit
    

    As you can see we can get the following information:

    1. When the conflict was faced: when rebasing the branch feature-2 on top of the feature-1.2.
    2. What commit introduced the conflict: the commit with hash b175ce38688b.

Considering the idea of the automation is to only check for the conflicts, and not resolve them or directly launch a tool that will resolve the conflict in place - the data provided on the git refs and commits data level will be more appropriate here, because:

  1. The data still has the format of the git commits and git refs. This will be consistent with the D3.1.
  2. This is small amount of data to store.
  3. This is a sufficient amount of data for some other tool (e.g., a tool for automatic conflict resolution) to reproduce the conflict.

Ok, now we know what we need to store. The next question is how to store it? The ideas:

  1. Just redirect it to STDOUT as logs.
    • Pros:
      1. The simplest solution possible. One can simply let the git print the information.
      2. The STDOUT can be considered as a generic channel.
    • Cons:
      1. One would need to implement a solution to save and redirect the logs, as the logs are not always saved or are not always automatically redirected (e.g., moving the logs between the GitHub Actions jobs).
      2. Possible integration problems. As one will need to teach the consecutive automation step how to parse the logs.
  2. Redirect somewhere else instead of STDOUT.
    • Pros:
      1. Easy integration with the technologies that understand the channel where one redirects the data out of the box.
    • Cons:
      1. One will need to maintain either a channel or a place where the data will be redirected.
      2. Dependency on some non-generic channel or place.
      3. Not easy integration with the technologies that do not understand the channel where one redirects the data out of the box.
  3. Encode the information to some temporary git ref.
    • Pros:
      1. Consistency with other steps in this automation: all outputs are stored as a git ref.
      2. git refs can be considered a generic channel.
    • Cons:
      1. The git refs are limited in terms of amout of the information they can contain.
      2. One will need to add an additional ref to save the data.

For me the third option seems to be the most optimal. More than that, instead of creating some ref on some random commit - I can create a ref on the last successfully-rebased commit. This approach has three additional advantages:

  1. This is a way to save the rebase state even if the rebase was performed automatically in a CI&CD.
  2. Instead of encoding the names of two branches (the upstream and downstream) and a commit hash I can leave only the name of the donwstream branch and the commit hash.
  3. To reproduce the conflict one will only need to do one custom command: extract the commit hash from the ref name. Then the commit can be cherry-picked on the ref with the current rebase state.

So, the S4.2 should look like this:

fetching and preparation S4.2 action diagram

As you can see, the flow is pretty similar to the S4.1.

S3

Lets get to the core component, the step that is responsible for the rebases. One would say, just add the following as the S3:

rebasing-raw

But I will say no. Remember the idea, that this automation should be launched periodically? This periodical launch can cause two problems:

  1. The downstream repository can be polluted with the -conflict branches, in three cases:

    1. The conflict commit hash which is a part of the -conflict branch name has changed without resolving the actual conflict. In such case the next call of the automation will result in creation of another -conflict branch with different name because of the different conflict commit hash but with the same actual conflict.
    2. The entire downstream branch history has been rewritten, but the developers have forgotten to delete the -conflict branches.
    3. The developers have forgotten to delete the -conflict branch after resolving the conflict.
  2. The automation will fail every time it will try to rebase but will not be able to force push the -conflict branch if it does already exist. But it will fail with inappropriate message that it cannot force-push, instead of reminding the lazy (or busy) developers so they solve the conflict.

Hence, taking into account the above problems, the optimal solution for the S3 will be:

rebasing-raw S3 action diagram

Some other notes

The content I have provided above is not the final solution, it is only the logic that comes out of my experience. To turn it into the final solution you will need:

  1. Turn the logic into code.
  2. Add error handling, output values, logs, etc.
  3. Add information what a developer should do for every result produced by the automation (I have written instructions for devs. as logs for local rebases, and as PRs for remote ones).
  4. Cover it with tests.
  5. And many more.

The openshift-eng/rebasebot

Lets check whether the openshift-eng/rebasebot have the described functionalities or if it has something else to propose to cover the use case.

The testbench

Firstly, let me introduce the testbench. For testing I will be using two local repositories that have remote counterparts on my GitHub profile. The donwstream repository with name auto-rebase-script-tests has the following downstream branch:

danik in ~/Repos/auto-rebase-script-tests on feature λ git lol
* 68bd7e88b1b1 (HEAD -> feature) C7: feature edits line2 again
* 9a9fcbb6c12f C6: some empty feature commit
* 6bf2a43b8a1c C5: feature edits line2
* 09bd53e925cd C4: feature edits line3
* 3c5a8a05eb87 C3: some feature commit
* 312c0a8a9f40 C2: some feature commit
* 48d1e817815f C1: some feature commit
* a2df0ad56e71 (origin/master, origin/HEAD, master) A: base file

The commits edit the only file in the repository with the following content on the commit 68bd7e88b1b1:

danik in ~/Repos/auto-rebase-script-tests on feature λ cat file.txt
line1: shared
line2: FEATURE VERSION2
line3: feature change
line4: feature change
line5: feature change
line6: feature change
line7: feature change

The upstream repository with the name auto-rebase-script-tests-upstream has the following upstream branch:

danik in ~/Repos/auto-rebase-script-tests-upstream on feature-upstream λ git lol
* 9749a2447b0a (HEAD -> feature-upstream, origin/feature-upstream) B: feature edits line2
* a2df0ad56e71 (origin/master) A: base file

The commits edit the only file in the repository with the following content on the commit 9749a2447b0a:

danik in ~/Repos/auto-rebase-script-tests-upstream on feature-upstream λ cat file.txt
line1: shared
line2: upstream VERSION
line3: shared

The upstream and downstream branches have a common ancestor:

danik in ~/Repos/auto-rebase-script-tests-upstream on feature-upstream λ git show a2df0ad56e71
commit a2df0ad56e71518d9248ca1afec282d90e599663 (origin/master)
Author: Daniil Klimuk <danik.klimuk13@gmail.com>
Date:   Thu Apr 9 10:49:09 2026 +0200

    A: base file

diff --git a/file.txt b/file.txt
new file mode 100644
index 000000000000..27bb0bbea916
--- /dev/null
+++ b/file.txt
@@ -0,0 +1,3 @@
+line1: shared
+line2: original
+line3: shared

The git rebase feature-upstream feature should result in two conflicts:

  1. When applying the commit dda06281d86b:

     danik in ~/Repos/auto-rebase-script-tests on feature ● ● performing a rebase-i λ git diff
     diff --cc file.txt
     index f721a4c5399b,7ccf77245863..000000000000
     --- a/file.txt
     +++ b/file.txt
     @@@ -1,6 -1,6 +1,11 @@@
       line1: shared
     ++<<<<<<< HEAD
      +line2: upstream VERSION
      +line3: shared
     ++=======
     + line2: original
     + line3: feature change
     ++>>>>>>> dda06281d86b (C4: feature edits line3)
       line4: feature change
       line5: feature change
       line6: feature change
    
  2. And on the commit 6f900d451bc4:

     danik in ~/Repos/auto-rebase-script-tests on feature ● ● performing a rebase-i λ git diff
     diff --cc file.txt
     index 7d38a0443217,5e058651399f..000000000000
     --- a/file.txt
     +++ b/file.txt
     @@@ -1,5 -1,5 +1,9 @@@
       line1: shared
     ++<<<<<<< HEAD
      +line2: upstream VERSION
     ++=======
     + line2: FEATURE VERSION
     ++>>>>>>> 6f900d451bc4 (C5: feature edits line2)
       line3: feature change
       line4: feature change
       line5: feature change
    

After all the conflicts have been resolved and the rebase finishes we should we should end up with the following result:

danik in ~/Repos/auto-rebase-script-tests on feature λ git lol
* c453f23a5583 (HEAD -> feature) C7: feature edits line2 again
* 513b6a4110cd C6: some empty feature commit
* 943f8d3a855c C5: feature edits line2
* 7b0e7488a306 C4: feature edits line3
* 33673fea21c2 C3: some feature commit
* 32ee361ee83e C2: some feature commit
* ac84b3568f2b C1: some feature commit
* 9749a2447b0a (upstream/feature-upstream) B: feature edits line2
* a2df0ad56e71 (upstream/master, origin/master, origin/HEAD, master) A: base file

So the history is linear and could be used for easy upstreaming of the C[1-7] commits. The file should contain:

danik in ~/Repos/auto-rebase-script-tests on feature λ cat file.txt
line1: shared
line2: FEATURE VERSION2
line3: feature change
line4: feature change
line5: feature change
line6: feature change
line7: feature change

Using the openshift-eng/rebasebot

I am using the tool from commit 99a09a17af1693c4d7a3782d9d360db60ce2a4e5.

git history management

The first rebase logs
(.venv) danik in ~/Repos/rebasebot on main ● λ rebasebot \
--source "https://github.com/DaniilKl/auto-rebase-script-tests-upstream:feature-upstream" \
--dest "DaniilKl/auto-rebase-script-tests:feature" \
--rebase "DaniilKl/auto-rebase-script-tests:feature" \
--working-dir "/tmp/for-tests" \
--github-user-token "./token" \
--dry-run
INFO - Using working directory: /tmp/for-tests
INFO - Logging to GitHub as a User
INFO - Logging to GitHub as a User
INFO - Destination repository is https://github.com/DaniilKl/auto-rebase-script-tests.git
INFO - rebase repository is https://github.com/DaniilKl/auto-rebase-script-tests.git
INFO - source repository is https://github.com/DaniilKl/auto-rebase-script-tests-upstream.git
INFO - Fetching feature from dest
INFO - Fetching feature-upstream from source
INFO - Fetching all tags from source
INFO - Fetching all branches from source
INFO - Checking out source/feature-upstream
INFO - Checking for existing rebase branch feature in https://github.com/DaniilKl/auto-rebase-script-tests
INFO - Fetching existing rebase branch
INFO - Branches with commit:
  source/feature-upstream
INFO - Preparing rebase branch
INFO - Merging upstream/feature-upstream into feature
INFO - Performing rebase
INFO - Merge base of source/feature-upstream and dest/feature: a2df0ad56e71518d9248ca1afec282d90e599663
INFO - Merges on ancestry-path from merge_base=(a2df0ad56e71518d9248ca1afec282d90e599663) to dest/feature branch:

INFO - Searching for merge commit from previous rebasebot run to identify downstream commits
INFO - Didn't find last rebase merge commit. Likely this is the first upstream rebase for the
repository. If that's not the case, something is wrong with the last rebase identification.
Using a2df0ad56e71518d9248ca1afec282d90e599663 as cutoff commit
INFO - Cutoff commits: ['^a2df0ad56e71518d9248ca1afec282d90e599663']
INFO - Phase 2 - other downstream commits (7):
41584bd14d9a05c23a730cecda3ac53515a3bdd3 || C1: some feature commit || danik.klimuk13@gmail.com
b7b0afd48f73e3309b1cc649326a82095eb62bb3 || C2: some feature commit || danik.klimuk13@gmail.com
5025efc20400b84d7d7f1b30c2a5494e9b9128bd || C3: some feature commit || danik.klimuk13@gmail.com
dda06281d86b438c9d1ece5789102145779b24c4 || C4: feature edits line3 || danik.klimuk13@gmail.com
6f900d451bc49c0f9a607fae1ebe4de0a7984dac || C5: feature edits line2 || danik.klimuk13@gmail.com
1a31f61f3cd2485370b7868a5868b05642c75518 || C6: some empty feature commit || danik.klimuk13@gmail.com
3e5034672e4b67fe1a6d49320035d7e69a235010 || C7: feature edits line2 again || danik.klimuk13@gmail.com
INFO - Total downstream commits: 7
INFO - Picking commit: 41584bd14d9a05c23a730cecda3ac53515a3bdd3 - C1: some feature commit
INFO - Picking commit: b7b0afd48f73e3309b1cc649326a82095eb62bb3 - C2: some feature commit
INFO - Picking commit: 5025efc20400b84d7d7f1b30c2a5494e9b9128bd - C3: some feature commit
INFO - Picking commit: dda06281d86b438c9d1ece5789102145779b24c4 - C4: feature edits line3
INFO - Picking commit: 6f900d451bc49c0f9a607fae1ebe4de0a7984dac - C5: feature edits line2
INFO - Picking commit: 1a31f61f3cd2485370b7868a5868b05642c75518 - C6: some empty feature commit
INFO - Picking commit: 3e5034672e4b67fe1a6d49320035d7e69a235010 - C7: feature edits line2 again
INFO - Checking for ART pull request
INFO - Dry run mode is enabled. Do not create a PR.


The tool, surprisingly, finishes the rebase without any conflicts, but with the following branch structure:

danik in ~/Repos/auto-rebase-script-tests on rebase-first-attemptλ git lol
* 394f65bf797e (HEAD -> rebase-first-attempt, origin/rebase-first-attempt) C7: feature edits line2 again
* 6fef2b0a47aa C6: some empty feature commit
* faef6dd0d65b C5: feature edits line2
* da05a2be5ffa C4: feature edits line3
* 55010e5e1847 C3: some feature commit
* ecf5df19a134 C2: some feature commit
* b0f3cd5a50ca C1: some feature commit
*   9de9c447ea6e merge upstream/feature-upstream into feature
|\
| * 9749a2447b0a (upstream/feature-upstream) B: feature edits line2
* | 3e5034672e4b (origin/feature) C7: feature edits line2 again
* | 1a31f61f3cd2 C6: some empty feature commit
* | 6f900d451bc4 C5: feature edits line2
* | dda06281d86b C4: feature edits line3
* | 5025efc20400 C3: some feature commit
* | b7b0afd48f73 C2: some feature commit
* | 41584bd14d9a C1: some feature commit
|/
* a2df0ad56e71 (upstream/master, origin/master, origin/HEAD, master) A: base file

I guess that is how the non-linear history looks like. Lest add one more commit to the feature-upstream that will cause another conflict:

commit 0c1435ac701392f5b0a78071a5b5387226cea8d0 (HEAD -> feature-upstream, upstream/feature-upstream)
Author: Daniil Klimuk <danik.klimuk13@gmail.com>
Date:   Tue May 12 21:55:43 2026 +0200

    B1: feature edits line 2 again

    Signed-off-by: Daniil Klimuk <danik.klimuk13@gmail.com>

diff --git a/file.txt b/file.txt
index 765cf070fc65..1aef70e2e93b 100644
--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
 line1: shared
-line2: upstream VERSION
+line2: upstream VERSION2
 line3: shared

And do the rebase again.

The second rebase logs
(.venv) danik in ~/Repos/rebasebot on main ● λ rebasebot \
--source "https://github.com/DaniilKl/auto-rebase-script-tests-upstream:feature-upstream" \
--dest "DaniilKl/auto-rebase-script-tests:rebase-first-attempt" \
--rebase "DaniilKl/auto-rebase-script-tests:rebase-first-attempt" \
--working-dir "/tmp/for-tests" \
--github-user-token "./token" \
--dry-run
INFO - Using working directory: /tmp/for-tests
INFO - Logging to GitHub as a User
INFO - Logging to GitHub as a User
INFO - Destination repository is https://github.com/DaniilKl/auto-rebase-script-tests.git
INFO - rebase repository is https://github.com/DaniilKl/auto-rebase-script-tests.git
INFO - source repository is https://github.com/DaniilKl/auto-rebase-script-tests-upstream.git
INFO - Fetching rebase-first-attempt from dest
INFO - Fetching feature-upstream from source
INFO - Fetching all tags from source
INFO - Fetching all branches from source
INFO - Checking out source/feature-upstream
INFO - Checking for existing rebase branch rebase-first-attempt in https://github.com/DaniilKl/auto-rebase-script-tests
INFO - Fetching existing rebase branch
INFO - Branches with commit:
  source/feature-upstream
INFO - Preparing rebase branch
INFO - Merging upstream/feature-upstream into rebase-first-attempt
INFO - Performing rebase
INFO - Merge base of source/feature-upstream and dest/rebase-first-attempt: 9749a2447b0a166c883b8052d72dbf8f7f67c443
INFO - Merges on ancestry-path from merge_base=(9749a2447b0a166c883b8052d72dbf8f7f67c443) to dest/rebase-first-attempt branch:
9de9c447ea6eb3dc198d6f9446a0c83cfaab1ca9 || merge upstream/feature-upstream into feature || danik.klimuk13@gmail.com
INFO - Searching for merge commit from previous rebasebot run to identify downstream commits
INFO - Found merge commit from previous rebase: 9de9c447ea6eb3dc198d6f9446a0c83cfaab1ca9
INFO - Its parent 9749a2447b0a166c883b8052d72dbf8f7f67c443 is on an upstream branch
INFO - Cutoff commits: ['^3e5034672e4b67fe1a6d49320035d7e69a235010', '^9749a2447b0a166c883b8052d72dbf8f7f67c443']
INFO - Could not find rebase PR merge on dest, skipping phase 1
INFO - Phase 2 - other downstream commits (7):
b0f3cd5a50caf4c7d0faa3ffcea4c3261416ed2d || C1: some feature commit || danik.klimuk13@gmail.com
ecf5df19a134c9bd610a4e06b2b3b7b79fc67bd6 || C2: some feature commit || danik.klimuk13@gmail.com
55010e5e18470518a0c0649be195504e62e086f3 || C3: some feature commit || danik.klimuk13@gmail.com
da05a2be5ffa945294a9bda63c0b3f6896fe2832 || C4: feature edits line3 || danik.klimuk13@gmail.com
faef6dd0d65b8ed26910184568ce34bf501b2642 || C5: feature edits line2 || danik.klimuk13@gmail.com
6fef2b0a47aac96620974609cf234aa5b631f754 || C6: some empty feature commit || danik.klimuk13@gmail.com
394f65bf797e29dfa3e940e2ee04dd59ea93c681 || C7: feature edits line2 again || danik.klimuk13@gmail.com
INFO - Total downstream commits: 7
INFO - Picking commit: b0f3cd5a50caf4c7d0faa3ffcea4c3261416ed2d - C1: some feature commit
INFO - Picking commit: ecf5df19a134c9bd610a4e06b2b3b7b79fc67bd6 - C2: some feature commit
INFO - Picking commit: 55010e5e18470518a0c0649be195504e62e086f3 - C3: some feature commit
INFO - Picking commit: da05a2be5ffa945294a9bda63c0b3f6896fe2832 - C4: feature edits line3
INFO - Picking commit: faef6dd0d65b8ed26910184568ce34bf501b2642 - C5: feature edits line2
INFO - Picking commit: 6fef2b0a47aac96620974609cf234aa5b631f754 - C6: some empty feature commit
INFO - Picking commit: 394f65bf797e29dfa3e940e2ee04dd59ea93c681 - C7: feature edits line2 again
INFO - Checking for ART pull request
INFO - Dry run mode is enabled. Do not create a PR.


And the branch structure after the second attempt:

danik in ~/Repos/auto-rebase-script-tests on rebase-second-attempt λ git lol
* ddc7f9264420 (HEAD -> rebase-second-attempt, origin/rebase-second-attempt) C7: feature edits line2 again
* 9633a8ee4d9b C6: some empty feature commit
* 54c98bed5832 C5: feature edits line2
* 0945663de419 C4: feature edits line3
* bf72600daf1e C3: some feature commit
* b8f27430066b C2: some feature commit
* ed73ca24e534 C1: some feature commit
*   37175c178f05 merge upstream/feature-upstream into rebase-first-attempt
|\
| * 0c1435ac7013 (upstream/feature-upstream, feature-upstream) B1: feature edits line 2 again
* | 394f65bf797e (origin/rebase-first-attempt, origin/rebase, rebase-first-attempt, rebase) C7: feature edits line2 again
* | 6fef2b0a47aa C6: some empty feature commit
* | faef6dd0d65b C5: feature edits line2
* | da05a2be5ffa C4: feature edits line3
* | 55010e5e1847 C3: some feature commit
* | ecf5df19a134 C2: some feature commit
* | b0f3cd5a50ca C1: some feature commit
* | 9de9c447ea6e merge upstream/feature-upstream into feature
|\|
| * 9749a2447b0a B: feature edits line2
* | 3e5034672e4b (origin/feature) C7: feature edits line2 again
* | 1a31f61f3cd2 C6: some empty feature commit
* | 6f900d451bc4 C5: feature edits line2
* | dda06281d86b C4: feature edits line3
* | 5025efc20400 C3: some feature commit
* | b7b0afd48f73 C2: some feature commit
* | 41584bd14d9a C1: some feature commit
|/
* a2df0ad56e71 (upstream/master, origin/master, origin/HEAD, master) A: base file

I am not sure whether it is a good idea or not to have such history. To me it seems odd. Can it cause problems when, for example, trying to upstream the commits from the downstream branch?

Remote management

Lets now see how this tool handles repetitive work with the remote repositories on GitHub. The main things I want to check are:

The tool’s help message says it can provide feedback via GitHub PRs and Slack messages:

rebasebot -h
(...)
  --rebase REBASE       The base GitHub repo that will be used to create a pull request in the form
                        <user or organization>/<repo>:<branch>, e.g. kubernetes/cloud-provider-
                        openstack:master
(...)
  --slack-webhook SLACK_WEBHOOK
                        The path where credentials for the slack webhook are.
(...)

And it has an additional option to control the PR titles:

rebasebot -h
(...)
  --title-prefix TITLE_PREFIX
                        Prefix to prepend to PR titles. For example, 'UPSTREAM-SYNC' will create
                        titles like 'UPSTREAM-SYNC: Merge ...'.

Which is great. I am going to test only the PRs thing, though. I am going to use the remote counterparts of the repositories I used before.

The first rebase with remote logs
(.venv) danik in ~/Repos/rebasebot on main ● λ rebasebot \
--source "https://github.com/DaniilKl/auto-rebase-script-tests-upstream:feature-upstream" \
--dest "DaniilKl/auto-rebase-script-tests:feature" \
--rebase "DaniilKl/auto-rebase-script-tests:feature" \
--working-dir "/tmp/for-tests" \
--github-user-token "./token"
INFO - Using working directory: /tmp/for-tests
INFO - Logging to GitHub as a User
INFO - Logging to GitHub as a User
INFO - Destination repository is https://github.com/DaniilKl/auto-rebase-script-tests.git
INFO - rebase repository is https://github.com/DaniilKl/auto-rebase-script-tests.git
INFO - source repository is https://github.com/DaniilKl/auto-rebase-script-tests-upstream.git
INFO - Fetching feature from dest
INFO - Fetching feature-upstream from source
INFO - Fetching all tags from source
INFO - Fetching all branches from source
INFO - Checking out source/feature-upstream
INFO - Checking for existing rebase branch feature in https://github.com/DaniilKl/auto-rebase-script-tests
INFO - Fetching existing rebase branch
INFO - Branches with commit:
  source/feature-upstream
INFO - Preparing rebase branch
INFO - Merging upstream/feature-upstream into feature
INFO - Performing rebase
INFO - Merge base of source/feature-upstream and dest/feature: a2df0ad56e71518d9248ca1afec282d90e599663
INFO - Merges on ancestry-path from merge_base=(a2df0ad56e71518d9248ca1afec282d90e599663) to dest/feature branch:

INFO - Searching for merge commit from previous rebasebot run to identify downstream commits
INFO - Didn't find last rebase merge commit. Likely this is the first upstream rebase for the
repository. If that's not the case, something is wrong with the last rebase identification.
Using a2df0ad56e71518d9248ca1afec282d90e599663 as cutoff commit
INFO - Cutoff commits: ['^a2df0ad56e71518d9248ca1afec282d90e599663']
INFO - Phase 2 - other downstream commits (7):
41584bd14d9a05c23a730cecda3ac53515a3bdd3 || C1: some feature commit || danik.klimuk13@gmail.com
b7b0afd48f73e3309b1cc649326a82095eb62bb3 || C2: some feature commit || danik.klimuk13@gmail.com
5025efc20400b84d7d7f1b30c2a5494e9b9128bd || C3: some feature commit || danik.klimuk13@gmail.com
dda06281d86b438c9d1ece5789102145779b24c4 || C4: feature edits line3 || danik.klimuk13@gmail.com
6f900d451bc49c0f9a607fae1ebe4de0a7984dac || C5: feature edits line2 || danik.klimuk13@gmail.com
1a31f61f3cd2485370b7868a5868b05642c75518 || C6: some empty feature commit || danik.klimuk13@gmail.com
3e5034672e4b67fe1a6d49320035d7e69a235010 || C7: feature edits line2 again || danik.klimuk13@gmail.com
INFO - Total downstream commits: 7
INFO - Picking commit: 41584bd14d9a05c23a730cecda3ac53515a3bdd3 - C1: some feature commit
INFO - Picking commit: b7b0afd48f73e3309b1cc649326a82095eb62bb3 - C2: some feature commit
INFO - Picking commit: 5025efc20400b84d7d7f1b30c2a5494e9b9128bd - C3: some feature commit
INFO - Picking commit: dda06281d86b438c9d1ece5789102145779b24c4 - C4: feature edits line3
INFO - Picking commit: 6f900d451bc49c0f9a607fae1ebe4de0a7984dac - C5: feature edits line2
INFO - Picking commit: 1a31f61f3cd2485370b7868a5868b05642c75518 - C6: some empty feature commit
INFO - Picking commit: 3e5034672e4b67fe1a6d49320035d7e69a235010 - C7: feature edits line2 again
INFO - Checking for ART pull request
INFO - Existing rebase branch already contains source.
INFO - Checking for existing pull request
INFO - No existing pull request found
INFO - Rebase branch does not introduce changes compared to dest.
INFO - No PR required - no changes between rebase and dest.
INFO - Destination repo https://github.com/DaniilKl/auto-rebase-script-tests already contains the latest changes


The first attempt did not result in an automatic creation of a branch with rebased changes as well as a PR with information for maintainers.

where

The tool explains it: INFO - Rebase branch does not introduce changes compared to dest. The git diff is really empty between the feature and rebase (which is the result of launching the script above):

danik in /tmp/for-tests on rebase λ git diff rebase dest/feature

This is probably because the feature completely overwrites changes in the file.txt from the feature-upstream branch. But I would prefer to track not only the real changes in the file.txt but the commits as well. So the automation does only the rebase, it does not determine what it should keep and what should not. The rebase contains the commits from the feature-upstream that feature does not:

danik in /tmp/for-tests on rebase λ git log dest/feature..rebase --oneline
00dbaeb7bc2b (HEAD -> rebase, rebase/rebased) C7: feature edits line2 again
b08c56c9bbda C6: some empty feature commit
770d25db02b4 C5: feature edits line2
88cb49317463 C4: feature edits line3
ce149770ca2e C3: some feature commit
143fe6566c9c C2: some feature commit
6be6a9d54134 C1: some feature commit
db96ee3cfd3a merge upstream/feature-upstream into feature
0c1435ac7013 (source/feature-upstream, merge-tmp, feature-upstream) B1: feature edits line 2 again
9749a2447b0a B: feature edits line2

And I would prefer the script to push the rebase to remote and open a PR informing me, that the feature has been rebased on top of the feature-upstream but the changes in it were completely overwritten by the changes in feature. This is because I would prefer the automation to track not only the real changes in the upstream repositories, but the history as well.

Lets add a commit to the feature-upstream with the changes that will not be overwritten by the changes in feature branch and lets see what the tool will do. The new commit is:

commit 36eb48133c73d9e6b494f9e8499802dde70920ad (HEAD -> feature-upstream, upstream/feature-upstream)
Author: Daniil Klimuk <danik.klimuk13@gmail.com>
Date:   Sat May 23 12:53:42 2026 +0200

    B2: feature edits line 1

    Signed-off-by: Daniil Klimuk <danik.klimuk13@gmail.com>

diff --git a/file.txt b/file.txt
index 1aef70e2e93b..94d9992464e5 100644
--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
-line1: shared
+line1: upstream VERSION1
 line2: upstream VERSION2
 line3: shared

So the file.txt after the rebase should have the following content:

danik in ~/Repos/auto-rebase-script-tests on feature-upstream λ cat file.txt
line1: upstream VERSION1
line2: upstream VERSION2
line3: shared
The second rebase with remote logs
(.venv) danik in ~/Repos/rebasebot on main ● λ rebasebot \
--source "https://github.com/DaniilKl/auto-rebase-script-tests-upstream:feature-upstream" \
--dest "DaniilKl/auto-rebase-script-tests:feature" \
--rebase "DaniilKl/auto-rebase-script-tests:feature" \
--working-dir "/tmp/for-tests" \
--github-user-token "./token"
INFO - Using working directory: /tmp/for-tests
INFO - Logging to GitHub as a User
INFO - Logging to GitHub as a User
INFO - Destination repository is https://github.com/DaniilKl/auto-rebase-script-tests.git
INFO - rebase repository is https://github.com/DaniilKl/auto-rebase-script-tests.git
INFO - source repository is https://github.com/DaniilKl/auto-rebase-script-tests-upstream.git
INFO - Fetching feature from dest
INFO - Fetching feature-upstream from source
INFO - Fetching all tags from source
INFO - Fetching all branches from source
INFO - Checking out source/feature-upstream
INFO - Checking for existing rebase branch rebased in https://github.com/DaniilKl/auto-rebase-script-tests
INFO - Fetching existing rebase branch
INFO - Branches with commit:
  source/feature-upstream
INFO - Preparing rebase branch
INFO - Merging upstream/feature-upstream into feature
INFO - Performing rebase
INFO - Merge base of source/feature-upstream and dest/feature: a2df0ad56e71518d9248ca1afec282d90e599663
INFO - Merges on ancestry-path from merge_base=(a2df0ad56e71518d9248ca1afec282d90e599663) to dest/feature branch:

INFO - Searching for merge commit from previous rebasebot run to identify downstream commits
INFO - Didn't find last rebase merge commit. Likely this is the first upstream rebase for the
repository. If that's not the case, something is wrong with the last rebase identification.
Using a2df0ad56e71518d9248ca1afec282d90e599663 as cutoff commit
INFO - Cutoff commits: ['^a2df0ad56e71518d9248ca1afec282d90e599663']
INFO - Phase 2 - other downstream commits (7):
41584bd14d9a05c23a730cecda3ac53515a3bdd3 || C1: some feature commit || danik.klimuk13@gmail.com
b7b0afd48f73e3309b1cc649326a82095eb62bb3 || C2: some feature commit || danik.klimuk13@gmail.com
5025efc20400b84d7d7f1b30c2a5494e9b9128bd || C3: some feature commit || danik.klimuk13@gmail.com
dda06281d86b438c9d1ece5789102145779b24c4 || C4: feature edits line3 || danik.klimuk13@gmail.com
6f900d451bc49c0f9a607fae1ebe4de0a7984dac || C5: feature edits line2 || danik.klimuk13@gmail.com
1a31f61f3cd2485370b7868a5868b05642c75518 || C6: some empty feature commit || danik.klimuk13@gmail.com
3e5034672e4b67fe1a6d49320035d7e69a235010 || C7: feature edits line2 again || danik.klimuk13@gmail.com
INFO - Total downstream commits: 7
INFO - Picking commit: 41584bd14d9a05c23a730cecda3ac53515a3bdd3 - C1: some feature commit
INFO - Picking commit: b7b0afd48f73e3309b1cc649326a82095eb62bb3 - C2: some feature commit
INFO - Picking commit: 5025efc20400b84d7d7f1b30c2a5494e9b9128bd - C3: some feature commit
INFO - Picking commit: dda06281d86b438c9d1ece5789102145779b24c4 - C4: feature edits line3
INFO - Picking commit: 6f900d451bc49c0f9a607fae1ebe4de0a7984dac - C5: feature edits line2
INFO - Picking commit: 1a31f61f3cd2485370b7868a5868b05642c75518 - C6: some empty feature commit
INFO - Picking commit: 3e5034672e4b67fe1a6d49320035d7e69a235010 - C7: feature edits line2 again
INFO - Checking for ART pull request
INFO - Existing rebase branch already contains source.
INFO - Checking for existing pull request
INFO - No existing pull request found
INFO - Rebase branch does not introduce changes compared to dest.
INFO - No PR required - no changes between rebase and dest.
INFO - Destination repo https://github.com/DaniilKl/auto-rebase-script-tests already contains the latest changes


And again not PR nor rebased branch is being created on remote. And the tool’s logs say INFO - Rebase branch does not introduce changes compared to dest.. Though the rebase branch contains the commits from the feature-upstream, it does not contain the changes from the feature-upstream in the file.txt:

danik in /tmp/for-tests on rebase λ git log dest/feature..rebase --oneline
dbe0ccfcb97a (HEAD -> rebase) C7: feature edits line2 again
3b6d07d6132d C6: some empty feature commit
54aaef176cff C5: feature edits line2
8a3b21e7b05a C4: feature edits line3
601175fbcdb8 C3: some feature commit
fff5ce260801 C2: some feature commit
f78a6ae6797f C1: some feature commit
aff31aaa3bfb merge upstream/feature-upstream into feature
36eb48133c73 (source/feature-upstream, merge-tmp, feature-upstream) B2: feature edits line 1
0c1435ac7013 B1: feature edits line 2 again
9749a2447b0a B: feature edits line2
danik in /tmp/for-tests on rebase λ cat file.txt
line1: shared
line2: FEATURE VERSION2
line3: feature change
line4: feature change
line5: feature change
line6: feature change
line7: feature change

This is strange, as such rebase seems pointless…

After checking the tool’s code I have figured out, that without the --conflict-policy strict the script will just ignore all the conflicts and just apply the changes from the feature. This was the reason why the file.txt did not contain the changes from the feature-upstream after rebase: the changes were dropped when the tool ignored the commit. Mea culpa, mea maxima culpa…

  --conflict-policy [{auto,warn,strict}]
                        Detect upstream content silently dropped by -Xtheirs. 'auto': no detection.
                        'warn': log warnings. 'strict': fail. (default: auto)

now-i-get-it

Now, lest try again:

The third rebase with remote logs
(.venv) danik in ~/Repos/rebasebot on main ● λ rebasebot \
--source "https://github.com/DaniilKl/auto-rebase-script-tests-upstream:feature-upstream" \
--dest "DaniilKl/auto-rebase-script-tests:feature" \
--rebase "DaniilKl/auto-rebase-script-tests:feature" \
--working-dir "/tmp/for-tests" \
--github-user-token "./token" \
--conflict-policy strict
INFO - Using working directory: /tmp/for-tests
INFO - Logging to GitHub as a User
INFO - Logging to GitHub as a User
INFO - Destination repository is https://github.com/DaniilKl/auto-rebase-script-tests.git
INFO - rebase repository is https://github.com/DaniilKl/auto-rebase-script-tests.git
INFO - source repository is https://github.com/DaniilKl/auto-rebase-script-tests-upstream.git
INFO - Fetching feature from dest
INFO - Fetching feature-upstream from source
INFO - Fetching all tags from source
INFO - Fetching all branches from source
INFO - Checking out source/feature-upstream
INFO - Checking for existing rebase branch feature in https://github.com/DaniilKl/auto-rebase-script-tests
INFO - Fetching existing rebase branch
INFO - Branches with commit:
  source/feature-upstream
INFO - Preparing rebase branch
INFO - Merging upstream/feature-upstream into feature
INFO - Performing rebase
INFO - Merge base of source/feature-upstream and dest/feature: a2df0ad56e71518d9248ca1afec282d90e599663
INFO - Merges on ancestry-path from merge_base=(a2df0ad56e71518d9248ca1afec282d90e599663) to dest/feature branch:
INFO - Searching for merge commit from previous rebasebot run to identify downstream commits
INFO - Didn't find last rebase merge commit. Likely this is the first upstream rebase for the
repository. If that's not the case, something is wrong with the last rebase identification.
Using a2df0ad56e71518d9248ca1afec282d90e599663 as cutoff commit
INFO - Cutoff commits: ['^a2df0ad56e71518d9248ca1afec282d90e599663']
INFO - Phase 2 - other downstream commits (7):
41584bd14d9a05c23a730cecda3ac53515a3bdd3 || C1: some feature commit || danik.klimuk13@gmail.com
b7b0afd48f73e3309b1cc649326a82095eb62bb3 || C2: some feature commit || danik.klimuk13@gmail.com
5025efc20400b84d7d7f1b30c2a5494e9b9128bd || C3: some feature commit || danik.klimuk13@gmail.com
dda06281d86b438c9d1ece5789102145779b24c4 || C4: feature edits line3 || danik.klimuk13@gmail.com
6f900d451bc49c0f9a607fae1ebe4de0a7984dac || C5: feature edits line2 || danik.klimuk13@gmail.com
1a31f61f3cd2485370b7868a5868b05642c75518 || C6: some empty feature commit || danik.klimuk13@gmail.com
3e5034672e4b67fe1a6d49320035d7e69a235010 || C7: feature edits line2 again || danik.klimuk13@gmail.com
INFO - Total downstream commits: 7
INFO - Picking commit: 41584bd14d9a05c23a730cecda3ac53515a3bdd3 - C1: some feature commit
INFO - Picking commit: b7b0afd48f73e3309b1cc649326a82095eb62bb3 - C2: some feature commit
INFO - Picking commit: 5025efc20400b84d7d7f1b30c2a5494e9b9128bd - C3: some feature commit
INFO - Picking commit: dda06281d86b438c9d1ece5789102145779b24c4 - C4: feature edits line3
WARNING - Upstream content may have been dropped from 'file.txt' by cherry-pick of: dda06281d86b438c9d1ece5789102145779b24c4 - C4: feature edits line3
WARNING -   lost line: line1: upstream VERSION1
WARNING -   lost line: line2: upstream VERSION2
WARNING -   lost line: line3: shared
ERROR - Manual intervention is needed to rebase https://github.com/DaniilKl/auto-rebase-script-tests-upstream:feature-upstream into DaniilKl/auto-rebase-script-tests:feature


So, as you can see, the tool actually detected the conflict and informed about dropping the feature-upstream changes. That is good. The logs are pretty informative and the tool leaves the local repository with the following structure:

danik in /tmp/for-tests on rebase λ git log dest/feature..rebase --oneline
2ca85e679568 (HEAD -> rebase) C4: feature edits line3
30b67feade22 C3: some feature commit
6c4ba5b60d13 C2: some feature commit
ee8105bdba7c C1: some feature commit
53bf2fa84708 merge upstream/feature-upstream into feature
36eb48133c73 (source/feature-upstream, merge-tmp, feature-upstream) B2: feature edits line 1
0c1435ac7013 B1: feature edits line 2 again
9749a2447b0a B: feature edits line2

So the tool stops with an automatically-created branch rebase which has commits from feature-upstream, plus the strange merge commit we talked about before, plus all the commits that was rebased without conflicts (C1, C2, and C3), and the commit that introduced the conflict (the C4).

The only problem is the fact, that the tool did not report the conflict to the remote repository as well as did not push the branch rebase with the rebase state to remote.

Lets add another downstream branch named feature-2 that will contain one empty commit and one commit with a change that will not introduce a conflict:


danik in ~/Repos/auto-rebase-script-tests on feature-2 λ git lol
* 059e547de5c6 (HEAD -> feature-2, origin/feature-2) E: feature-2 adds line 4
* 953148b2aac7 D: feature-2 commit 1
* a2df0ad56e71 (upstream/master, origin/master, origin/HEAD, master) A: base file
danik in ~/Repos/auto-rebase-script-tests on feature-2 λ git show
commit 059e547de5c6c4c046819be04e0b9d9b0d4c9448 (HEAD -> feature-2, origin/feature-2)
Author: Danil Klimuk <danik.klimuk13@gmail.com>
Date:   Thu Apr 9 10:49:09 2026 +0200

    E: feature-2 adds line 4

diff --git a/file.txt b/file.txt
index 27bb0bbea916..70026ed45e0a 100644
--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,4 @@
 line1: shared
 line2: original
 line3: shared
+line4: feature
danik in ~/Repos/auto-rebase-script-tests on feature-2 λ git show HEAD^
commit 953148b2aac78115aebeca08ae7d48398463429b
Author: Danil Klimuk <danik.klimuk13@gmail.com>
Date:   Thu Apr 9 10:49:09 2026 +0200

    D: feature-2 commit 1

This is to test how the tool will report to remote repository in case of a successful rebase.

The fourth rebase with remote logs
(.venv) danik in ~/Repos/rebasebot on main ● λ rebasebot \
--source "https://github.com/DaniilKl/auto-rebase-script-tests-upstream:feature-upstream" \
--dest "DaniilKl/auto-rebase-script-tests:feature-2" \
--rebase "DaniilKl/auto-rebase-script-tests:feature-2-rebased" \
--working-dir "/tmp/for-tests" \
--github-user-token "./token" \
--conflict-policy strict
INFO - Using working directory: /tmp/for-tests
INFO - Logging to GitHub as a User
INFO - Logging to GitHub as a User
INFO - Destination repository is https://github.com/DaniilKl/auto-rebase-script-tests.git
INFO - rebase repository is https://github.com/DaniilKl/auto-rebase-script-tests.git
INFO - source repository is https://github.com/DaniilKl/auto-rebase-script-tests-upstream.git
INFO - Fetching feature-2 from dest
INFO - Fetching feature-upstream from source
INFO - Fetching all tags from source
INFO - Fetching all branches from source
INFO - Checking out source/feature-upstream
INFO - Checking for existing rebase branch feature-2-rebased in https://github.com/DaniilKl/auto-rebase-script-tests
INFO - Branches with commit:
  rebase/feature-2-rebased
  source/feature-upstream
INFO - Preparing rebase branch
INFO - Merging upstream/feature-upstream into feature-2
INFO - Performing rebase
INFO - Merge base of source/feature-upstream and dest/feature-2: a2df0ad56e71518d9248ca1afec282d90e599663
INFO - Merges on ancestry-path from merge_base=(a2df0ad56e71518d9248ca1afec282d90e599663) to dest/feature-2 branch:
INFO - Searching for merge commit from previous rebasebot run to identify downstream commits
INFO - Didn't find last rebase merge commit. Likely this is the first upstream rebase for the
repository. If that's not the case, something is wrong with the last rebase identification.
Using a2df0ad56e71518d9248ca1afec282d90e599663 as cutoff commit
INFO - Cutoff commits: ['^a2df0ad56e71518d9248ca1afec282d90e599663']
INFO - Phase 2 - other downstream commits (2):
953148b2aac78115aebeca08ae7d48398463429b || D: feature-2 commit 1 || danik.klimuk13@gmail.com
059e547de5c6c4c046819be04e0b9d9b0d4c9448 || E: feature-2 adds line 4 || danik.klimuk13@gmail.com
INFO - Total downstream commits: 2
INFO - Picking commit: 953148b2aac78115aebeca08ae7d48398463429b - D: feature-2 commit 1
INFO - Conflict has been resolved. Continue rebase.
INFO - Picking commit: 059e547de5c6c4c046819be04e0b9d9b0d4c9448 - E: feature-2 adds line 4
INFO - Checking for ART pull request
INFO - Existing rebase branch contains changes.
INFO - Checking for existing pull request
INFO - No existing pull request found
INFO - Existing rebase branch needs to be updated.
INFO - Rebase branch introduces changes compared to dest.
INFO - Creating a pull request
INFO - I created a new rebase PR: https://github.com/DaniilKl/auto-rebase-script-tests/pull/47


As you can see it finished the rebase without conflicts, pushed the branch with the rebase result and created a PR. The PR looks:

the-pr-look

IMHO it should provide more information, so a developer would not need to scratch his head thinking what is this PR and would not need to look for the context of this rebase in CI&CD files and CI&CD logs to find out what to do with it.

And the tool skipped the commit D: feature-2 commit 1 from the feature-2 branch. This is probably because it was empty. IMHO it should be for developer to decide whether to skip the empty commits or not, unless there is a clear policy that can be used by the automation tools.

Lets now assume the developer did not have time to manage the PR https://github.com/DaniilKl/auto-rebase-script-tests/pull/47 with the branch feature-2-rebased. The feature-upstream had one more commit added. Hence, now both, the feature-2 and the feature-2-rebased need a rebase. What the tool will do? Will it force-push feature-2-rebased? Will it fail because it cannot force-push feature-2-rebased? Or will it create another *-rebased branch? Lets see.

The commit that I have added to the feature-upstream branch:

commit 0490071a3bc9e1f787e91590c962f4e64ef9ecb9 (HEAD -> feature-upstream, upstream/feature-upstream)
Author: Daniil Klimuk <danik.klimuk13@gmail.com>
Date:   Sat May 23 16:11:32 2026 +0200

    B3: feature edits line 2 again

    Signed-off-by: Daniil Klimuk <danik.klimuk13@gmail.com>

diff --git a/file.txt b/file.txt
index 94d9992464e5..a4296f794682 100644
--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
 line1: upstream VERSION1
-line2: upstream VERSION2
+line2: upstream VERSION3
 line3: shared

And the branch feature-upstream looks like:

danik in ~/Repos/auto-rebase-script-tests on feature-upstream λ git lol
* 0490071a3bc9 (HEAD -> feature-upstream, upstream/feature-upstream) B3: feature edits line 2 again
* 36eb48133c73 B2: feature edits line 1
* 0c1435ac7013 B1: feature edits line 2 again
* 9749a2447b0a B: feature edits line2
* a2df0ad56e71 (upstream/master, origin/master, origin/HEAD, master) A: base file
The fifth rebase with remote log
(.venv) danik in ~/Repos/rebasebot on main ● λ rebasebot \
--source "https://github.com/DaniilKl/auto-rebase-script-tests-upstream:feature-upstream" \
--dest "DaniilKl/auto-rebase-script-tests:feature-2" \
--rebase "DaniilKl/auto-rebase-script-tests:feature-2-rebased" \
--working-dir "/tmp/for-tests" \
--github-user-token "./token" \
--conflict-policy strict
INFO - Using working directory: /tmp/for-tests
INFO - Logging to GitHub as a User
INFO - Logging to GitHub as a User
INFO - Destination repository is https://github.com/DaniilKl/auto-rebase-script-tests.git
INFO - rebase repository is https://github.com/DaniilKl/auto-rebase-script-tests.git
INFO - source repository is https://github.com/DaniilKl/auto-rebase-script-tests-upstream.git
INFO - Fetching feature-2 from dest
INFO - Fetching feature-upstream from source
INFO - Fetching all tags from source
INFO - Fetching all branches from source
INFO - Checking out source/feature-upstream
INFO - Checking for existing rebase branch feature-2-rebased in https://github.com/DaniilKl/auto-rebase-script-tests
INFO - Fetching existing rebase branch
INFO - Branches with commit:
  source/feature-upstream
INFO - Preparing rebase branch
INFO - Merging upstream/feature-upstream into feature-2
INFO - Performing rebase
INFO - Merge base of source/feature-upstream and dest/feature-2: a2df0ad56e71518d9248ca1afec282d90e599663
INFO - Merges on ancestry-path from merge_base=(a2df0ad56e71518d9248ca1afec282d90e599663) to dest/feature-2 branch:
INFO - Searching for merge commit from previous rebasebot run to identify downstream commits
INFO - Didn't find last rebase merge commit. Likely this is the first upstream rebase for the
repository. If that's not the case, something is wrong with the last rebase identification.
Using a2df0ad56e71518d9248ca1afec282d90e599663 as cutoff commit
INFO - Cutoff commits: ['^a2df0ad56e71518d9248ca1afec282d90e599663']
INFO - Phase 2 - other downstream commits (2):
953148b2aac78115aebeca08ae7d48398463429b || D: feature-2 commit 1 || danik.klimuk13@gmail.com
059e547de5c6c4c046819be04e0b9d9b0d4c9448 || E: feature-2 adds line 4 || danik.klimuk13@gmail.com
INFO - Total downstream commits: 2
INFO - Picking commit: 953148b2aac78115aebeca08ae7d48398463429b - D: feature-2 commit 1
INFO - Conflict has been resolved. Continue rebase.
INFO - Picking commit: 059e547de5c6c4c046819be04e0b9d9b0d4c9448 - E: feature-2 adds line 4
INFO - Checking for ART pull request
INFO - Existing rebase branch contains changes.
INFO - Checking for existing pull request
INFO - Found existing pull request: "Merge https://github.com/DaniilKl/auto-rebase-script-tests-upstream:feature-upstream (36eb481) into feature-2" https://github.com/DaniilKl/auto-rebase-script-tests/pull/47
INFO - Existing rebase branch needs to be updated.
INFO - Updating pull request title: Merge https://github.com/DaniilKl/auto-rebase-script-tests-upstream:feature-upstream (0490071) into feature-2
INFO - I updated existing rebase PR: https://github.com/DaniilKl/auto-rebase-script-tests/pull/47


So the tool has force-pushed the branch and updated the PR! This is cool! No polluted remote repositories with branches and PRs. The commit D: feature-2 commit 1 is still missing though and the branch feature-2-rebased history still contains the merge commit:

danik in /tmp/for-tests on rebase λ git lol
* bbfed71efe26 (HEAD -> rebase, rebase/feature-2-rebased) E: feature-2 adds line 4
*   76ee4710eed3 merge upstream/feature-upstream into feature-2
|\
| * 0490071a3bc9 (source/feature-upstream, merge-tmp, feature-upstream) B3: feature edits line 2 again
| * 36eb48133c73 B2: feature edits line 1
| * 0c1435ac7013 B1: feature edits line 2 again
| * 9749a2447b0a B: feature edits line2
* | 059e547de5c6 (dest/feature-2) E: feature-2 adds line 4
* | 953148b2aac7 D: feature-2 commit 1
|/
* a2df0ad56e71 (source/master, master) A: base file

The conclusions

Thank you for reading my blog post. As you can see, there is not a lot of solutions for the problem I presented. The openshift-eng/rebasebot is the closest solution. Though, before using it I would:

  1. Check the history after the rebase. I am not sure whether the merge commits this tool creates can cause any troubles.
  2. Add feature to push the state of the rebase on conflict to remote and inform the maintainers about it.
  3. Add more information for maintainers to the PR created after a successful rebase.

On the other hand, the logic is not that complex to create the solution by yourself. In my opinion a Bash script that uses git, GNU tools and curl is enough. And you can create tests for it in BATS.

I have few other side quests for this topic I might write blog posts about. I hope you will enjoy them too :).

To be able to devote oneself completely to something is already a talent.