Search code examples
pythongitcommand-line-interfacegitpython

Git, linux shell command alternatives for GitPython


I am trying to get all merged branches as well as stale branches (not being used for 3 months) I have the command written for linux shell. However, I need to write an alternative command in Gitpyhon. Could anybody help me with this?

self.cmd("git branch --merged master") # <-- Display, merged branches
self.cmd(                              # <-- Display stale branches
    "for branch in `git branch -r | grep -v HEAD`;"
    "do echo -e `git log --before=\"3 months\" --no-merges"
    " -n 1 --format=\"%cr | %an | %ae | \""
    " $branch | head -n 1` \\t$branch; done | sort -M"
)

Solution

  • Here are tips on how to translate git commands straightforwardly in GitPython.

    1. Always use import git.
    2. Use g = git.Git() to create a git handler.
    3. Use env = {'GIT_DIR': '/path/to/repo/.git', 'GIT_WORK_TREE': '/path/to/repo'} to pass these two variables so that we can call git commands anywhere. If the script involves multiple repositories, remember to switch variable values.
    4. For commands, turn git foo to g.foo(), and git foo-bar to g.foo_bar(). For example, git commit is equal to g.commit() and git cherry-pick is equal to g.cherry_pick().
    5. For options, turn -f to f=True, -f b to f=b, --foo to foo=True, --foo-bar to foo_bar=True and --foo-bar=baz to foo_bar=baz.
    6. Pass with_extended_output=True to commands so that we can catch status, stdout, and stderr.
    7. Use try...catch... to catch command errors. Note that when stderr is not empty, there might be no problem at all. Some commands print results to stderr even if the commands are successful.
    8. If you don't want to do translation, call g.execute() instead.

    Now try your commands. Suppose the non-bare repository is /path/to/repo.

    import git
    import logging
    
    g = git.Git()
    env = {'GIT_DIR': '/path/to/repo.git', 'GIT_WORK_TREE': '/path/to/repo'}
    # git branch --merged master
    try:
        status, stdout, stderr = g.branch(
            merged='master',
            env=env,
            with_extended_output=True)
        print(stdout.split('\n'))
    except Exception as e:
        logging.exception(e)
        # more stuff
    
    # git branch -r
    stale_branches = []
    try:
        status, stdout, stderr = g.branch(
            r=True,
            env=env
            with_extended_output=True)
        stale_branches += [l.strip() for l in stdout.split('\n')]
    except Exception as e:
        logging.exception(e)
        # more stuff
    
    for branch in stale_branches:
        if 'HEAD' in branch:
            continue
        # git log
        logs = []
        try:
            status, stdout, stderr = g.log(
                branch,
                before='3 months',
                no_merges=True,
                n=1,
                format='"%cr | %an | %ae | "',
                env=env,
                with_extended_output=True)
            logs += stdout.split('\n')
        except Exception as e:
            logging.exception(e)
            # more stuff
    
    # leave the rest to you
            
    

    For some commands like git worktree add --no-checkout foo HEAD, g.worktree('add', 'foo', 'HEAD', no_checkout=True) always fails because it calls an invalid command git worktree --no-checkout add foo HEAD. In such case, we can use g.execute(). Convert the whole command to a list of parameters, ['git', 'worktree', 'add', '--no-checkout', 'foo', 'HEAD'], and pass it to g.execute().

    try:
        status, stdout, stderr = g.execute(
            ['git', 'worktree', 'add', '--no-checkout', 'foo', 'HEAD'],
            env=env,
            with_extended_output=True)
        print(stdout)
    except Exception as e:
        logging.exception(e)
        # more stuff