Repository: flyingcircusio/batou

Issue:
git component can't handle quoted filenames
batou stumbled when it tried to unlink this file (taken from "untracked files" git status porcelain output):

```
D  ".run/Rebuild & Start Backend.run.xml"
?? ".run/Rebuild & Start Backend.run.xml"
```

```
  File "/deployment/.batou/70c14fb1/lib/python3.8/site-packages/batou/component.py", line 320, in deploy
    sub_component.deploy(predict_only)
  File "//deployment/.batou/70c14fb1/lib/python3.8/site-packages/batou/component.py", line 339, in deploy
    self.update()
  File "//deployment/.batou/70c14fb1/lib/python3.8/site-packages/batou/lib/git.py", line 123, in update
    os.unlink(os.path.join(self.target, filepath))
FileNotFoundError: [Errno 2] No such file or directory: '/srv/.../checkout/".run/Rebuild & Start Backend.run.xml"'
```



Discussion:

Patch:
diff --git a/src/batou/lib/git.py b/src/batou/lib/git.py
index d91a232c..d7e08c78 100644
--- a/src/batou/lib/git.py
+++ b/src/batou/lib/git.py
@@ -176,7 +176,20 @@ class Clone(Component):
             "git status --porcelain --untracked-files=all"
         )
         items = (line.split(None, 1) for line in stdout.splitlines())
-        return [filepath for status, filepath in items if status == "??"]
+        untracked_items = [
+            filepath for status, filepath in items if status == "??"
+        ]
+
+        # from the manpage of git-status: If a filename contains whitespace
+        # or other nonprintable characters, that field will be quoted
+        # in the manner of a C string literal
+        # so we need to unquote the filenames
+        def unquote_git(s):
+            if s.startswith('"') and s.endswith('"'):
+                return s[1:-1].encode().decode("unicode-escape")
+            return s
+
+        return [unquote_git(item) for item in untracked_items]
 
     def last_updated(self):
         with self.chdir(self.target):


Test patch:
diff --git a/src/batou/lib/tests/test_git.py b/src/batou/lib/tests/test_git.py
index c54d3f2a..09db8b5d 100644
--- a/src/batou/lib/tests/test_git.py
+++ b/src/batou/lib/tests/test_git.py
@@ -325,3 +325,24 @@ def test_clone_into_workdir_works(root, repos_path, repos_path2):
     root.component += git
     root.component.deploy()
     assert not os.path.exists(root.component.map("asdf"))
+
+
+@pytest.mark.slow
+def test_can_read_untracked_files_correctly(root, repos_path):
+    fun_strings = [
+        "this string has spaces in it",
+        "this string has a ' in it",
+        'this string has a " in it',
+        '"this string has a \' and a " in it"',
+        "this string has a \\ in it",
+        "this string has a \\ and a ' in it",
+        "this string has a \\ and a & in it",
+    ]
+    git = batou.lib.git.Clone(repos_path, branch="master")
+    root.component += git
+    root.component.deploy()
+    for s in fun_strings:
+        with open(root.component.map(s), "w") as f:
+            f.write(s)
+    with git.chdir(git.target):
+        assert set(git.untracked_files()) == set(fun_strings)