views:

94

answers:

3

For example, say I moved a file from /project/file.cs to /project/subdir/file.cs. It would be nice if nautilus automatically converted this to bzr mv /project/file.cs /project/subdir/file.cs. Is it possible to set this up?

It would also be nice if I was warned when doing a plain old mv on version controlled files, but I suppose that's a separate question.

A: 

This is a great idea, but I think nautilus-bzr does no include it yet, try it, I'm not sure.

mkotechno
+1  A: 

As one of the developers of RabbitVCS, I'm pretty sure such a thing is not currently possible. Nautilus extensions can provide context menus, property pages, additional columns and they can respond to a file being displayed in the browser main window. They can't hook into arbirtary events like moving or deletion. (I'd love it if that were the case, but it's not a priority for us right now.) You would have modify the Nautilus extension API yourself.

If you're feeling up to that, but don't know where to start, you should look at the Nautilus source and ask on the Nautilus mailing list. They could certainly tell you if you're barking up the wrong tree about this.

It's possible that a Nautilus extension is the wrong place for this sort of thing. It might be possible to do something with GVFS instead of a Nautilus extension, but I'm out of my depth on that.

detly
I think I overshot.
detly
Interesting! I missed this when you posted it. I don't think you'd necessarily need to hook into Nautilus. For example, Dropbox runs a daemon that listens for events like deletion, moving, renaming, etc. It doesn't require Nautilus, you can run it in a command-line-only environment. I don't know much about how it works, but it is possible.
Matthew
Oooh... I see what you're saying. One of the things I'm looking at now for RabbitVCS is to add file system monitors to the root of the working copy to update a cache upon changes. (eg. gio.GileMonitor, or use the pyinotify interface on Linux.) Not sure how you'd do it globally though with adding a recursive event monitor on "/"... and I don't know whether there would be problems associated with that.
detly
Recursive watch on "/" */me shudders* ;-)
Bruce van der Kooij
@Bruce van der Kooij - hi Bruce! Yes, tell me about it :P But pyinotify is not that much fun either...
detly
+1  A: 

Like you already indicated yourself you basically need something that listens for moves, so I thought I'd code something up that would give you an indication of how this would work.

I tried using gio.FileMonitor but eventually went back to using plain old pyinotify because the latter has built-in support for detecting file renames/moves.

import pyinotify

import bzrlib
from bzrlib.workingtree import WorkingTree
from bzrlib.errors import NotBranchError, BzrRenameFailedError

directories_to_watch = [
    # Add the paths to your working copies / branches to watch here
]

wm = pyinotify.WatchManager()

# When you listen to both MOVED_FROM and MOVED_TO the event for MOVED_TO will include both 
# pathname (new path) and src_pathname (previous path).
mask = pyinotify.IN_MOVED_FROM | pyinotify.IN_MOVED_TO

class EventHandler(pyinotify.ProcessEvent):

    def process_IN_MOVED_TO(self, event):
        try:
            tree, path = WorkingTree.open_containing(event.src_pathname)
            root = event.src_pathname[:-len(path)] # Ugh, hackish

            if not path.startswith(".bzr"): # Also hackish (to exclude events for anything in the .bzr subdirectory)
                try:
                    tree.lock_tree_write()
                    source = event.src_pathname[len(root):] # Again hackish
                    target = event.pathname[len(root):] # Same
                    tree.rename_one(source, target)
                    print "Renamed %s to %s" % (source, target)
                except BzrRenameFailedError: # Same
                    pass
                finally:
                    tree.unlock()
        except NotBranchError:
            return

handler = EventHandler()
notifier = pyinotify.Notifier(wm, handler)

for path in directories_to_watch:
    wdd = wm.add_watch(path, mask, rec=True, auto_add=True)
    print "Recursively watching %s" % path

notifier.loop()

Here's how it works:

$ mv afile bfile
$ bzr status
renamed:
  afile => bfile

$ mv bfile foobar/
$ bzr status
renamed:
  afile => foobar/bfile

$ mv foobar/ zoobar
$ bzr status
renamed:
  afile => zoobar/bfile
  foobar/ => zoobar/

$ mv zoobar/ foobar
$ bzr status
renamed:
  afile => foobar/bfile

$ mv foobar/bfile afile

And we're back where we got started ;-)

[edit]

If you don't want to manually list the various directories to watch it might be a good idea to write a Nautilus extension which keeps track of the various working copies it encounters as you navigate. Here's something to get you started (this goes into ~/.nautilus/python-extensions):

import os
import pickle

import nautilus 

import gio

from xdg import BaseDirectory as basedir

import bzrlib
from bzrlib.workingtree import WorkingTree
from bzrlib.errors import NotBranchError

class BzrMonitor(nautilus.InfoProvider, nautilus.MenuProvider):

    data_directory = basedir.save_data_path("bzrmonitor")
    data_filename = os.path.join(data_directory, "workingcopies.db")

    def __init__(self):
        print "Initializing BzrMonitor extension..."

        try:
            data_file = open(self.data_filename, "r")
            self.data = pickle.load(data_file)
        except IOError:
            self.data = []
            data_file = open(self.data_filename, "w")
            pickle.dump(self.data, data_file)
            data_file.close()

    def detect_and_save_branch(self, path):
        try:
            tree, rel_path = WorkingTree.open_containing(path)

            # TODO: Still can't figure out how to get the path from the tree itself
            if len(rel_path) > 0: 
                root = path[:-len(rel_path)]
            else:
                root = path

            root = root.rstrip(os.path.sep)

            if root not in self.data: 
                print "Added not seen before branch %s to cache..." % root
                self.data.append(root)
                data_file = open(self.data_filename, "w")
                pickle.dump(self.data, data_file)
                data_file.close()

        except NotBranchError:
            return

    def update_file_info(self, item):
        """
        This function is called when:

          - When you enter a directory (once for each item but only when the
            item was modified since the last time it was listed)
          - When you refresh (once for each item visible)
          - When an item viewable from the current window is created or modified
        """
        self.detect_and_save_branch(gio.File(item.get_uri()).get_path())

    def get_file_items(self, window, items):
        """
        Menu activated with items selected. Nautilus also calls this function
        when rendering submenus, even though this is not needed since the entire
        menu has already been returned.
        """

        pass

    def get_background_items(self, window, item):
        """
        Menu activated on entering a directory. Builds context menu for File
        menu and for window background.
        """

        self.detect_and_save_branch(gio.File(item.get_uri()).get_path())

I borrowed the various docstrings from RabbitVCS's extension code ;-)

In your monitor you'll probably want to watch the workingcopies.db file for additions and register watches on any new working copies it has found.

Resources

Bruce van der Kooij
You are amazing! There are some limitations, of course. Having to manually list the directories you want to watch is a pain, and it fails if you move a file to a directory that has not yet been added to bzr. But for an answer to a question from a stranger on the internet, this is great! I'll look and see if I can refine the script as I use it.
Matthew
*/me blushes* Thanks for the kind words :-) To prevent having to manually list the directories you want to watch it might be a good idea to create a Nautilus extension which keeps track of where your working copies are as you navigate along. I added an example.I'd be very interested in seeing what you come up with.
Bruce van der Kooij