Rebase is base

July 2023 · 45 minute read

This is a translation of Git pro mě/Rebase je základ.


Now the notes for creating the repository for rebase is base workshop. The task is clear: Create Python3 package args that contains module evaluator with the ev procedure.

def ev(op, *args):
    """Return value computed from ``args`` based on ``op``.

    Procedure ``ev`` returns:
    - sum of arguments for ``op == "+"``,
    - product of arguments for ``op == "*"``,
    - true if arguments are non-descending for ``op == "<"``,
    - true if arguments are non-increasing for ``op == ">"``.

    :param op: An operator (as string) to be applied to ``args``.
    :param args: The list (of numbers) to apply ``op`` to.
    """
    pass  # TODO

Outputs of the commands often repeat and are long. It’s because I log the commands (including their outputs) as I created the history. I often check the repository state and just scan the commands outputs instead of reading. All the commands are written to the command line prompt in the args directory (repository root).

Table of content:

Repository initialization

Before creating the history the repository needs to be prepared for changes tracing:

$ git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint:   git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint:   git branch -m <name>
Initialized empty Git repository in /full/path/to/args/.git/

and the basic info about the author of the changes needs to be set:

$ git config user.email 'foo@bar.buzz'
$ git config --global user.name 'Foo Bar'

Add license, readme

As the first commit I add license and readme. The license is important for the others to let them reuse the code. I often use the MIT License.

I write readme for myself. I want to know WHAT is the point, because I forget it by tomorrow. Sometimes the writing looks weird, so I add WHY of that. I also add HOW to do the things I do all the time (like how to compile the project). And last but not least, I add WHERE and WHO to write questions and bug reports (communication channel) to the readme file.

$ git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        LICENSE
        README

 nothing added to commit but untracked files present (use "git add" to track)
$ git add LICENSE README
$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   LICENSE
        new file:   README
$ git diff --cached
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..5fd1bda
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2023 Foo Bar
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README b/README
new file mode 100644
index 0000000..4e3d26f
--- /dev/null
+++ b/README
@@ -0,0 +1,2 @@
+Python3 package with the procedure evaluating arguments based on the
+operator.

When I am satisfied with the output of the git diff --cached I add the change to the history:

$ git commit

The default editor is opened, I write short message about what is changed, and close the editor.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
#
# Initial commit
#
# Changes to be committed:
#new file:   LICENSE
#new file:   README
#

Vim can be closed by ZZ (means “shift” and “z” twice). The default editor can be set:

git config --global core.editor 'vim'

The short message is called commit message. It may have multiple lines where the first line starts with capital letter, it is at most 52 characters long, does not ending with a dot, and it is the imperative continuation of the “When applied, this patch will …” sentence. Then single blank line and the rest of the text, saying WHAT and WHY, wrapped to 72 characters. HOW is in the code.

Add license, readme

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
#
# Initial commit
#
# Changes to be committed:
#new file:   LICENSE
#new file:   README
#
$ git commit
[master (root-commit) 8dc1c0a] Add license, readme
 2 files changed, 21 insertions(+)
 create mode 100644 LICENSE
 create mode 100644 README
$ git status
On branch master
nothing to commit, working tree clean

I scan and check the history of the changes:

$ git log
commit 8dc1c0a0e483227c03810acbc6a52882492d0012 (HEAD -> master)
Author: Foo Bar <foo@bar.buzz>
Date:   Mon Jun 26 22:39:23 2023 +0200

    Add license, readme
$ git show
commit 8dc1c0a0e483227c03810acbc6a52882492d0012 (HEAD -> master)
Author: Foo Bar <foo@bar.buzz>
Date:   Mon Jun 26 22:39:23 2023 +0200

    Add license, readme

diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..5fd1bda
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2023 Foo Bar
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README b/README
new file mode 100644
index 0000000..4e3d26f
--- /dev/null
+++ b/README
@@ -0,0 +1,2 @@
+Python3 package with the procedure evaluating arguments based on the
+operator.

Very often, also in other sections, I check the repository with the commands:

git status
git diff
git diff --cached
git log
git show
git logg

The last one is the alias for git log --oneline --graph --decorate --all, which can be set by:

git config --global --add alias.logg 'log --oneline --graph --decorate --all'
$ git logg
* d0dbc16 (HEAD -> master) Add license, readme

Add operator “+”

I add the args Python3 package first.

$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        args/

nothing added to commit but untracked files present (use "git add" to track)
$ tree
.
├── args
│   └── __init__.py
├── LICENSE
└── README

1 directory, 3 files
$ git add args/
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   args/__init__.py
$ git commit
[master d26defa] Add args package
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 args/__init__.py
$ git show
commit d26defa80cf953415b2b826825c2d641fa28960a (HEAD -> master)
Author: Foo Bar <foo@bar.buzz>
Date:   Tue Jun 27 00:56:49 2023 +0200

    Add args Python3 package

diff --git a/args/__init__.py b/args/__init__.py
new file mode 100644
index 0000000..e69de29

Then, I add evaluator module with the ev procedure; no time to loose time, so I add the implementation of the operator "+", too.

$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        args/evaluator.py

 nothing added to commit but untracked files present (use "git add" to track)
$ tree
.
├── args
│   ├── evaluator.py
│   └── __init__.py
├── LICENSE
└── README

1 directory, 4 files
$ cat args/evaluator.py
def ev(op, *args):
    if "+" == op:
        s = 0
        for a in args:
            s += a
        return s
$ git add args/evaluator.py
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   args/evaluator.py
$ git commit
[master 0da38ec] Add evaluator module, operator "+"
1 file changed, 6 insertions(+)
create mode 100644 args/evaluator.py
$ git show
commit 0da38ece7090b024298053cc6ee7fba1c4b05b3d (HEAD -> master)
Author: Foo Bar <foo@bar.buzz>
Date:   Tue Jun 27 01:10:10 2023 +0200

    Add evaluator module, operator "+"

diff --git a/args/evaluator.py b/args/evaluator.py
new file mode 100644
index 0000000..13c2609
--- /dev/null
+++ b/args/evaluator.py
@@ -0,0 +1,6 @@
+def ev(op, *args):
+    if "+" == op:
+        s = 0
+        for a in args:
+            s += a
+        return s

Finally, I add the unit tests …

$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        tests/

 nothing added to commit but untracked files present (use "git add" to track)
$ tree
.
├── args
│   ├── evaluator.py
│   └── __init__.py
├── LICENSE
├── README
└── tests
    └── test_sum.py

2 directories, 5 files
$ cat tests/test_sum.py
from unittest import TestCase

from args.evaluator import ev


class TestEvaluator(TestCase):
    def test_sum(self):
        assert 6 == ev("+", 1, 2, 3)
$ git add tests/
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   tests/test_sum.py
$ git commit
[master 2bd56ca] Add operator "+" unit test
 1 file changed, 8 insertions(+)
 create mode 100644 tests/test_sum.py
$ git show
commit 2bd56ca80495fd03819b7a6fe842136593a2343d (HEAD -> master)
Author: Foo Bar <foo@bar.buzz>
Date:   Tue Jun 27 01:20:38 2023 +0200

    Add operator "+" unit test

diff --git a/tests/test_sum.py b/tests/test_sum.py
new file mode 100644
index 0000000..9699439
--- /dev/null
+++ b/tests/test_sum.py
@@ -0,0 +1,8 @@
+from unittest import TestCase
+
+from args.evaluator import ev
+
+
+class TestEvaluator(TestCase):
+    def test_sum(self):
+        assert 6 == ev("+", 1, 2, 3)
$ python3 -m unittest discover tests
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

… and check the current state of the repository.

$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        args/__pycache__/
        tests/__pycache__/

 nothing added to commit but untracked files present (use "git add" to track)
$ tree
.
├── args
│   ├── evaluator.py
│   ├── __init__.py
│   └── __pycache__
│       ├── evaluator.cpython-39.pyc
│       └── __init__.cpython-39.pyc
├── LICENSE
├── README
└── tests
    ├── __pycache__
    │   └── test_sum.cpython-39.pyc
    └── test_sum.py

4 directories, 8 files
$ git log
commit 2bd56ca80495fd03819b7a6fe842136593a2343d (HEAD -> master)
Author: Foo Bar <foo@bar.buzz>
Date:   Tue Jun 27 01:20:38 2023 +0200

    Add operator "+" unit test

commit 0da38ece7090b024298053cc6ee7fba1c4b05b3d
Author: Foo Bar <foo@bar.buzz>
Date:   Tue Jun 27 01:10:10 2023 +0200

    Add evaluator module, operator "+"

commit fe22dec52bb37dba41e0f211c2c5ab9a0a90720d
Author: Foo Bar <foo@bar.buzz>
Date:   Tue Jun 27 00:56:49 2023 +0200

    Add args Python3 package

commit d0dbc16408ef5bbe2c2949c8a123185ee3e2614c
Author: Foo Bar <foo@bar.buzz>
Date:   Mon Jun 26 22:59:20 2023 +0200

    Add license, readme
$ git show
commit 2bd56ca80495fd03819b7a6fe842136593a2343d (HEAD -> master)
Author: Foo Bar <foo@bar.buzz>
Date:   Tue Jun 27 01:20:38 2023 +0200

    Add operator "+" unit test

diff --git a/tests/test_sum.py b/tests/test_sum.py
new file mode 100644
index 0000000..9699439
--- /dev/null
+++ b/tests/test_sum.py
@@ -0,0 +1,8 @@
+from unittest import TestCase
+
+from args.evaluator import ev
+
+
+class TestEvaluator(TestCase):
+    def test_sum(self):
+        assert 6 == ev("+", 1, 2, 3)
$ git show HEAD^
commit 0da38ece7090b024298053cc6ee7fba1c4b05b3d
Author: Foo Bar <foo@bar.buzz>
Date:   Tue Jun 27 01:10:10 2023 +0200

    Add evaluator module, operator "+"

diff --git a/args/evaluator.py b/args/evaluator.py
new file mode 100644
index 0000000..13c2609
--- /dev/null
+++ b/args/evaluator.py
@@ -0,0 +1,6 @@
+def ev(op, *args):
+    if "+" == op:
+        s = 0
+        for a in args:
+            s += a
+        return s
$ git logg
* 2bd56ca (HEAD -> master) Add operator "+" unit test
* 0da38ec Add evaluator module, operator "+"
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme

The last, I hide the generated files to avoid interfering with them.

$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .gitignore

nothing added to commit but untracked files present (use "git add" to track)
$ cat .gitignore
__pycache__
$ git add .gitignore
$ git commit -m'Add gitignore'
[master 1fe5611] Add gitignore
 1 file changed, 1 insertion(+)
 create mode 100644 .gitignore
$ git show
commit 1fe561194e50072a9895f44dc0ebec84a70b1faf (HEAD -> master)
Author: Foo Bar <foo@bar.buzz>
Date:   Tue Jun 27 01:44:13 2023 +0200

    Add gitignore

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bee8a64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+__pycache__
$ git logg
* 1fe5611 (HEAD -> master) Add gitignore
* 096d3c8 Add operator "+" unit test
* 0da38ec Add evaluator module, operator "+"
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme
$ git status
On branch master
nothing to commit, working tree clean

Add operator “*”

I reorder the history a little bit – when I add Python3 package, it’s clear that there will be some generated files I don’t want to store in the history. I move the commit that adds the .gitignore immediately after the commit that adds the package.

$ git rebase -i d0dbc16

The original history (from top to bottom) …

pick fe22dec Add args Python3 package
pick 0da38ec Add evaluator module, operator "+"
pick 5d73571 Add operator "+" unit test
pick 4450ade Add gitignore

# Rebase d0dbc16..4450ade onto d0dbc16 (4 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

… is changed by reordering the lines.

pick fe22dec Add args Python3 package
pick 4450ade Add gitignore
pick 0da38ec Add evaluator module, operator "+"
pick 5d73571 Add operator "+" unit test

# Rebase d0dbc16..4450ade onto d0dbc16 (4 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
$ git rebase -i d0dbc16
Successfully rebased and updated refs/heads/master.
$ git logg
* ac5242f (HEAD -> master) Add operator "+" unit test
* 4cae1c7 Add evaluator module, operator "+"
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme

It is probably better to put the tests into the single file, where all the operators are tested. I fix the last commit.

$ git mv tests/test_sum.py tests/test_operators.py
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        renamed:    tests/test_sum.py -> tests/test_operators.py
$ git commit --amend

I leave the commit message.

Add operator "+" unit test

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Tue Jun 27 01:20:38 2023 +0200
#
# On branch master
# Changes to be committed:
#new file:   tests/test_operators.py
#
$ git commit --amend
[master fdebe49] Add operator "+" unit test
 Date: Tue Jun 27 01:20:38 2023 +0200
 1 file changed, 8 insertions(+)
 create mode 100644 tests/test_operators.py
$ git status
On branch master
nothing to commit, working tree clean
$ tree
.
├── args
│   ├── evaluator.py
│   ├── __init__.py
│   └── __pycache__
│       ├── evaluator.cpython-39.pyc
│       └── __init__.cpython-39.pyc
├── LICENSE
├── README
└── tests
    ├── __pycache__
    │   └── test_sum.cpython-39.pyc
    └── test_operators.py

4 directories, 8 files

I add the unit tests for the operator "*".

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   tests/test_operators.py

no changes added to commit (use "git add" and/or "git commit -a")
$ git add tests/test_operators.py
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   tests/test_operators.py
$ git diff
$ git diff --cached
diff --git a/tests/test_operators.py b/tests/test_operators.py
index 9699439..b57f0f3 100644
--- a/tests/test_operators.py
+++ b/tests/test_operators.py
@@ -6,3 +6,6 @@ from args.evaluator import ev
 class TestEvaluator(TestCase):
     def test_sum(self):
         assert 6 == ev("+", 1, 2, 3)
+
+    def test_prod(self):
+        assert 6 == ev("*", 1, 2, 3)
$ python3 -m unittest discover tests
F.
======================================================================
FAIL: test_prod (test_operators.TestEvaluator)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/jiri/args/tests/test_operators.py", line 11, in test_prod
    assert 6 == ev("*", 1, 2, 3)
AssertionError

----------------------------------------------------------------------
Ran 2 tests in 0.000s

FAILED (failures=1)
$ git commit -m'Add operator "*" unit test'
[master 865b12b] Add operator "*" unit test
 1 file changed, 3 insertions(+)
$ git logg
* 865b12b (HEAD -> master) Add operator "*" unit test
* 44eda15 Add operator "+" unit test
* 4cae1c7 Add evaluator module, operator "+"
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme

I implement the operator "*".

$ git diff
diff --git a/args/evaluator.py b/args/evaluator.py
index 13c2609..d964dca 100644
--- a/args/evaluator.py
+++ b/args/evaluator.py
@@ -4,3 +4,8 @@ def ev(op, *args):
         for a in args:
             s += a
         return s
+    elif "*" == op:
+        p = 1
+        for a in args:
+            p *= a
+        return p
$ python3 -m unittest discover tests
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
$ git add args/evaluator.py
$ git commit -m'Add operator "*"'
[master d9212c1] Add operator "*"
 1 file changed, 5 insertions(+)
$ git logg
* d9212c1 (HEAD -> master) Add operator "*"
* 865b12b Add operator "*" unit test
* 44eda15 Add operator "+" unit test
* 4cae1c7 Add evaluator module, operator "+"
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme

The Test Driven Development says that the tests are added before the implementation, so I make it correct at least in the history.

$ git rebase -i d0dbc16
pick fe22dec Add args Python3 package
pick 24784c8 Add gitignore
pick 4cae1c7 Add evaluator module, operator "+"
pick 44eda15 Add operator "+" unit test
pick 865b12b Add operator "*" unit test
pick d9212c1 Add operator "*"

# Rebase d0dbc16..d9212c1 onto d0dbc16 (6 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

Reorder the changes (commits):

pick fe22dec Add args Python3 package
pick 24784c8 Add gitignore
pick 44eda15 Add operator "+" unit test
pick 4cae1c7 Add evaluator module, operator "+"
pick 865b12b Add operator "*" unit test
pick d9212c1 Add operator "*"

# Rebase d0dbc16..d9212c1 onto d0dbc16 (6 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

The result of the rebase:

$ git rebase -i d0dbc16
Successfully rebased and updated refs/heads/master.

Actual history:

$ git logg
* 0681891 (HEAD -> master) Add operator "*"
* 03b9ab3 Add operator "*" unit test
* 5ed5048 Add evaluator module, operator "+"
* e77e2a0 Add operator "+" unit test
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme

At the end I check that all tests pass successfully:

$ python3 -m unittest discover tests
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

Add docstrings for “+” and “*” unit tests

I add the docstrings for test_operators module and for test_sum and test_prod methods, and add the changes to the history.

$ git add -p tests/test_operators.py
diff --git a/tests/test_operators.py b/tests/test_operators.py
index b57f0f3..5dfc209 100644
--- a/tests/test_operators.py
+++ b/tests/test_operators.py
@@ -1,3 +1,4 @@
+"""Unit tests for the Evaluator's operators."""
 from unittest import TestCase

 from args.evaluator import ev
(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? y
@@ -5,7 +6,9 @@ from args.evaluator import ev

 class TestEvaluator(TestCase):
     def test_sum(self):
+        """Test the operator "+"."""
         assert 6 == ev("+", 1, 2, 3)

     def test_prod(self):
+        """Test the operator "*"."""
         assert 6 == ev("*", 1, 2, 3)
(2/2) Stage this hunk [y,n,q,a,d,K,g,/,s,e,?]? s
Split into 2 hunks.
@@ -5,6 +6,7 @@

 class TestEvaluator(TestCase):
     def test_sum(self):
+        """Test the operator "+"."""
         assert 6 == ev("+", 1, 2, 3)

     def test_prod(self):
(2/3) Stage this hunk [y,n,q,a,d,K,j,J,g,/,e,?]? y
@@ -8,4 +10,5 @@
         assert 6 == ev("+", 1, 2, 3)

     def test_prod(self):
+        """Test the operator "*"."""
         assert 6 == ev("*", 1, 2, 3)
(3/3) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? n

git add -p allows me to add only some changes of the file. y adds the change (called hunk), n does not add the hunk, and s splits the hunk into the smaller hunks if it’s possible.

By the way, the current state of the repository, particularly the test_operators.py file, nicely shows the difference between the changes ready to be committed (to the history) and those that are not ready.

$ git diff
diff --git a/tests/test_operators.py b/tests/test_operators.py
index a01cdb0..5dfc209 100644
--- a/tests/test_operators.py
+++ b/tests/test_operators.py
@@ -10,4 +10,5 @@ class TestEvaluator(TestCase):
         assert 6 == ev("+", 1, 2, 3)

     def test_prod(self):
+        """Test the operator "*"."""
         assert 6 == ev("*", 1, 2, 3)
$ git diff --cached
diff --git a/tests/test_operators.py b/tests/test_operators.py
index b57f0f3..a01cdb0 100644
--- a/tests/test_operators.py
+++ b/tests/test_operators.py
@@ -1,3 +1,4 @@
+"""Unit tests for the Evaluator's operators."""
 from unittest import TestCase

 from args.evaluator import ev
@@ -5,6 +6,7 @@ from args.evaluator import ev

 class TestEvaluator(TestCase):
     def test_sum(self):
+        """Test the operator "+"."""
         assert 6 == ev("+", 1, 2, 3)

     def test_prod(self):
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   tests/test_operators.py

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   tests/test_operators.py

I add this change to fix the change that already is in the history – docstrings should be a part of the change that adds the module and the test method. Therefore, the commit message looks strange:

$ git commit -m'F e77e2a0'
[master c823f04] F e77e2a0
 1 file changed, 2 insertions(+)

Then I add the second change (commit) that fixes the second commit already present in the history.

$ git add tests/test_operators.py
$ git diff --cached
diff --git a/tests/test_operators.py b/tests/test_operators.py
index a01cdb0..5dfc209 100644
--- a/tests/test_operators.py
+++ b/tests/test_operators.py
@@ -10,4 +10,5 @@ class TestEvaluator(TestCase):
         assert 6 == ev("+", 1, 2, 3)

     def test_prod(self):
+        """Test the operator "*"."""
         assert 6 == ev("*", 1, 2, 3)
$ git commit -m'F 03b9ab3'
[master a7f67a9] F 03b9ab3
 1 file changed, 1 insertion(+)
$ git status
On branch master
nothing to commit, working tree clean
$ git logg
* a7f67a9 (HEAD -> master) F 03b9ab3
* c823f04 F e77e2a0
* 0681891 Add operator "*"
* 03b9ab3 Add operator "*" unit test
* 5ed5048 Add evaluator module, operator "+"
* e77e2a0 Add operator "+" unit test
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme

Finally, I rewrite the history to include the fixed changes (commits).

$ git rebase -i 24784c8

In the interactive rebase, I change the

pick e77e2a0 Add operator "+" unit test
pick 5ed5048 Add evaluator module, operator "+"
pick 03b9ab3 Add operator "*" unit test
pick 0681891 Add operator "*"
pick c823f04 F e77e2a0
pick a7f67a9 F 03b9ab3

# Rebase 24784c8..a7f67a9 onto 24784c8 (6 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

to the

pick e77e2a0 Add operator "+" unit test
f c823f04 F e77e2a0
pick 5ed5048 Add evaluator module, operator "+"
pick 03b9ab3 Add operator "*" unit test
f a7f67a9 F 03b9ab3
pick 0681891 Add operator "*"

# Rebase 24784c8..a7f67a9 onto 24784c8 (6 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

which nicely shows why “strange” commit messages are useful. (F is the shortcut for FIX.)

During the interactive rebase, i.e. rebase -i COMMIT-IDENTIFIER, the text editor with the list of the changes is opened. The changes apply to the COMMIT-IDENTIFIER change (from the history) consecutively from the top to bottom. The changes (commits) are applied because there is pick before each change. Other options that can be put instead of pick are presented as the comment (in the opened editor). I use f (as fixup). f melds the change (commit) into the previous one (the change on the line above) and keeps the original commit message (the message on the previous line). Maybe yet another point of view – during the rebase the pick commits are applied from the top to bottom. When pick commit is followed by the f commit, it’s the same as if git commit --amend is run on the f commit. There may be a sequence of multiple f commits.

$ git rebase -i 24784c8
Successfully rebased and updated refs/heads/master.
$ git status
On branch master
nothing to commit, working tree clean
$ git logg
* f8467dc (HEAD -> master) Add operator "*"
* 4e81de9 Add operator "*" unit test
* 2eaeb68 Add evaluator module, operator "+"
* 0715dc9 Add operator "+" unit test
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme
$ git show 0715dc9
commit 0715dc9c3bae0e38f6e7498c9a363352855b14c6
Author: Foo Bar <foo@bar.buzz>
Date:   Tue Jun 27 01:20:38 2023 +0200

    Add operator "+" unit test

diff --git a/tests/test_operators.py b/tests/test_operators.py
new file mode 100644
index 0000000..a8f199e
--- /dev/null
+++ b/tests/test_operators.py
@@ -0,0 +1,10 @@
+"""Unit tests for the Evaluator's operators."""
+from unittest import TestCase
+
+from args.evaluator import ev
+
+
+class TestEvaluator(TestCase):
+    def test_sum(self):
+        """Test the operator "+"."""
+        assert 6 == ev("+", 1, 2, 3)
$ git show 4e81de9
commit 4e81de9f7c30480f3639e7822f233740f59be746
Author: Foo Bar <foo@bar.buzz>
Date:   Wed Jun 28 01:11:46 2023 +0200

    Add operator "*" unit test

diff --git a/tests/test_operators.py b/tests/test_operators.py
index a8f199e..5dfc209 100644
--- a/tests/test_operators.py
+++ b/tests/test_operators.py
@@ -8,3 +8,7 @@ class TestEvaluator(TestCase):
     def test_sum(self):
         """Test the operator "+"."""
         assert 6 == ev("+", 1, 2, 3)
+
+    def test_prod(self):
+        """Test the operator "*"."""
+        assert 6 == ev("*", 1, 2, 3)

Alternative history

Currently, the history is straightforward and I can travel in the history.

$ git logg
* f8467dc (HEAD -> master) Add operator "*"
* 4e81de9 Add operator "*" unit test
* 2eaeb68 Add evaluator module, operator "+"
* 0715dc9 Add operator "+" unit test
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme
$ tree
.
├── args
│   ├── evaluator.py
│   ├── __init__.py
│   └── __pycache__
│       ├── evaluator.cpython-39.pyc
│       └── __init__.cpython-39.pyc
├── LICENSE
├── README
└── tests
    ├── __pycache__
    │   ├── test_operators.cpython-39.pyc
    │   └── test_sum.cpython-39.pyc
    └── test_operators.py

4 directories, 9 files
$ git checkout 24784c8
Note: switching to '24784c8'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 24784c8 Add gitignore
$ git logg
* f8467dc (master) Add operator "*"
* 4e81de9 Add operator "*" unit test
* 2eaeb68 Add evaluator module, operator "+"
* 0715dc9 Add operator "+" unit test
* 24784c8 (HEAD) Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme
$ tree
.
├── args
│   ├── __init__.py
│   └── __pycache__
│       ├── evaluator.cpython-39.pyc
│       └── __init__.cpython-39.pyc
├── LICENSE
├── README
└── tests
    └── __pycache__
        ├── test_operators.cpython-39.pyc
        └── test_sum.cpython-39.pyc

4 directories, 7 files

HEAD means the change (commit) where in the history I currently am. master is a branch – a name for a change (commit), from which I create the history (by adding new changes). There may be multiple branches, so there may be multiple changes from which I create the (alternative) history.

$ git checkout master
Previous HEAD position was 24784c8 Add gitignore
Switched to branch 'master'
$ git logg
* f8467dc (HEAD -> master) Add operator "*"
* 4e81de9 Add operator "*" unit test
* 2eaeb68 Add evaluator module, operator "+"
* 0715dc9 Add operator "+" unit test
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme
$ git branch add-sum-and-prod-operators
$ git logg
* f8467dc (HEAD -> master, add-sum-and-prod-operators) Add operator "*"
* 4e81de9 Add operator "*" unit test
* 2eaeb68 Add evaluator module, operator "+"
* 0715dc9 Add operator "+" unit test
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme

I can set the branch to the arbitrary change in the history. (WARNING! If there are changes in the repository that can be seen by git diff or git diff --cached, those will be lost!)

$ git reset --hard 24784c8
HEAD is now at 24784c8 Add gitignore
$ git logg
* f8467dc (add-sum-and-prod-operators) Add operator "*"
* 4e81de9 Add operator "*" unit test
* 2eaeb68 Add evaluator module, operator "+"
* 0715dc9 Add operator "+" unit test
* 24784c8 (HEAD -> master) Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme

I can switch between the branches as I can switch between the changes (commits) in the history. At the end, the branches are names of the changes.

$ git checkout add-sum-and-prod-operators
Switched to branch 'add-sum-and-prod-operators'
$ git logg
* f8467dc (HEAD -> add-sum-and-prod-operators) Add operator "*"
* 4e81de9 Add operator "*" unit test
* 2eaeb68 Add evaluator module, operator "+"
* 0715dc9 Add operator "+" unit test
* 24784c8 (master) Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme
$ git checkout master
Switched to branch 'master'
$ git logg
* f8467dc (add-sum-and-prod-operators) Add operator "*"
* 4e81de9 Add operator "*" unit test
* 2eaeb68 Add evaluator module, operator "+"
* 0715dc9 Add operator "+" unit test
* 24784c8 (HEAD -> master) Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme

And finally, I can merge the branches.

$ git merge --no-ff add-sum-and-prod-operators
Merge branch 'add-sum-and-prod-operators'
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
$ git merge --no-ff add-sum-and-prod-operators
Merge made by the 'recursive' strategy.
 args/evaluator.py       | 11 +++++++++++
 tests/test_operators.py | 14 ++++++++++++++
 2 files changed, 25 insertions(+)
 create mode 100644 args/evaluator.py
 create mode 100644 tests/test_operators.py
$ git logg
*   8febb4c (HEAD -> master) Merge branch 'add-sum-and-prod-operators'
|\
| * f8467dc (add-sum-and-prod-operators) Add operator "*"
| * 4e81de9 Add operator "*" unit test
| * 2eaeb68 Add evaluator module, operator "+"
| * 0715dc9 Add operator "+" unit test
|/
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme

It is more precise to say that I can merge one branch (add-sum-and-prod-operators in this case) into the other one, the current one (master).

This workflow is typically used for adding new functionality to the existing codebase. When the temporary branch (add-sum-and-prod-operators in this case) is merged to the main development branch (master), it is not needed anymore and can be deleted.

$ git branch -d add-sum-and-prod-operators
Deleted branch add-sum-and-prod-operators (was f8467dc).
$ git logg
*   8febb4c (HEAD -> master) Merge branch 'add-sum-and-prod-operators'
|\
| * f8467dc Add operator "*"
| * 4e81de9 Add operator "*" unit test
| * 2eaeb68 Add evaluator module, operator "+"
| * 0715dc9 Add operator "+" unit test
|/
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme

Conflicting history

What is left is to add operators "<" and ">". I will do the development in new branches, so I will create two alternative histories. Each branch will contain two changes (commits) – first for the unit test, the second for the implementation.

$ git branch add-non-desc-op
$ git checkout add-non-desc-op
Switched to branch 'add-non-desc-op'
$ git logg
*   8febb4c (HEAD -> add-non-desc-op, master) Merge branch 'add-sum-and-prod-operators'
|\
| * f8467dc Add operator "*"
| * 4e81de9 Add operator "*" unit test
| * 2eaeb68 Add evaluator module, operator "+"
| * 0715dc9 Add operator "+" unit test
|/
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme
$ git diff
diff --git a/tests/test_operators.py b/tests/test_operators.py
index 5dfc209..0056ab1 100644
--- a/tests/test_operators.py
+++ b/tests/test_operators.py
@@ -12,3 +12,9 @@ class TestEvaluator(TestCase):
     def test_prod(self):
         """Test the operator "*"."""
         assert 6 == ev("*", 1, 2, 3)
+
+    def test_non_desc(self):
+        """Test the operator "<"."""
+        assert ev("<", 1, 2, 3)
+        assert ev("<", 1, 1, 3)
+        assert not ev("<", 3, 2, 1)
$ git add tests/test_operators.py
$ git commit -m'Add operator "<" unit test'
[add-non-desc-op 4d80713] Add operator "<" unit test
 1 file changed, 6 insertions(+)
$ git logg
* 4d80713 (HEAD -> add-non-desc-op) Add operator "<" unit test
*   8febb4c (master) Merge branch 'add-sum-and-prod-operators'
|\
| * f8467dc Add operator "*"
| * 4e81de9 Add operator "*" unit test
| * 2eaeb68 Add evaluator module, operator "+"
| * 0715dc9 Add operator "+" unit test
|/
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme
$ python3 -m unittest discover tests
F..
======================================================================
FAIL: test_non_desc (test_operators.TestEvaluator)
Test the operator "<".
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/jiri/args/tests/test_operators.py", line 18, in test_non_desc
    assert ev("<", 1, 2, 3)
AssertionError

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1)

Oh, I forgot to put the instructions on how to run the tests into the README. And that should be in the main development branch.

$ git checkout master
Switched to branch 'master'
$ git diff
diff --git a/README b/README
index 4e3d26f..470c1a1 100644
--- a/README
+++ b/README
@@ -1,2 +1,6 @@
 Python3 package with the procedure evaluating arguments based on the
 operator.
+
+Run tests:
+
+       python3 -m unittest discover tests
$ git add README
$ git commit -m'Update README with how to run tests'
[master 250bc4e] Update README with how to run tests
 1 file changed, 4 insertions(+)
$ git logg
* 250bc4e (HEAD -> master) Update README with how to run tests
| * 4d80713 (add-non-desc-op) Add operator "<" unit test
|/
*   8febb4c Merge branch 'add-sum-and-prod-operators'
|\
| * f8467dc Add operator "*"
| * 4e81de9 Add operator "*" unit test
| * 2eaeb68 Add evaluator module, operator "+"
| * 0715dc9 Add operator "+" unit test
|/
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme

And back to the operator "<".

$ git checkout add-non-desc-op
Switched to branch 'add-non-desc-op'
$ git logg
* 250bc4e (master) Update README with how to run tests
| * 4d80713 (HEAD -> add-non-desc-op) Add operator "<" unit test
|/
*   8febb4c Merge branch 'add-sum-and-prod-operators'
|\
| * f8467dc Add operator "*"
| * 4e81de9 Add operator "*" unit test
| * 2eaeb68 Add evaluator module, operator "+"
| * 0715dc9 Add operator "+" unit test
|/
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme
$ git diff
diff --git a/args/evaluator.py b/args/evaluator.py
index d964dca..1397bec 100644
--- a/args/evaluator.py
+++ b/args/evaluator.py
@@ -9,3 +9,8 @@ def ev(op, *args):
         for a in args:
             p *= a
         return p
+    elif "<" == op:
+        for i in range(1, len(args)):
+            if args[i - 1] > args[i]:
+                return False
+        return True
$ git commit -m'Implement operator "<"'
On branch add-non-desc-op
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   args/evaluator.py

no changes added to commit (use "git add" and/or "git commit -a")

Ugh, I forgot to add the file.

$ git add args/evaluator.py
$ git commit -m'Implement operator "<"'
[add-non-desc-op 715cfee] Implement operator "<"
 1 file changed, 5 insertions(+)
$ git logg
* 715cfee (HEAD -> add-non-desc-op) Implement operator "<"
* 4d80713 Add operator "<" unit test
| * 250bc4e (master) Update README with how to run tests
|/
*   8febb4c Merge branch 'add-sum-and-prod-operators'
|\
| * f8467dc Add operator "*"
| * 4e81de9 Add operator "*" unit test
| * 2eaeb68 Add evaluator module, operator "+"
| * 0715dc9 Add operator "+" unit test
|/
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme
$ python3 -m unittest discover tests
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

Finally, the operator ">". The branch should start where the branch for the operator "<" starts.

$ git branch add-non-incr-op 8febb4c
$ git checkout add-non-incr-op
Switched to branch 'add-non-incr-op'
$ git logg
* 715cfee (add-non-desc-op) Implement operator "<"
* 4d80713 Add operator "<" unit test
| * 250bc4e (master) Update README with how to run tests
|/
*   8febb4c (HEAD -> add-non-incr-op) Merge branch 'add-sum-and-prod-operators'
|\
| * f8467dc Add operator "*"
| * 4e81de9 Add operator "*" unit test
| * 2eaeb68 Add evaluator module, operator "+"
| * 0715dc9 Add operator "+" unit test
|/
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme
$ git diff
diff --git a/args/evaluator.py b/args/evaluator.py
index d964dca..adfe8e2 100644
--- a/args/evaluator.py
+++ b/args/evaluator.py
@@ -9,3 +9,8 @@ def ev(op, *args):
         for a in args:
             p *= a
         return p
+    elif ">" == op:
+        for i in range(1, len(args)):
+            if args[i - 1] < args[i]:
+                return False
+        return True
diff --git a/tests/test_operators.py b/tests/test_operators.py
index 5dfc209..4c75980 100644
--- a/tests/test_operators.py
+++ b/tests/test_operators.py
@@ -12,3 +12,9 @@ class TestEvaluator(TestCase):
     def test_prod(self):
         """Test the operator "*"."""
         assert 6 == ev("*", 1, 2, 3)
+
+    def test_non_incr(self):
+        """Test the operator ">"."""
+        assert ev(">", 3, 2, 1)
+        assert ev(">", 3, 2, 2)
+        assert not ev(">", 1, 2, 3)
$ git add -p
diff --git a/args/evaluator.py b/args/evaluator.py
index d964dca..adfe8e2 100644
--- a/args/evaluator.py
+++ b/args/evaluator.py
@@ -9,3 +9,8 @@ def ev(op, *args):
         for a in args:
             p *= a
         return p
+    elif ">" == op:
+        for i in range(1, len(args)):
+            if args[i - 1] < args[i]:
+                return False
+        return True
(1/1) Stage this hunk [y,n,q,a,d,e,?]? n

diff --git a/tests/test_operators.py b/tests/test_operators.py
index 5dfc209..4c75980 100644
--- a/tests/test_operators.py
+++ b/tests/test_operators.py
@@ -12,3 +12,9 @@ class TestEvaluator(TestCase):
     def test_prod(self):
         """Test the operator "*"."""
         assert 6 == ev("*", 1, 2, 3)
+
+    def test_non_incr(self):
+        """Test the operator ">"."""
+        assert ev(">", 3, 2, 1)
+        assert ev(">", 3, 2, 2)
+        assert not ev(">", 1, 2, 3)
(1/1) Stage this hunk [y,n,q,a,d,e,?]? y
$ git diff --cached
diff --git a/tests/test_operators.py b/tests/test_operators.py
index 5dfc209..4c75980 100644
--- a/tests/test_operators.py
+++ b/tests/test_operators.py
@@ -12,3 +12,9 @@ class TestEvaluator(TestCase):
     def test_prod(self):
         """Test the operator "*"."""
         assert 6 == ev("*", 1, 2, 3)
+
+    def test_non_incr(self):
+        """Test the operator ">"."""
+        assert ev(">", 3, 2, 1)
+        assert ev(">", 3, 2, 2)
+        assert not ev(">", 1, 2, 3)
$ git commit -m'Add operator ">" unit test'
[add-non-incr-op 676ab0f] Add operator ">" unit test
 1 file changed, 6 insertions(+)
$ git diff
diff --git a/args/evaluator.py b/args/evaluator.py
index d964dca..adfe8e2 100644
--- a/args/evaluator.py
+++ b/args/evaluator.py
@@ -9,3 +9,8 @@ def ev(op, *args):
         for a in args:
             p *= a
         return p
+    elif ">" == op:
+        for i in range(1, len(args)):
+            if args[i - 1] < args[i]:
+                return False
+        return True
$ git add args/evaluator.py
$ git commit -m'Implement operator ">"'
[add-non-incr-op 7d1e491] Implement operator ">"
 1 file changed, 5 insertions(+)
$ git logg
* 7d1e491 (HEAD -> add-non-incr-op) Implement operator ">"
* 676ab0f Add operator ">" unit test
| * 715cfee (add-non-desc-op) Implement operator "<"
| * 4d80713 Add operator "<" unit test
|/
| * 250bc4e (master) Update README with how to run tests
|/
*   8febb4c Merge branch 'add-sum-and-prod-operators'
|\
| * f8467dc Add operator "*"
| * 4e81de9 Add operator "*" unit test
| * 2eaeb68 Add evaluator module, operator "+"
| * 0715dc9 Add operator "+" unit test
|/
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme

There is nothing more left than merging the temporary development branches into the main development branch. Everyone who creates branches should understand that the goal is to merge.

$ git checkout master
Switched to branch 'master'
$ git logg
* 7d1e491 (add-non-incr-op) Implement operator ">"
* 676ab0f Add operator ">" unit test
| * 715cfee (add-non-desc-op) Implement operator "<"
| * 4d80713 Add operator "<" unit test
|/
| * 250bc4e (HEAD -> master) Update README with how to run tests
|/
*   8febb4c Merge branch 'add-sum-and-prod-operators'
|\
| * f8467dc Add operator "*"
| * 4e81de9 Add operator "*" unit test
| * 2eaeb68 Add evaluator module, operator "+"
| * 0715dc9 Add operator "+" unit test
|/
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme
$ git merge --no-ff add-non-desc-op add-non-incr-op
Trying simple merge with add-non-desc-op
Trying simple merge with add-non-incr-op
Simple merge did not work, trying automatic merge.
Auto-merging args/evaluator.py
ERROR: content conflict in args/evaluator.py
Auto-merging tests/test_operators.py
ERROR: content conflict in tests/test_operators.py
fatal: merge program failed
Automatic merge failed; fix conflicts and then commit the result.

Conflict is when Git does not know how to join alternative histories. In this case it’s easy because I want the both operators and their unit tests.

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   args/evaluator.py
        both modified:   tests/test_operators.py

no changes added to commit (use "git add" and/or "git commit -a")
$ git diff
diff --cc args/evaluator.py
index 1397bec,adfe8e2..0000000
--- a/args/evaluator.py
+++ b/args/evaluator.py
@@@ -9,8 -9,8 +9,14 @@@ def ev(op, *args)
          for a in args:
              p *= a
          return p
++<<<<<<< .merge_file_w7odEj
 +    elif "<" == op:
 +        for i in range(1, len(args)):
 +            if args[i - 1] > args[i]:
++=======
+     elif ">" == op:
+         for i in range(1, len(args)):
+             if args[i - 1] < args[i]:
++>>>>>>> .merge_file_sUueSl
                  return False
          return True
diff --cc tests/test_operators.py
index 0056ab1,4c75980..0000000
--- a/tests/test_operators.py
+++ b/tests/test_operators.py
@@@ -13,8 -13,8 +13,16 @@@ class TestEvaluator(TestCase)
          """Test the operator "*"."""
          assert 6 == ev("*", 1, 2, 3)

++<<<<<<< .merge_file_PX9087
 +    def test_non_desc(self):
 +        """Test the operator "<"."""
 +        assert ev("<", 1, 2, 3)
 +        assert ev("<", 1, 1, 3)
 +        assert not ev("<", 3, 2, 1)
++=======
+     def test_non_incr(self):
+         """Test the operator ">"."""
+         assert ev(">", 3, 2, 1)
+         assert ev(">", 3, 2, 2)
+         assert not ev(">", 1, 2, 3)
++>>>>>>> .merge_file_Tlr21b
$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   args/evaluator.py
        both modified:   tests/test_operators.py

no changes added to commit (use "git add" and/or "git commit -a")
$ git diff
diff --cc args/evaluator.py
index 1397bec,adfe8e2..0000000
--- a/args/evaluator.py
+++ b/args/evaluator.py
@@@ -9,8 -9,8 +9,13 @@@ def ev(op, *args)
          for a in args:
              p *= a
          return p
 +    elif "<" == op:
 +        for i in range(1, len(args)):
 +            if args[i - 1] > args[i]:
 +                return False
 +        return True
+     elif ">" == op:
+         for i in range(1, len(args)):
+             if args[i - 1] < args[i]:
+                 return False
+         return True
diff --cc tests/test_operators.py
index 0056ab1,4c75980..0000000
--- a/tests/test_operators.py
+++ b/tests/test_operators.py
@@@ -13,8 -13,8 +13,14 @@@ class TestEvaluator(TestCase)
          """Test the operator "*"."""
          assert 6 == ev("*", 1, 2, 3)

 +    def test_non_desc(self):
 +        """Test the operator "<"."""
 +        assert ev("<", 1, 2, 3)
 +        assert ev("<", 1, 1, 3)
 +        assert not ev("<", 3, 2, 1)
++
+     def test_non_incr(self):
+         """Test the operator ">"."""
+         assert ev(">", 3, 2, 1)
+         assert ev(">", 3, 2, 2)
+         assert not ev(">", 1, 2, 3)
$ python3 -m unittest discover tests
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK
$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   args/evaluator.py
        both modified:   tests/test_operators.py

no changes added to commit (use "git add" and/or "git commit -a")
$ git add args/evaluator.py tests/test_operators.py
$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:
        modified:   args/evaluator.py
        modified:   tests/test_operators.py
$ git diff --cached
diff --git a/args/evaluator.py b/args/evaluator.py
index d964dca..b00bfab 100644
--- a/args/evaluator.py
+++ b/args/evaluator.py
@@ -9,3 +9,13 @@ def ev(op, *args):
         for a in args:
             p *= a
         return p
+    elif "<" == op:
+        for i in range(1, len(args)):
+            if args[i - 1] > args[i]:
+                return False
+        return True
+    elif ">" == op:
+        for i in range(1, len(args)):
+            if args[i - 1] < args[i]:
+                return False
+        return True
diff --git a/tests/test_operators.py b/tests/test_operators.py
index 5dfc209..114275b 100644
--- a/tests/test_operators.py
+++ b/tests/test_operators.py
@@ -12,3 +12,15 @@ class TestEvaluator(TestCase):
     def test_prod(self):
         """Test the operator "*"."""
         assert 6 == ev("*", 1, 2, 3)
+
+    def test_non_desc(self):
+        """Test the operator "<"."""
+        assert ev("<", 1, 2, 3)
+        assert ev("<", 1, 1, 3)
+        assert not ev("<", 3, 2, 1)
+
+    def test_non_incr(self):
+        """Test the operator ">"."""
+        assert ev(">", 3, 2, 1)
+        assert ev(">", 3, 2, 2)
+        assert not ev(">", 1, 2, 3)
$ git merge --continue
Merge branches 'add-non-desc-op' and 'add-non-incr-op'

# Conflicts:
#args/evaluator.py
#tests/test_operators.py
#
# It looks like you may be committing a merge.
# If this is not correct, please run
#git update-ref -d MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#modified:   args/evaluator.py
#modified:   tests/test_operators.py
#
$ git merge --continue
[master 3ef21b4] Merge branches 'add-non-desc-op' and 'add-non-incr-op'
$ git status
On branch master
nothing to commit, working tree clean
$ git logg
*-.   3ef21b4 (HEAD -> master) Merge branches 'add-non-desc-op' and 'add-non-incr-op'
|\ \
| | * 7d1e491 (add-non-incr-op) Implement operator ">"
| | * 676ab0f Add operator ">" unit test
| * | 715cfee (add-non-desc-op) Implement operator "<"
| * | 4d80713 Add operator "<" unit test
| |/
* / 250bc4e Update README with how to run tests
|/
*   8febb4c Merge branch 'add-sum-and-prod-operators'
|\
| * f8467dc Add operator "*"
| * 4e81de9 Add operator "*" unit test
| * 2eaeb68 Add evaluator module, operator "+"
| * 0715dc9 Add operator "+" unit test
|/
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme
$ python3 -m unittest discover tests
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

Delete temporary development branches.

$ git branch -d add-non-desc-op add-non-incr-op
Deleted branch add-non-desc-op (was 715cfee).
Deleted branch add-non-incr-op (was 7d1e491).
$ git logg
*-.   3ef21b4 (HEAD -> master) Merge branches 'add-non-desc-op' and 'add-non-incr-op'
|\ \
| | * 7d1e491 Implement operator ">"
| | * 676ab0f Add operator ">" unit test
| * | 715cfee Implement operator "<"
| * | 4d80713 Add operator "<" unit test
| |/
* / 250bc4e Update README with how to run tests
|/
*   8febb4c Merge branch 'add-sum-and-prod-operators'
|\
| * f8467dc Add operator "*"
| * 4e81de9 Add operator "*" unit test
| * 2eaeb68 Add evaluator module, operator "+"
| * 0715dc9 Add operator "+" unit test
|/
* 24784c8 Add gitignore
* fe22dec Add args Python3 package
* d0dbc16 Add license, readme
$ git show
commit 3ef21b4a5bb92bd014838709945c7fcf89341fd6 (HEAD -> master)
Merge: 250bc4e 715cfee 7d1e491
Author: Foo Bar <foo@bar.buzz>
Date:   Thu Jun 29 00:48:53 2023 +0200

    Merge branches 'add-non-desc-op' and 'add-non-incr-op'

diff --cc args/evaluator.py
index d964dca,1397bec,adfe8e2..b00bfab
--- a/args/evaluator.py
+++ b/args/evaluator.py
@@@@ -9,3 -9,8 -9,8 +9,13 @@@@ def ev(op, *args)
           for a in args:
               p *= a
           return p
+ +    elif "<" == op:
+ +        for i in range(1, len(args)):
+ +            if args[i - 1] > args[i]:
+ +                return False
+ +        return True
++     elif ">" == op:
++         for i in range(1, len(args)):
++             if args[i - 1] < args[i]:
++                 return False
++         return True
diff --cc tests/test_operators.py
index 5dfc209,0056ab1,4c75980..114275b
--- a/tests/test_operators.py
+++ b/tests/test_operators.py
@@@@ -12,3 -12,9 -12,9 +12,15 @@@@ class TestEvaluator(TestCase)
       def test_prod(self):
           """Test the operator "*"."""
           assert 6 == ev("*", 1, 2, 3)
+
+ +    def test_non_desc(self):
+ +        """Test the operator "<"."""
+ +        assert ev("<", 1, 2, 3)
+ +        assert ev("<", 1, 1, 3)
+ +        assert not ev("<", 3, 2, 1)
+++
++     def test_non_incr(self):
++         """Test the operator ">"."""
++         assert ev(">", 3, 2, 1)
++         assert ev(">", 3, 2, 2)
++         assert not ev(">", 1, 2, 3)
$ git status
On branch master
nothing to commit, working tree clean

Commands used

Repository initialization:

$ git init
$ git config user.email 'foo@bar.buzz'
$ git config --global user.name 'Foo Bar'

Adding files, repository exploration, editor and alias setup:

$ git status
$ git add FILENAME
$ git diff --cached
$ git commit
$ git config --global core.editor 'vim'
$ git log
$ git show
$ git log --oneline --graph --decorate --all
$ git config --global --add alias.logg 'log --oneline --graph --decorate --all'
$ git logg
$ python3 -m unittest discover tests
$ git show HEAD^
$ git commit -m'When applied, this commit will ... (52 characters)'

Reordering and fixing the history:

$ git rebase -i COMMIT-IDENTIFIER
$ git mv OLD-FILENAME NEW-FILENAME
$ git commit --amend
$ git add -p FILENAME
$ git commit -m'F COMMIT-IDENTIFIER'

Alternative history (branch):

$ git checkout COMMIT-IDENTIFIER
$ git branch NEW-BRANCH-NAME
$ git reset --hard COMMIT-IDENTIFIER
$ git checkout BRANCH-NAME
$ git merge --no-ff BRANCH-NAME
$ git branch -d BRANCH-NAME

Conflicting history:

$ git branch NEW-BRANCH-NAME COMMIT-IDENTIFIER
$ git add -p
$ git merge --no-ff FIRST-BRANCH-NAME SECOND-BRANCH-NAME
$ git merge --continue
$ git branch -d FIRST-BRANCH-NAME SECOND-BRANCH-NAME