Search code examples
gitworkflow

Prevent accidental direct edit or commit on git main branch


Since I am still learning, sometimes when I'm using I just forget and edit / commit a file directly to main.

I don't like having to roll the change back and re-commit on a non-main git branch. I don't want to rebase or merge my main branch into development prematurely.

Is there something I can configure in my git client so it will stop me before I make a direct edit or commit to main? FYI, this is a occupational problem that may be foreign to some; in my job, git / vim / python are intimately woven together in this tapestry and I cannot expect everyone to get all the nuances of the relationships.


Solution

  • Is there something I can configure in my git client so it will stop me before I make a direct commit to main?

    I am not sure about configuring a git client to do that; as one alternative you can wrap vim with a script such that it checks the git branch before editing.

    warn on git main

    I wrote a vim wrapper in python to do the checks for you.

    python wrapper to check for edit git main

    Save this and make it executable; /usr/bin/env is native to Linux and WSL... I'm not sure about running it native to a Windows filesystem.

    #!/usr/bin/env python
    from subprocess import run
    import locale
    import shutil
    import shlex
    import sys
    import os
    
    
    class bcolors:
        HEADER = '\033[95m'
        OKBLUE = '\033[94m'
        OKCYAN = '\033[96m'
        OKGREEN = '\033[92m'
        RED = '\033[91m'
        RED_FLASH = '\033[5;37;91m'
        RED_REVERSE = '\033[0;37;41m'
        RED_FLASH_REVERSE = '\033[5;37;41m'
        WARNING = '\033[93m'
        ENDC = '\033[0m'
        BOLD = '\033[1m'
        UNDERLINE = '\033[4m'
    
    
    class _GetchUnix:
    
        def __call__(self):
            import termios
            import tty
            import sys
            fd = sys.stdin.fileno()
            old_settings = termios.tcgetattr(fd)
            try:
                tty.setraw(sys.stdin.fileno())
                ch = sys.stdin.read(1)
            finally:
                termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
            return ch
    
    
    class _GetchWindows:
        def __init__(self):
            pass
    
        def __call__(self):
            import msvcrt
            return msvcrt.getch()
    
    
    class Getchar:
        """Gets a single character from standard input.  Does not echo to the screen."""
        def __init__(self):
            try:
                import msvcrt
                msvcrt.LK_LOCK
                self.impl = _GetchWindows()
            except ImportError:
                self.impl = _GetchUnix()
    
        def __call__(self):
            return self.impl()
    
    
    class cd:
        """Context manager for changing the current working directory"""
        def __init__(self, newPath):
            self.newPath = os.path.expanduser(newPath)
    
        def __enter__(self):
            self.savedPath = os.getcwd()
            os.chdir(self.newPath)
    
        def __exit__(self, etype, value, traceback):
            os.chdir(self.savedPath)
    
    
    getchar = Getchar()
    
    
    def find_git_branch(directory):
        """cd into a directory and return the git branch that it's on"""
        with cd(directory):
            enc_str = locale.getpreferredencoding()
            proc = run(shlex.split("git status"), capture_output=True)
            try:
                git_branch_list = proc.stdout.decode(enc_str).splitlines()[0].split()[2:]
                git_branch = ' '.join(git_branch_list).strip()
            except IndexError:
                git_branch = ""
            return git_branch
    
    
    def check_git_edit(directory, vi_command):
        """if a file is on a git master branch, ask before editing it"""
        # check the git branch of the directory...
        git_branch = find_git_branch(directory)
        if git_branch == "main" or git_branch == "master":
            print(f"{bcolors.RED_FLASH_REVERSE}You are editing on git {git_branch}, are you sure? (Y/N){bcolors.ENDC}")
            user_input = getchar()
            if user_input.upper() == "Y":
                # Run vim.basic which allows ~/.vimrc... vim.tiny does not!
                run(shlex.split(vi_command))
        else:
            run(shlex.split(vi_command))
    
    
    if __name__ == "__main__":
        if shutil.which("vim") and not shutil.which("vim.basic"):
            vim = "vim"
        elif shutil.which("vim.basic"):
            vim = "vim.basic"
        else:
            raise OSError("vim is not installed")
        vi_command = f"{vim} {' '.join(sys.argv[1:])}"
        filepath = os.path.expanduser(' '.join(sys.argv[1:]))
        dir_part = os.path.split(filepath)[0]
        if dir_part != "":
            dirname = os.path.dirname(filepath)
            check_git_edit(dirname, vi_command)
        else:
            check_git_edit(os.getcwd(), vi_command)