mercurial.py 5.68 KB
Newer Older
Kai Willadsen's avatar
Kai Willadsen committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# Copyright (C) 2002-2005 Stephen Kennedy <stevek@gnome.org>

# 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.
Stephen Kennedy's avatar
Stephen Kennedy committed
23

24
import errno
25
import os
26 27 28
import shutil
import subprocess
import tempfile
29 30

from . import _vc
Stephen Kennedy's avatar
Stephen Kennedy committed
31

32 33

class Vc(_vc.CachedVc):
34 35

    CMD = "hg"
Stephen Kennedy's avatar
Stephen Kennedy committed
36
    NAME = "Mercurial"
37
    VC_DIR = ".hg"
38

39 40 41 42 43 44 45 46 47
    state_map = {
        "?": _vc.STATE_NONE,
        "A": _vc.STATE_NEW,
        "C": _vc.STATE_NORMAL,
        "!": _vc.STATE_MISSING,
        "I": _vc.STATE_IGNORED,
        "M": _vc.STATE_MODIFIED,
        "R": _vc.STATE_REMOVED,
    }
48

Stephen Kennedy's avatar
Stephen Kennedy committed
49
    def commit_command(self, message):
Kai Willadsen's avatar
Kai Willadsen committed
50 51
        return [self.CMD, "commit", "-m", message]

Stephen Kennedy's avatar
Stephen Kennedy committed
52
    def update_command(self):
Kai Willadsen's avatar
Kai Willadsen committed
53 54
        return [self.CMD, "update"]

55
    def add_command(self):
Kai Willadsen's avatar
Kai Willadsen committed
56 57
        return [self.CMD, "add"]

Stephen Kennedy's avatar
Stephen Kennedy committed
58
    def remove_command(self, force=0):
Kai Willadsen's avatar
Kai Willadsen committed
59 60
        return [self.CMD, "rm"]

Stephen Kennedy's avatar
Stephen Kennedy committed
61
    def revert_command(self):
Kai Willadsen's avatar
Kai Willadsen committed
62 63
        return [self.CMD, "revert"]

64
    def valid_repo(self):
65
        if _vc.call([self.CMD, "root"], cwd=self.root):
66 67 68
            return False
        else:
            return True
Kai Willadsen's avatar
Kai Willadsen committed
69

70
    def get_working_directory(self, workdir):
71 72 73 74
        if workdir.startswith("/"):
            return self.root
        else:
            return ''
Stephen Kennedy's avatar
Stephen Kennedy committed
75

76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
    def get_path_for_repo_file(self, path, commit=None):
        if commit is not None:
            raise NotImplementedError()

        if not path.startswith(self.root + os.path.sep):
            raise _vc.InvalidVCPath(self, path, "Path not in repository")
        path = path[len(self.root) + 1:]

        process = subprocess.Popen([self.CMD, "cat", path], cwd=self.root,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)

        with tempfile.NamedTemporaryFile(prefix='meld-tmp', delete=False) as f:
            shutil.copyfileobj(process.stdout, f)
        return f.name

92 93
    def _update_tree_state_cache(self, path, tree_state):
        """ Update the state of the file(s) at tree_state['path'] """
Stephen Kennedy's avatar
Stephen Kennedy committed
94 95
        while 1:
            try:
96 97
                # Get the status of modified files
                proc = _vc.popen([self.CMD, "status", '-A', path],
Kai Willadsen's avatar
Kai Willadsen committed
98
                                 cwd=self.location)
99 100 101 102 103
                entries = proc.read().split("\n")[:-1]

                # The following command removes duplicate file entries.
                # Just in case.
                entries = list(set(entries))
Stephen Kennedy's avatar
Stephen Kennedy committed
104
                break
105
            except OSError as e:
Stephen Kennedy's avatar
Stephen Kennedy committed
106 107 108
                if e.errno != errno.EAGAIN:
                    raise

109 110 111 112 113 114 115 116 117 118 119 120 121
        if len(entries) == 0 and os.path.isfile(path):
            # If we're just updating a single file there's a chance that it
            # was it was previously modified, and now has been edited
            # so that it is un-modified.  This will result in an empty
            # 'entries' list, and tree_state['path'] will still contain stale
            # data.  When this corner case occurs we force tree_state['path']
            # to STATE_NORMAL.
            tree_state[path] = _vc.STATE_NORMAL
        else:
            # There are 1 or more modified files, parse their state
            for entry in entries:
                # we might have a space in file name, it should be ignored
                statekey, name = entry.split(" ", 1)
122
                path = os.path.join(self.location, name.strip())
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
                state = self.state_map.get(statekey.strip(), _vc.STATE_NONE)
                tree_state[path] = state

    def _lookup_tree_cache(self, rootdir):
        # Get a list of all files in rootdir, as well as their status
        tree_state = {}
        self._update_tree_state_cache("./", tree_state)

        return tree_state

    def update_file_state(self, path):
        tree_state = self._get_tree_cache(os.path.dirname(path))
        self._update_tree_state_cache(path, tree_state)

    def _get_dirsandfiles(self, directory, dirs, files):

        tree = self._get_tree_cache(directory)

Stephen Kennedy's avatar
Stephen Kennedy committed
141 142
        retfiles = []
        retdirs = []
143 144 145 146 147 148
        for name, path in files:
            state = tree.get(path, _vc.STATE_NORMAL)
            retfiles.append(_vc.File(path, name, state))
        for name, path in dirs:
            # mercurial does not operate on dirs, just files
            retdirs.append(_vc.Dir(path, name, _vc.STATE_NORMAL))
149
        for path, state in tree.items():
150
            # removed files are not in the filesystem, so must be added here
151
            if state in (_vc.STATE_REMOVED, _vc.STATE_MISSING):
152 153 154
                folder, name = os.path.split(path)
                if folder == directory:
                    retfiles.append(_vc.File(path, name, state))
Stephen Kennedy's avatar
Stephen Kennedy committed
155
        return retdirs, retfiles