Exemple #1
0
    def update_file(self, path, content, author, parent_revision,
                    commit_message=None):
        commit_message = get_commit_message(commit_message)
        self.branch.lock_write()
        try:
            file_id = self.tree.path2id(path)
            if file_id is not None:
                f = File(self, path, file_id)
                content = self._get_final_text(content, f, parent_revision)
            else:
                content = normalize_content(content)

            with TransformPreview(self.tree) as tt:
                trans_id = tt.trans_id_tree_path(path)
                if tt.tree_kind(trans_id) is not None:
                    tt.delete_contents(trans_id)
                else:
                    name = splitpath(path)[-1]
                    tt.version_file(gen_file_id(name), trans_id)
                    create_parents(tt, path, trans_id)
                tt.create_file(content, trans_id)
                try:
                    tt.commit(self.branch, commit_message, authors=[author])
                except MalformedTransform, e:
                    for conflict in e.conflicts:
                        if conflict[0] == 'non-directory parent':
                            path = FinalPaths(tt).get_path(trans_id)
                            raise FileExists(
                                '%s exists and is not a directory' %
                                conflict[1])
                    raise

            self.tree = self.branch.basis_tree()
Exemple #2
0
def inventory_to_tree_and_blobs(repo, mapping, revision_id):
    stack = []
    cur = ""
    tree = Tree()

    inv = repo.get_inventory(revision_id)

    for path, entry in inv.iter_entries():
        while stack and not path.startswith(cur):
            tree.serialize()
            sha = tree.sha().hexdigest()
            yield sha, tree
            t = (stat.S_IFDIR, splitpath(cur)[-1:][0].encode('UTF-8'), sha)
            cur, tree = stack.pop()
            tree.add(*t)

        if type(entry) == InventoryDirectory:
            stack.append((cur, tree))
            cur = path
            tree = Tree()

        if type(entry) == InventoryFile:
            #FIXME: We can make potentially make this Lazy to avoid shaing lots of stuff
            # and having all these objects in memory at once
            blob = Blob()
            _, blob._text = repo.iter_files_bytes([(entry.file_id, revision_id,
                                                    path)]).next()
            sha = blob.sha().hexdigest()
            yield sha, blob

            name = splitpath(path)[-1:][0].encode('UTF-8')
            mode = stat.S_IFREG | 0644
            if entry.executable:
                mode |= 0111
            tree.add(mode, name, sha)

    while len(stack) > 1:
        tree.serialize()
        sha = tree.sha().hexdigest()
        yield sha, tree
        t = (stat.S_IFDIR, splitpath(cur)[-1:][0].encode('UTF-8'), sha)
        cur, tree = stack.pop()
        tree.add(*t)

    tree.serialize()
    yield tree.sha().hexdigest(), tree
Exemple #3
0
def iter_paths(path):
    path_segments = splitpath(path)
    while len(path_segments) > 0:
        tail = path_segments.pop()
        if len(path_segments) == 0:
            yield '', tail
        else:
            yield joinpath(*path_segments), tail
Exemple #4
0
def inventory_to_tree_and_blobs(repo, mapping, revision_id):
    stack = []
    cur = ""
    tree = Tree()

    inv = repo.get_inventory(revision_id)

    for path, entry in inv.iter_entries():
        while stack and not path.startswith(cur):
            tree.serialize()
            sha = tree.sha().hexdigest()
            yield sha, tree
            t = (stat.S_IFDIR, splitpath(cur)[-1:][0].encode('UTF-8'), sha)
            cur, tree = stack.pop()
            tree.add(*t)

        if type(entry) == InventoryDirectory:
            stack.append((cur, tree))
            cur = path
            tree = Tree()

        if type(entry) == InventoryFile:
            #FIXME: We can make potentially make this Lazy to avoid shaing lots of stuff
            # and having all these objects in memory at once
            blob = Blob()
            _, blob._text = repo.iter_files_bytes([(entry.file_id, revision_id, path)]).next()
            sha = blob.sha().hexdigest()
            yield sha, blob

            name = splitpath(path)[-1:][0].encode('UTF-8')
            mode = stat.S_IFREG | 0644
            if entry.executable:
                mode |= 0111
            tree.add(mode, name, sha)

    while len(stack) > 1:
        tree.serialize()
        sha = tree.sha().hexdigest()
        yield sha, tree
        t = (stat.S_IFDIR, splitpath(cur)[-1:][0].encode('UTF-8'), sha)
        cur, tree = stack.pop()
        tree.add(*t)

    tree.serialize()
    yield tree.sha().hexdigest(), tree
Exemple #5
0
    def add(self, files, ids=None, kinds=None):
        """Add paths to the set of versioned paths.

        Note that the command line normally calls smart_add instead,
        which can automatically recurse.

        This adds the files to the inventory, so that they will be
        recorded by the next commit.

        :param files: List of paths to add, relative to the base of the tree.
        :param ids: If set, use these instead of automatically generated ids.
            Must be the same length as the list of files, but may
            contain None for ids that are to be autogenerated.
        :param kinds: Optional parameter to specify the kinds to be used for
            each file.

        TODO: Perhaps callback with the ids and paths as they're added.
        """
        if isinstance(files, basestring):
            # XXX: Passing a single string is inconsistent and should be
            # deprecated.
            if not (ids is None or isinstance(ids, basestring)):
                raise AssertionError()
            if not (kinds is None or isinstance(kinds, basestring)):
                raise AssertionError()
            files = [files]
            if ids is not None:
                ids = [ids]
            if kinds is not None:
                kinds = [kinds]

        files = [path.strip('/') for path in files]

        if ids is None:
            ids = [None] * len(files)
        else:
            if not (len(ids) == len(files)):
                raise AssertionError()
        if kinds is None:
            kinds = [None] * len(files)
        elif not len(kinds) == len(files):
            raise AssertionError()
        for f in files:
            # generic constraint checks:
            if self.is_control_filename(f):
                raise errors.ForbiddenControlFileError(filename=f)
            fp = splitpath(f)
        # fill out file kinds for all files [not needed when we stop 
        # caring about the instantaneous file kind within a uncommmitted tree
        #
        self._gather_kinds(files, kinds)
        self._add(files, ids, kinds)
Exemple #6
0
    def add(self, files, ids=None, kinds=None):
        """Add paths to the set of versioned paths.

        Note that the command line normally calls smart_add instead,
        which can automatically recurse.

        This adds the files to the inventory, so that they will be
        recorded by the next commit.

        :param files: List of paths to add, relative to the base of the tree.
        :param ids: If set, use these instead of automatically generated ids.
            Must be the same length as the list of files, but may
            contain None for ids that are to be autogenerated.
        :param kinds: Optional parameter to specify the kinds to be used for
            each file.

        TODO: Perhaps callback with the ids and paths as they're added.
        """
        if isinstance(files, basestring):
            # XXX: Passing a single string is inconsistent and should be
            # deprecated.
            if not (ids is None or isinstance(ids, basestring)):
                raise AssertionError()
            if not (kinds is None or isinstance(kinds, basestring)):
                raise AssertionError()
            files = [files]
            if ids is not None:
                ids = [ids]
            if kinds is not None:
                kinds = [kinds]

        files = [path.strip('/') for path in files]

        if ids is None:
            ids = [None] * len(files)
        else:
            if not (len(ids) == len(files)):
                raise AssertionError()
        if kinds is None:
            kinds = [None] * len(files)
        elif not len(kinds) == len(files):
            raise AssertionError()
        for f in files:
            # generic constraint checks:
            if self.is_control_filename(f):
                raise errors.ForbiddenControlFileError(filename=f)
            fp = splitpath(f)
        # fill out file kinds for all files [not needed when we stop
        # caring about the instantaneous file kind within a uncommmitted tree
        #
        self._gather_kinds(files, kinds)
        self._add(files, ids, kinds)
Exemple #7
0
 def invalidateDirectory(self, path):
     path = unicode(path)
     try:
         parts = osutils.splitpath(path)
         entry = self.cache
         for part in parts[:-1]:
             entry = entry.children[part]
         print "Removing", path, "from the cache"
         del entry.children[parts[-1]]
     except KeyError:
         pass
     else:
         self.autoRefreshPath = path
         self.autoRefreshTimer.start(1000)
Exemple #8
0
    def _cacheDirectoryStatus(self, path):
        p = '/'.join(path)
        if sys.platform != 'win32':
            p = '/' + p
        #print "caching", p
        try:
            # to stop bzr-svn from trying to give status on svn checkouts
            #if not QtCore.QDir(p).exists('.bzr'):
            #    raise errors.NotBranchError(p)
            wt, relpath = workingtree.WorkingTree.open_containing(p)
        except errors.BzrError:
            self.fileSystemWatcher.addPath(p)
            return self._cacheStatus(path, 'non-versioned')
        self.fileSystemWatcher.addPath(wt.basedir)
        bt = wt.basis_tree()
        root = self._cacheStatus(osutils.splitpath(wt.basedir), 'branch')
        delta = wt.changes_from(bt, want_unchanged=True, want_unversioned=True)
        for entry in delta.added:
            self._cacheStatus(osutils.splitpath(entry[0]), 'added', root=root)
        for entry in delta.removed:
            # FIXME
            self._cacheStatus(osutils.splitpath(entry[0]),
                              'modified',
                              root=root)
#            self._cacheStatus(osutils.splitpath(entry[0]), 'removed', root=root)
        for entry in delta.modified:
            self._cacheStatus(osutils.splitpath(entry[0]),
                              'modified',
                              root=root)
        for entry in delta.unchanged:
            self._cacheStatus(osutils.splitpath(entry[0]),
                              'unchanged',
                              root=root)
        for entry in delta.unversioned:
            self._cacheStatus(osutils.splitpath(entry[0]),
                              'non-versioned',
                              root=root)
        try:
            return self._getCacheEntry(path)
        except KeyError:
            self.fileSystemWatcher.addPath(p)
            return self._cacheStatus(path, 'non-versioned')
    def _detect_moves(self, threshold, dry_run):
        delta = self.tree.changes_from(self.basis_tree, want_unversioned=True)
        inv = self.tree.inventory
        unknowns = self._find_unknowns(delta)
        removed = set()
        matches = []
        for path, file_id, kind in delta.removed:
            if kind == "directory":
                continue
            path = inv.id2path(file_id)
            for new_path, new_kind in unknowns:
                if kind != new_kind:
                    continue
                similarity = self._compare_files(file_id, new_path)
                matches.append((similarity, path, new_path))
            removed.add(path)
        matches.sort(reverse=True)

        # Try to detect file renames, based on text similarity
        used = set()
        file_renames = []
        for similarity, old_path, new_path in matches:
            if similarity < threshold:
                self.outf.write(
                    "Skipping %d file(s) with similarity below "
                    "%d%%.\n" % (len(removed), threshold * 100))
                break
            if old_path not in removed or new_path in used:
                trace.mutter("File %s already moved", old_path)
                continue
            used.add(new_path)
            removed.remove(old_path)
            file_renames.append((similarity, old_path, new_path))

        # Try to detect directory renames, based on file renames
        dir_renames = []
        dir_rename_map = {}
        for similarity, old_path, new_path in file_renames:
            old_dirs = osutils.splitpath(old_path)[:-1]
            new_dirs = osutils.splitpath(new_path)[:-1]
            for old_dir, new_dir in zip(old_dirs, new_dirs):
                dir_rename_map.setdefault(old_dir, set()).add(new_dir)
        for old_dir, new_dirs in sorted(dir_rename_map.iteritems()):
            if len(new_dirs) != 1:
                continue
            new_dir = iter(new_dirs).next()
            dir_renames.append((-1, old_dir, new_dir))
        # needs to be smarted to be enabled
        dir_renames = []

        # Actually rename
        renames = dir_renames + file_renames
        for similarity, old_path, new_path in renames:
            if not dry_run:
                parent_dirs = []
                to_dir = new_path
                while True:
                    to_dir, to_tail = os.path.split(to_dir)
                    if inv.path2id(to_dir) is None:
                        parent_dirs.append(to_dir)
                    if not to_tail or not to_dir:
                        break
                if parent_dirs:
                    self.tree.add(reversed(parent_dirs))
                    self.tree.flush()
                self.tree.rename_one(old_path, new_path, after=True)
            if similarity == -1:
                self.outf.write("%s => %s\n" % (old_path, new_path))
            else:
                self.outf.write("%s => %s (%d%% similar)\n" % (
                    old_path, new_path, similarity * 100))
Exemple #10
0
    def _populate_from_inventory(self):
        """Populate the CommitBuilder by walking the working tree inventory."""
        if self.strict:
            # raise an exception as soon as we find a single unknown.
            for unknown in self.work_tree.unknowns():
                raise StrictCommitFailed()
        
        specific_files = self.specific_files
        exclude = self.exclude
        report_changes = self.reporter.is_verbose()
        deleted_ids = []
        # A tree of paths that have been deleted. E.g. if foo/bar has been
        # deleted, then we have {'foo':{'bar':{}}}
        deleted_paths = {}
        # XXX: Note that entries may have the wrong kind because the entry does
        # not reflect the status on disk.
        work_inv = self.work_tree.inventory
        # NB: entries will include entries within the excluded ids/paths
        # because iter_entries_by_dir has no 'exclude' facility today.
        entries = work_inv.iter_entries_by_dir(
            specific_file_ids=self.specific_file_ids, yield_parents=True)
        for path, existing_ie in entries:
            file_id = existing_ie.file_id
            name = existing_ie.name
            parent_id = existing_ie.parent_id
            kind = existing_ie.kind
            if kind == 'directory':
                self._next_progress_entry()
            # Skip files that have been deleted from the working tree.
            # The deleted path ids are also recorded so they can be explicitly
            # unversioned later.
            if deleted_paths:
                path_segments = splitpath(path)
                deleted_dict = deleted_paths
                for segment in path_segments:
                    deleted_dict = deleted_dict.get(segment, None)
                    if not deleted_dict:
                        # We either took a path not present in the dict
                        # (deleted_dict was None), or we've reached an empty
                        # child dir in the dict, so are now a sub-path.
                        break
                else:
                    deleted_dict = None
                if deleted_dict is not None:
                    # the path has a deleted parent, do not add it.
                    continue
            if exclude and is_inside_any(exclude, path):
                # Skip excluded paths. Excluded paths are processed by
                # _update_builder_with_changes.
                continue
            content_summary = self.work_tree.path_content_summary(path)
            # Note that when a filter of specific files is given, we must only
            # skip/record deleted files matching that filter.
            if not specific_files or is_inside_any(specific_files, path):
                if content_summary[0] == 'missing':
                    if not deleted_paths:
                        # path won't have been split yet.
                        path_segments = splitpath(path)
                    deleted_dict = deleted_paths
                    for segment in path_segments:
                        deleted_dict = deleted_dict.setdefault(segment, {})
                    self.reporter.missing(path)
                    deleted_ids.append(file_id)
                    continue
            # TODO: have the builder do the nested commit just-in-time IF and
            # only if needed.
            if content_summary[0] == 'tree-reference':
                # enforce repository nested tree policy.
                if (not self.work_tree.supports_tree_reference() or
                    # repository does not support it either.
                    not self.branch.repository._format.supports_tree_reference):
                    content_summary = ('directory',) + content_summary[1:]
            kind = content_summary[0]
            # TODO: specific_files filtering before nested tree processing
            if kind == 'tree-reference':
                if self.recursive == 'down':
                    nested_revision_id = self._commit_nested_tree(
                        file_id, path)
                    content_summary = content_summary[:3] + (
                        nested_revision_id,)
                else:
                    content_summary = content_summary[:3] + (
                        self.work_tree.get_reference_revision(file_id),)

            # Record an entry for this item
            # Note: I don't particularly want to have the existing_ie
            # parameter but the test suite currently (28-Jun-07) breaks
            # without it thanks to a unicode normalisation issue. :-(
            definitely_changed = kind != existing_ie.kind
            self._record_entry(path, file_id, specific_files, kind, name,
                parent_id, definitely_changed, existing_ie, report_changes,
                content_summary)

        # Unversion IDs that were found to be deleted
        self.work_tree.unversion(deleted_ids)
Exemple #11
0
    def _populate_from_inventory(self):
        """Populate the CommitBuilder by walking the working tree inventory."""
        # Build the revision inventory.
        #
        # This starts by creating a new empty inventory. Depending on
        # which files are selected for commit, and what is present in the
        # current tree, the new inventory is populated. inventory entries
        # which are candidates for modification have their revision set to
        # None; inventory entries that are carried over untouched have their
        # revision set to their prior value.
        #
        # ESEPARATIONOFCONCERNS: this function is diffing and using the diff
        # results to create a new inventory at the same time, which results
        # in bugs like #46635.  Any reason not to use/enhance Tree.changes_from?
        # ADHB 11-07-2006

        specific_files = self.specific_files
        exclude = self.exclude
        report_changes = self.reporter.is_verbose()
        deleted_ids = []
        # A tree of paths that have been deleted. E.g. if foo/bar has been
        # deleted, then we have {'foo':{'bar':{}}}
        deleted_paths = {}
        # XXX: Note that entries may have the wrong kind because the entry does
        # not reflect the status on disk.
        # NB: entries will include entries within the excluded ids/paths
        # because iter_entries_by_dir has no 'exclude' facility today.
        entries = self.work_tree.iter_entries_by_dir(
            specific_file_ids=self.specific_file_ids, yield_parents=True)
        for path, existing_ie in entries:
            file_id = existing_ie.file_id
            name = existing_ie.name
            parent_id = existing_ie.parent_id
            kind = existing_ie.kind
            # Skip files that have been deleted from the working tree.
            # The deleted path ids are also recorded so they can be explicitly
            # unversioned later.
            if deleted_paths:
                path_segments = splitpath(path)
                deleted_dict = deleted_paths
                for segment in path_segments:
                    deleted_dict = deleted_dict.get(segment, None)
                    if not deleted_dict:
                        # We either took a path not present in the dict
                        # (deleted_dict was None), or we've reached an empty
                        # child dir in the dict, so are now a sub-path.
                        break
                else:
                    deleted_dict = None
                if deleted_dict is not None:
                    # the path has a deleted parent, do not add it.
                    continue
            if exclude and is_inside_any(exclude, path):
                # Skip excluded paths. Excluded paths are processed by
                # _update_builder_with_changes.
                continue
            content_summary = self.work_tree.path_content_summary(path)
            kind = content_summary[0]
            # Note that when a filter of specific files is given, we must only
            # skip/record deleted files matching that filter.
            if not specific_files or is_inside_any(specific_files, path):
                if kind == 'missing':
                    if not deleted_paths:
                        # path won't have been split yet.
                        path_segments = splitpath(path)
                    deleted_dict = deleted_paths
                    for segment in path_segments:
                        deleted_dict = deleted_dict.setdefault(segment, {})
                    self.reporter.missing(path)
                    self._next_progress_entry()
                    deleted_ids.append(file_id)
                    continue
            # TODO: have the builder do the nested commit just-in-time IF and
            # only if needed.
            if kind == 'tree-reference':
                # enforce repository nested tree policy.
                if (not self.work_tree.supports_tree_reference() or
                    # repository does not support it either.
                    not self.branch.repository._format.supports_tree_reference):
                    kind = 'directory'
                    content_summary = (kind, None, None, None)
                elif self.recursive == 'down':
                    nested_revision_id = self._commit_nested_tree(
                        file_id, path)
                    content_summary = (kind, None, None, nested_revision_id)
                else:
                    nested_revision_id = self.work_tree.get_reference_revision(file_id)
                    content_summary = (kind, None, None, nested_revision_id)

            # Record an entry for this item
            # Note: I don't particularly want to have the existing_ie
            # parameter but the test suite currently (28-Jun-07) breaks
            # without it thanks to a unicode normalisation issue. :-(
            definitely_changed = kind != existing_ie.kind
            self._record_entry(path, file_id, specific_files, kind, name,
                parent_id, definitely_changed, existing_ie, report_changes,
                content_summary)

        # Unversion IDs that were found to be deleted
        self.deleted_ids = deleted_ids
Exemple #12
0
    def _populate_from_inventory(self):
        """Populate the CommitBuilder by walking the working tree inventory."""
        # Build the revision inventory.
        #
        # This starts by creating a new empty inventory. Depending on
        # which files are selected for commit, and what is present in the
        # current tree, the new inventory is populated. inventory entries
        # which are candidates for modification have their revision set to
        # None; inventory entries that are carried over untouched have their
        # revision set to their prior value.
        #
        # ESEPARATIONOFCONCERNS: this function is diffing and using the diff
        # results to create a new inventory at the same time, which results
        # in bugs like #46635.  Any reason not to use/enhance Tree.changes_from?
        # ADHB 11-07-2006

        specific_files = self.specific_files
        exclude = self.exclude
        report_changes = self.reporter.is_verbose()
        deleted_ids = []
        # A tree of paths that have been deleted. E.g. if foo/bar has been
        # deleted, then we have {'foo':{'bar':{}}}
        deleted_paths = {}
        # XXX: Note that entries may have the wrong kind because the entry does
        # not reflect the status on disk.
        # NB: entries will include entries within the excluded ids/paths
        # because iter_entries_by_dir has no 'exclude' facility today.
        entries = self.work_tree.iter_entries_by_dir(
            specific_file_ids=self.specific_file_ids, yield_parents=True)
        for path, existing_ie in entries:
            file_id = existing_ie.file_id
            name = existing_ie.name
            parent_id = existing_ie.parent_id
            kind = existing_ie.kind
            # Skip files that have been deleted from the working tree.
            # The deleted path ids are also recorded so they can be explicitly
            # unversioned later.
            if deleted_paths:
                path_segments = splitpath(path)
                deleted_dict = deleted_paths
                for segment in path_segments:
                    deleted_dict = deleted_dict.get(segment, None)
                    if not deleted_dict:
                        # We either took a path not present in the dict
                        # (deleted_dict was None), or we've reached an empty
                        # child dir in the dict, so are now a sub-path.
                        break
                else:
                    deleted_dict = None
                if deleted_dict is not None:
                    # the path has a deleted parent, do not add it.
                    continue
            if exclude and is_inside_any(exclude, path):
                # Skip excluded paths. Excluded paths are processed by
                # _update_builder_with_changes.
                continue
            content_summary = self.work_tree.path_content_summary(path)
            kind = content_summary[0]
            # Note that when a filter of specific files is given, we must only
            # skip/record deleted files matching that filter.
            if not specific_files or is_inside_any(specific_files, path):
                if kind == 'missing':
                    if not deleted_paths:
                        # path won't have been split yet.
                        path_segments = splitpath(path)
                    deleted_dict = deleted_paths
                    for segment in path_segments:
                        deleted_dict = deleted_dict.setdefault(segment, {})
                    self.reporter.missing(path)
                    self._next_progress_entry()
                    deleted_ids.append(file_id)
                    continue
            # TODO: have the builder do the nested commit just-in-time IF and
            # only if needed.
            if kind == 'tree-reference':
                # enforce repository nested tree policy.
                if (not self.work_tree.supports_tree_reference() or
                        # repository does not support it either.
                        not self.branch.repository._format.
                        supports_tree_reference):
                    kind = 'directory'
                    content_summary = (kind, None, None, None)
                elif self.recursive == 'down':
                    nested_revision_id = self._commit_nested_tree(
                        file_id, path)
                    content_summary = (kind, None, None, nested_revision_id)
                else:
                    nested_revision_id = self.work_tree.get_reference_revision(
                        file_id)
                    content_summary = (kind, None, None, nested_revision_id)

            # Record an entry for this item
            # Note: I don't particularly want to have the existing_ie
            # parameter but the test suite currently (28-Jun-07) breaks
            # without it thanks to a unicode normalisation issue. :-(
            definitely_changed = kind != existing_ie.kind
            self._record_entry(path, file_id, specific_files, kind, name,
                               parent_id, definitely_changed, existing_ie,
                               report_changes, content_summary)

        # Unversion IDs that were found to be deleted
        self.deleted_ids = deleted_ids