Exemplo n.º 1
0
    def test_index_file_base(self):
        # read from file
        index = IndexFile(self.rorepo, fixture_path("index"))
        assert index.entries
        assert index.version > 0

        # test entry
        entry = next(iter(index.entries.values()))
        for attr in ("path", "ctime", "mtime", "dev", "inode", "mode", "uid",
                     "gid", "size", "binsha", "hexsha", "stage"):
            getattr(entry, attr)
        # END for each method

        # test update
        entries = index.entries
        assert isinstance(index.update(), IndexFile)
        assert entries is not index.entries

        # test stage
        index_merge = IndexFile(self.rorepo, fixture_path("index_merge"))
        self.assertEqual(len(index_merge.entries), 106)
        assert len(list(e for e in index_merge.entries.values() if e.stage != 0))

        # write the data - it must match the original
        tmpfile = tempfile.mktemp()
        index_merge.write(tmpfile)
        with open(tmpfile, 'rb') as fp:
            self.assertEqual(fp.read(), fixture("index_merge"))
        os.remove(tmpfile)
Exemplo n.º 2
0
    def test_index_merge_tree(self, rw_repo):
        # A bit out of place, but we need a different repo for this:
        self.assertNotEqual(self.rorepo, rw_repo)
        self.assertEqual(len(set((self.rorepo, self.rorepo, rw_repo, rw_repo))), 2)

        # SINGLE TREE MERGE
        # current index is at the (virtual) cur_commit
        next_commit = "4c39f9da792792d4e73fc3a5effde66576ae128c"
        parent_commit = rw_repo.head.commit.parents[0]
        manifest_key = IndexFile.entry_key('MANIFEST.in', 0)
        manifest_entry = rw_repo.index.entries[manifest_key]
        rw_repo.index.merge_tree(next_commit)
        # only one change should be recorded
        assert manifest_entry.binsha != rw_repo.index.entries[manifest_key].binsha

        rw_repo.index.reset(rw_repo.head)
        self.assertEqual(rw_repo.index.entries[manifest_key].binsha, manifest_entry.binsha)

        # FAKE MERGE
        #############
        # Add a change with a NULL sha that should conflict with next_commit. We
        # pretend there was a change, but we do not even bother adding a proper
        # sha for it ( which makes things faster of course )
        manifest_fake_entry = BaseIndexEntry((manifest_entry[0], b"\0" * 20, 0, manifest_entry[3]))
        # try write flag
        self._assert_entries(rw_repo.index.add([manifest_fake_entry], write=False))
        # add actually resolves the null-hex-sha for us as a feature, but we can
        # edit the index manually
        assert rw_repo.index.entries[manifest_key].binsha != Object.NULL_BIN_SHA
        # must operate on the same index for this ! Its a bit problematic as
        # it might confuse people
        index = rw_repo.index
        index.entries[manifest_key] = IndexEntry.from_base(manifest_fake_entry)
        index.write()
        self.assertEqual(rw_repo.index.entries[manifest_key].hexsha, Diff.NULL_HEX_SHA)

        # write an unchanged index ( just for the fun of it )
        rw_repo.index.write()

        # a three way merge would result in a conflict and fails as the command will
        # not overwrite any entries in our index and hence leave them unmerged. This is
        # mainly a protection feature as the current index is not yet in a tree
        self.failUnlessRaises(GitCommandError, index.merge_tree, next_commit, base=parent_commit)

        # the only way to get the merged entries is to safe the current index away into a tree,
        # which is like a temporary commit for us. This fails as well as the NULL sha deos not
        # have a corresponding object
        # NOTE: missing_ok is not a kwarg anymore, missing_ok is always true
        # self.failUnlessRaises(GitCommandError, index.write_tree)

        # if missing objects are okay, this would work though ( they are always okay now )
        # As we can't read back the tree with NULL_SHA, we rather set it to something else
        index.entries[manifest_key] = IndexEntry(manifest_entry[:1] + (hex_to_bin('f' * 40),) + manifest_entry[2:])
        tree = index.write_tree()

        # now make a proper three way merge with unmerged entries
        unmerged_tree = IndexFile.from_tree(rw_repo, parent_commit, tree, next_commit)
        unmerged_blobs = unmerged_tree.unmerged_blobs()
        self.assertEqual(len(unmerged_blobs), 1)
        self.assertEqual(list(unmerged_blobs.keys())[0], manifest_key[0])
Exemplo n.º 3
0
    def test_index_merge_tree(self, rw_repo):
        # A bit out of place, but we need a different repo for this:
        self.assertNotEqual(self.rorepo, rw_repo)
        self.assertEqual(len(set((self.rorepo, self.rorepo, rw_repo, rw_repo))), 2)

        # SINGLE TREE MERGE
        # current index is at the (virtual) cur_commit
        next_commit = "4c39f9da792792d4e73fc3a5effde66576ae128c"
        parent_commit = rw_repo.head.commit.parents[0]
        manifest_key = IndexFile.entry_key('MANIFEST.in', 0)
        manifest_entry = rw_repo.index.entries[manifest_key]
        rw_repo.index.merge_tree(next_commit)
        # only one change should be recorded
        assert manifest_entry.binsha != rw_repo.index.entries[manifest_key].binsha

        rw_repo.index.reset(rw_repo.head)
        self.assertEqual(rw_repo.index.entries[manifest_key].binsha, manifest_entry.binsha)

        # FAKE MERGE
        #############
        # Add a change with a NULL sha that should conflict with next_commit. We
        # pretend there was a change, but we do not even bother adding a proper
        # sha for it ( which makes things faster of course )
        manifest_fake_entry = BaseIndexEntry((manifest_entry[0], b"\0" * 20, 0, manifest_entry[3]))
        # try write flag
        self._assert_entries(rw_repo.index.add([manifest_fake_entry], write=False))
        # add actually resolves the null-hex-sha for us as a feature, but we can
        # edit the index manually
        assert rw_repo.index.entries[manifest_key].binsha != Object.NULL_BIN_SHA
        # must operate on the same index for this ! Its a bit problematic as
        # it might confuse people
        index = rw_repo.index
        index.entries[manifest_key] = IndexEntry.from_base(manifest_fake_entry)
        index.write()
        self.assertEqual(rw_repo.index.entries[manifest_key].hexsha, Diff.NULL_HEX_SHA)

        # write an unchanged index ( just for the fun of it )
        rw_repo.index.write()

        # a three way merge would result in a conflict and fails as the command will
        # not overwrite any entries in our index and hence leave them unmerged. This is
        # mainly a protection feature as the current index is not yet in a tree
        self.failUnlessRaises(GitCommandError, index.merge_tree, next_commit, base=parent_commit)

        # the only way to get the merged entries is to safe the current index away into a tree,
        # which is like a temporary commit for us. This fails as well as the NULL sha deos not
        # have a corresponding object
        # NOTE: missing_ok is not a kwarg anymore, missing_ok is always true
        # self.failUnlessRaises(GitCommandError, index.write_tree)

        # if missing objects are okay, this would work though ( they are always okay now )
        # As we can't read back the tree with NULL_SHA, we rather set it to something else
        index.entries[manifest_key] = IndexEntry(manifest_entry[:1] + (hex_to_bin('f' * 40),) + manifest_entry[2:])
        tree = index.write_tree()

        # now make a proper three way merge with unmerged entries
        unmerged_tree = IndexFile.from_tree(rw_repo, parent_commit, tree, next_commit)
        unmerged_blobs = unmerged_tree.unmerged_blobs()
        self.assertEqual(len(unmerged_blobs), 1)
        self.assertEqual(list(unmerged_blobs.keys())[0], manifest_key[0])
Exemplo n.º 4
0
 def _git_rel_path(path):
     try:
         repo = Repo(path, search_parent_directories=True)
         index = IndexFile(repo)
         path = Path(index._to_relative_path(path))
         return path.as_posix()
     except Exception:
         return ''
Exemplo n.º 5
0
    def test_index_file_from_tree(self, rw_repo):
        if sys.version_info < (2, 7):
            ## Skipped, not `assertRaisesRegexp` in py2.6
            return
        common_ancestor_sha = "5117c9c8a4d3af19a9958677e45cda9269de1541"
        cur_sha = "4b43ca7ff72d5f535134241e7c797ddc9c7a3573"
        other_sha = "39f85c4358b7346fee22169da9cad93901ea9eb9"

        # simple index from tree
        base_index = IndexFile.from_tree(rw_repo, common_ancestor_sha)
        assert base_index.entries
        self._cmp_tree_index(common_ancestor_sha, base_index)

        # merge two trees - its like a fast-forward
        two_way_index = IndexFile.from_tree(rw_repo, common_ancestor_sha,
                                            cur_sha)
        assert two_way_index.entries
        self._cmp_tree_index(cur_sha, two_way_index)

        # merge three trees - here we have a merge conflict
        three_way_index = IndexFile.from_tree(rw_repo, common_ancestor_sha,
                                              cur_sha, other_sha)
        assert len(
            list(e for e in three_way_index.entries.values() if e.stage != 0))

        # ITERATE BLOBS
        merge_required = lambda t: t[0] != 0
        merge_blobs = list(three_way_index.iter_blobs(merge_required))
        assert merge_blobs
        assert merge_blobs[0][0] in (1, 2, 3)
        assert isinstance(merge_blobs[0][1], Blob)

        # test BlobFilter
        prefix = 'lib/git'
        for stage, blob in base_index.iter_blobs(BlobFilter(
            [prefix])):  # @UnusedVariable
            assert blob.path.startswith(prefix)

        # writing a tree should fail with an unmerged index
        self.failUnlessRaises(UnmergedEntriesError, three_way_index.write_tree)

        # removed unmerged entries
        unmerged_blob_map = three_way_index.unmerged_blobs()
        assert unmerged_blob_map

        # pick the first blob at the first stage we find and use it as resolved version
        three_way_index.resolve_blobs(l[0][1]
                                      for l in unmerged_blob_map.values())
        tree = three_way_index.write_tree()
        assert isinstance(tree, Tree)
        num_blobs = 0
        for blob in tree.traverse(
                predicate=lambda item, d: item.type == "blob"):
            assert (blob.path, 0) in three_way_index.entries
            num_blobs += 1
        # END for each blob
        self.assertEqual(num_blobs, len(three_way_index.entries))
Exemplo n.º 6
0
    def test_index_file_from_tree(self, rw_repo):
        if sys.version_info < (2, 7):
            ## Skipped, not `assertRaisesRegexp` in py2.6
            return
        common_ancestor_sha = "5117c9c8a4d3af19a9958677e45cda9269de1541"
        cur_sha = "4b43ca7ff72d5f535134241e7c797ddc9c7a3573"
        other_sha = "39f85c4358b7346fee22169da9cad93901ea9eb9"

        # simple index from tree
        base_index = IndexFile.from_tree(rw_repo, common_ancestor_sha)
        assert base_index.entries
        self._cmp_tree_index(common_ancestor_sha, base_index)

        # merge two trees - its like a fast-forward
        two_way_index = IndexFile.from_tree(rw_repo, common_ancestor_sha, cur_sha)
        assert two_way_index.entries
        self._cmp_tree_index(cur_sha, two_way_index)

        # merge three trees - here we have a merge conflict
        three_way_index = IndexFile.from_tree(rw_repo, common_ancestor_sha, cur_sha, other_sha)
        assert len(list(e for e in three_way_index.entries.values() if e.stage != 0))

        # ITERATE BLOBS
        merge_required = lambda t: t[0] != 0
        merge_blobs = list(three_way_index.iter_blobs(merge_required))
        assert merge_blobs
        assert merge_blobs[0][0] in (1, 2, 3)
        assert isinstance(merge_blobs[0][1], Blob)

        # test BlobFilter
        prefix = 'lib/git'
        for stage, blob in base_index.iter_blobs(BlobFilter([prefix])):  # @UnusedVariable
            assert blob.path.startswith(prefix)

        # writing a tree should fail with an unmerged index
        self.failUnlessRaises(UnmergedEntriesError, three_way_index.write_tree)

        # removed unmerged entries
        unmerged_blob_map = three_way_index.unmerged_blobs()
        assert unmerged_blob_map

        # pick the first blob at the first stage we find and use it as resolved version
        three_way_index.resolve_blobs(l[0][1] for l in unmerged_blob_map.values())
        tree = three_way_index.write_tree()
        assert isinstance(tree, Tree)
        num_blobs = 0
        for blob in tree.traverse(predicate=lambda item, d: item.type == "blob"):
            assert (blob.path, 0) in three_way_index.entries
            num_blobs += 1
        # END for each blob
        self.assertEqual(num_blobs, len(three_way_index.entries))
Exemplo n.º 7
0
    def test__to_relative_path_at_root(self):
        root = osp.abspath(os.sep)

        class Mocked(object):
            bare = False
            git_dir = root
            working_tree_dir = root

        repo = Mocked()
        path = os.path.join(root, 'file')
        index = IndexFile(repo)

        rel = index._to_relative_path(path)
        self.assertEqual(rel, os.path.relpath(path, root))
Exemplo n.º 8
0
    def test__to_relative_path_at_root(self):
        root = osp.abspath(os.sep)

        class Mocked(object):
            bare = False
            git_dir = root
            working_tree_dir = root

        repo = Mocked()
        path = os.path.join(root, 'file')
        index = IndexFile(repo)

        rel = index._to_relative_path(path)
        self.assertEqual(rel, os.path.relpath(path, root))
Exemplo n.º 9
0
    def dependencies(self, revision='HEAD', paths=None):
        """Return dependencies from a revision or paths."""
        result = []

        if paths:
            paths = (self.normalize_path(path) for path in paths)
        else:
            if revision == 'HEAD':
                index = self.client.repo.index
            else:
                from git import IndexFile
                index = IndexFile.from_tree(self.client.repo, revision)

            paths = (path for path, _ in index.entries.keys())

        for path in paths:
            try:
                result.append(
                    Usage.from_revision(
                        self.client,
                        path=path,
                        revision=revision,
                    ))
            except KeyError:
                continue

        return result
Exemplo n.º 10
0
    def test_index_new(self):
        B = self.rorepo.tree("6d9b1f4f9fa8c9f030e3207e7deacc5d5f8bba4e")
        H = self.rorepo.tree("25dca42bac17d511b7e2ebdd9d1d679e7626db5f")
        M = self.rorepo.tree("e746f96bcc29238b79118123028ca170adc4ff0f")

        for args in ((B, ), (B, H), (B, H, M)):
            index = IndexFile.new(self.rorepo, *args)
            assert isinstance(index, IndexFile)
Exemplo n.º 11
0
    def test_index_new(self):
        B = self.rorepo.tree("6d9b1f4f9fa8c9f030e3207e7deacc5d5f8bba4e")
        H = self.rorepo.tree("25dca42bac17d511b7e2ebdd9d1d679e7626db5f")
        M = self.rorepo.tree("e746f96bcc29238b79118123028ca170adc4ff0f")

        for args in ((B,), (B, H), (B, H, M)):
            index = IndexFile.new(self.rorepo, *args)
            assert isinstance(index, IndexFile)
Exemplo n.º 12
0
    def test_index_file_base(self):
        # read from file
        index = IndexFile(self.rorepo, fixture_path("index"))
        assert index.entries
        assert index.version > 0

        # test entry
        entry = next(iter(index.entries.values()))
        for attr in ("path", "ctime", "mtime", "dev", "inode", "mode", "uid",
                     "gid", "size", "binsha", "hexsha", "stage"):
            getattr(entry, attr)
        # END for each method

        # test update
        entries = index.entries
        assert isinstance(index.update(), IndexFile)
        assert entries is not index.entries

        # test stage
        index_merge = IndexFile(self.rorepo, fixture_path("index_merge"))
        self.assertEqual(len(index_merge.entries), 106)
        assert len([e for e in index_merge.entries.values() if e.stage != 0])

        # write the data - it must match the original
        tmpfile = tempfile.mktemp()
        index_merge.write(tmpfile)
        with open(tmpfile, 'rb') as fp:
            self.assertEqual(fp.read(), fixture("index_merge"))
        os.remove(tmpfile)
Exemplo n.º 13
0
    def add_data(self, index: git.IndexFile, path: str, data: bytes):
        """
        Adds physical files to the database.

        WARNING: this function touches physical files in the Git Repo which can result in a race
        condition to modify a file while it is also being pushed. ONLY CALL THIS FUNCTION INSIDE
        A COMMIT_LOCK.

        @param index:
        @param path:
        @param data:
        @return:
        """
        fullpath = os.path.join(os.path.dirname(index.repo.git_dir), path)
        pathlib.Path(fullpath).parent.mkdir(parents=True, exist_ok=True)
        with open(fullpath, 'wb') as fp:
            fp.write(data)
        index.add([fullpath])
Exemplo n.º 14
0
    def time_warp(self, c_o):
        """
        History rewriting occurs here.  We read everything from the original
        commit, reformat the python, and checkin, mirroring the original
        commit history.
        :param c_o: Commit object representing "before"
        :return: None
        """

        log.info('warping: {} | {} | {:f} MB | {}s'.format(
            time_convert(c_o.authored_date, c_o.author_tz_offset), c_o.summary,
            resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1000,
            time.clock()))

        items = self.handle_commit(c_o)

        parent_commits = tuple(self.converted[v] for v in c_o.parents)

        # for the singular case of init / root / the genesis
        if len(parent_commits) == 0:
            parent_commits = {c_o}

        self.repo.head.reference = c_o
        self.repo.head.reset(index=True, working_tree=True)

        idx = IndexFile(self.repo)

        idx.add(items)

        idx.write_tree()

        com_msg = [c_o.message]

        com_msg.extend('\n'.join(self.convert_errors))
        self.convert_errors = []  # todo: rearchitect - too easy to forget

        com_msg.append('\n[gitreformat yapf-ify (github/ghtdak) on {}]'.format(
            time.strftime('%c')))
        com_msg.append('\n[from commit: {}]'.format(c_o.hexsha))

        c_n = idx.commit(''.join(com_msg),
                         parent_commits=parent_commits,
                         author=c_o.author,
                         author_date=time_convert(c_o.authored_date,
                                                  c_o.author_tz_offset),
                         committer=c_o.committer,
                         commit_date=time_convert(c_o.committed_date,
                                                  c_o.committer_tz_offset))

        self.repo.head.reference = c_n
        self.repo.head.reset(index=True, working_tree=True)

        self.verify_paths(c_o.tree, c_n.tree)

        self.converted[c_o] = c_n

        return c_n
Exemplo n.º 15
0
def git_commit(index: IndexFile, message: str) -> Commit:
    """
    Commit all staged files in the current repo.

    :param IndexFile index:
        the current index of the current branch of the current repo
    :param message:
        the commit message to include with the files to be committed
    :return:
        a record of the commit (:py:class:`~git.objects.commit.Commit`), itself
    """
    return index.commit(message, author=get_author())
Exemplo n.º 16
0
def git_add(index: IndexFile, files) -> List[BaseIndexEntry]:
    """
    `git add` all of the given files within the current repo

    :param IndexFile index:
        the current index of the current branch of the current repo
    :param files:
        the files to be `git-add`'ed
    :return:
        the entries just added
    """
    return index.add(files)
Exemplo n.º 17
0
    def time_warp(self, c_o):
        """
        History rewriting occurs here.  We read everything from the original
        commit, reformat the python, and checkin, mirroring the original
        commit history.
        :param c_o: Commit object representing "before"
        :return: None
        """

        log.info('warping: {} | {} | {:f} MB | {}s'.format(
                time_convert(c_o.authored_date, c_o.author_tz_offset),
                c_o.summary,
                resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1000,
                time.clock()))

        items = self.handle_commit(c_o)

        parent_commits = tuple(self.converted[v] for v in c_o.parents)

        # for the singular case of init / root / the genesis
        if len(parent_commits) == 0:
            parent_commits = {c_o}

        self.repo.head.reference = c_o
        self.repo.head.reset(index=True, working_tree=True)

        idx = IndexFile(self.repo)

        idx.add(items)

        idx.write_tree()

        com_msg = [c_o.message]

        com_msg.extend('\n'.join(self.convert_errors))
        self.convert_errors = []  # todo: rearchitect - too easy to forget

        com_msg.append(
                '\n[gitreformat yapf-ify (github/ghtdak) on {}]'.format(
                        time.strftime('%c')))
        com_msg.append('\n[from commit: {}]'.format(c_o.hexsha))

        c_n = idx.commit(
                ''.join(com_msg),
                parent_commits=parent_commits,
                author=c_o.author,
                author_date=time_convert(c_o.authored_date,
                                         c_o.author_tz_offset),
                committer=c_o.committer,
                commit_date=time_convert(c_o.committed_date,
                                         c_o.committer_tz_offset))

        self.repo.head.reference = c_n
        self.repo.head.reset(index=True, working_tree=True)

        self.verify_paths(c_o.tree, c_n.tree)

        self.converted[c_o] = c_n

        return c_n
Exemplo n.º 18
0
    def build_status(self, revision='HEAD'):
        """Return files from the revision grouped by their status."""
        index = self.client.git.index if revision == 'HEAD' \
            else IndexFile.from_tree(self.client.git, revision)

        current_files = set()

        for filepath, _ in index.entries.keys():
            if not filepath.startswith('.renku') and \
                    filepath not in {'.gitignore', '.gitattributes'}:
                self.add_file(filepath, revision=revision)
                current_files.add(filepath)

        # Prepare status info for each file.
        self._need_update()

        graph_files = sorted(
            ((commit, filepath)
             for (commit, filepath) in self.G if filepath in current_files),
            key=itemgetter(1))

        status = {'up-to-date': {}, 'outdated': {}, 'multiple-versions': {}}

        for filepath, keys in groupby(graph_files, itemgetter(1)):
            keys = list(keys)

            if len(keys) > 1:
                status['multiple-versions'][filepath] = keys

            nodes = [self.G.nodes[key] for key in keys]

            # Any latest version of a file needs an update.
            is_outdated = any(
                len(node['_need_update']) > 1 for node in nodes
                if node['latest'] is None)

            if is_outdated:
                updates = list(self.G.nodes[key]['_need_update']
                               for key in keys)
                status['outdated'][filepath] = updates
            else:
                status['up-to-date'][filepath] = keys[0][0]

        return status
Exemplo n.º 19
0
    def dependencies(self, revision='HEAD', paths=None):
        """Return dependencies from a revision or paths."""
        if paths:
            return {
                Usage.from_revision(self.client, path=path, revision=revision)
                for path in paths
            }

        if revision == 'HEAD':
            index = self.client.git.index
        else:
            from git import IndexFile
            index = IndexFile.from_tree(self.client.git, revision)

        return {
            Usage.from_revision(
                client=self.client,
                path=path,
                revision=revision,
            )
            for path, _ in index.entries.keys()
        }
Exemplo n.º 20
0
def add_data(index: git.IndexFile, path: str, data: bytes):
    fullpath = os.path.join(os.path.dirname(index.repo.git_dir), path)
    pathlib.Path(fullpath).parent.mkdir(parents=True, exist_ok=True)
    with open(fullpath, 'wb') as fp:
        fp.write(data)
    index.add([fullpath])
Exemplo n.º 21
0
def squash_blame(preserve_committers):
    """Squash history while preserving blame"""
    repo = Repo()
    head_commit = repo.head.commit
    mutated_blobs = []
    author_blob_lists = defaultdict(list)

    with click.progressbar(head_commit.tree.traverse()) as git_objects:
        for blob in git_objects:
            if isinstance(blob, Blob):
                blob_bytes = blob.data_stream.read()
                mutated_blob_bytes = blob_bytes.replace(b"\n", b"\r\n")
                mutated_blob = make_blob(repo, mutated_blob_bytes, blob.mode,
                                         blob.path)
                mutated_blobs.append(mutated_blob)

                author_lines = defaultdict(set)
                for blame in repo.blame_incremental(head_commit, blob.path):
                    author_lines[blame.commit.author].update(blame.linenos)

                for author, authors_line_numbers in author_lines.items():
                    authors_fixed_blob_bytes_lines = []
                    for line_number, line in enumerate(
                            mutated_blob_bytes.splitlines(keepends=True),
                            start=1):
                        if line_number in authors_line_numbers:
                            line = line.replace(b"\r\n", b"\n")
                        authors_fixed_blob_bytes_lines.append(line)

                    authors_fixed_blob_bytes = b"".join(
                        authors_fixed_blob_bytes_lines)
                    authors_fixed_blob = make_blob(repo,
                                                   authors_fixed_blob_bytes,
                                                   blob.mode, blob.path)
                    author_blob_lists[author].append(authors_fixed_blob)

    base_index = IndexFile.from_tree(repo, head_commit)
    base_index.add(mutated_blobs, write=False)
    new_root = base_index.commit(message="Initial commit",
                                 parent_commits=[],
                                 head=False)

    author_commits = []
    for author, author_blobs in author_blob_lists.items():
        index = IndexFile.from_tree(repo, new_root)
        index.add(author_blobs, write=False)
        author_commits.append(
            index.commit(
                message=f"Squash while preserving git-blame for {author.name}",
                parent_commits=[new_root],
                head=False,
                author=author,
                committer=author if preserve_committers else None,
            ))

    final_index = IndexFile.from_tree(repo, head_commit)
    final_commit = final_index.commit(
        message="Merge commits that preserve git-blame",
        parent_commits=author_commits,
        head=False,
    )

    print(final_commit.hexsha)
Exemplo n.º 22
0
 def remove_data(self, index: git.IndexFile, path: str):
     fullpath = os.path.join(os.path.dirname(index.repo.git_dir), path)
     pathlib.Path(fullpath).parent.mkdir(parents=True, exist_ok=True)
     index.remove([fullpath], working_tree=True)
Exemplo n.º 23
0
    def test_index_file_diffing(self, rw_repo):
        # default Index instance points to our index
        index = IndexFile(rw_repo)
        assert index.path is not None
        assert len(index.entries)

        # write the file back
        index.write()

        # could sha it, or check stats

        # test diff
        # resetting the head will leave the index in a different state, and the
        # diff will yield a few changes
        cur_head_commit = rw_repo.head.reference.commit
        rw_repo.head.reset('HEAD~6', index=True, working_tree=False)

        # diff against same index is 0
        diff = index.diff()
        self.assertEqual(len(diff), 0)

        # against HEAD as string, must be the same as it matches index
        diff = index.diff('HEAD')
        self.assertEqual(len(diff), 0)

        # against previous head, there must be a difference
        diff = index.diff(cur_head_commit)
        assert len(diff)

        # we reverse the result
        adiff = index.diff(str(cur_head_commit), R=True)
        odiff = index.diff(cur_head_commit,
                           R=False)  # now its not reversed anymore
        assert adiff != odiff
        self.assertEqual(odiff, diff)  # both unreversed diffs against HEAD

        # against working copy - its still at cur_commit
        wdiff = index.diff(None)
        assert wdiff != adiff
        assert wdiff != odiff

        # against something unusual
        self.failUnlessRaises(ValueError, index.diff, int)

        # adjust the index to match an old revision
        cur_branch = rw_repo.active_branch
        cur_commit = cur_branch.commit
        rev_head_parent = 'HEAD~1'
        assert index.reset(rev_head_parent) is index

        self.assertEqual(cur_branch, rw_repo.active_branch)
        self.assertEqual(cur_commit, rw_repo.head.commit)

        # there must be differences towards the working tree which is in the 'future'
        assert index.diff(None)

        # reset the working copy as well to current head,to pull 'back' as well
        new_data = b"will be reverted"
        file_path = osp.join(rw_repo.working_tree_dir, "CHANGES")
        with open(file_path, "wb") as fp:
            fp.write(new_data)
        index.reset(rev_head_parent, working_tree=True)
        assert not index.diff(None)
        self.assertEqual(cur_branch, rw_repo.active_branch)
        self.assertEqual(cur_commit, rw_repo.head.commit)
        with open(file_path, 'rb') as fp:
            assert fp.read() != new_data

        # test full checkout
        test_file = osp.join(rw_repo.working_tree_dir, "CHANGES")
        with open(test_file, 'ab') as fd:
            fd.write(b"some data")
        rval = index.checkout(None, force=True, fprogress=self._fprogress)
        assert 'CHANGES' in list(rval)
        self._assert_fprogress([None])
        assert osp.isfile(test_file)

        os.remove(test_file)
        rval = index.checkout(None, force=False, fprogress=self._fprogress)
        assert 'CHANGES' in list(rval)
        self._assert_fprogress([None])
        assert osp.isfile(test_file)

        # individual file
        os.remove(test_file)
        rval = index.checkout(test_file, fprogress=self._fprogress)
        self.assertEqual(list(rval)[0], 'CHANGES')
        self._assert_fprogress([test_file])
        assert osp.exists(test_file)

        # checking out non-existing file throws
        self.failUnlessRaises(CheckoutError, index.checkout,
                              "doesnt_exist_ever.txt.that")
        self.failUnlessRaises(CheckoutError,
                              index.checkout,
                              paths=["doesnt/exist"])

        # checkout file with modifications
        append_data = b"hello"
        with open(test_file, "ab") as fp:
            fp.write(append_data)
        try:
            index.checkout(test_file)
        except CheckoutError as e:
            self.assertEqual(len(e.failed_files), 1)
            self.assertEqual(e.failed_files[0], osp.basename(test_file))
            self.assertEqual(len(e.failed_files), len(e.failed_reasons))
            self.assertIsInstance(e.failed_reasons[0], str)
            self.assertEqual(len(e.valid_files), 0)
            with open(test_file, 'rb') as fd:
                s = fd.read()
            self.assertTrue(s.endswith(append_data), s)
        else:
            raise AssertionError("Exception CheckoutError not thrown")

        # if we force it it should work
        index.checkout(test_file, force=True)
        assert not open(test_file, 'rb').read().endswith(append_data)

        # checkout directory
        rmtree(osp.join(rw_repo.working_tree_dir, "lib"))
        rval = index.checkout('lib')
        assert len(list(rval)) > 1
Exemplo n.º 24
0
    def test_references_and_objects(self, rw_dir):
        # [1-test_references_and_objects]
        import git
        repo = git.Repo.clone_from(self._small_repo_url(), os.path.join(rw_dir, 'repo'), branch='master')

        heads = repo.heads
        master = heads.master       # lists can be accessed by name for convenience
        master.commit               # the commit pointed to by head called master
        master.rename('new_name')   # rename heads
        master.rename('master')
        # ![1-test_references_and_objects]

        # [2-test_references_and_objects]
        tags = repo.tags
        tagref = tags[0]
        tagref.tag                  # tags may have tag objects carrying additional information
        tagref.commit               # but they always point to commits
        repo.delete_tag(tagref)     # delete or
        repo.create_tag("my_tag")   # create tags using the repo for convenience
        # ![2-test_references_and_objects]

        # [3-test_references_and_objects]
        head = repo.head            # the head points to the active branch/ref
        master = head.reference     # retrieve the reference the head points to
        master.commit               # from here you use it as any other reference
        # ![3-test_references_and_objects]

        # [4-test_references_and_objects]
        log = master.log()
        log[0]                      # first (i.e. oldest) reflog entry
        log[-1]                     # last (i.e. most recent) reflog entry
        # ![4-test_references_and_objects]

        # [5-test_references_and_objects]
        new_branch = repo.create_head('new')     # create a new one
        new_branch.commit = 'HEAD~10'            # set branch to another commit without changing index or working trees
        repo.delete_head(new_branch)             # delete an existing head - only works if it is not checked out
        # ![5-test_references_and_objects]

        # [6-test_references_and_objects]
        new_tag = repo.create_tag('my_new_tag', message='my message')
        # You cannot change the commit a tag points to. Tags need to be re-created
        self.failUnlessRaises(AttributeError, setattr, new_tag, 'commit', repo.commit('HEAD~1'))
        repo.delete_tag(new_tag)
        # ![6-test_references_and_objects]

        # [7-test_references_and_objects]
        new_branch = repo.create_head('another-branch')
        repo.head.reference = new_branch
        # ![7-test_references_and_objects]

        # [8-test_references_and_objects]
        hc = repo.head.commit
        hct = hc.tree
        hc != hct
        hc != repo.tags[0]
        hc == repo.head.reference.commit
        # ![8-test_references_and_objects]

        # [9-test_references_and_objects]
        assert hct.type == 'tree'           # preset string type, being a class attribute
        assert hct.size > 0                 # size in bytes
        assert len(hct.hexsha) == 40
        assert len(hct.binsha) == 20
        # ![9-test_references_and_objects]

        # [10-test_references_and_objects]
        assert hct.path == ''                  # root tree has no path
        assert hct.trees[0].path != ''         # the first contained item has one though
        assert hct.mode == 0o40000              # trees have the mode of a linux directory
        assert hct.blobs[0].mode == 0o100644   # blobs have a specific mode though comparable to a standard linux fs
        # ![10-test_references_and_objects]

        # [11-test_references_and_objects]
        hct.blobs[0].data_stream.read()        # stream object to read data from
        hct.blobs[0].stream_data(open(os.path.join(rw_dir, 'blob_data'), 'wb'))  # write data to given stream
        # ![11-test_references_and_objects]

        # [12-test_references_and_objects]
        repo.commit('master')
        repo.commit('v0.8.1')
        repo.commit('HEAD~10')
        # ![12-test_references_and_objects]

        # [13-test_references_and_objects]
        fifty_first_commits = list(repo.iter_commits('master', max_count=50))
        assert len(fifty_first_commits) == 50
        # this will return commits 21-30 from the commit list as traversed backwards master
        ten_commits_past_twenty = list(repo.iter_commits('master', max_count=10, skip=20))
        assert len(ten_commits_past_twenty) == 10
        assert fifty_first_commits[20:30] == ten_commits_past_twenty
        # ![13-test_references_and_objects]

        # [14-test_references_and_objects]
        headcommit = repo.head.commit
        assert len(headcommit.hexsha) == 40
        assert len(headcommit.parents) > 0
        assert headcommit.tree.type == 'tree'
        assert headcommit.author.name == 'Sebastian Thiel'
        assert isinstance(headcommit.authored_date, int)
        assert headcommit.committer.name == 'Sebastian Thiel'
        assert isinstance(headcommit.committed_date, int)
        assert headcommit.message != ''
        # ![14-test_references_and_objects]

        # [15-test_references_and_objects]
        import time
        time.asctime(time.gmtime(headcommit.committed_date))
        time.strftime("%a, %d %b %Y %H:%M", time.gmtime(headcommit.committed_date))
        # ![15-test_references_and_objects]

        # [16-test_references_and_objects]
        assert headcommit.parents[0].parents[0].parents[0] == repo.commit('master^^^')
        # ![16-test_references_and_objects]

        # [17-test_references_and_objects]
        tree = repo.heads.master.commit.tree
        assert len(tree.hexsha) == 40
        # ![17-test_references_and_objects]

        # [18-test_references_and_objects]
        assert len(tree.trees) > 0          # trees are subdirectories
        assert len(tree.blobs) > 0          # blobs are files
        assert len(tree.blobs) + len(tree.trees) == len(tree)
        # ![18-test_references_and_objects]

        # [19-test_references_and_objects]
        assert tree['smmap'] == tree / 'smmap'          # access by index and by sub-path
        for entry in tree:                                         # intuitive iteration of tree members
            print(entry)
        blob = tree.trees[0].blobs[0]                              # let's get a blob in a sub-tree
        assert blob.name
        assert len(blob.path) < len(blob.abspath)
        assert tree.trees[0].name + '/' + blob.name == blob.path   # this is how the relative blob path is generated
        assert tree[blob.path] == blob                             # you can use paths like 'dir/file' in tree[...]
        # ![19-test_references_and_objects]

        # [20-test_references_and_objects]
        assert tree / 'smmap' == tree['smmap']
        assert tree / blob.path == tree[blob.path]
        # ![20-test_references_and_objects]

        # [21-test_references_and_objects]
        # This example shows the various types of allowed ref-specs
        assert repo.tree() == repo.head.commit.tree
        past = repo.commit('HEAD~5')
        assert repo.tree(past) == repo.tree(past.hexsha)
        assert repo.tree('v0.8.1').type == 'tree'               # yes, you can provide any refspec - works everywhere
        # ![21-test_references_and_objects]

        # [22-test_references_and_objects]
        assert len(tree) < len(list(tree.traverse()))
        # ![22-test_references_and_objects]

        # [23-test_references_and_objects]
        index = repo.index
        # The index contains all blobs in a flat list
        assert len(list(index.iter_blobs())) == len([o for o in repo.head.commit.tree.traverse() if o.type == 'blob'])
        # Access blob objects
        for (path, stage), entry in index.entries.items():
            pass
        new_file_path = os.path.join(repo.working_tree_dir, 'new-file-name')
        open(new_file_path, 'w').close()
        index.add([new_file_path])                                             # add a new file to the index
        index.remove(['LICENSE'])                                              # remove an existing one
        assert os.path.isfile(os.path.join(repo.working_tree_dir, 'LICENSE'))  # working tree is untouched

        assert index.commit("my commit message").type == 'commit'              # commit changed index
        repo.active_branch.commit = repo.commit('HEAD~1')                      # forget last commit

        from git import Actor
        author = Actor("An author", "*****@*****.**")
        committer = Actor("A committer", "*****@*****.**")
        # commit by commit message and author and committer
        index.commit("my commit message", author=author, committer=committer)
        # ![23-test_references_and_objects]

        # [24-test_references_and_objects]
        from git import IndexFile
        # loads a tree into a temporary index, which exists just in memory
        IndexFile.from_tree(repo, 'HEAD~1')
        # merge two trees three-way into memory
        merge_index = IndexFile.from_tree(repo, 'HEAD~10', 'HEAD', repo.merge_base('HEAD~10', 'HEAD'))
        # and persist it
        merge_index.write(os.path.join(rw_dir, 'merged_index'))
        # ![24-test_references_and_objects]

        # [25-test_references_and_objects]
        empty_repo = git.Repo.init(os.path.join(rw_dir, 'empty'))
        origin = empty_repo.create_remote('origin', repo.remotes.origin.url)
        assert origin.exists()
        assert origin == empty_repo.remotes.origin == empty_repo.remotes['origin']
        origin.fetch()                  # assure we actually have data. fetch() returns useful information
        # Setup a local tracking branch of a remote branch
        empty_repo.create_head('master', origin.refs.master).set_tracking_branch(origin.refs.master)
        origin.rename('new_origin')   # rename remotes
        # push and pull behaves similarly to `git push|pull`
        origin.pull()
        origin.push()
        # assert not empty_repo.delete_remote(origin).exists()     # create and delete remotes
        # ![25-test_references_and_objects]

        # [26-test_references_and_objects]
        assert origin.url == repo.remotes.origin.url
        cw = origin.config_writer
        cw.set("pushurl", "other_url")
        cw.release()

        # Please note that in python 2, writing origin.config_writer.set(...) is totally safe.
        # In py3 __del__ calls can be delayed, thus not writing changes in time.
        # ![26-test_references_and_objects]

        # [27-test_references_and_objects]
        hcommit = repo.head.commit
        hcommit.diff()                  # diff tree against index
        hcommit.diff('HEAD~1')          # diff tree against previous tree
        hcommit.diff(None)              # diff tree against working tree
        
        index = repo.index
        index.diff()                    # diff index against itself yielding empty diff
        index.diff(None)                # diff index against working copy
        index.diff('HEAD')              # diff index against current HEAD tree
        # ![27-test_references_and_objects]

        # [28-test_references_and_objects]
        # Traverse added Diff objects only
        for diff_added in hcommit.diff('HEAD~1').iter_change_type('A'):
            print(diff_added)
        # ![28-test_references_and_objects]

        # [29-test_references_and_objects]
        # Reset our working tree 10 commits into the past
        past_branch = repo.create_head('past_branch', 'HEAD~10')
        repo.head.reference = past_branch
        assert not repo.head.is_detached
        # reset the index and working tree to match the pointed-to commit
        repo.head.reset(index=True, working_tree=True)

        # To detach your head, you have to point to a commit directy
        repo.head.reference = repo.commit('HEAD~5')
        assert repo.head.is_detached
        # now our head points 15 commits into the past, whereas the working tree
        # and index are 10 commits in the past
        # ![29-test_references_and_objects]

        # [30-test_references_and_objects]
        # checkout the branch using git-checkout. It will fail as the working tree appears dirty
        self.failUnlessRaises(git.GitCommandError, repo.heads.master.checkout)
        repo.heads.past_branch.checkout()
        # ![30-test_references_and_objects]

        # [31-test_references_and_objects]
        git = repo.git
        git.checkout('HEAD', b="my_new_branch")         # create a new branch
        git.branch('another-new-one')
        git.branch('-D', 'another-new-one')             # pass strings for full control over argument order
        git.for_each_ref()                              # '-' becomes '_' when calling it
        # ![31-test_references_and_objects]

        # [32-test_references_and_objects]
        ssh_executable = os.path.join(rw_dir, 'my_ssh_executable.sh')
        with repo.git.custom_environment(GIT_SSH=ssh_executable):
            # Note that we don't actually make the call here, as our test-setup doesn't permit it to
            # succeed.
            # It will in your case :)
            repo.remotes.origin.fetch
Exemplo n.º 25
0
    def test_index_file_diffing(self, rw_repo):
        # default Index instance points to our index
        index = IndexFile(rw_repo)
        assert index.path is not None
        assert len(index.entries)

        # write the file back
        index.write()

        # could sha it, or check stats

        # test diff
        # resetting the head will leave the index in a different state, and the
        # diff will yield a few changes
        cur_head_commit = rw_repo.head.reference.commit
        rw_repo.head.reset('HEAD~6', index=True, working_tree=False)

        # diff against same index is 0
        diff = index.diff()
        self.assertEqual(len(diff), 0)

        # against HEAD as string, must be the same as it matches index
        diff = index.diff('HEAD')
        self.assertEqual(len(diff), 0)

        # against previous head, there must be a difference
        diff = index.diff(cur_head_commit)
        assert len(diff)

        # we reverse the result
        adiff = index.diff(str(cur_head_commit), R=True)
        odiff = index.diff(cur_head_commit, R=False)    # now its not reversed anymore
        assert adiff != odiff
        self.assertEqual(odiff, diff)                    # both unreversed diffs against HEAD

        # against working copy - its still at cur_commit
        wdiff = index.diff(None)
        assert wdiff != adiff
        assert wdiff != odiff

        # against something unusual
        self.failUnlessRaises(ValueError, index.diff, int)

        # adjust the index to match an old revision
        cur_branch = rw_repo.active_branch
        cur_commit = cur_branch.commit
        rev_head_parent = 'HEAD~1'
        assert index.reset(rev_head_parent) is index

        self.assertEqual(cur_branch, rw_repo.active_branch)
        self.assertEqual(cur_commit, rw_repo.head.commit)

        # there must be differences towards the working tree which is in the 'future'
        assert index.diff(None)

        # reset the working copy as well to current head,to pull 'back' as well
        new_data = b"will be reverted"
        file_path = osp.join(rw_repo.working_tree_dir, "CHANGES")
        with open(file_path, "wb") as fp:
            fp.write(new_data)
        index.reset(rev_head_parent, working_tree=True)
        assert not index.diff(None)
        self.assertEqual(cur_branch, rw_repo.active_branch)
        self.assertEqual(cur_commit, rw_repo.head.commit)
        with open(file_path, 'rb') as fp:
            assert fp.read() != new_data

        # test full checkout
        test_file = osp.join(rw_repo.working_tree_dir, "CHANGES")
        with open(test_file, 'ab') as fd:
            fd.write(b"some data")
        rval = index.checkout(None, force=True, fprogress=self._fprogress)
        assert 'CHANGES' in list(rval)
        self._assert_fprogress([None])
        assert osp.isfile(test_file)

        os.remove(test_file)
        rval = index.checkout(None, force=False, fprogress=self._fprogress)
        assert 'CHANGES' in list(rval)
        self._assert_fprogress([None])
        assert osp.isfile(test_file)

        # individual file
        os.remove(test_file)
        rval = index.checkout(test_file, fprogress=self._fprogress)
        self.assertEqual(list(rval)[0], 'CHANGES')
        self._assert_fprogress([test_file])
        assert osp.exists(test_file)

        # checking out non-existing file throws
        self.failUnlessRaises(CheckoutError, index.checkout, "doesnt_exist_ever.txt.that")
        self.failUnlessRaises(CheckoutError, index.checkout, paths=["doesnt/exist"])

        # checkout file with modifications
        append_data = b"hello"
        with open(test_file, "ab") as fp:
            fp.write(append_data)
        try:
            index.checkout(test_file)
        except CheckoutError as e:
            self.assertEqual(len(e.failed_files), 1)
            self.assertEqual(e.failed_files[0], osp.basename(test_file))
            self.assertEqual(len(e.failed_files), len(e.failed_reasons))
            self.assertIsInstance(e.failed_reasons[0], string_types)
            self.assertEqual(len(e.valid_files), 0)
            with open(test_file, 'rb') as fd:
                s = fd.read()
            self.assertTrue(s.endswith(append_data), s)
        else:
            raise AssertionError("Exception CheckoutError not thrown")

        # if we force it it should work
        index.checkout(test_file, force=True)
        assert not open(test_file, 'rb').read().endswith(append_data)

        # checkout directory
        rmtree(osp.join(rw_repo.working_tree_dir, "lib"))
        rval = index.checkout('lib')
        assert len(list(rval)) > 1