Exemple #1
0
def get_changes(tree, parents, base_path=''):
    if not parents:
        for line in Git.ls_tree(tree, base_path, recursive=True):
            mode, typ, sha1, path = line
            yield path[3:], sha1, ()
    elif len(parents) == 1:
        for path, node, parent in manifest_diff(parents[0], tree, base_path):
            yield path, node, (parent,)
    else:
        for path, node, parents in manifest_diff2(parents[0], parents[1],
                                                  tree, base_path):
            yield path, node, parents
Exemple #2
0
def do_rollback(ref):
    checked = Git.resolve_ref('refs/cinnabar/checked')
    if ref:
        sha1 = Git.resolve_ref(ref)
        if not sha1:
            logging.error('Invalid ref: %s', ref)
            return 1
        if sha1 != NULL_NODE_ID:
            # Validate that the sha1 is in the history of the current metadata
            metadata = Git.resolve_ref('refs/cinnabar/metadata')
            while metadata and metadata != sha1:
                previous_metadata = get_previous_metadata(metadata)
                if checked == metadata:
                    checked = previous_metadata
                metadata = previous_metadata
            if not metadata:
                logging.error('Cannot rollback to %s, it is not in the '
                              'history of the current metadata.', ref)
                return 1
    else:
        metadata = Git.resolve_ref('refs/cinnabar/metadata')
        if metadata:
            sha1 = get_previous_metadata(metadata) or NULL_NODE_ID
        else:
            sha1 = NULL_NODE_ID
        if checked and checked == metadata:
            checked = sha1

    refs = VersionedDict(
        (ref, commit)
        for commit, ref in Git.for_each_ref('refs/cinnabar',
                                            'refs/notes/cinnabar')
    )
    for ref in refs:
        if sha1 == NULL_NODE_ID or ref not in (b'refs/cinnabar/checked',
                                               b'refs/cinnabar/broken'):
            del refs[ref]
    if sha1 != NULL_NODE_ID:
        refs[b'refs/cinnabar/metadata'] = sha1
        if checked:
            refs[b'refs/cinnabar/checked'] = checked
        for line in Git.ls_tree(sha1):
            mode, typ, commit, path = line
            refs[b'refs/cinnabar/replace/%s' % path] = commit

    for status, ref, commit in refs.iterchanges():
        if status == VersionedDict.REMOVED:
            Git.delete_ref(ref)
        else:
            Git.update_ref(ref, commit)
    GitHgHelper.close(rollback=False)

    return 0
Exemple #3
0
def get_changes(tree, parents, all=False):
    if not parents:
        for line in Git.ls_tree(tree, recursive=True):
            mode, typ, sha1, path = line
            yield path, sha1, ()
    elif len(parents) == 1:
        for path, node, parent in manifest_diff(parents[0], tree):
            yield path, node, (parent, )
    else:
        diff = manifest_diff2_all if all else manifest_diff2
        for path, node, parents in diff(parents[0], parents[1], tree):
            yield path, node, parents
Exemple #4
0
def get_changes(tree, parents, all=False):
    if not parents:
        for line in Git.ls_tree(tree, recursive=True):
            mode, typ, sha1, path = line
            yield path, sha1, ()
    elif len(parents) == 1:
        for path, node, parent in manifest_diff(parents[0], tree):
            yield path, node, (parent,)
    else:
        diff = manifest_diff2_all if all else manifest_diff2
        for path, node, parents in diff(parents[0], parents[1], tree):
            yield path, node, parents
Exemple #5
0
def get_changes(tree, parents, base_path=''):
    if not parents:
        for line in Git.ls_tree(tree, base_path, recursive=True):
            mode, typ, sha1, path = line
            yield path[len(base_path) + 1:] if base_path else path, sha1, ()
    elif len(parents) == 1:
        for path, node, parent in manifest_diff(parents[0], tree, base_path):
            yield path, node, (parent,)
    else:
        for path, node, parents in manifest_diff2(parents[0], parents[1],
                                                  tree, base_path):
            yield path, node, parents
Exemple #6
0
def do_rollback(ref):
    checked = Git.resolve_ref('refs/cinnabar/checked')
    if ref:
        sha1 = Git.resolve_ref(ref)
        if not sha1:
            logging.error('Invalid ref: %s', ref)
            return 1
        if sha1 != NULL_NODE_ID:
            # Validate that the sha1 is in the history of the current metadata
            metadata = Git.resolve_ref('refs/cinnabar/metadata')
            while metadata and metadata != sha1:
                previous_metadata = get_previous_metadata(metadata)
                if checked == metadata:
                    checked = previous_metadata
                metadata = previous_metadata
            if not metadata:
                logging.error('Cannot rollback to %s, it is not in the '
                              'history of the current metadata.', ref)
                return 1
    else:
        metadata = Git.resolve_ref('refs/cinnabar/metadata')
        if metadata:
            sha1 = get_previous_metadata(metadata) or NULL_NODE_ID
        else:
            sha1 = NULL_NODE_ID
        if checked and checked == metadata:
            checked = sha1

    refs = VersionedDict(
        (ref, commit)
        for commit, ref in Git.for_each_ref('refs/cinnabar',
                                            'refs/notes/cinnabar')
    )
    for ref in refs:
        if ref not in ('refs/cinnabar/checked', 'refs/cinnabar/broken'):
            del refs[ref]
    if sha1 != NULL_NODE_ID:
        refs['refs/cinnabar/metadata'] = sha1
        if checked:
            refs['refs/cinnabar/checked'] = checked
        for line in Git.ls_tree(sha1):
            mode, typ, commit, path = line
            refs['refs/cinnabar/replace/%s' % path] = commit

    for status, ref, commit in refs.iterchanges():
        if status == VersionedDict.REMOVED:
            Git.delete_ref(ref)
        else:
            Git.update_ref(ref, commit)
    GitHgHelper.close(rollback=False)

    return 0
Exemple #7
0
def do_rollback(ref):
    sha1 = Git.resolve_ref(ref)
    if not sha1:
        logging.error('Invalid ref: %s', ref)
        return 1
    if sha1 != NULL_NODE_ID:
        # Validate that the sha1 is in the history of the current metadata
        metadata = Git.resolve_ref('refs/cinnabar/metadata')
        while metadata:
            if sha1 == metadata:
                break
            commit = GitCommit(metadata)
            flags = commit.body.split(' ')
            if len(commit.parents) == 5 + ('files-meta' in flags):
                metadata = commit.parents[-1]
            else:
                metadata = None
        if not metadata:
            logging.error(
                'Cannot rollback to %s, it is not in the history of '
                'the current metadata.', ref)
            return 1

    refs = VersionedDict((ref, commit) for commit, ref in Git.for_each_ref(
        'refs/cinnabar', 'refs/notes/cinnabar'))
    for ref in refs:
        del refs[ref]
    if sha1 != NULL_NODE_ID:
        refs['refs/cinnabar/metadata'] = sha1
        for line in Git.ls_tree(sha1):
            mode, typ, commit, path = line
            refs['refs/cinnabar/replace/%s' % path] = commit

    for status, ref, commit in refs.iterchanges():
        if status == VersionedDict.REMOVED:
            Git.delete_ref(ref)
        else:
            Git.update_ref(ref, commit)
    Git._close_update_ref()

    return 0
Exemple #8
0
    def close(self, rollback=False):
        if rollback:
            self._closed = True
        if self._closed:
            return
        for manifest in self._push_manifests.itervalues():
            self.store_manifest(manifest)
            ls = one(Git.ls_tree(self.manifest_ref(manifest.node), 'git'))
            if self._manifest_git_tree[manifest.node] == EMPTY_TREE and not ls:
                pass
            else:
                mode, typ, sha1, path = ls
                assert sha1 == self._manifest_git_tree[manifest.node]

        for file in self._push_files.itervalues():
            if isinstance(self._files[file.node], Mark):
                mark = self._fast_import.new_mark()
                self._fast_import.put_blob(data=file.data, mark=mark)
                self._files[file.node] = Mark(mark)

        super(PushStore, self).close()
Exemple #9
0
    def close(self, rollback=False):
        if rollback:
            self._closed = True
        if self._closed:
            return
        for manifest in self._push_manifests.itervalues():
            self.store_manifest(manifest)
            ls = one(Git.ls_tree(self.manifest_ref(manifest.node), 'git'))
            if self._manifest_git_tree[manifest.node] == EMPTY_TREE and not ls:
                pass
            else:
                mode, typ, sha1, path = ls
                assert sha1 == self._manifest_git_tree[manifest.node]

        for file in self._push_files.itervalues():
            if isinstance(self._files[file.node], Mark):
                mark = self._fast_import.new_mark()
                self._fast_import.put_blob(data=file.data, mark=mark)
                self._files[file.node] = Mark(mark)

        super(PushStore, self).close()
def fsck(args):
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--manifests', action='store_true',
        help='Validate manifests hashes')
    parser.add_argument(
        '--files', action='store_true',
        help='Validate files hashes')
    parser.add_argument(
        'commit', nargs='*',
        help='Specific commit or changeset to check')
    args = parser.parse_args(args)

    status = {
        'broken': False,
        'fixed': False,
    }

    def info(message):
        sys.stderr.write('\r')
        print message

    def fix(message):
        status['fixed'] = True
        info(message)

    def report(message):
        status['broken'] = True
        info(message)

    store = GitHgStore()
    store.init_fast_import(lambda: FastImport())

    if args.commit:
        all_hg2git = {}
        all_notes = set()
        commits = set()
        all_git_commits = {}

        for c in args.commit:
            data = store.read_changeset_data(c)
            if data:
                all_notes.add(c)
                commits.add(c)
                c = data['changeset']
            commit = GitHgHelper.hg2git(c)
            if commit == NULL_NODE_ID and not data:
                info('Unknown commit or changeset: %s' % c)
                return 1
            if commit != NULL_NODE_ID:
                all_hg2git[c] = commit, 'commit'
            if not data:
                data = store.read_changeset_data(commit)
                commits.add(commit)
                if data:
                    all_notes.add(commit)

        all_git_commits = Git.iter(
            'log', '--no-walk=unsorted', '--stdin', '--format=%T %H',
            stdin=commits)
    else:
        all_hg2git = {
            path.replace('/', ''): (filesha1, intern(typ))
            for mode, typ, filesha1, path in
            progress_iter('Reading %d mercurial to git mappings',
                          Git.ls_tree('refs/cinnabar/hg2git', recursive=True))
        }

        all_notes = set(path.replace('/', '') for mode, typ, filesha1, path in
                        progress_iter(
                            'Reading %d commit to changeset mappings',
                            Git.ls_tree('refs/notes/cinnabar',
                                        recursive=True)))

        manifest_commits = OrderedDict((m, None) for m in progress_iter(
            'Reading %d manifest trees',
            Git.iter('rev-list', '--full-history',
                     '--topo-order', 'refs/cinnabar/manifest'))
        )

        all_git_heads = Git.for_each_ref('refs/cinnabar/branches',
                                         format='%(refname)')

        all_git_commits = Git.iter('log', '--topo-order', '--full-history',
                                   '--reverse', '--stdin', '--format=%T %H',
                                   stdin=all_git_heads)

    store._hg2git_cache = {p: s for p, (s, t) in all_hg2git.iteritems()}

    seen_changesets = set()
    seen_manifests = set()
    seen_manifest_refs = {}
    seen_files = set()
    seen_notes = set()

    hg_manifest = None

    dag = gitdag()

    for line in progress_iter('Checking %d changesets', all_git_commits):
        tree, node = line.split(' ')
        if node not in all_notes:
            report('Missing note for git commit: ' + node)
            continue
        seen_notes.add(node)

        changeset_data = store.read_changeset_data(node)
        changeset = changeset_data['changeset']
        if 'extra' in changeset_data:
            extra = changeset_data['extra']
            header, message = GitHgHelper.cat_file(
                'commit', node).split('\n\n', 1)
            header = dict(l.split(' ', 1) for l in header.splitlines())
            if 'committer' in extra:
                committer_info = store.hg_author_info(header['committer'])
                committer = '%s %d %d' % committer_info
                if (committer != extra['committer'] and
                        header['committer'] != extra['committer'] and
                        committer_info[0] != extra['committer']):
                    report('Committer mismatch between commit and metadata for'
                           ' changeset %s' % changeset)
                if committer == extra['committer']:
                    fix('Fixing useless committer metadata for changeset %s'
                        % changeset)
                    del changeset_data['extra']['committer']
                    store._changesets[changeset] = LazyString(node)
            if header['committer'] != header['author'] and not extra:
                fix('Fixing useless empty extra metadata for changeset %s'
                    % changeset)
                del changeset_data['extra']
                store._changesets[changeset] = LazyString(node)

        seen_changesets.add(changeset)
        changeset_ref = store.changeset_ref(changeset)
        if not changeset_ref:
            report('Missing changeset in hg2git branch: %s' % changeset)
        elif str(changeset_ref) != node:
            report('Commit mismatch for changeset %s\n'
                   '  hg2git: %s\n  commit: %s'
                   % (changeset, changeset_ref, node))

        hg_changeset = store.changeset(changeset, include_parents=True)
        sha1 = hg_changeset.sha1
        if hg_changeset.node != sha1:
            try_fixup = False
            if (changeset, sha1) in (
                ('8c557b7c03a4a753e5c163038f04862e9f65fce1',
                 '249b59139de8e08abeb6c4e261a137c756e7af0e'),
                ('ffdee4a4eb7fc7cae80dfc4cb2fe0c3178773dcf',
                 '415e9d2eac83d508bf58a4df585c5f6b2b0f44ed'),
            ):
                header = hg_changeset.data.split('\n', 4)
                start = sum(len(h) for h in header[:3]) + 1
                changeset_data['patch'] = ((start, start + 1, '1'),)
                try_fixup = True

            # Some know cases of corruptions involve a whitespace after the
            # timezone. Adding an empty extra metadata works around those.
            elif 'extra' not in changeset_data:
                changeset_data['extra'] = {}
                try_fixup = True

            if try_fixup:
                hg_changeset = store.changeset(changeset, include_parents=True)
                sha1 = hg_changeset.sha1
                if hg_changeset.node == sha1:
                    fix('Fixing known sha1 mismatch for changeset %s' %
                        changeset)
                    store._changesets[changeset] = LazyString(node)

        if hg_changeset.node != sha1:
            report('Sha1 mismatch for changeset %s' % changeset)

        dag.add(hg_changeset.node,
                (hg_changeset.parent1, hg_changeset.parent2),
                changeset_data.get('extra', {}).get('branch', 'default'))

        manifest = changeset_data['manifest']
        if manifest in seen_manifests:
            continue
        seen_manifests.add(manifest)
        manifest_ref = store.manifest_ref(manifest)
        if manifest_ref:
            seen_manifest_refs[manifest_ref] = manifest
        if not manifest_ref:
            report('Missing manifest in hg2git branch: %s' % manifest)
        elif not args.commit and manifest_ref not in manifest_commits:
            report('Missing manifest commit in manifest branch: %s' %
                   manifest_ref)

        if args.manifests or args.files:
            parents = tuple(
                store.read_changeset_data(store.changeset_ref(p))['manifest']
                for p in (hg_changeset.parent1, hg_changeset.parent2)
                if p != NULL_NODE_ID
            )

        if args.manifests:
            try:
                with GitHgHelper.query('check-manifest', manifest,
                                       *parents) as stdout:
                    if stdout.readline().strip() != 'ok':
                        report('Sha1 mismatch for manifest %s' % manifest)
            except NoHelperException:
                hg_manifest = store.manifest(manifest)
                hg_manifest.set_parents(*parents)
                if hg_manifest.node != hg_manifest.sha1:
                    report('Sha1 mismatch for manifest %s' % manifest)

        git_ls = one(Git.ls_tree(manifest_ref, 'git'))
        if git_ls:
            mode, typ, sha1, path = git_ls
        else:
            header, message = GitHgHelper.cat_file(
                'commit', manifest_ref).split('\n\n', 1)
            header = dict(l.split(' ', 1) for l in header.splitlines())
            if header['tree'] == EMPTY_TREE:
                sha1 = EMPTY_TREE
            else:
                report('Missing git tree in manifest commit %s' % manifest_ref)
                sha1 = None
        if sha1 and sha1 != tree:
            report('Tree mismatch between manifest commit %s and commit %s'
                   % (manifest_ref, node))

        if args.files:
            changes = get_changes(
                manifest_ref, tuple(store.manifest_ref(p) for p in parents),
                'hg')
            for path, hg_file, hg_fileparents in changes:
                if hg_file != NULL_NODE_ID and hg_file not in seen_files:
                    file = store.file(hg_file)
                    file.set_parents(*hg_fileparents)
                    if file.node != file.sha1:
                        report('Sha1 mismatch for file %s in manifest %s'
                               % (hg_file, manifest_ref))
                    seen_files.add(hg_file)

    if args.files:
        all_hg2git = set(all_hg2git.iterkeys())
    else:
        all_hg2git = set(k for k, (s, t) in all_hg2git.iteritems()
                         if t == 'commit')

    adjusted = {}
    if not args.commit:
        dangling = set(manifest_commits) - set(seen_manifest_refs)
        if dangling:
            def iter_manifests():
                removed_one = False
                yielded = False
                previous = None
                for obj in reversed(manifest_commits):
                    if obj in dangling:
                        fix('Removing metadata commit %s with no hg2git entry'
                            % obj)
                        removed_one = True
                    else:
                        if removed_one:
                            yield obj, previous
                            yielded = True
                        previous = obj

                if removed_one and not yielded:
                    yield obj, False

            for obj, parent in progress_iter('Adjusting %d metadata commits',
                                             iter_manifests()):
                mark = store._fast_import.new_mark()
                if parent is False:
                    Git.update_ref('refs/cinnabar/manifest', obj)
                    continue
                elif parent:
                    parents = (adjusted.get(parent, parent),)
                with store._fast_import.commit(
                        ref='refs/cinnabar/manifest',
                        parents=parents, mark=mark) as commit:
                    mode, typ, tree, path = store._fast_import.ls(obj)
                    commit.filemodify('', tree, typ='tree')
                adjusted[obj] = Mark(mark)

    dangling = all_hg2git - seen_changesets - seen_manifests - seen_files
    if dangling or adjusted:
        with store._fast_import.commit(
                ref='refs/cinnabar/hg2git',
                parents=('refs/cinnabar/hg2git^0',)) as commit:
            for obj in dangling:
                fix('Removing dangling metadata for ' + obj)
                commit.filedelete(sha1path(obj))
            for obj, mark in progress_iter(
                    'Updating hg2git for %d metadata commits',
                    adjusted.iteritems()):
                commit.filemodify(sha1path(seen_manifest_refs[obj]), mark,
                                  typ='commit')

    dangling = all_notes - seen_notes
    if dangling:
        with store._fast_import.commit(
                ref='refs/notes/cinnabar',
                parents=('refs/notes/cinnabar^0',)) as commit:
            for c in dangling:
                fix('Removing dangling note for commit ' + c)
                # That's brute force, but meh.
                for l in range(0, 10):
                    commit.filedelete(sha1path(c, l))

    if status['broken']:
        info('Your git-cinnabar repository appears to be corrupted. There\n'
             'are known issues in older revisions that have been fixed.\n'
             'Please try running the following command to reset:\n'
             '  git cinnabar reclone\n\n'
             'Please note this command may change the commit sha1s. Your\n'
             'local branches will however stay untouched.\n'
             'Please report any corruption that fsck would detect after a\n'
             'reclone.')

    if not args.commit:
        info('Checking head references...')
        computed_heads = defaultdict(set)
        for branch, head in dag.all_heads():
            computed_heads[branch].add(head)

        for branch in sorted(dag.tags()):
            stored_heads = store.heads({branch})
            for head in computed_heads[branch] - stored_heads:
                fix('Adding missing head %s in branch %s' %
                    (head, branch))
                store.add_head(head)
            for head in stored_heads - computed_heads[branch]:
                fix('Removing non-head reference to %s in branch %s' %
                    (head, branch))
                store._hgheads.remove((branch, head))

    store.close()

    if status['broken']:
        return 1
    if status['fixed']:
        return 2
    return 0
def fsck(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('--manifests',
                        action='store_true',
                        help='Validate manifests hashes')
    parser.add_argument('--files',
                        action='store_true',
                        help='Validate files hashes')
    parser.add_argument('commit',
                        nargs='*',
                        help='Specific commit or changeset to check')
    args = parser.parse_args(args)

    status = {
        'broken': False,
        'fixed': False,
    }

    def info(message):
        sys.stderr.write('\r')
        print message

    def fix(message):
        status['fixed'] = True
        info(message)

    def report(message):
        status['broken'] = True
        info(message)

    store = GitHgStore()
    store.init_fast_import(lambda: FastImport())

    if args.commit:
        all_hg2git = {}
        all_notes = set()
        commits = set()
        all_git_commits = {}

        for c in args.commit:
            data = store.read_changeset_data(c)
            if data:
                all_notes.add(c)
                commits.add(c)
                c = data['changeset']
            commit = GitHgHelper.hg2git(c)
            if commit == NULL_NODE_ID and not data:
                info('Unknown commit or changeset: %s' % c)
                return 1
            if commit != NULL_NODE_ID:
                all_hg2git[c] = commit, 'commit'
            if not data:
                data = store.read_changeset_data(commit)
                commits.add(commit)
                if data:
                    all_notes.add(commit)

        all_git_commits = Git.iter('log',
                                   '--no-walk=unsorted',
                                   '--stdin',
                                   '--format=%T %H',
                                   stdin=commits)
    else:
        all_hg2git = {
            path.replace('/', ''): (filesha1, intern(typ))
            for mode, typ, filesha1, path in progress_iter(
                'Reading %d mercurial to git mappings',
                Git.ls_tree('refs/cinnabar/hg2git', recursive=True))
        }

        all_notes = set(
            path.replace('/', '')
            for mode, typ, filesha1, path in progress_iter(
                'Reading %d commit to changeset mappings',
                Git.ls_tree('refs/notes/cinnabar', recursive=True)))

        manifest_commits = OrderedDict((m, None) for m in progress_iter(
            'Reading %d manifest trees',
            Git.iter('rev-list', '--full-history', '--topo-order',
                     'refs/cinnabar/manifest')))

        all_git_heads = Git.for_each_ref('refs/cinnabar/branches',
                                         format='%(refname)')

        all_git_commits = Git.iter('log',
                                   '--topo-order',
                                   '--full-history',
                                   '--reverse',
                                   '--stdin',
                                   '--format=%T %H',
                                   stdin=all_git_heads)

    store._hg2git_cache = {p: s for p, (s, t) in all_hg2git.iteritems()}

    seen_changesets = set()
    seen_manifests = set()
    seen_manifest_refs = {}
    seen_files = set()
    seen_notes = set()

    hg_manifest = None

    dag = gitdag()

    for line in progress_iter('Checking %d changesets', all_git_commits):
        tree, node = line.split(' ')
        if node not in all_notes:
            report('Missing note for git commit: ' + node)
            continue
        seen_notes.add(node)

        changeset_data = store.read_changeset_data(node)
        changeset = changeset_data['changeset']
        if 'extra' in changeset_data:
            extra = changeset_data['extra']
            header, message = GitHgHelper.cat_file('commit',
                                                   node).split('\n\n', 1)
            header = dict(l.split(' ', 1) for l in header.splitlines())
            if 'committer' in extra:
                committer_info = store.hg_author_info(header['committer'])
                committer = '%s %d %d' % committer_info
                if (committer != extra['committer']
                        and header['committer'] != extra['committer']
                        and committer_info[0] != extra['committer']):
                    report('Committer mismatch between commit and metadata for'
                           ' changeset %s' % changeset)
                if committer == extra['committer']:
                    fix('Fixing useless committer metadata for changeset %s' %
                        changeset)
                    del changeset_data['extra']['committer']
                    store._changesets[changeset] = LazyString(node)
            if header['committer'] != header['author'] and not extra:
                fix('Fixing useless empty extra metadata for changeset %s' %
                    changeset)
                del changeset_data['extra']
                store._changesets[changeset] = LazyString(node)

        seen_changesets.add(changeset)
        changeset_ref = store.changeset_ref(changeset)
        if not changeset_ref:
            report('Missing changeset in hg2git branch: %s' % changeset)
        elif str(changeset_ref) != node:
            report('Commit mismatch for changeset %s\n'
                   '  hg2git: %s\n  commit: %s' %
                   (changeset, changeset_ref, node))

        hg_changeset = store.changeset(changeset, include_parents=True)
        sha1 = hg_changeset.sha1
        if hg_changeset.node != sha1:
            try_fixup = False
            if (changeset, sha1) in (
                ('8c557b7c03a4a753e5c163038f04862e9f65fce1',
                 '249b59139de8e08abeb6c4e261a137c756e7af0e'),
                ('ffdee4a4eb7fc7cae80dfc4cb2fe0c3178773dcf',
                 '415e9d2eac83d508bf58a4df585c5f6b2b0f44ed'),
            ):
                header = hg_changeset.data.split('\n', 4)
                start = sum(len(h) for h in header[:3]) + 1
                changeset_data['patch'] = ((start, start + 1, '1'), )
                try_fixup = True

            # Some know cases of corruptions involve a whitespace after the
            # timezone. Adding an empty extra metadata works around those.
            elif 'extra' not in changeset_data:
                changeset_data['extra'] = {}
                try_fixup = True

            if try_fixup:
                hg_changeset = store.changeset(changeset, include_parents=True)
                sha1 = hg_changeset.sha1
                if hg_changeset.node == sha1:
                    fix('Fixing known sha1 mismatch for changeset %s' %
                        changeset)
                    store._changesets[changeset] = LazyString(node)

        if hg_changeset.node != sha1:
            report('Sha1 mismatch for changeset %s' % changeset)

        dag.add(hg_changeset.node,
                (hg_changeset.parent1, hg_changeset.parent2),
                changeset_data.get('extra', {}).get('branch', 'default'))

        manifest = changeset_data['manifest']
        if manifest in seen_manifests:
            continue
        seen_manifests.add(manifest)
        manifest_ref = store.manifest_ref(manifest)
        if manifest_ref:
            seen_manifest_refs[manifest_ref] = manifest
        if not manifest_ref:
            report('Missing manifest in hg2git branch: %s' % manifest)
        elif not args.commit and manifest_ref not in manifest_commits:
            report('Missing manifest commit in manifest branch: %s' %
                   manifest_ref)

        if args.manifests or args.files:
            parents = tuple(
                store.read_changeset_data(store.changeset_ref(p))['manifest']
                for p in (hg_changeset.parent1, hg_changeset.parent2)
                if p != NULL_NODE_ID)

        if args.manifests:
            try:
                with GitHgHelper.query('check-manifest', manifest,
                                       *parents) as stdout:
                    if stdout.readline().strip() != 'ok':
                        report('Sha1 mismatch for manifest %s' % manifest)
            except NoHelperException:
                hg_manifest = store.manifest(manifest)
                hg_manifest.set_parents(*parents)
                if hg_manifest.node != hg_manifest.sha1:
                    report('Sha1 mismatch for manifest %s' % manifest)

        git_ls = one(Git.ls_tree(manifest_ref, 'git'))
        if git_ls:
            mode, typ, sha1, path = git_ls
        else:
            header, message = GitHgHelper.cat_file('commit',
                                                   manifest_ref).split(
                                                       '\n\n', 1)
            header = dict(l.split(' ', 1) for l in header.splitlines())
            if header['tree'] == EMPTY_TREE:
                sha1 = EMPTY_TREE
            else:
                report('Missing git tree in manifest commit %s' % manifest_ref)
                sha1 = None
        if sha1 and sha1 != tree:
            report('Tree mismatch between manifest commit %s and commit %s' %
                   (manifest_ref, node))

        if args.files:
            changes = get_changes(
                manifest_ref, tuple(store.manifest_ref(p) for p in parents),
                'hg')
            for path, hg_file, hg_fileparents in changes:
                if hg_file != NULL_NODE_ID and hg_file not in seen_files:
                    file = store.file(hg_file)
                    file.set_parents(*hg_fileparents)
                    if file.node != file.sha1:
                        report('Sha1 mismatch for file %s in manifest %s' %
                               (hg_file, manifest_ref))
                    seen_files.add(hg_file)

    if args.files:
        all_hg2git = set(all_hg2git.iterkeys())
    else:
        all_hg2git = set(k for k, (s, t) in all_hg2git.iteritems()
                         if t == 'commit')

    adjusted = {}
    if not args.commit:
        dangling = set(manifest_commits) - set(seen_manifest_refs)
        if dangling:

            def iter_manifests():
                removed_one = False
                yielded = False
                previous = None
                for obj in reversed(manifest_commits):
                    if obj in dangling:
                        fix('Removing metadata commit %s with no hg2git entry'
                            % obj)
                        removed_one = True
                    else:
                        if removed_one:
                            yield obj, previous
                            yielded = True
                        previous = obj

                if removed_one and not yielded:
                    yield obj, False

            for obj, parent in progress_iter('Adjusting %d metadata commits',
                                             iter_manifests()):
                mark = store._fast_import.new_mark()
                if parent is False:
                    Git.update_ref('refs/cinnabar/manifest', obj)
                    continue
                elif parent:
                    parents = (adjusted.get(parent, parent), )
                with store._fast_import.commit(ref='refs/cinnabar/manifest',
                                               parents=parents,
                                               mark=mark) as commit:
                    mode, typ, tree, path = store._fast_import.ls(obj)
                    commit.filemodify('', tree, typ='tree')
                adjusted[obj] = Mark(mark)

    dangling = all_hg2git - seen_changesets - seen_manifests - seen_files
    if dangling or adjusted:
        with store._fast_import.commit(
                ref='refs/cinnabar/hg2git',
                parents=('refs/cinnabar/hg2git^0', )) as commit:
            for obj in dangling:
                fix('Removing dangling metadata for ' + obj)
                commit.filedelete(sha1path(obj))
            for obj, mark in progress_iter(
                    'Updating hg2git for %d metadata commits',
                    adjusted.iteritems()):
                commit.filemodify(sha1path(seen_manifest_refs[obj]),
                                  mark,
                                  typ='commit')

    dangling = all_notes - seen_notes
    if dangling:
        with store._fast_import.commit(
                ref='refs/notes/cinnabar',
                parents=('refs/notes/cinnabar^0', )) as commit:
            for c in dangling:
                fix('Removing dangling note for commit ' + c)
                # That's brute force, but meh.
                for l in range(0, 10):
                    commit.filedelete(sha1path(c, l))

    if status['broken']:
        info('Your git-cinnabar repository appears to be corrupted. There\n'
             'are known issues in older revisions that have been fixed.\n'
             'Please try running the following command to reset:\n'
             '  git cinnabar reclone\n\n'
             'Please note this command may change the commit sha1s. Your\n'
             'local branches will however stay untouched.\n'
             'Please report any corruption that fsck would detect after a\n'
             'reclone.')

    if not args.commit:
        info('Checking head references...')
        computed_heads = defaultdict(set)
        for branch, head in dag.all_heads():
            computed_heads[branch].add(head)

        for branch in sorted(dag.tags()):
            stored_heads = store.heads({branch})
            for head in computed_heads[branch] - stored_heads:
                fix('Adding missing head %s in branch %s' % (head, branch))
                store.add_head(head)
            for head in stored_heads - computed_heads[branch]:
                fix('Removing non-head reference to %s in branch %s' %
                    (head, branch))
                store._hgheads.remove((branch, head))

    store.close()

    if status['broken']:
        return 1
    if status['fixed']:
        return 2
    return 0
Exemple #12
0
    def create_hg_manifest(self, commit, parents):
        manifest = GeneratedManifestInfo(NULL_NODE_ID)
        changeset_files = []

        if parents:
            parent_changeset = self.changeset(self.hg_changeset(parents[0]))
            parent_manifest = self.manifest(parent_changeset.manifest)
            parent_node = parent_manifest.node

        if len(parents) == 2:
            parent2_changeset = self.changeset(self.hg_changeset(parents[1]))
            parent2_manifest = self.manifest(parent2_changeset.manifest)
            parent2_node = parent2_manifest.node
            if parent_node == parent2_node:
                parents = parents[:1]

        if not parents:
            for line in Git.ls_tree(commit, recursive=True):
                mode, typ, sha1, path = line
                node = self.create_file(sha1,
                                        git_manifest_parents=(),
                                        path=path)
                manifest.add(path, node, self.ATTR[mode], modified=True)
                changeset_files.append(path)

            manifest.parents = []
            manifest.delta_node = NULL_NODE_ID
            return manifest, changeset_files

        elif len(parents) == 2:
            if not experiment('merge'):
                raise Exception('Pushing merges is not supported yet')
            if not self._merge_warn:
                logging.warning('Pushing merges is experimental.')
                logging.warning('This may irremediably push bad state to the '
                                'mercurial server!')
                self._merge_warn = 1
            git_manifests = (self.manifest_ref(parent_node),
                             self.manifest_ref(parent2_node))

            # TODO: this would benefit from less git queries
            changes = list(get_changes(commit, parents))

            files = [
                (path, mode, sha1)
                for mode, _, sha1, path in Git.ls_tree(commit, recursive=True)
            ]
            manifests = sorted_merge(parent_manifest,
                                     parent2_manifest,
                                     key=lambda i: i.path,
                                     non_key=lambda i: i)
            for line in sorted_merge(files, sorted_merge(changes, manifests)):
                path, f, (change, (manifest_line_p1, manifest_line_p2)) = line
                if not f:  # File was removed
                    if manifest_line_p1:
                        manifest.removed.add(path)
                        changeset_files.append(path)
                    continue
                mode, sha1 = f
                attr = self.ATTR[mode]
                if manifest_line_p1 and not manifest_line_p2:
                    file_parents = (manifest_line_p1.sha1, )
                elif manifest_line_p2 and not manifest_line_p1:
                    file_parents = (manifest_line_p2.sha1, )
                elif not manifest_line_p1 and not manifest_line_p2:
                    file_parents = ()
                elif manifest_line_p1.sha1 == manifest_line_p2.sha1:
                    file_parents = (manifest_line_p1.sha1, )
                else:
                    if self._merge_warn == 1:
                        logging.warning('This may take a while...')
                        self._merge_warn = 2
                    file_parents = (manifest_line_p1.sha1,
                                    manifest_line_p2.sha1)

                assert file_parents is not None
                f = self._create_file_internal(
                    sha1,
                    *file_parents,
                    git_manifest_parents=git_manifests,
                    path=path)
                file_parents = tuple(p for p in (f.parent1, f.parent2)
                                     if p != NULL_NODE_ID)
                merged = len(file_parents) == 2
                if not merged and file_parents:
                    if self.git_file_ref(file_parents[0]) == sha1:
                        node = file_parents[0]
                    else:
                        merged = True
                if merged:
                    node = self._store_file_internal(f)
                else:
                    node = file_parents[0]

                attr_change = (manifest_line_p1
                               and manifest_line_p1.attr != attr)
                manifest.add(path, node, attr, modified=merged or attr_change)
                if merged or attr_change:
                    changeset_files.append(path)
            if manifest.raw_data == parent_manifest.raw_data:
                return parent_manifest, []
            manifest.parents = (parent_node, parent2_node)
            return manifest, changeset_files

        def process_diff(diff):
            for (mode_before, mode_after, sha1_before, sha1_after, status,
                 path) in diff:
                if status[:1] == b'R':
                    yield status[1:], (b'000000', sha1_before, NULL_NODE_ID,
                                       b'D')
                yield path, (mode_after, sha1_before, sha1_after, status)

        git_diff = sorted(l for l in process_diff(
            GitHgHelper.diff_tree(parents[0], commit, detect_copy=True)))
        if not git_diff:
            return parent_manifest, []

        parent_lines = OrderedDict((l.path, l) for l in parent_manifest)
        items = manifest.items
        for line in sorted_merge(iteritems(parent_lines),
                                 git_diff,
                                 non_key=lambda i: i[1]):
            path, manifest_line, change = line
            if not change:
                items.append(manifest_line)
                continue
            mode_after, sha1_before, sha1_after, status = change
            path2 = status[1:]
            status = status[:1]
            attr = self.ATTR.get(mode_after)
            if status == b'D':
                manifest.removed.add(path)
                changeset_files.append(path)
                continue
            if status in b'MT':
                if sha1_before == sha1_after:
                    node = manifest_line.sha1
                else:
                    node = self.create_file(
                        sha1_after,
                        manifest_line.sha1,
                        git_manifest_parents=(
                            self.manifest_ref(parent_node), ),
                        path=path)
            elif status in b'RC':
                if sha1_after != EMPTY_BLOB:
                    node = self.create_copy(
                        (path2, parent_lines[path2].sha1),
                        sha1_after,
                        git_manifest_parents=(
                            self.manifest_ref(parent_node), ),
                        path=path)
                else:
                    node = self.create_file(
                        sha1_after,
                        git_manifest_parents=(
                            self.manifest_ref(parent_node), ),
                        path=path)
            else:
                assert status == b'A'
                node = self.create_file(
                    sha1_after,
                    git_manifest_parents=(self.manifest_ref(parent_node), ),
                    path=path)
            manifest.add(path, node, attr, modified=True)
            changeset_files.append(path)
        manifest.parents = (parent_node, )
        manifest.delta_node = parent_node
        return manifest, changeset_files
Exemple #13
0
    def create_hg_manifest(self, commit, parents):
        manifest = GeneratedManifestInfo(NULL_NODE_ID)
        changeset_files = []

        if parents:
            parent_changeset = self.changeset(self.hg_changeset(parents[0]))
            parent_manifest = self.manifest(parent_changeset.manifest)
            parent_node = parent_manifest.node

        if len(parents) == 2:
            parent2_changeset = self.changeset(self.hg_changeset(parents[1]))
            parent2_manifest = self.manifest(parent2_changeset.manifest)
            parent2_node = parent2_manifest.node
            if parent_node == parent2_node:
                parents = parents[:1]

        if not parents:
            for line in Git.ls_tree(commit, recursive=True):
                mode, typ, sha1, path = line
                node = self.create_file(sha1, git_manifest_parents=(),
                                        path=path)
                manifest.append_line(ManifestLine(path, node, self.ATTR[mode]),
                                     modified=True)
                changeset_files.append(path)

            manifest.set_parents(NULL_NODE_ID)
            manifest.delta_node = NULL_NODE_ID
            return manifest, changeset_files

        elif len(parents) == 2:
            if not experiment('merge'):
                raise Exception('Pushing merges is not supported yet')
            if not self._merge_warn:
                logging.warning('Pushing merges is experimental.')
                logging.warning('This may irremediably push bad state to the '
                                'mercurial server!')
                self._merge_warn = 1
            git_manifests = (self.manifest_ref(parent_node),
                             self.manifest_ref(parent2_node))

            # TODO: this would benefit from less git queries
            changes = list(get_changes(commit, parents))

            files = [(path, mode, sha1) for mode, _, sha1, path in
                     Git.ls_tree(commit, recursive=True)]
            manifests = sorted_merge(parent_manifest._lines,
                                     parent2_manifest._lines,
                                     key=lambda i: i.name, non_key=lambda i: i)
            for line in sorted_merge(files, sorted_merge(changes, manifests)):
                path, f, (change, (manifest_line_p1, manifest_line_p2)) = line
                if not f:  # File was removed
                    if manifest_line_p1:
                        manifest.removed.add(path)
                        changeset_files.append(path)
                    continue
                mode, sha1 = f
                attr = self.ATTR[mode]
                if manifest_line_p1 and not manifest_line_p2:
                    file_parents = (manifest_line_p1.node,)
                elif manifest_line_p2 and not manifest_line_p1:
                    file_parents = (manifest_line_p2.node,)
                elif not manifest_line_p1 and not manifest_line_p2:
                    file_parents = ()
                elif manifest_line_p1.node == manifest_line_p2.node:
                    file_parents = (manifest_line_p1.node,)
                else:
                    if self._merge_warn == 1:
                        logging.warning('This may take a while...')
                        self._merge_warn = 2
                    file_parents = (manifest_line_p1.node,
                                    manifest_line_p2.node)

                assert file_parents is not None
                f = self._create_file_internal(
                    sha1, *file_parents,
                    git_manifest_parents=git_manifests,
                    path=path
                )
                file_parents = tuple(p for p in (f.parent1, f.parent2)
                                     if p != NULL_NODE_ID)
                merged = len(file_parents) == 2
                if not merged and file_parents:
                    if self.git_file_ref(file_parents[0]) == sha1:
                        node = file_parents[0]
                    else:
                        merged = True
                if merged:
                    node = self._store_file_internal(f)
                else:
                    node = file_parents[0]

                attr_change = (manifest_line_p1 and
                               manifest_line_p1.attr != attr)
                manifest.append_line(ManifestLine(path, node, attr),
                                     modified=merged or attr_change)
                if merged or attr_change:
                    changeset_files.append(path)
            if manifest.data == parent_manifest.data:
                return parent_manifest, []
            manifest.set_parents(parent_node, parent2_node)
            return manifest, changeset_files

        def process_diff(diff):
            for (mode_before, mode_after, sha1_before, sha1_after, status,
                 path) in diff:
                if status[0] == 'R':
                    yield status[1:], (
                        '000000', sha1_before, NULL_NODE_ID, 'D')
                yield path, (mode_after, sha1_before, sha1_after,
                             status)
        git_diff = sorted(
            l for l in process_diff(GitHgHelper.diff_tree(
                parents[0], commit, detect_copy=True))
        )
        if not git_diff:
            return parent_manifest, []

        parent_lines = OrderedDict((l.name, l)
                                   for l in parent_manifest._lines)
        for line in sorted_merge(parent_lines.iteritems(), git_diff,
                                 non_key=lambda i: i[1]):
            path, manifest_line, change = line
            if not change:
                manifest.append_line(manifest_line)
                continue
            mode_after, sha1_before, sha1_after, status = change
            path2 = status[1:]
            status = status[0]
            attr = self.ATTR.get(mode_after)
            if status == 'D':
                manifest.removed.add(path)
                changeset_files.append(path)
                continue
            if status in 'MT':
                if sha1_before == sha1_after:
                    node = manifest_line.node
                else:
                    node = self.create_file(
                        sha1_after, str(manifest_line.node),
                        git_manifest_parents=(
                            self.manifest_ref(parent_node),),
                        path=path)
            elif status in 'RC':
                if sha1_after != EMPTY_BLOB:
                    node = self.create_copy(
                        (path2, parent_lines[path2].node), sha1_after,
                        git_manifest_parents=(
                            self.manifest_ref(parent_node),),
                        path=path)
                else:
                    node = self.create_file(
                        sha1_after,
                        git_manifest_parents=(
                            self.manifest_ref(parent_node),),
                        path=path)
            else:
                assert status == 'A'
                node = self.create_file(
                    sha1_after,
                    git_manifest_parents=(
                        self.manifest_ref(parent_node),),
                    path=path)
            manifest.append_line(ManifestLine(path, node, attr),
                                 modified=True)
            changeset_files.append(path)
        manifest.set_parents(parent_node)
        manifest.delta_node = parent_node
        return manifest, changeset_files
Exemple #14
0
    def create_hg_metadata(self, commit, parents):
        if len(parents) > 1:
            raise Exception('Pushing merges is not supported yet')

        manifest = GeneratedManifestInfo(NULL_NODE_ID)

        # TODO: share code with GitHgStore.manifest
        removed = set()
        modified = {}
        copies = {}
        created = OrderedDict()

        if parents:
            parent_changeset_data = self.read_changeset_data(parents[0])
            parent_manifest = self.manifest(parent_changeset_data['manifest'])
            parent_node = parent_manifest.node
            parent_lines = list(parent_manifest._lines)
            branch = parent_changeset_data.get('extra', {}).get('branch')

            line = None
            for line in Git.diff_tree(parents[0], commit, detect_copy=True,
                                      recursive=True):
                mode_before, mode_after, sha1_before, sha1_after, status, \
                    path = line
                status = status[0]
                if status == 'D':
                    removed.add(path)
                elif status in 'MT':
                    if sha1_before == sha1_after:
                        modified[path] = (None, self.ATTR[mode_after])
                    else:
                        modified[path] = (sha1_after, self.ATTR[mode_after])
                elif status in 'RC':
                    path1, path2 = path.split('\t', 1)
                    if status == 'R':
                        removed.add(path1)
                    if sha1_after != EMPTY_BLOB:
                        copies[path2] = path1
                    created[path2] = (sha1_after, self.ATTR[mode_after])
                else:
                    assert status == 'A'
                    created[path] = (sha1_after, self.ATTR[mode_after])
            if line is None:
                manifest = parent_manifest
                parent_lines = []
        else:
            parent_node = NULL_NODE_ID
            parent_lines = []
            branch = None

            for line in Git.ls_tree(commit, recursive=True):
                mode, typ, sha1, path = line
                created[path] = (sha1, self.ATTR[mode])

        if copies:
            copied = {k: () for k in copies.values()}
            for line in parent_lines:
                name = str(line.name)
                if name in copied:
                    copied[name] = line.node

        iter_created = created.iteritems()
        next_created = next(iter_created)
        modified_lines = []
        for line in parent_lines:
            if line.name in removed and line.name not in created:
                continue
            mod = modified.get(line.name)
            if mod:
                node, attr = mod
                if attr is None:
                    attr = line.attr
                if node is None:
                    node = PseudoString(line.node)
                else:
                    node = self.create_file(node, str(line.node))
                line = ManifestLine(line.name, node, attr)
                modified_lines.append(line)
            while next_created and next_created[0] < line.name:
                node, attr = next_created[1]
                if next_created[0] in copies:
                    copied_name = copies[next_created[0]]
                    node = self.create_copy((copied_name, copied[copied_name]),
                                            node)
                else:
                    node = self.create_file(node)
                created_line = ManifestLine(next_created[0], node, attr)
                modified_lines.append(created_line)
                manifest.append_line(created_line)
                next_created = next(iter_created)
            manifest.append_line(line)
        while next_created:
            node, attr = next_created[1]
            if next_created[0] in copies:
                copied_name = copies[next_created[0]]
                node = self.create_copy((copied_name, copied[copied_name]),
                                        node)
            else:
                node = self.create_file(node)
            created_line = ManifestLine(next_created[0], node, attr)
            modified_lines.append(created_line)
            manifest.append_line(created_line)
            next_created = next(iter_created)

        commit_data = GitCommit(commit)

        if manifest.node == NULL_NODE_ID:
            manifest.set_parents(parent_node)
            manifest.node = manifest.sha1
            manifest.removed = removed
            manifest.modified = {l.name: (l.node, l.attr)
                                 for l in modified_lines}
            manifest.delta_node = parent_node
            self._push_manifests[manifest.node] = manifest
            self.manifest_ref(manifest.node, hg2git=False, create=True)
            self._manifest_git_tree[manifest.node] = commit_data.tree

        extra = {}
        if commit_data.author != commit_data.committer:
            committer = self.hg_author_info(commit_data.committer)
            extra['committer'] = '%s %d %d' % committer

        if branch:
            extra['branch'] = branch

        changeset_data = self._changeset_data_cache[commit] = {
            'files': sorted(chain(removed, modified, created)),
            'manifest': manifest.node,
        }
        if extra:
            changeset_data['extra'] = extra
        changeset = self._changeset(commit, include_parents=True)
        if self._graft is True and parents and changeset.data[-1] == '\n':
            parent_cs = self._changeset(parents[0], skip_patch=True)
            if 'patch' not in self._changeset_data_cache[parents[0]]:
                self._graft = False
            else:
                patch = self._changeset_data_cache[parents[0]]['patch'][-1]
                self._graft = (patch[1] == len(parent_cs.data) and
                               parent_cs.data[-1] == '\n')
            if self._graft:
                self._graft = 'true'

        if self._graft == 'true' and changeset.data[-1] == '\n':
            changeset.data = changeset.data[:-1]
            changeset_data['patch'] = (
                (len(changeset.data), len(changeset.data) + 1, ''),
            )
        changeset_data['changeset'] = changeset.changeset = changeset.node = \
            changeset.sha1
        self._push_changesets[changeset.node] = changeset
        # This is a horrible way to do this, but this method is not doing much
        # better overall anyways.
        if extra:
            if 'committer' in extra:
                del extra['committer']
            if not extra:
                del changeset_data['extra']
        self._changesets[changeset.node] = PseudoString(commit)
Exemple #15
0
    def create_hg_manifest(self, commit, parents):
        manifest = GeneratedManifestInfo(NULL_NODE_ID)

        if parents:
            parent_changeset_data = self.read_changeset_data(parents[0])
            parent_manifest = self.manifest(parent_changeset_data['manifest'])
            parent_node = parent_manifest.node

        if len(parents) == 2:
            parent2_changeset_data = self.read_changeset_data(parents[1])
            parent2_manifest = self.manifest(
                parent2_changeset_data['manifest'])
            parent2_node = parent2_manifest.node
            if parent_node == parent2_node:
                parents = parents[:1]

        if not parents:
            for line in Git.ls_tree(commit, recursive=True):
                mode, typ, sha1, path = line
                node = self.create_file(sha1,
                                        git_manifest_parents=(),
                                        path=path)
                manifest.append_line(ManifestLine(path, node, self.ATTR[mode]),
                                     modified=True)

            manifest.set_parents(NULL_NODE_ID)
            manifest.delta_node = NULL_NODE_ID
            return manifest

        elif len(parents) == 2:
            if not experiment('merge'):
                raise Exception('Pushing merges is not supported yet')
            logging.warning('Pushing merges is experimental.')
            logging.warning('This may irremediably push bad state to the '
                            'mercurial server!')
            warned = False
            git_manifests = (self.manifest_ref(parent_node),
                             self.manifest_ref(parent2_node))

            # TODO: this would benefit from less git queries
            changes = list(get_changes(commit, parents))

            files = [
                (path, mode, sha1)
                for mode, _, sha1, path in Git.ls_tree(commit, recursive=True)
            ]
            manifests = sorted_merge(parent_manifest._lines,
                                     parent2_manifest._lines,
                                     key=lambda i: i.name,
                                     non_key=lambda i: i)
            for line in sorted_merge(files, sorted_merge(changes, manifests)):
                path, f, (change, (manifest_line_p1, manifest_line_p2)) = line
                if not f:  # File was removed
                    if manifest_line_p1:
                        manifest.removed.add(path)
                    continue
                mode, sha1 = f
                attr = self.ATTR[mode]
                if manifest_line_p1 and not manifest_line_p2:
                    file_parents = (manifest_line_p1.node, )
                elif manifest_line_p2 and not manifest_line_p1:
                    file_parents = (manifest_line_p2.node, )
                elif not manifest_line_p1 and not manifest_line_p2:
                    file_parents = ()
                elif manifest_line_p1.node == manifest_line_p2.node:
                    file_parents = (manifest_line_p1.node, )
                else:
                    if (any(isinstance(p, Mark) for p in git_manifests)):
                        raise Exception(
                            'Cannot push %s. Please first push %s separately' %
                            (commit, ' and '.join(
                                p for i, p in enumerate(parents)
                                if isinstance(git_manifests[i], Mark))))
                    if not warned:
                        logging.warning('This may take a while...')
                        warned = True
                    file_parents = (manifest_line_p1.node,
                                    manifest_line_p2.node)

                assert file_parents is not None
                f = self._create_file_internal(
                    sha1,
                    *file_parents,
                    git_manifest_parents=git_manifests,
                    path=path)
                file_parents = tuple(p for p in (f.parent1, f.parent2)
                                     if p != NULL_NODE_ID)
                merged = len(file_parents) == 2
                if not merged and file_parents:
                    if self.git_file_ref(file_parents[0]) == sha1:
                        node = file_parents[0]
                    else:
                        merged = True
                if merged:
                    node = self._store_file_internal(f)
                else:
                    node = PseudoString(file_parents[0])

                attr_change = (manifest_line_p1
                               and manifest_line_p1.attr != attr)
                manifest.append_line(ManifestLine(path, node, attr),
                                     modified=merged or attr_change)
            if manifest.data == parent_manifest.data:
                return parent_manifest
            manifest.set_parents(parent_node, parent2_node)
            return manifest

        def process_diff(diff):
            for (mode_before, mode_after, sha1_before, sha1_after, status,
                 path) in diff:
                if status[0] == 'R':
                    yield status[1:], ('000000', sha1_before, NULL_NODE_ID,
                                       'D')
                yield path, (mode_after, sha1_before, sha1_after, status)

        git_diff = sorted(l for l in process_diff(
            Git.diff_tree(parents[0], commit, detect_copy=True)))
        if not git_diff:
            return parent_manifest

        parent_lines = OrderedDict((l.name, l) for l in parent_manifest._lines)
        for line in sorted_merge(parent_lines.iteritems(),
                                 git_diff,
                                 non_key=lambda i: i[1]):
            path, manifest_line, change = line
            if not change:
                manifest.append_line(manifest_line)
                continue
            mode_after, sha1_before, sha1_after, status = change
            path2 = status[1:]
            status = status[0]
            attr = self.ATTR.get(mode_after)
            if status == 'D':
                manifest.removed.add(path)
                continue
            if status in 'MT':
                if sha1_before == sha1_after:
                    node = PseudoString(manifest_line.node)
                else:
                    node = self.create_file(
                        sha1_after,
                        str(manifest_line.node),
                        git_manifest_parents=(
                            self.manifest_ref(parent_node), ),
                        path=path)
            elif status in 'RC':
                if sha1_after != EMPTY_BLOB:
                    node = self.create_copy(
                        (path2, parent_lines[path2].node),
                        sha1_after,
                        git_manifest_parents=(
                            self.manifest_ref(parent_node), ),
                        path=path)
                else:
                    node = self.create_file(
                        sha1_after,
                        git_manifest_parents=(
                            self.manifest_ref(parent_node), ),
                        path=path)
            else:
                assert status == 'A'
                node = self.create_file(
                    sha1_after,
                    git_manifest_parents=(self.manifest_ref(parent_node), ),
                    path=path)
            manifest.append_line(ManifestLine(path, node, attr), modified=True)
        manifest.set_parents(parent_node)
        manifest.delta_node = parent_node
        return manifest
Exemple #16
0
    def test_store_changeset(self):
        files = {}
        f = File()
        f.content = b'foo\n'
        f.node = f.sha1

        chunk = f.to_chunk(self.RevChunk)
        GitHgHelper.store(b'file', chunk)
        files[f.node] = GitHgHelper.hg2git(chunk.node)

        f2 = File()
        f2.content = b'bar\n'
        f2.node = f2.sha1

        chunk = f2.to_chunk(self.RevChunk)
        GitHgHelper.store(b'file', chunk)
        files[f2.node] = GitHgHelper.hg2git(chunk.node)

        m = Manifest()
        m.add(b'bar', f.node)
        m.add(b'foo/.bar', f.node)
        m.add(b'foo/.foo', f.node)
        m.add(b'foo/bar/baz', f.node)
        m.add(b'foo/bar/foo', f.node)
        m.add(b'foo/bar/qux', f.node)
        m.add(b'foo/foo', f.node)
        m.add(b'foo/hoge', f.node)
        m.add(b'foo/qux', f.node)
        m.add(b'qux', f.node)
        m.node = m.sha1

        chunk = m.to_chunk(self.RevChunk)
        GitHgHelper.store(b'manifest', chunk)

        store = GitHgStore()

        c = Changeset()
        c.manifest = m.node
        c.author = b'Cinnabar test <cinnabar@test>'
        c.timestamp = b'0'
        c.utcoffset = b'0'
        c.files = [i.path for i in m]
        c.body = b'Test commit'
        c.node = c.sha1

        store.store_changeset(c)
        c_gen = store.changeset(c.node)
        self.assertEqual(c.raw_data, c_gen.raw_data)

        commit = GitCommit(GitHgHelper.hg2git(c.node))
        self.assertEqual(commit.body, c.body)
        ct = self.commit_tree(m, files)
        self.assertEqual(commit.tree, ct)

        # Weird case as seen in the GNU octave repo.
        # The bar subdirectory is supposed to be transposed to the same
        # content as the git tree for the manifest above.
        m2 = Manifest()
        m2.add(b'bar/bar', f.node)
        m2.add(b'bar/foo/.foo', f.node)
        m2.add(b'bar/foo//.bar', f.node)
        m2.add(b'bar/foo//.foo', f2.node)
        m2.add(b'bar/foo//bar/baz', f2.node)
        m2.add(b'bar/foo//bar/foo', f.node)
        m2.add(b'bar/foo//hoge', f.node)
        m2.add(b'bar/foo/bar/baz', f.node)
        m2.add(b'bar/foo/bar/qux', f.node)
        m2.add(b'bar/foo/foo', f.node)
        m2.add(b'bar/foo/qux', f.node)
        m2.add(b'bar/qux', f.node)
        m2.node = m2.sha1

        chunk = m2.to_chunk(self.RevChunk, m)
        GitHgHelper.store(b'manifest', chunk)

        c2 = Changeset()
        c2.parent1 = c.node
        c2.manifest = m2.node
        c2.author = b'Cinnabar test <cinnabar@test>'
        c2.timestamp = b'0'
        c2.utcoffset = b'0'
        c2.files = [i.path for i in m2]
        c2.body = b'Test commit'
        c2.node = c2.sha1

        store.store_changeset(c2)
        c2_gen = store.changeset(c2.node)
        self.assertEqual(c2.raw_data, c2_gen.raw_data)

        commit = GitCommit(GitHgHelper.hg2git(c2.node))
        self.assertEqual(commit.body, c2.body)
        self.assertEqual(ct, one(Git.ls_tree(commit.tree, b'bar'))[2])

        # Corner case: empty manifest
        m3 = Manifest()
        m3.node = m3.sha1
        self.assertEqual(b'b80de5d138758541c5f05265ad144ab9fa86d1db', m3.node)

        chunk = m3.to_chunk(self.RevChunk, m2)
        GitHgHelper.store(b'manifest', chunk)
        commit = GitCommit(GitHgHelper.hg2git(m3.node))
        self.assertEqual(EMPTY_TREE, commit.tree)

        c3 = Changeset()
        c3.parent1 = c2.node
        c3.manifest = m3.node
        c3.author = b'Cinnabar test <cinnabar@test>'
        c3.timestamp = b'0'
        c3.utcoffset = b'0'
        c3.body = b'Test commit'
        c3.node = c3.sha1

        store.store_changeset(c3)
        c3_gen = store.changeset(c3.node)
        self.assertEqual(c3.raw_data, c3_gen.raw_data)

        commit = GitCommit(GitHgHelper.hg2git(c3.node))
        self.assertEqual(commit.body, c3.body)
        self.assertEqual(EMPTY_TREE, commit.tree)

        # Corner case: null manifest
        c4 = Changeset()
        c4.parent1 = c3.node
        c4.manifest = NULL_NODE_ID
        c4.author = b'Cinnabar test <cinnabar@test>'
        c4.timestamp = b'0'
        c4.utcoffset = b'0'
        c4.body = b'Test commit'
        c4.node = c4.sha1

        store.store_changeset(c4)
        c4_gen = store.changeset(c4.node)
        self.assertEqual(c4.raw_data, c4_gen.raw_data)

        commit = GitCommit(GitHgHelper.hg2git(c4.node))
        self.assertEqual(commit.body, c4.body)
        self.assertEqual(EMPTY_TREE, commit.tree)

        # Corner case: identical changeset with a difference that wouldn't
        # appear in the git commit without adjustment (which is: cinnabar adds
        # a nul character to the commit message..
        chunk = c2.to_chunk(RawRevChunk02)
        c5 = Changeset.from_chunk(chunk)
        c5.branch = b'branched'
        c5.node = c5.sha1

        store.store_changeset(c5)
        c5_gen = store.changeset(c5.node)
        self.assertEqual(c5.raw_data, c5_gen.raw_data)

        commit = GitCommit(GitHgHelper.hg2git(c5.node))
        self.assertEqual(commit.body, c5.body + b'\0')
        self.assertEqual(ct, one(Git.ls_tree(commit.tree, b'bar'))[2])
Exemple #17
0
    def test_store_changeset(self):
        files = {}
        f = File()
        f.content = b'foo\n'
        f.node = f.sha1

        chunk = f.to_chunk(self.RevChunk)
        GitHgHelper.store(b'file', chunk)
        files[f.node] = GitHgHelper.hg2git(chunk.node)

        f2 = File()
        f2.content = b'bar\n'
        f2.node = f2.sha1

        chunk = f2.to_chunk(self.RevChunk)
        GitHgHelper.store(b'file', chunk)
        files[f2.node] = GitHgHelper.hg2git(chunk.node)

        m = Manifest()
        m.add(b'bar', f.node)
        m.add(b'foo/.bar', f.node)
        m.add(b'foo/.foo', f.node)
        m.add(b'foo/bar/baz', f.node)
        m.add(b'foo/bar/foo', f.node)
        m.add(b'foo/bar/qux', f.node)
        m.add(b'foo/foo', f.node)
        m.add(b'foo/hoge', f.node)
        m.add(b'foo/qux', f.node)
        m.add(b'qux', f.node)
        m.node = m.sha1

        chunk = m.to_chunk(self.RevChunk)
        GitHgHelper.store(b'manifest', chunk)

        store = GitHgStore()

        c = Changeset()
        c.manifest = m.node
        c.author = b'Cinnabar test <cinnabar@test>'
        c.timestamp = b'0'
        c.utcoffset = b'0000'
        c.files = [i.path for i in m]
        c.body = b'Test commit'
        c.node = c.sha1

        store.store_changeset(c)
        c_gen = store.changeset(c.node)
        self.assertEqual(c.raw_data, c_gen.raw_data)

        commit = GitCommit(GitHgHelper.hg2git(c.node))
        self.assertEqual(commit.body, c.body)
        ct = self.commit_tree(m, files)
        self.assertEqual(commit.tree, ct)

        # Weird case as seen in the GNU octave repo.
        # The bar subdirectory is supposed to be transposed to the same
        # content as the git tree for the manifest above.
        m2 = Manifest()
        m2.add(b'bar/bar', f.node)
        m2.add(b'bar/foo/.foo', f.node)
        m2.add(b'bar/foo//.bar', f.node)
        m2.add(b'bar/foo//.foo', f2.node)
        m2.add(b'bar/foo//bar/baz', f2.node)
        m2.add(b'bar/foo//bar/foo', f.node)
        m2.add(b'bar/foo//hoge', f.node)
        m2.add(b'bar/foo/bar/baz', f.node)
        m2.add(b'bar/foo/bar/qux', f.node)
        m2.add(b'bar/foo/foo', f.node)
        m2.add(b'bar/foo/qux', f.node)
        m2.add(b'bar/qux', f.node)
        m2.node = m2.sha1

        chunk = m2.to_chunk(self.RevChunk, m)
        GitHgHelper.store(b'manifest', chunk)

        c2 = Changeset()
        c2.parent1 = c.node
        c2.manifest = m2.node
        c2.author = b'Cinnabar test <cinnabar@test>'
        c2.timestamp = b'0'
        c2.utcoffset = b'0000'
        c2.files = [i.path for i in m2]
        c2.body = b'Test commit'
        c2.node = c2.sha1

        store.store_changeset(c2)
        c2_gen = store.changeset(c2.node)
        self.assertEqual(c2.raw_data, c2_gen.raw_data)

        commit = GitCommit(GitHgHelper.hg2git(c2.node))
        self.assertEqual(commit.body, c2.body)
        self.assertEqual(ct, one(Git.ls_tree(commit.tree, b'bar'))[2])