diff --git a/qutil/git.py b/qutil/git.py
new file mode 100644
index 0000000000000000000000000000000000000000..c129f3b460fb440d5e85b36ab65b53395b31871f
--- /dev/null
+++ b/qutil/git.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Mon Aug 14 18:36:43 2023
+
+@author: Simon Humpohl
+"""
+from dataclasses import dataclass
+from pathlib import Path
+import logging
+import tkinter
+from subprocess import check_output, check_call
+
+try:
+    import git
+except ImportError:
+    git = None
+
+
+@dataclass
+class SimpleRepo:
+    """Re-implementation of the GitPython functionality that we use"""
+    repo_path: Path
+    
+    @property
+    def name(self):
+        return self.repo_path.name
+    
+    def get_status(self) -> str:
+        """Returns modified/untracked files and submodules"""
+        check_output(['git', 'status', '--short'], cwd=self.repo_path)
+    
+    def add_all(self):
+        check_call(['git', 'add', '--all'], cwd=self.repo_path)
+    
+    def commit_staged_changes(self, msg):
+        check_call(['git', 'commit', '-m', msg], cwd=self.repo_path)
+    
+    def get_submodules(self):
+        sms = check_output(['git', 'submodule'], cwd=self.repo_path).splitlines()
+        folders = []
+        for sm in sms.trim():
+            hash_val, folder, *_ = sm.trim().split()
+            folders.append(SimpleRepo(Path(folder).absolute()))
+        return folders
+    
+    def current_hash(self):
+        raise NotImplementedError('todo')
+
+
+def auto_commit(repo: SimpleRepo):
+    msg = 'Automatic commit. Lets hope this is nothing that needs explanation.'
+    repo.add_all()
+    repo.commit_staged_changes(msg)    
+
+
+def manual_commit(repo: SimpleRepo, name: str):
+    while status := repo.get_status():
+        title = f'Manual commit: {name}'
+        
+        msg = (f'The repository {repo.name} is dirty. '
+              'Commit, ignore or stash the changes and press OK. '
+              'Pressing cancel will raise a KeyboardInterrupt.\n\n'
+              f'{status}')
+        
+        if not tkinter.messagebox.askokcancel(title, msg):
+            raise KeyboardInterrupt(name)
+
+
+class GitRules:
+    auto_commit: set
+    auto_push: dict
+    
+    def process(self, repo: SimpleRepo):
+        if not repo.get_status():
+            return
+        
+        for sm in repo.get_submodules():
+            self.process(sm)
+            
+        if not repo.get_status():
+            return
+        
+        if repo.repo_path in auto_commit:
+            auto_commit(repo)
+        else:
+            manual_commit(repo)