Posts Tagged ‘version control’

F Commits: Poor Man’s Sub-commits

Sunday, July 24th, 2011

Lately I find myself typing the following command almost a hundred times in a day:

git commit -a -m"f"

Instead of forming a whole changeset carefully, I just go with it and do many small f commits. If it gets hairy I can revert back to a previous step or remove one or more of them. When I am happy with the result, I rebase and mark all the f commits after the initial commit as fixup. Initial commit usually has the commit message this changeset will finally have but if I want to change it I just mark is as reword.

Note that, what I call an f commit is not an atomic set of modifications, they’re usually undoing or overriding the previous changes. Another way to put it; they are silly commits that I wouldn’t want anyone else to see. They are a better undo mechanism than whatever editor you are using offers, and they allow you to synchronize multiple files.

I am not saying this is revolutionary or anything. But this is one example of how git changed my workflow and my way of thinking, definitely in a positive way. If you are not familiar with interactive rebasing please take the time to learn how it works. It is going to pay really well, trust me.

Bookmark and Share

Adding GIT support to Meld

Wednesday, November 19th, 2008

Meld is a great diffing/merging tool with version control support. GIT support doesn’t come out of the box though. To enable GIT support you need to copy this file into your /usr/lib/meld/vc directory. Then you can open the directory where your GIT repository is checked out (using New -> Version Control Browser of course).

I would like to include the contents of the file here as well (git.py):

# -*- coding: utf-8 -*- 

# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

### Copyright (C) 2002-2005 Stephen Kennedy <stevek@gnome.org>
### Copyright (C) 2005 Aaron Bentley <aaron.bentley@utoronto.ca>
### Copyright (C) 2007 José Fonseca <j_r_fonseca@yahoo.co.uk>

### Redistribution and use in source and binary forms, with or without
### modification, are permitted provided that the following conditions
### are met:
### 
### 1. Redistributions of source code must retain the above copyright
###    notice, this list of conditions and the following disclaimer.
### 2. Redistributions in binary form must reproduce the above copyright
###    notice, this list of conditions and the following disclaimer in the
###    documentation and/or other materials provided with the distribution.

### THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
### IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
### OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
### IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
### INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
### NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
### DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
### THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
### (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
### THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import os
import errno
import _vc

class Vc(_vc.Vc):

    CMD = "git"
    NAME = "Git"
    PATCH_STRIP_NUM = 1
    PATCH_INDEX_RE = "^diff --git a/(.*) b/.*$"

    def __init__(self, location):
        self._tree_cache = None
        while location != "/":
            if os.path.isdir( "%s/.git" % location):
                self.root = location
                return
            location = os.path.dirname(location)
        raise ValueError()

    def commit_command(self, message):
        return [self.CMD,"commit","-m",message]
    def diff_command(self):
        return [self.CMD,"diff","HEAD"]
    def update_command(self):
        return [self.CMD,"pull"]
    def add_command(self, binary=0):
        return [self.CMD,"add"]
    def remove_command(self, force=0):
        return [self.CMD,"rm"]
    def revert_command(self):
        return [self.CMD,"checkout"]
    def get_working_directory(self, workdir):
        if workdir.startswith("/"):
            return self.root
        else:
            return ''

    def cache_inventory(self, topdir):
        self._tree_cache = self.lookup_tree()

    def uncache_inventory(self):
        self._tree_cache = None

    def lookup_tree(self):
        while 1:
            try:
                proc = os.popen("cd %s && git status --untracked-files" % self.root)
                entries = proc.read().split("\\n")[:-1]
                break
            except OSError, e:
                if e.errno != errno.EAGAIN:
                    raise
        statemap = {
            "unknown": _vc.STATE_NONE,
            "new file": _vc.STATE_NEW,
            "deleted": _vc.STATE_REMOVED,
            "modified": _vc.STATE_MODIFIED,
            "typechange": _vc.STATE_NORMAL,
            "unmerged": _vc.STATE_CONFLICT }
        tree_state = {}
        for entry in entries:
            if not entry.startswith("#\t"):
                continue
            try:
                statekey, name = entry[2:].split(":", 2)
            except ValueError:
                # untracked
                name = entry[2:]
                path = os.path.join(self.root, name.strip())
                tree_state[path] = _vc.STATE_NONE
            else:
                statekey = statekey.strip()
                name = name.strip()
                try:
                    src, dst = name.split(" -> ", 2)
                except ValueError:
                    path = os.path.join(self.root, name.strip())
                    state = statemap.get(statekey, _vc.STATE_NONE)
                    tree_state[path] = state
                else:
                    # copied, renamed
                    if statekey == "renamed":
                        tree_state[os.path.join(self.root, src)] = _vc.STATE_REMOVED
                    tree_state[os.path.join(self.root, dst)] = _vc.STATE_NEW
        return tree_state

    def get_tree(self):
        if self._tree_cache is None:
            return self.lookup_tree()
        else:
            return self._tree_cache

    def lookup_files(self, dirs, files):
        "files is array of (name, path). assume all files in same dir"

        if len(files):
            directory = os.path.dirname(files[0][1])
        elif len(dirs):
            directory = os.path.dirname(dirs[0][1])
        else:
            return [],[]

        tree = self.get_tree()

        retfiles = []
        retdirs = []
        for name,path in files:
            state = tree.get(path, _vc.STATE_IGNORED)
            retfiles.append( _vc.File(path, name, state) )
        for name,path in dirs:
            # git does not operate on dirs, just files
            retdirs.append( _vc.Dir(path, name, _vc.STATE_NORMAL))
        for path, state in tree.iteritems():
            # removed files are not in the filesystem, so must be added here
            if state is _vc.STATE_REMOVED:
                if os.path.dirname(path) == directory:
                    retfiles.append( _vc.File(path, name, state) )
        return retdirs, retfiles

    def listdir(self, start):
        # just like _vc.Vc.listdir, but ignores just .git
        if start=="": start="."
        if start[-1] != "/": start+="/"
        cfiles = []
        cdirs = []
        try:
            entries = os.listdir(start)
            entries.sort()
        except OSError:
            entries = []
        for f in [f for f in entries if f!=".git"]:
            fname = start + f
            lname = fname
            if os.path.isdir(fname):
                cdirs.append( (f, lname) )
            else:
                cfiles.append( (f, lname) )
        dirs, files = self.lookup_files(cdirs, cfiles)
        return dirs+files

Enjoy!

Bookmark and Share