Repository: VirtusLab/git-machete

Issue:
Line numbers displayed in errors refer to non-empty lines, not all lines
.git/machete file:

```
master
  develop


  develop
```

The empty lines lead to an error in `git machete status`:

`.git/machete, line 3: branch develop re-appears in the tree definition. Edit the definition file manually with git machete edit`

while it should say:

`.git/machete, line 5: branch develop re-appears in the tree definition. Edit the definition file manually with git machete edit`

This is due to how `read_definition_file` is implemented, should be pretty easy to fix.

Discussion:

Patch:
diff --git a/git_machete/cmd.py b/git_machete/cmd.py
index 03f722b..9772a79 100644
--- a/git_machete/cmd.py
+++ b/git_machete/cmd.py
@@ -89,7 +89,7 @@ class MacheteClient:
 
     def read_definition_file(self, verify_branches: bool = True) -> None:
         with open(self._definition_file_path) as f:
-            lines: List[str] = [line.rstrip() for line in f.readlines() if not line.isspace()]
+            lines: List[str] = [line.rstrip() for line in f.readlines()]
 
         at_depth = {}
         last_depth = -1
@@ -98,6 +98,8 @@ class MacheteClient:
 
         invalid_branches: List[str] = []
         for index, line in enumerate(lines):
+            if line == "" or line.isspace():
+                continue
             prefix = "".join(itertools.takewhile(str.isspace, line))
             if prefix and not self.__indent:
                 self.__indent = prefix
@@ -1819,7 +1821,6 @@ def launch(orig_args: List[str]) -> None:
 
     cli_opts = CommandLineOptions()
     git = GitContext(cli_opts)
-    machete_client = MacheteClient(cli_opts, git)
 
     if sys.version_info.major == 2 or (sys.version_info.major == 3 and sys.version_info.minor < 6):
         version_str = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
@@ -1967,6 +1968,7 @@ def launch(orig_args: List[str]) -> None:
             return in_args[0]
 
     try:
+        machete_client = MacheteClient(cli_opts, git)
         cmd = None
         # Let's first extract the common options like `--help` or `--verbose` that might appear BEFORE the command,
         # as in e.g. `git machete --verbose status`.


Test patch:
diff --git a/git_machete/tests/functional/test_machete.py b/git_machete/tests/functional/test_machete.py
index f549acf..fb587a0 100644
--- a/git_machete/tests/functional/test_machete.py
+++ b/git_machete/tests/functional/test_machete.py
@@ -10,8 +10,10 @@ from contextlib import redirect_stdout
 from typing import Iterable
 
 from git_machete import cmd
+from git_machete.exceptions import MacheteException
 from git_machete.git_operations import GitContext
 from git_machete.options import CommandLineOptions
+from git_machete.utils import fmt
 
 cli_opts: CommandLineOptions = CommandLineOptions()
 git: GitContext = GitContext(cli_opts)
@@ -92,6 +94,12 @@ class MacheteTester(unittest.TestCase):
                 git.flush_caches()
             return out.getvalue()
 
+    @staticmethod
+    def rewrite_definition_file(new_body: str) -> None:
+        definition_file_path = git.get_git_subpath("machete")
+        with open(os.path.join(os.getcwd(), definition_file_path), 'w') as def_file:
+            def_file.writelines(new_body)
+
     def assert_command(self, cmds: Iterable[str], expected_result: str) -> None:
         self.assertEqual(self.launch_command(*cmds), self.adapt(expected_result))
 
@@ -175,6 +183,25 @@ class MacheteTester(unittest.TestCase):
             """,
         )
 
+    def test_branch_reappers_in_definition(self) -> None:
+        body: str = \
+            """master
+            \tdevelop
+            \t\n
+            develop
+            """
+        expected_error_msg: str = fmt('.git/machete, line 5: branch `develop` re-appears in the tree definition. Edit the definition file manually with `git machete edit`')
+
+        self.repo_sandbox.new_branch("root")
+        self.rewrite_definition_file(body)
+
+        machete_client = cmd.MacheteClient(cli_opts, git)  # Only to workaround sys.exit while calling launch(['status'])
+        try:
+            machete_client.read_definition_file()
+        except MacheteException as e:
+            if e.parameter != expected_error_msg:
+                self.fail(f'Actual Exception message: {e} \nis not equal to expected message: {expected_error_msg}')
+
     def test_show(self) -> None:
         self.setup_discover_standard_tree()