コード例 #1
0
ファイル: remote_helper.py プロジェクト: jhlin/git-cinnabar
    def __init__(self, store, remote, stdin=sys.stdin, stdout=sys.stdout):
        self._store = store
        self._repo = get_repo(remote)
        if isinstance(self._repo, bundlerepo):
            self._repo.init(self._store)
        self._remote = remote
        self._helper = IOLogger(logging.getLogger('remote-helper'),
                                stdin, stdout)

        self._branchmap = None
        self._bookmarks = {}
        self._HEAD = 'branches/default/tip'
        self._has_unknown_heads = False

        GRAFT = {
            None: False,
            'false': False,
            'true': True,
        }
        try:
            self._graft = Git.config('cinnabar.graft', remote=remote.name,
                                     values=GRAFT)
        except InvalidConfig as e:
            logging.error(e.message)
            return 1
        if Git.config('cinnabar.graft-refs') is not None:
            logging.warn(
                'The cinnabar.graft-refs configuration is deprecated.\n'
                'Please unset it.'
            )
コード例 #2
0
    def __init__(self, store, remote, stdin=bytes_stdin, stdout=bytes_stdout):
        super(GitRemoteHelper, self).__init__(stdin, stdout)
        self._store = store
        self._repo = get_repo(remote)
        if isinstance(self._repo, bundlerepo):
            self._repo.init(self._store)
        self._remote = remote

        self._head_template = None
        self._tip_template = None
        self._bookmark_template = None

        self._branchmap = None
        self._bookmarks = {}
        self._has_unknown_heads = False

        GRAFT = {
            None: False,
            b'false': False,
            b'true': True,
        }
        try:
            self._graft = Git.config('cinnabar.graft', remote=remote.name,
                                     values=GRAFT)
        except InvalidConfig as e:
            logging.error(str(e))
            return 1
        if Git.config('cinnabar.graft-refs') is not None:
            logging.warn(
                'The cinnabar.graft-refs configuration is deprecated.\n'
                'Please unset it.'
            )
コード例 #3
0
    def __init__(self, store, remote, stdin=sys.stdin, stdout=sys.stdout):
        self._store = store
        self._repo = get_repo(remote)
        if isinstance(self._repo, bundlerepo):
            self._repo.init(self._store)
        self._remote = remote
        self._helper = IOLogger(logging.getLogger('remote-helper'),
                                stdin, stdout)

        self._dry_run = False
        self._branchmap = None
        self._bookmarks = {}
        self._HEAD = 'branches/default/tip'
        self._has_unknown_heads = False

        GRAFT = {
            None: False,
            'false': False,
            'true': True,
        }
        try:
            self._graft = Git.config('cinnabar.graft', remote=remote.name,
                                     values=GRAFT)
        except InvalidConfig as e:
            logging.error(e.message)
            return 1
        if Git.config('cinnabar.graft-refs') is not None:
            logging.warn(
                'The cinnabar.graft-refs configuration is deprecated.\n'
                'Please unset it.'
            )
コード例 #4
0
ファイル: fetch.py プロジェクト: runt18/git-cinnabar
def fetch(args):
    '''fetch a changeset from a mercurial remote'''

    remote = args.remote
    revs = args.revs
    full_revs = []
    for rev in revs:
        if not re.match('[0-9a-f]{40}$', rev.lower()):
            if remote.startswith('hg:'):
                url = remote
            else:
                url = Git.config('remote.%s.url' % remote)
            if not url:
                print >> sys.stderr, "Unknown remote:", remote
                return 1
            if url.startswith('hg::'):
                url = url[4:]
            repo = get_repo(Remote(remote, url))
            if repo.capable('lookup'):
                rev = hexlify(repo.lookup(rev))
            else:
                print >> sys.stderr, (
                    'Remote repository does not support the "lookup" command. '
                    'Please use a non-abbreviated mercurial revision.')
                return 1
        full_revs.append(rev)

    refs = ['hg/revs/%s' % r for r in full_revs]

    proc = GitProcess('fetch',
                      remote,
                      *refs,
                      stdout=sys.stdout,
                      config={'cinnabar.fetch': ' '.join(full_revs)})
    return proc.wait()
コード例 #5
0
ファイル: reclone.py プロジェクト: vishal-raj15/git-cinnabar
def reclone(args):
    '''reclone all mercurial remotes'''

    from cinnabar.cmd.rollback import do_rollback
    git_config = {}
    metadata_commit = Git.resolve_ref('refs/cinnabar/metadata')
    if metadata_commit:
        git_config['cinnabar.previous-metadata'] = \
            metadata_commit.decode('ascii')
    # TODO: Avoid resetting at all, possibly leaving the repo with no metadata
    # if this is interrupted somehow.
    do_rollback(NULL_NODE_ID.decode('ascii'))
    for line in Git.iter('config', '--get-regexp', 'remote\..*\.url'):
        config, url = line.split()
        name = config[len('remote.'):-len('.url')]
        skip_pref = 'remote.%s.skipDefaultUpdate' % name.decode('ascii')
        if (url.startswith((b'hg::', b'hg://'))
                and Git.config(skip_pref) != 'true'):
            Git.run('remote',
                    'update',
                    '--prune',
                    fsdecode(name),
                    config=git_config)
            git_config = {}

    print('Please note that reclone left your local branches untouched.')
    print('They may be based on entirely different commits.')
コード例 #6
0
ファイル: fetch.py プロジェクト: glandium/git-cinnabar
def fetch(args):
    '''fetch a changeset from a mercurial remote'''

    remote = args.remote
    revs = args.revs
    full_revs = []
    for rev in revs:
        if not re.match('[0-9a-f]{40}$', rev.lower()):
            if remote.startswith('hg:'):
                url = remote
            else:
                url = Git.config('remote.%s.url' % remote)
            if not url:
                print >>sys.stderr, "Unknown remote:", remote
                return 1
            if url.startswith('hg::'):
                url = url[4:]
            repo = get_repo(Remote(remote, url))
            if repo.capable('lookup'):
                rev = hexlify(repo.lookup(rev))
            else:
                print >>sys.stderr, (
                    'Remote repository does not support the "lookup" command. '
                    'Please use a non-abbreviated mercurial revision.')
                return 1
        full_revs.append(rev)

    refs = ['hg/revs/%s' % r for r in full_revs]

    proc = GitProcess('fetch', remote, *refs, stdout=sys.stdout,
                      config={'cinnabar.fetch': ' '.join(full_revs)})
    return proc.wait()
コード例 #7
0
def getbundle(repo, store, heads, branch_names):
    if isinstance(repo, bundlerepo):
        bundle = repo._unbundler
    else:
        common = findcommon(repo, store, store.heads(branch_names))
        logging.info('common: %s', common)
        bundle = None
        got_partial = False
        if not common:
            if not store._has_metadata and not store._graft:
                manifest = Git.config('cinnabar.clone')
                if manifest is None and repo.capable('cinnabarclone'):
                    manifest = repo._call('cinnabarclone')
                if manifest:
                    got_partial = do_cinnabarclone(repo, manifest, store)
                    if not got_partial:
                        if check_enabled('cinnabarclone'):
                            raise Exception('cinnabarclone failed.')
                        logging.warn('Falling back to normal clone.')
            if not got_partial and repo.capable('clonebundles'):
                bundle = get_clonebundle(repo)
                got_partial = bool(bundle)
                if not got_partial and check_enabled('clonebundles'):
                    raise Exception('clonebundles failed.')
        if bundle:
            bundle = unbundler(bundle)
            # Manual move semantics
            apply_bundle = BundleApplier(bundle)
            del bundle
            apply_bundle(store)
            if not changegroup:
                BundleHelper.close()
        if got_partial:
            # Eliminate the heads that we got from the clonebundle or
            # cinnabarclone.
            heads = [h for h in heads if not store.changeset_ref(h)]
            if not heads:
                return
            common = findcommon(repo, store, store.heads(branch_names))
            logging.info('common: %s', common)

        kwargs = {}
        if unbundle20 and repo.capable('bundle2'):
            bundle2caps = {
                'HG20': (),
                'changegroup': ('01', '02'),
            }
            kwargs['bundlecaps'] = set((
                'HG20', 'bundle2=%s' % urllib.quote(encodecaps(bundle2caps))))

        bundle = repo.getbundle('bundle', heads=[unhexlify(h) for h in heads],
                                common=[unhexlify(h) for h in common],
                                **kwargs)

        bundle = unbundler(bundle)

    # Manual move semantics
    apply_bundle = BundleApplier(bundle)
    del bundle
    apply_bundle(store)
コード例 #8
0
ファイル: __init__.py プロジェクト: jhlin/git-cinnabar
def get_repo(remote):
    if remote.parsed_url.scheme == 'file':
        path = remote.parsed_url.path
        if sys.platform == 'win32':
            # TODO: This probably needs more thought.
            path = path.lstrip('/')
        if not os.path.isdir(path):
            return bundlerepo(path)
    if not changegroup or Git.config('cinnabar.experiments') == 'true':
        if not changegroup:
            logging.warning('Mercurial libraries not found. Falling back to '
                            'native access.')
        logging.warning(
            'Native access to mercurial repositories is experimental!')
        try:
            return HelperRepo(remote.url)
        except NoHelperException:
            raise Exception('Native access to mercurial repositories requires '
                            'the helper.')
    if changegroup and remote.parsed_url.scheme == 'file':
        repo = localpeer(get_ui(), path)
    else:
        repo = hg.peer(get_ui(), {}, remote.url)
    assert repo.capable('getbundle')

    return repo
コード例 #9
0
ファイル: bundle.py プロジェクト: zalun/git-cinnabar
def bundle(args):
    '''create a mercurial bundle'''

    bundle_commits = list((c, p) for c, t, p in GitHgHelper.rev_list(
        '--topo-order', '--full-history', '--parents', '--reverse', *args.rev))
    if bundle_commits:
        # TODO: better UX. For instance, this will fail with an exception when
        # the parent commit doesn't have mercurial metadata.
        GRAFT = {
            None: False,
            'false': False,
            'true': True,
        }
        try:
            graft = Git.config('cinnabar.graft', values=GRAFT)
        except InvalidConfig as e:
            logging.error(e.message)
            return 1
        store = PushStore(graft=graft)
        if args.version == 1:
            b2caps = {}
        elif args.version == 2:
            b2caps = {
                'HG20': (),
                'changegroup': ('01', '02'),
            }
        with open(args.path, 'wb') as fh:
            if not b2caps:
                fh.write('HG10UN')
            for data in create_bundle(store, bundle_commits, b2caps):
                fh.write(data)
        store.close(rollback=True)
コード例 #10
0
ファイル: util.py プロジェクト: EnderDev/git-cinnabar
def run(func, args):
    reexec = None
    assert not experiment('python3') or sys.version_info[0] != 2
    if os.environ.pop('GIT_CINNABAR_COVERAGE', None):
        if not reexec:
            reexec = [sys.executable]
        reexec.extend(['-m', 'coverage', 'run', '--append'])
    init_logging()
    if reexec:
        reexec.append(os.path.abspath(sys.argv[0]))
        reexec.extend(sys.argv[1:])
        os.execlp(reexec[0], *reexec)
        assert False
    if check_enabled('memory') or check_enabled('cpu'):
        reporter = MemoryCPUReporter(memory=check_enabled('memory'),
                                     cpu=check_enabled('cpu'))

    version_check = VersionCheck()
    try:
        from cinnabar.git import Git
        objectformat = Git.config('extensions.objectformat') or 'sha1'
        if objectformat != 'sha1':
            sys.stderr.write(
                'Git repository uses unsupported %s object format\n' %
                objectformat)
            retcode = 65  # Data format error
        else:
            retcode = func(args)
    except Abort as e:
        # These exceptions are normal abort and require no traceback
        retcode = 1
        logging.error(str(e))
    except Exception as e:
        # Catch all exceptions and provide a nice message
        retcode = 70  # Internal software error
        message = getattr(e, 'message', None) or getattr(e, 'reason', None)
        if check_enabled('traceback') or not message:
            traceback.print_exc()
        else:
            logging.error(message)

            sys.stderr.write(
                'Run the command again with '
                '`git -c cinnabar.check=traceback <command>` to see the '
                'full traceback.\n')
    finally:
        if check_enabled('memory') or check_enabled('cpu'):
            reporter.shutdown()
        version_check.join()
    if check_enabled('no-mercurial'):
        if any(
                k.startswith('mercurial.') or k == 'mercurial'
                for k in sys.modules):
            sys.stderr.write('Mercurial libraries were loaded!')
            retcode = 70
    sys.exit(retcode)
コード例 #11
0
ファイル: reclone.py プロジェクト: zalun/git-cinnabar
def reclone(args):
    '''reclone all mercurial remotes'''

    from cinnabar.cmd.rollback import do_rollback
    do_rollback(NULL_NODE_ID)
    for line in Git.iter('config', '--get-regexp', 'remote\..*\.url'):
        config, url = line.split()
        name = config[len('remote.'):-len('.url')]
        skip_pref = 'remote.%s.skipDefaultUpdate' % name
        if (url.startswith(('hg::', 'hg://'))
                and Git.config(skip_pref) != 'true'):
            Git.run('remote', 'update', '--prune', name)

    print 'Please note that reclone left your local branches untouched.'
    print 'They may be based on entirely different commits.'
コード例 #12
0
ファイル: repo.py プロジェクト: vishal-raj15/git-cinnabar
def get_clonebundle(repo):
    url = Git.config('cinnabar.clonebundle', remote=repo.remote)
    if not url:
        url = get_clonebundle_url(repo)

    if not url:
        return None

    parsed_url = urlparse(url)
    if parsed_url.scheme not in (b'http', b'https'):
        logging.warn('Server advertizes clone bundle but provided a non '
                     'http/https url. Skipping.')
        return None

    sys.stderr.write('Getting clone bundle from %s\n' % fsdecode(url))
    return get_bundle(url)
コード例 #13
0
ファイル: git-cinnabar.py プロジェクト: vvuk/git-cinnabar
def main(args):
    cmd = args.pop(0)
    if cmd == 'data':
        store = GitHgStore()
        if args[0] == '-c':
            sys.stdout.write(store.changeset(args[1]).data)
        elif args[0] == '-m':
            sys.stdout.write(store.manifest(args[1]).data)
        store.close()
    elif cmd == 'fsck':
        return fsck(args)
    elif cmd == 'reclone':
        for ref in Git.for_each_ref('refs/cinnabar',
                                    'refs/remote-hg',
                                    'refs/notes/cinnabar',
                                    'refs/notes/remote-hg/git2hg',
                                    format='%(refname)'):
            Git.delete_ref(ref)
        Git.close()

        for line in Git.iter('config', '--get-regexp', 'remote\..*\.url'):
            config, url = line.split()
            name = config[len('remote.'):-len('.url')]
            skip_pref = 'remote.%s.skipDefaultUpdate' % name
            if (url.startswith('hg::')
                    and Git.config(skip_pref, 'bool') != 'true'):
                Git.run('remote', 'update', '--prune', name)

        print 'Please note that reclone left your local branches untouched.'
        print 'They may be based on entirely different commits.'
    elif cmd == 'hg2git':
        for arg in args:
            print GitHgHelper.hg2git(arg)
    elif cmd == 'git2hg':
        for arg in args:
            data = GitHgHelper.git2hg(arg)
            if data:
                data = ChangesetData.parse(data)
                print data.get('changeset', NULL_NODE_ID)
            else:
                print NULL_NODE_ID
    else:
        print >> sys.stderr, 'Unknown command:', cmd
        return 1
コード例 #14
0
def main(args):
    cmd = args.pop(0)
    if cmd == 'data':
        store = GitHgStore()
        if args[0] == '-c':
            sys.stdout.write(store.changeset(args[1]).data)
        elif args[0] == '-m':
            sys.stdout.write(store.manifest(args[1]).data)
        store.close()
    elif cmd == 'fsck':
        return fsck(args)
    elif cmd == 'reclone':
        for ref in Git.for_each_ref('refs/cinnabar', 'refs/remote-hg',
                                    'refs/notes/cinnabar',
                                    'refs/notes/remote-hg/git2hg',
                                    format='%(refname)'):
            Git.delete_ref(ref)
        Git.close()

        for line in Git.iter('config', '--get-regexp', 'remote\..*\.url'):
            config, url = line.split()
            name = config[len('remote.'):-len('.url')]
            skip_pref = 'remote.%s.skipDefaultUpdate' % name
            if (url.startswith('hg::') and
                    Git.config(skip_pref, 'bool') != 'true'):
                Git.run('remote', 'update', '--prune', name)

        print 'Please note that reclone left your local branches untouched.'
        print 'They may be based on entirely different commits.'
    elif cmd == 'hg2git':
        for arg in args:
            print GitHgHelper.hg2git(arg)
    elif cmd == 'git2hg':
        for arg in args:
            data = GitHgHelper.git2hg(arg)
            if data:
                data = ChangesetData.parse(data)
                print data.get('changeset', NULL_NODE_ID)
            else:
                print NULL_NODE_ID
    else:
        print >>sys.stderr, 'Unknown command:', cmd
        return 1
コード例 #15
0
def get_clonebundle(repo):
    url = Git.config('cinnabar.clonebundle')
    if not url:
        try:
            if check_enabled('no-mercurial'):
                raise ImportError('Do not use mercurial')
            from mercurial.exchange import (
                parseclonebundlesmanifest,
                filterclonebundleentries,
            )
        except ImportError:
            return None

        bundles = repo._call('clonebundles')

        class dummy(object):
            pass

        fakerepo = dummy()
        fakerepo.requirements = set()
        fakerepo.supportedformats = set()
        fakerepo.ui = repo.ui

        entries = parseclonebundlesmanifest(fakerepo, bundles)
        if not entries:
            return None

        entries = filterclonebundleentries(fakerepo, entries)
        if not entries:
            return None

        url = entries[0].get('URL')

    if not url:
        return None

    sys.stderr.write('Getting clone bundle from %s\n' % url)

    return unbundle_fh(HTTPReader(url), url)
コード例 #16
0
ファイル: reclone.py プロジェクト: glandium/git-cinnabar
def reclone(args):
    '''reclone all mercurial remotes'''

    from cinnabar.cmd.rollback import do_rollback
    git_config = {}
    metadata_commit = Git.resolve_ref('refs/cinnabar/metadata')
    if metadata_commit:
        git_config['cinnabar.previous-metadata'] = metadata_commit
    # TODO: Avoid resetting at all, possibly leaving the repo with no metadata
    # if this is interrupted somehow.
    do_rollback(NULL_NODE_ID)
    for line in Git.iter('config', '--get-regexp', 'remote\..*\.url'):
        config, url = line.split()
        name = config[len('remote.'):-len('.url')]
        skip_pref = 'remote.%s.skipDefaultUpdate' % name
        if (url.startswith(('hg::', 'hg://')) and
                Git.config(skip_pref) != 'true'):
            Git.run('remote', 'update', '--prune', name, config=git_config)
            git_config = {}

    print 'Please note that reclone left your local branches untouched.'
    print 'They may be based on entirely different commits.'
コード例 #17
0
ファイル: bundle.py プロジェクト: vishal-raj15/git-cinnabar
def unbundle(args):
    '''apply a mercurial bundle to the repository'''
    # Make git emit its error when the current directory is not in a git repo.
    proc = GitProcess('rev-parse')
    ret = proc.wait()
    if ret:
        return ret
    remote = Remote(b'', fsencode(args.url))
    if remote.parsed_url.scheme not in (b'file', b'http', b'https'):
        logging.error('%s urls are not supported.' % remote.parsed_url.scheme)
        return 1
    if args.clonebundle:
        repo = get_repo(remote)
        if not repo.capable(b'clonebundles'):
            logging.error('Repository does not support clonebundles')
            return 1
        bundle = get_clonebundle(repo)
    else:
        bundle = get_bundle(remote.url)

    store = GitHgStore()
    GRAFT = {
        None: False,
        b'false': False,
        b'true': True,
    }
    try:
        graft = Git.config('cinnabar.graft', values=GRAFT)
    except InvalidConfig as e:
        logging.error(str(e))
        return 1
    if graft:
        store.prepare_graft()
    bundle = unbundler(bundle)
    apply_bundle = BundleApplier(bundle)
    del bundle
    apply_bundle(store)
    store.close()
コード例 #18
0
ファイル: repo.py プロジェクト: glandium/git-cinnabar
def get_clonebundle(repo):
    url = Git.config('cinnabar.clonebundle')
    if not url:
        url = get_clonebundle_url(repo)

    if not url:
        return None

    parsed_url = urlparse(url)
    if parsed_url.scheme not in ('http', 'https'):
        logging.warn('Server advertizes clone bundle but provided a non '
                     'http/https url. Skipping.')
        return None

    sys.stderr.write('Getting clone bundle from %s\n' % url)

    reader = None
    if not changegroup:
        reader = BundleHelper.connect(url)
        if not reader:
            BundleHelper.close()
    if not reader:
        reader = HTTPReader(url)
    return unbundle_fh(reader, url)
コード例 #19
0
ファイル: remote_helper.py プロジェクト: jhlin/git-cinnabar
    def import_(self, *refs):
        # If anything wrong happens at any time, we risk git picking
        # the existing refs/cinnabar refs, so remove them preventively.
        for sha1, ref in Git.for_each_ref('refs/cinnabar/refs/heads',
                                          'refs/cinnabar/HEAD'):
            Git.delete_ref(ref)

        def resolve_head(head):
            if head.startswith('refs/heads/branches/'):
                head = head[20:]
                if head[-4:] == '/tip':
                    return self._branchmap.tip(unquote(head[:-4]))
                return head[-40:]
            if head.startswith('refs/heads/bookmarks/'):
                head = head[21:]
                return self._bookmarks[unquote(head)]
            if head == 'HEAD':
                return (self._bookmarks.get('@') or
                        self._branchmap.tip('default'))
            return None

        wanted_refs = {k: v for k, v in (
                       (h, resolve_head(h)) for h in refs) if v}
        heads = wanted_refs.values()
        if not heads:
            heads = self._branchmap.heads()

        try:
            # Mercurial can be an order of magnitude slower when creating
            # a bundle when not giving topological heads, which some of
            # the branch heads might not be.
            # http://bz.selenic.com/show_bug.cgi?id=4595
            # So, when we're pulling all branch heads, just ask for the
            # topological heads instead.
            # `heads` might contain known heads, if e.g. the remote has
            # never been pulled from, but we happen to have some of its
            # heads locally already.
            if self._has_unknown_heads:
                unknown_heads = self._branchmap.unknown_heads()
                if set(heads).issuperset(unknown_heads):
                    heads = set(self._branchmap.heads()) & unknown_heads
                self._store.init_fast_import()
                getbundle(self._repo, self._store, heads,
                          self._branchmap.names())
        except:
            wanted_refs = {}
            raise
        finally:
            for ref, value in wanted_refs.iteritems():
                ref = 'refs/cinnabar/' + ref
                Git.update_ref(ref, self._store.changeset_ref(value))

        self._store.close()

        self._helper.write('done\n')
        self._helper.flush()

        if self._remote.name:
            if Git.config('fetch.prune', self._remote.name) != 'true':
                prune = 'remote.%s.prune' % self._remote.name
                sys.stderr.write(
                    'It is recommended that you set "%(conf)s" or '
                    '"fetch.prune" to "true".\n'
                    '  git config %(conf)s true\n'
                    'or\n'
                    '  git config fetch.prune true\n'
                    % {'conf': prune}
                )

        if self._store.tag_changes:
            sys.stderr.write(
                '\nRun the following command to update remote tags:\n')
            if self._remote.name:
                sys.stderr.write(
                    '  git remote update %s\n' % self._remote.name)
            else:
                sys.stderr.write(
                    '  git fetch --tags %s\n' % self._remote.git_url)
コード例 #20
0
    def import_(self, *refs):
        # If anything wrong happens at any time, we risk git picking
        # the existing refs/cinnabar refs, so remove them preventively.
        for sha1, ref in Git.for_each_ref('refs/cinnabar/refs/heads',
                                          'refs/cinnabar/hg',
                                          'refs/cinnabar/HEAD'):
            Git.delete_ref(ref)

        def resolve_head(head):
            if head.startswith('refs/heads/branches/'):
                head = head[20:]
                if head[-4:] == '/tip':
                    return self._branchmap.tip(unquote(head[:-4]))
                return head[-40:]
            if head.startswith('refs/heads/bookmarks/'):
                head = head[21:]
                return self._bookmarks[unquote(head)]
            if head.startswith('hg/heads/'):
                branch, sha1 = head[9:].rsplit('/', 1)
                return sha1
            if head.startswith('hg/tips/'):
                return self._branchmap.tip(unquote(head[8:]))
            if head.startswith('hg/bookmarks/'):
                return self._bookmarks[unquote(heads[13:])]
            if head.startswith('hg/revs/'):
                return head[8:]
            if head == 'HEAD':
                return (self._bookmarks.get('@') or
                        self._branchmap.tip('default'))
            return None

        wanted_refs = {k: v for k, v in (
                       (h, resolve_head(h)) for h in refs) if v}
        heads = wanted_refs.values()
        if not heads:
            heads = self._branchmap.heads()

        try:
            # Mercurial can be an order of magnitude slower when creating
            # a bundle when not giving topological heads, which some of
            # the branch heads might not be.
            # http://bz.selenic.com/show_bug.cgi?id=4595
            # So, when we're pulling all branch heads, just ask for the
            # topological heads instead.
            # `heads` might contain known heads, if e.g. the remote has
            # never been pulled from, but we happen to have some of its
            # heads locally already.
            if self._has_unknown_heads:
                unknown_heads = self._branchmap.unknown_heads()
                if set(heads).issuperset(unknown_heads):
                    heads = set(self._branchmap.heads()) & unknown_heads
                self._store.init_fast_import()
                getbundle(self._repo, self._store, heads,
                          self._branchmap.names())
        except:
            wanted_refs = {}
            raise
        finally:
            for ref, value in wanted_refs.iteritems():
                ref = 'refs/cinnabar/' + ref
                Git.update_ref(ref, self._store.changeset_ref(value))

        self._store.close()

        self._helper.write('done\n')
        self._helper.flush()

        if self._remote.name:
            if Git.config('fetch.prune', self._remote.name) != 'true':
                prune = 'remote.%s.prune' % self._remote.name
                sys.stderr.write(
                    'It is recommended that you set "%(conf)s" or '
                    '"fetch.prune" to "true".\n'
                    '  git config %(conf)s true\n'
                    'or\n'
                    '  git config fetch.prune true\n'
                    % {'conf': prune}
                )

        if self._store.tag_changes:
            sys.stderr.write(
                '\nRun the following command to update remote tags:\n')
            if self._remote.name:
                sys.stderr.write(
                    '  git remote update %s\n' % self._remote.name)
            else:
                sys.stderr.write(
                    '  git fetch --tags %s\n' % self._remote.git_url)
コード例 #21
0
    def push(self, *refspecs):
        try:
            default = 'never' if self._graft else 'phase'
            values = {
                None: default,
                '': default,
                'never': 'never',
                'phase': 'phase',
                'always': 'always',
            }
            data = Git.config('cinnabar.data', self._remote.name,
                              values=values)
        except InvalidConfig as e:
            logging.error(e.message)
            return 1

        pushes = {s.lstrip('+'): (d, s.startswith('+'))
                  for s, d in (r.split(':', 1) for r in refspecs)}
        if not self._repo.capable('unbundle'):
            for source, (dest, force) in pushes.iteritems():
                self._helper.write(
                    'error %s Remote does not support the "unbundle" '
                    'capability\n' % dest)
            self._helper.write('\n')
            self._helper.flush()
        else:
            repo_heads = self._branchmap.heads()
            PushStore.adopt(self._store, self._graft)
            pushed = push(self._repo, self._store, pushes, repo_heads,
                          self._branchmap.names(), self._dry_run)

            status = {}
            for source, (dest, _) in pushes.iteritems():
                if dest.startswith('refs/tags/'):
                    if source:
                        status[dest] = 'Pushing tags is unsupported'
                    else:
                        status[dest] = \
                            'Deleting remote tags is unsupported'
                    continue
                if not dest.startswith(('refs/heads/bookmarks/',
                                        'hg/bookmarks/')):
                    if source:
                        status[dest] = bool(len(pushed))
                    else:
                        status[dest] = \
                            'Deleting remote branches is unsupported'
                    continue
                name = unquote(dest[21:])
                if source:
                    source = self._store.hg_changeset(Git.resolve_ref(source))\
                        or ''
                status[dest] = self._repo.pushkey(
                    'bookmarks', name, self._bookmarks.get(name, ''), source)

            for source, (dest, force) in pushes.iteritems():
                if status[dest] is True:
                    self._helper.write('ok %s\n' % dest)
                elif status[dest]:
                    self._helper.write('error %s %s\n' % (dest, status[dest]))
                else:
                    self._helper.write('error %s nothing changed on remote\n'
                                       % dest)
            self._helper.write('\n')
            self._helper.flush()

            if not pushed or self._dry_run:
                data = False
            elif data == 'always':
                data = True
            elif data == 'phase':
                phases = self._repo.listkeys('phases')
                drafts = {}
                if not phases.get('publishing', False):
                    drafts = set(p for p, is_draft in phases.iteritems()
                                 if int(is_draft))
                if not drafts:
                    data = True
                else:
                    def draft_commits():
                        for d in drafts:
                            c = self._store.changeset_ref(d)
                            if c:
                                yield '^%s^@' % c
                        for h in pushed.heads():
                            yield h

                    args = ['--ancestry-path', '--topo-order']
                    args.extend(draft_commits())

                    pushed_drafts = tuple(
                        c for c, t, p in GitHgHelper.rev_list(*args))

                    # Theoretically, we could have commits with no
                    # metadata that the remote declares are public, while
                    # the rest of our push is in a draft state. That is
                    # however so unlikely that it's not worth the effort
                    # to support partial metadata storage.
                    data = not bool(pushed_drafts)
            elif data == 'never':
                data = False

            self._store.close(rollback=not data)
コード例 #22
0
ファイル: remote_helper.py プロジェクト: jhlin/git-cinnabar
    def push(self, *refspecs):
        try:
            default = 'never' if self._graft else 'phase'
            values = {
                None: default,
                '': default,
                'never': 'never',
                'phase': 'phase',
                'always': 'always',
            }
            data = Git.config('cinnabar.data', self._remote.name,
                              values=values)
        except InvalidConfig as e:
            logging.error(e.message)
            return 1

        pushes = {s.lstrip('+'): (d, s.startswith('+'))
                  for s, d in (r.split(':', 1) for r in refspecs)}
        if not self._repo.capable('unbundle'):
            for source, (dest, force) in pushes.iteritems():
                self._helper.write(
                    'error %s Remote does not support the "unbundle" '
                    'capability\n' % dest)
            self._helper.write('\n')
            self._helper.flush()
        else:
            repo_heads = self._branchmap.heads()
            PushStore.adopt(self._store, self._graft)
            pushed = push(self._repo, self._store, pushes, repo_heads,
                          self._branchmap.names())

            status = {}
            for source, (dest, _) in pushes.iteritems():
                if dest.startswith('refs/tags/'):
                    if source:
                        status[dest] = 'Pushing tags is unsupported'
                    else:
                        status[dest] = \
                            'Deleting remote tags is unsupported'
                    continue
                if not dest.startswith('refs/heads/bookmarks/'):
                    if source:
                        status[dest] = bool(len(pushed))
                    else:
                        status[dest] = \
                            'Deleting remote branches is unsupported'
                    continue
                name = unquote(dest[21:])
                if source:
                    source = self._store.hg_changeset(Git.resolve_ref(source))\
                        or ''
                status[dest] = self._repo.pushkey(
                    'bookmarks', name, self._bookmarks.get(name, ''), source)

            for source, (dest, force) in pushes.iteritems():
                if status[dest] is True:
                    self._helper.write('ok %s\n' % dest)
                elif status[dest]:
                    self._helper.write('error %s %s\n' % (dest, status[dest]))
                else:
                    self._helper.write('error %s nothing changed on remote\n'
                                       % dest)
            self._helper.write('\n')
            self._helper.flush()

            if not pushed:
                data = False
            elif data == 'always':
                data = True
            elif data == 'phase':
                phases = self._repo.listkeys('phases')
                drafts = {}
                if not phases.get('publishing', False):
                    drafts = set(p for p, is_draft in phases.iteritems()
                                 if int(is_draft))
                if not drafts:
                    data = True
                else:
                    def draft_commits():
                        for d in drafts:
                            c = self._store.changeset_ref(d)
                            if c:
                                yield '^%s^@' % c
                        for h in pushed.heads():
                            yield h

                    args = ['rev-list', '--ancestry-path', '--topo-order',
                            '--stdin']

                    pushed_drafts = tuple(
                        Git.iter(*args, stdin=draft_commits()))

                    # Theoretically, we could have commits with no
                    # metadata that the remote declares are public, while
                    # the rest of our push is in a draft state. That is
                    # however so unlikely that it's not worth the effort
                    # to support partial metadata storage.
                    data = not bool(pushed_drafts)
            elif data == 'never':
                data = False

            self._store.close(rollback=not data)
コード例 #23
0
    def list(self, arg=None):
        assert not arg or arg == 'for-push'

        fetch = Git.config('cinnabar.fetch')
        if fetch:
            heads = [unhexlify(fetch)]
            branchmap = {None: heads}
            bookmarks = {}

        elif self._repo.capable('batch'):
            batch = self._repo.batch()
            branchmap = batch.branchmap()
            heads = batch.heads()
            bookmarks = batch.listkeys('bookmarks')
            batch.submit()
            branchmap = branchmap.value
            heads = heads.value
            bookmarks = bookmarks.value
            if heads == ['\0' * 20]:
                heads = []
        else:
            while True:
                branchmap = self._repo.branchmap()
                heads = self._repo.heads()
                if heads == ['\0' * 20]:
                    heads = []
                # Some branch heads can be non-heads topologically, but if
                # some heads don't appear in the branchmap, then something
                # was pushed to the repo between branchmap() and heads()
                if set(heads).issubset(set(chain(*branchmap.values()))):
                    break
            bookmarks = self._repo.listkeys('bookmarks')

        self._bookmarks = bookmarks
        branchmap = self._branchmap = BranchMap(self._store, branchmap,
                                                heads)
        self._has_unknown_heads = bool(self._branchmap.unknown_heads())
        if self._graft and self._has_unknown_heads and not arg:
            self._store.prepare_graft()
            self._store.init_fast_import()
            get_heads = set(branchmap.heads()) & branchmap.unknown_heads()
            getbundle(self._repo, self._store, get_heads, branchmap.names())
            # We may have failed to graft all changesets, in which case we
            # skipped them. If that's what happened, we want to create a
            # new branchmap containing all we do know about, so that we can
            # avoid telling git about things we don't know, because if we
            # didn't, it would ask for them, and subsequently fail because
            # they are missing.
            # Since we can't know for sure what the right tips might be for
            # each branch, we won't expose the tips. This means we don't
            # need to care about the order of the heads for the new
            # branchmap.
            self._has_unknown_heads = any(not(self._store.changeset_ref(h))
                                          for h in get_heads)
            if self._has_unknown_heads:
                new_branchmap = {
                    branch: set(h for h in branchmap.heads(branch))
                    for branch in branchmap.names()
                }
                new_branchmap = {
                    branch: set(h for h in branchmap.heads(branch)
                                if h not in branchmap.unknown_heads())
                    for branch in branchmap.names()
                }
                new_heads = set(h for h in branchmap.heads()
                                if h not in branchmap.unknown_heads())
                for status, head, branch in self._store._hgheads.iterchanges():
                    branch_heads = new_branchmap.get(branch)
                    if status == VersionedDict.REMOVED:
                        if branch_heads and head in branch_heads:
                            branch_heads.remove(head)
                        if head in new_heads:
                            new_heads.remove(head)
                    else:
                        if not branch_heads:
                            branch_heads = new_branchmap[branch] = set()
                        branch_heads.add(head)
                        new_heads.add(head)

                branchmap = self._branchmap = BranchMap(
                    self._store, new_branchmap, list(new_heads))

        refs = []
        for branch in sorted(branchmap.names()):
            branch_tip = branchmap.tip(branch)
            for head in sorted(branchmap.heads(branch)):
                sha1 = branchmap.git_sha1(head)
                refs.append(
                    ('hg/heads/%s/%s' % (
                        sanitize_branch_name(branch), head), sha1))
                if head == branch_tip:
                    continue
                refs.append(
                    ('refs/heads/branches/%s/%s' % (
                        sanitize_branch_name(branch), head), sha1))
            if branch_tip:
                refs.append(
                    ('refs/heads/branches/%s/tip' % (
                        sanitize_branch_name(branch)),
                     branchmap.git_sha1(branch_tip)))
                refs.append(
                    ('hg/tips/%s' % (
                        sanitize_branch_name(branch)),
                     branchmap.git_sha1(branch_tip)))

        for name, sha1 in sorted(bookmarks.iteritems()):
            if sha1 == NULL_NODE_ID:
                continue
            ref = self._store.changeset_ref(sha1)
            if self._graft and not ref:
                continue
            refs.append(
                ('hg/bookmarks/%s' % sanitize_branch_name(name),
                 ref if ref else '?'))
            refs.append(
                ('refs/heads/bookmarks/%s' % sanitize_branch_name(name),
                 ref if ref else '?'))
        if fetch:
            sha1 = self._store.changeset_ref(fetch)
            refs.append(('hg/revs/%s' % fetch, sha1 or '?'))
        if not self._has_unknown_heads:
            for tag, ref in sorted(self._store.tags(branchmap.heads())):
                refs.append(
                    ('refs/tags/%s' % sanitize_branch_name(tag), ref))

        if '@' in bookmarks:
            self._HEAD = 'bookmarks/@'
        head = bookmarks.get('@', branchmap.tip('default'))
        if self._graft and head:
            head = self._store.changeset_ref(head)
        if head:
            refs.append(('HEAD', '@refs/heads/%s' % self._HEAD))

        for k, v in sorted(refs):
            self._helper.write('%s %s\n' % (v, k))

        self._helper.write('\n')
        self._helper.flush()
コード例 #24
0
ファイル: repo.py プロジェクト: glandium/git-cinnabar
def getbundle(repo, store, heads, branch_names):
    if isinstance(repo, bundlerepo):
        bundle = repo._unbundler
    else:
        common = findcommon(repo, store, store.heads(branch_names))
        logging.info('common: %s', common)
        bundle = None
        got_partial = False
        if not common:
            if not store._has_metadata and not store._graft:
                manifest = Git.config('cinnabar.clone')
                if not manifest and experiment('git-clone') and \
                        repo.capable('cinnabarclone'):
                    manifest = repo._call('cinnabarclone')
                if manifest:
                    got_partial = do_cinnabarclone(repo, manifest, store)
                    if not got_partial:
                        if check_enabled('cinnabarclone'):
                            raise Exception('cinnabarclone failed.')
                        logging.warn('Falling back to normal clone.')
            if not got_partial and repo.capable('clonebundles'):
                bundle = get_clonebundle(repo)
                got_partial = bool(bundle)
                if not got_partial and check_enabled('clonebundles'):
                    raise Exception('clonebundles failed.')
        if bundle:
            bundle = unbundler(bundle)
            # Manual move semantics
            apply_bundle = BundleApplier(bundle)
            del bundle
            apply_bundle(store)
            if not changegroup:
                BundleHelper.close()
        if got_partial:
            # Eliminate the heads that we got from the clonebundle or
            # cinnabarclone.
            heads = [h for h in heads if not store.changeset_ref(h)]
            if not heads:
                return
            common = findcommon(repo, store, store.heads(branch_names))
            logging.info('common: %s', common)

        kwargs = {}
        if unbundle20 and repo.capable('bundle2'):
            bundle2caps = {
                'HG20': (),
                'changegroup': ('01', '02'),
            }
            kwargs['bundlecaps'] = set((
                'HG20', 'bundle2=%s' % urllib.quote(encodecaps(bundle2caps))))

        bundle = repo.getbundle('bundle', heads=[unhexlify(h) for h in heads],
                                common=[unhexlify(h) for h in common],
                                **kwargs)

        bundle = unbundler(bundle)

    # Manual move semantics
    apply_bundle = BundleApplier(bundle)
    del bundle
    apply_bundle(store)
コード例 #25
0
    def import_(self, *refs):
        # If anything wrong happens at any time, we risk git picking
        # the existing refs/cinnabar refs, so remove them preventively.
        for sha1, ref in Git.for_each_ref('refs/cinnabar/refs/heads',
                                          'refs/cinnabar/hg',
                                          'refs/cinnabar/HEAD'):
            Git.delete_ref(ref)

        def resolve_head(head):
            resolved = self._refs.get(head)
            if resolved is None:
                return resolved
            if resolved.startswith('@'):
                return self._refs.get(resolved[1:])
            return resolved

        wanted_refs = {
            k: v
            for k, v in ((h, resolve_head(h)) for h in refs) if v
        }
        heads = wanted_refs.values()
        if not heads:
            heads = self._branchmap.heads()

        try:
            # Mercurial can be an order of magnitude slower when creating
            # a bundle when not giving topological heads, which some of
            # the branch heads might not be.
            # http://bz.selenic.com/show_bug.cgi?id=4595
            # So, when we're pulling all branch heads, just ask for the
            # topological heads instead.
            # `heads` might contain known heads, if e.g. the remote has
            # never been pulled from, but we happen to have some of its
            # heads locally already.
            if self._has_unknown_heads:
                unknown_heads = self._branchmap.unknown_heads()
                if set(heads).issuperset(unknown_heads):
                    heads = set(self._branchmap.heads()) & unknown_heads
                getbundle(self._repo, self._store, heads,
                          self._branchmap.names())
        except Exception:
            wanted_refs = {}
            raise
        finally:
            for ref, value in wanted_refs.iteritems():
                ref = 'refs/cinnabar/' + ref
                Git.update_ref(ref, self._store.changeset_ref(value))

        self._store.close()

        self._helper.write('done\n')
        self._helper.flush()

        if self._remote.name:
            if Git.config('fetch.prune', self._remote.name) != 'true':
                prune = 'remote.%s.prune' % self._remote.name
                sys.stderr.write(
                    'It is recommended that you set "%(conf)s" or '
                    '"fetch.prune" to "true".\n'
                    '  git config %(conf)s true\n'
                    'or\n'
                    '  git config fetch.prune true\n' % {'conf': prune})

        if self._store.tag_changes:
            sys.stderr.write('\nRun the following command to update tags:\n')
            sys.stderr.write('  git fetch --tags hg::tags: tag "*"\n')
コード例 #26
0
ファイル: repo.py プロジェクト: mtmiller/git-cinnabar
def getbundle(repo, store, heads, branch_names):
    if isinstance(repo, bundlerepo):
        bundle = repo._unbundler
    else:
        common = findcommon(repo, store, store.heads(branch_names))
        logging.info('common: %s', common)
        bundle = None
        got_partial = False
        if not common:
            if not store._has_metadata:
                manifest = Git.config('cinnabar.clone', remote=repo.remote)
                limit_schemes = False
                if manifest is None and repo.capable(b'cinnabarclone'):
                    # If no cinnabar.clone config was given, but a
                    # cinnabar.clonebundle config was, act as if an empty
                    # cinnabar.clone config had been given, and proceed with
                    # the mercurial clonebundle.
                    if not Git.config('cinnabar.clonebundle',
                                      remote=repo.remote):
                        manifest = repo._call(b'cinnabarclone')
                        limit_schemes = True
                if manifest:
                    got_partial = do_cinnabarclone(repo, manifest, store,
                                                   limit_schemes)
                    if not got_partial:
                        if check_enabled('cinnabarclone'):
                            raise Exception('cinnabarclone failed.')
                        logging.warn('Falling back to normal clone.')
            if not got_partial and repo.capable(b'clonebundles'):
                bundle = get_clonebundle(repo)
                got_partial = bool(bundle)
                if not got_partial and check_enabled('clonebundles'):
                    raise Exception('clonebundles failed.')
        if bundle:
            bundle = unbundler(bundle)
            # Manual move semantics
            apply_bundle = BundleApplier(bundle)
            del bundle
            apply_bundle(store)
            if not changegroup:
                BundleHelper.close()
        if got_partial:
            # Eliminate the heads that we got from the clonebundle or
            # cinnabarclone.
            heads = [h for h in heads if not store.changeset_ref(h)]
            if not heads:
                return
            common = findcommon(repo, store, store.heads(branch_names))
            logging.info('common: %s', common)

        kwargs = {}
        if unbundle20 and repo.capable(b'bundle2'):
            bundle2caps = {
                b'HG20': (),
                b'changegroup': (b'01', b'02'),
            }
            kwargs['bundlecaps'] = set(
                (b'HG20', b'bundle2=%s' %
                 quote_from_bytes(encodecaps(bundle2caps)).encode('ascii')))

        bundle = repo.getbundle(b'bundle',
                                heads=[unhexlify(h) for h in heads],
                                common=[unhexlify(h) for h in common],
                                **kwargs)

        bundle = unbundler(bundle)

    # Manual move semantics
    apply_bundle = BundleApplier(bundle)
    del bundle
    apply_bundle(store)
コード例 #27
0
ファイル: repo.py プロジェクト: mtmiller/git-cinnabar
def do_cinnabarclone(repo, manifest, store, limit_schemes=True):
    GRAFT = {
        None: None,
        b'false': False,
        b'true': True,
    }
    try:
        enable_graft = Git.config('cinnabar.graft',
                                  remote=repo.remote,
                                  values=GRAFT)
    except InvalidConfig:
        enable_graft = None

    url = None
    candidates = []
    for line in manifest.splitlines():
        line = line.strip()
        if not line:
            continue
        spec, _, params = line.partition(b' ')
        params = {
            k: v
            for k, _, v in (p.partition(b'=') for p in params.split())
        }
        graft = params.pop(b'graft', None)
        if params:
            # Future proofing: ignore lines with unknown params, even if we
            # support some that are present.
            continue
        # When grafting, ignore lines without a graft revision.
        if store._graft and not graft:
            continue
        # When explicitly disabling graft, ignore lines with a graft revision.
        if enable_graft is False and graft:
            continue

        graft = graft.split(b',') if graft else []
        graft_u = []
        for g in graft:
            if SHA1_RE.match(g):
                graft_u.append(g.decode('ascii'))
        if len(graft) != len(graft_u):
            continue
        if graft:
            revs = list(Git.iter('rev-parse', '--revs-only', *graft_u))
            if len(revs) != len(graft):
                continue
            # We apparently have all the grafted revisions locally, ensure
            # they're actually reachable.
            if not any(
                    Git.iter('rev-list',
                             '--branches',
                             '--tags',
                             '--remotes',
                             '--max-count=1',
                             '--ancestry-path',
                             '--stdin',
                             stdin=(b'^%s^@' % c for c in graft),
                             stderr=open(os.devnull, 'wb'))):
                continue

        candidates.append((spec, len(graft) != 0))

    if enable_graft is not False:
        graft_filters = [True, False]
    else:
        graft_filters = [False]
    for graft_filter in graft_filters:
        for spec, graft in candidates:
            if graft == graft_filter:
                url, _, branch = spec.partition(b'#')
                url, branch = (url.split(b'#', 1) + [None])[:2]
                if url:
                    break
        if url:
            break

    if not url:
        logging.warn('Server advertizes cinnabarclone but didn\'t provide '
                     'a git repository url to fetch from.')
        return False

    parsed_url = urlparse(url)
    if limit_schemes and parsed_url.scheme not in (b'http', b'https', b'git'):
        logging.warn('Server advertizes cinnabarclone but provided a non '
                     'http/https git repository. Skipping.')
        return False
    sys.stderr.write('Fetching cinnabar metadata from %s\n' % fsdecode(url))
    sys.stderr.flush()
    return store.merge(url, repo.url(), branch)
コード例 #28
0
def main(args):
    logger = logging.getLogger('-')
    logger.info(args)
    assert len(args) == 2
    remote, url = args
    git_dir = os.environ.get('GIT_DIR')
    if Git.config('core.ignorecase', 'bool') == 'true':
        sys.stderr.write(
            'Your git configuration has core.ignorecase set to "true".\n'
            'Usually, this means git detected the file system is case '
            'insensitive.\n'
            'Git-cinnabar does not support this setup.\n'
            'Either use a case sensitive file system or set '
            'core.ignorecase to "false".\n'
        )
        git_work_tree = os.path.dirname(git_dir)
        if os.path.abspath(os.getcwd() + os.sep).startswith(
                os.path.abspath(git_work_tree) + os.sep) or \
                remote == 'hg::' + url or tuple(
                Git.for_each_ref('refs/remotes/%s' % remote)):
            sys.stderr.write(
                'Use the following command to reclone:\n'
                '  git cinnabar reclone\n'
            )
        else:
            sys.stderr.write(
                'Use the following command to clone:\n'
                '  git -c core.ignorecase=false clone%(args)s hg::%(url)s '
                '%(dir)s\n'
                % {
                    'dir': git_work_tree,
                    'url': url,
                    'args': ' -o ' + remote if remote != 'origin' else ''
                }
            )
        return 1
    repo = get_repo(url)
    store = GitHgStore()
    logger.info(LazyString(lambda: '%s' % store.heads()))
    helper = IOLogger(logging.getLogger('remote-helper'),
                      sys.stdin, sys.stdout)
    branchmap = None
    bookmarks = {}
    HEAD = 'branches/default/tip'

    while True:
        cmd, args = read_cmd(helper)
        if not cmd:
            break

        if cmd == 'capabilities':
            assert not args
            helper.write(
                'option\n'
                'import\n'
                'bidi-import\n'
                'push\n'
                'refspec refs/heads/branches/*:'
                'refs/cinnabar/refs/heads/branches/*\n'
                'refspec refs/heads/bookmarks/*:'
                'refs/cinnabar/refs/heads/bookmarks/*\n'
                'refspec HEAD:refs/cinnabar/HEAD\n'
                '\n'
            )
            helper.flush()
        elif cmd == 'list':
            assert not args or args == ['for-push']

            if repo.capable('batch'):
                batch = repo.batch()
                branchmap = batch.branchmap()
                heads = batch.heads()
                bookmarks = batch.listkeys('bookmarks')
                batch.submit()
                branchmap = branchmap.value
                heads = heads.value
                bookmarks = bookmarks.value
            else:
                while True:
                    branchmap = repo.branchmap()
                    heads = repo.heads()
                    if heads == ['\0' * 20]:
                        heads = []
                    # Some branch heads can be non-heads topologically, but if
                    # some heads don't appear in the branchmap, then something
                    # was pushed to the repo between branchmap() and heads()
                    if set(heads).issubset(set(chain(*branchmap.values()))):
                        break
                bookmarks = repo.listkeys('bookmarks')

            branchmap = BranchMap(store, branchmap, heads)
            unknowns = False
            for branch in sorted(branchmap.names()):
                branch_tip = branchmap.tip(branch)
                for head in sorted(branchmap.heads(branch)):
                    sha1 = branchmap.git_sha1(head)
                    if sha1 == '?':
                        unknowns = True
                    if head == branch_tip:
                        continue
                    helper.write('%s refs/heads/branches/%s/%s\n' % (
                        sha1,
                        branch,
                        head,
                    ))
                if branch_tip:
                    helper.write('%s refs/heads/branches/%s/tip\n' % (
                        branchmap.git_sha1(branch_tip),
                        branch,
                    ))
            for name, sha1 in sorted(bookmarks.iteritems()):
                ref = store.changeset_ref(sha1)
                helper.write(
                    '%s refs/heads/bookmarks/%s\n'
                    % (ref if ref else '?', name)
                )
            if not unknowns:
                for tag, ref in sorted(store.tags(branchmap.heads())):
                    helper.write('%s refs/tags/%s\n' % (ref, tag))

            if '@' in bookmarks:
                HEAD = 'bookmarks/@'
            helper.write(
                '@refs/heads/%s HEAD\n'
                '\n'
                % HEAD
            )
            helper.flush()
        elif cmd == 'option':
            assert len(args) == 2
            name, value = args
            if name == 'progress':
                if value == 'true':
                    cinnabar.util.progress = True
                    helper.write('ok\n')
                elif value == 'false':
                    cinnabar.util.progress = False
                    helper.write('ok\n')
                else:
                    helper.write('unsupported\n')
            else:
                helper.write('unsupported\n')
            helper.flush()
        elif cmd == 'import':
            try:
                reflog = os.path.join(git_dir, 'logs', 'refs', 'cinnabar')
                mkpath(reflog)
                open(os.path.join(reflog, 'hg2git'), 'a').close()
                open(os.path.join(reflog, 'manifest'), 'a').close()
                assert len(args) == 1
                refs = args
                while cmd:
                    assert cmd == 'import'
                    cmd, args = read_cmd(helper)
                    assert args is None or len(args) == 1
                    if args:
                        refs.extend(args)
            except:
                # If anything wrong happens before we got all the import
                # commands, we risk git picking the existing refs/cinnabar
                # refs. Remove them.
                for line in Git.for_each_ref('refs/cinnabar/refs/heads',
                                             'refs/cinnabar/HEAD',
                                             format='%(refname)'):
                    Git.delete_ref(ref)
                raise

            try:
                def resolve_head(head):
                    if head.startswith('refs/heads/branches/'):
                        head = head[20:]
                        if head[-4:] == '/tip':
                            return branchmap.tip(head[:-4])
                        return head[-40:]
                    if head.startswith('refs/heads/bookmarks/'):
                        head = head[21:]
                        return bookmarks[head]
                    if head == 'HEAD':
                        return bookmarks.get('@') or branchmap.tip('default')
                    return None

                wanted_refs = {k: v for k, v in (
                               (h, resolve_head(h)) for h in refs) if v}
                heads = wanted_refs.values()
                if not heads:
                    heads = branchmap.heads()

                # Older versions would create a symbolic ref for
                # refs/remote-hg/HEAD. Newer versions don't, and
                # Git.update_ref doesn't remove the symbolic ref, so it needs
                # to be removed first.
                # Since git symbolic-ref only throws an error when the ref is
                # not symbolic, just try to remove the symbolic ref every time
                # and ignore errors.
                tuple(Git.iter('symbolic-ref', '-d', 'refs/remote-hg/HEAD',
                               stderr=open(os.devnull, 'wb')))

                refs_orig = {}
                for line in Git.for_each_ref(
                        'refs/cinnabar/refs/heads', 'refs/cinnabar/HEAD',
                        format='%(objectname) %(refname)'):
                    sha1, ref = line.split(' ', 1)
                    refs_orig[ref] = sha1
            except:
                # If anything wrong happens before we actually pull, we risk
                # git pucking the existing refs/cinnabar refs. Remove them.
                # Unlike in the case above, we now have the list of refs git
                # is expected, so we can just remove those.
                for ref in refs:
                    Git.delete_ref('refs/cinnabar/' + ref)
                raise

            try:
                store.init_fast_import(FastImport(sys.stdin, sys.stdout))
                getbundle(repo, store, heads, branchmap)
            except:
                wanted_refs = {}
                raise
            finally:
                for ref, value in wanted_refs.iteritems():
                    ref = 'refs/cinnabar/' + ref
                    if ref not in refs_orig or refs_orig[ref] != value:
                        Git.update_ref(ref, store.changeset_ref(value))
                for ref in refs_orig:
                    if ref[14:] not in wanted_refs:
                        Git.delete_ref(ref)

            store.close()

            if not remote.startswith('hg::'):
                prune = 'remote.%s.prune' % remote
                if (Git.config(prune) != 'true' and
                        Git.config('fetch.prune') != 'true'):
                    sys.stderr.write(
                        'It is recommended that you set "%(conf)s" or '
                        '"fetch.prune" to "true".\n'
                        '  git config %(conf)s true\n'
                        'or\n'
                        '  git config fetch.prune true\n'
                        % {'conf': prune}
                    )

            if store.tag_changes:
                sys.stderr.write(
                    '\nRun the following command to update remote tags:\n')
                if not remote.startswith('hg::'):
                    sys.stderr.write('  git remote update %s\n' % remote)
                else:
                    sys.stderr.write('  git fetch --tags %s\n' % remote)

        elif cmd == 'push':
            if not remote.startswith('hg::'):
                data_pref = 'remote.%s.cinnabar-data' % remote
                data = Git.config(data_pref) or 'phase'
            else:
                data = 'phase'

            if data not in ('never', 'phase', 'always'):
                sys.stderr.write('Invalid value for %s: %s\n'
                                 % (data_pref, data))
                return 1

            refspecs = []
            refspecs.extend(args)
            while True:
                cmd, args = read_cmd(helper)
                if not cmd:
                    break
                assert cmd == 'push'
                refspecs.extend(args)
            pushes = {s.lstrip('+'): (d, s.startswith('+'))
                      for s, d in (r.split(':', 1) for r in refspecs)}
            if isinstance(repo, bundlerepo):
                for source, (dest, force) in pushes.iteritems():
                    helper.write('error %s Cannot push to a bundle file\n'
                                 % dest)
                helper.write('\n')
                helper.flush()
            else:
                repo_heads = branchmap.heads()
                PushStore.adopt(store)
                pushed = push(repo, store, pushes, repo_heads,
                              branchmap.names())

                status = {}
                for source, (dest, _) in pushes.iteritems():
                    if dest.startswith('refs/tags/'):
                        if source:
                            status[dest] = 'Pushing tags is unsupported'
                        else:
                            status[dest] = \
                                'Deleting remote tags is unsupported'
                        continue
                    if not dest.startswith('refs/heads/bookmarks/'):
                        if source:
                            status[dest] = bool(len(pushed))
                        else:
                            status[dest] = \
                                'Deleting remote branches is unsupported'
                        continue
                    name = dest[21:]
                    if source:
                        source = store.hg_changeset(Git.resolve_ref(source)) \
                            or ''
                    status[dest] = repo.pushkey(
                        'bookmarks', name, bookmarks.get(name, ''), source)

                for source, (dest, force) in pushes.iteritems():
                    if status[dest] is True:
                        helper.write('ok %s\n' % dest)
                    elif status[dest]:
                        helper.write('error %s %s\n' % (dest, status[dest]))
                    else:
                        helper.write('error %s nothing changed on remote\n'
                                     % dest)
                helper.write('\n')
                helper.flush()

                if not pushed:
                    data = False
                elif data == 'always':
                    data = True
                elif data == 'phase':
                    phases = repo.listkeys('phases')
                    drafts = {}
                    if not phases.get('publishing', False):
                        drafts = set(p for p, is_draft in phases.iteritems()
                                     if int(is_draft))
                    if not drafts:
                        data = True
                    else:
                        def draft_commits():
                            for d in drafts:
                                c = store.changeset_ref(d)
                                if c:
                                    yield '^%s^@' % c
                            for h in pushed.heads():
                                yield h

                        args = ['rev-list', '--ancestry-path', '--topo-order',
                                '--stdin']

                        pushed_drafts = tuple(
                            Git.iter(*args, stdin=draft_commits()))

                        # Theoretically, we could have commits with no
                        # metadata that the remote declares are public, while
                        # the rest of our push is in a draft state. That is
                        # however so unlikely that it's not worth the effort
                        # to support partial metadata storage.
                        data = not bool(pushed_drafts)
                elif data == 'never':
                    data = False

                store.close(rollback=not data)

    store.close()
コード例 #29
0
def main(args):
    logger = logging.getLogger('-')
    logger.info(args)
    assert len(args) == 2
    remote, url = args
    git_dir = os.environ.get('GIT_DIR')
    if Git.config('core.ignorecase', 'bool') == 'true':
        sys.stderr.write(
            'Your git configuration has core.ignorecase set to "true".\n'
            'Usually, this means git detected the file system is case '
            'insensitive.\n'
            'Git-cinnabar does not support this setup.\n'
            'Either use a case sensitive file system or set '
            'core.ignorecase to "false".\n')
        git_work_tree = os.path.dirname(git_dir)
        if os.path.abspath(os.getcwd() + os.sep).startswith(
                os.path.abspath(git_work_tree) + os.sep) or \
                remote == 'hg::' + url or tuple(
                Git.for_each_ref('refs/remotes/%s' % remote)):
            sys.stderr.write('Use the following command to reclone:\n'
                             '  git cinnabar reclone\n')
        else:
            sys.stderr.write(
                'Use the following command to clone:\n'
                '  git -c core.ignorecase=false clone%(args)s hg::%(url)s '
                '%(dir)s\n' % {
                    'dir': git_work_tree,
                    'url': url,
                    'args': ' -o ' + remote if remote != 'origin' else ''
                })
        return 1
    repo = get_repo(url)
    store = GitHgStore()
    logger.info(LazyString(lambda: '%s' % store.heads()))
    helper = IOLogger(logging.getLogger('remote-helper'), sys.stdin,
                      sys.stdout)
    branchmap = None
    bookmarks = {}
    HEAD = 'branches/default/tip'

    while True:
        cmd, args = read_cmd(helper)
        if not cmd:
            break

        if cmd == 'capabilities':
            assert not args
            helper.write('option\n'
                         'import\n'
                         'bidi-import\n'
                         'push\n'
                         'refspec refs/heads/branches/*:'
                         'refs/cinnabar/refs/heads/branches/*\n'
                         'refspec refs/heads/bookmarks/*:'
                         'refs/cinnabar/refs/heads/bookmarks/*\n'
                         'refspec HEAD:refs/cinnabar/HEAD\n'
                         '\n')
            helper.flush()
        elif cmd == 'list':
            assert not args or args == ['for-push']

            if repo.capable('batch'):
                batch = repo.batch()
                branchmap = batch.branchmap()
                heads = batch.heads()
                bookmarks = batch.listkeys('bookmarks')
                batch.submit()
                branchmap = branchmap.value
                heads = heads.value
                bookmarks = bookmarks.value
            else:
                while True:
                    branchmap = repo.branchmap()
                    heads = repo.heads()
                    if heads == ['\0' * 20]:
                        heads = []
                    # Some branch heads can be non-heads topologically, but if
                    # some heads don't appear in the branchmap, then something
                    # was pushed to the repo between branchmap() and heads()
                    if set(heads).issubset(set(chain(*branchmap.values()))):
                        break
                bookmarks = repo.listkeys('bookmarks')

            branchmap = BranchMap(store, branchmap, heads)
            unknowns = False
            for branch in sorted(branchmap.names()):
                branch_tip = branchmap.tip(branch)
                for head in sorted(branchmap.heads(branch)):
                    sha1 = branchmap.git_sha1(head)
                    if sha1 == '?':
                        unknowns = True
                    if head == branch_tip:
                        continue
                    helper.write('%s refs/heads/branches/%s/%s\n' % (
                        sha1,
                        branch,
                        head,
                    ))
                if branch_tip:
                    helper.write('%s refs/heads/branches/%s/tip\n' % (
                        branchmap.git_sha1(branch_tip),
                        branch,
                    ))
            for name, sha1 in sorted(bookmarks.iteritems()):
                ref = store.changeset_ref(sha1)
                helper.write('%s refs/heads/bookmarks/%s\n' %
                             (ref if ref else '?', name))
            if not unknowns:
                for tag, ref in sorted(store.tags(branchmap.heads())):
                    helper.write('%s refs/tags/%s\n' % (ref, tag))

            if '@' in bookmarks:
                HEAD = 'bookmarks/@'
            helper.write('@refs/heads/%s HEAD\n' '\n' % HEAD)
            helper.flush()
        elif cmd == 'option':
            assert len(args) == 2
            name, value = args
            if name == 'progress':
                if value == 'true':
                    cinnabar.util.progress = True
                    helper.write('ok\n')
                elif value == 'false':
                    cinnabar.util.progress = False
                    helper.write('ok\n')
                else:
                    helper.write('unsupported\n')
            else:
                helper.write('unsupported\n')
            helper.flush()
        elif cmd == 'import':
            try:
                reflog = os.path.join(git_dir, 'logs', 'refs', 'cinnabar')
                mkpath(reflog)
                open(os.path.join(reflog, 'hg2git'), 'a').close()
                open(os.path.join(reflog, 'manifest'), 'a').close()
                assert len(args) == 1
                refs = args
                while cmd:
                    assert cmd == 'import'
                    cmd, args = read_cmd(helper)
                    assert args is None or len(args) == 1
                    if args:
                        refs.extend(args)
            except:
                # If anything wrong happens before we got all the import
                # commands, we risk git picking the existing refs/cinnabar
                # refs. Remove them.
                for line in Git.for_each_ref('refs/cinnabar/refs/heads',
                                             'refs/cinnabar/HEAD',
                                             format='%(refname)'):
                    Git.delete_ref(ref)
                raise

            try:

                def resolve_head(head):
                    if head.startswith('refs/heads/branches/'):
                        head = head[20:]
                        if head[-4:] == '/tip':
                            return branchmap.tip(head[:-4])
                        return head[-40:]
                    if head.startswith('refs/heads/bookmarks/'):
                        head = head[21:]
                        return bookmarks[head]
                    if head == 'HEAD':
                        return bookmarks.get('@') or branchmap.tip('default')
                    return None

                wanted_refs = {
                    k: v
                    for k, v in ((h, resolve_head(h)) for h in refs) if v
                }
                heads = wanted_refs.values()
                if not heads:
                    heads = branchmap.heads()

                # Older versions would create a symbolic ref for
                # refs/remote-hg/HEAD. Newer versions don't, and
                # Git.update_ref doesn't remove the symbolic ref, so it needs
                # to be removed first.
                # Since git symbolic-ref only throws an error when the ref is
                # not symbolic, just try to remove the symbolic ref every time
                # and ignore errors.
                tuple(
                    Git.iter('symbolic-ref',
                             '-d',
                             'refs/remote-hg/HEAD',
                             stderr=open(os.devnull, 'wb')))

                refs_orig = {}
                for line in Git.for_each_ref(
                        'refs/cinnabar/refs/heads',
                        'refs/cinnabar/HEAD',
                        format='%(objectname) %(refname)'):
                    sha1, ref = line.split(' ', 1)
                    refs_orig[ref] = sha1
            except:
                # If anything wrong happens before we actually pull, we risk
                # git pucking the existing refs/cinnabar refs. Remove them.
                # Unlike in the case above, we now have the list of refs git
                # is expected, so we can just remove those.
                for ref in refs:
                    Git.delete_ref('refs/cinnabar/' + ref)
                raise

            try:
                store.init_fast_import(FastImport(sys.stdin, sys.stdout))
                getbundle(repo, store, heads, branchmap)
            except:
                wanted_refs = {}
                raise
            finally:
                for ref, value in wanted_refs.iteritems():
                    ref = 'refs/cinnabar/' + ref
                    if ref not in refs_orig or refs_orig[ref] != value:
                        Git.update_ref(ref, store.changeset_ref(value))
                for ref in refs_orig:
                    if ref[14:] not in wanted_refs:
                        Git.delete_ref(ref)

            store.close()

            if not remote.startswith('hg::'):
                prune = 'remote.%s.prune' % remote
                if (Git.config(prune) != 'true'
                        and Git.config('fetch.prune') != 'true'):
                    sys.stderr.write(
                        'It is recommended that you set "%(conf)s" or '
                        '"fetch.prune" to "true".\n'
                        '  git config %(conf)s true\n'
                        'or\n'
                        '  git config fetch.prune true\n' % {'conf': prune})

            if store.tag_changes:
                sys.stderr.write(
                    '\nRun the following command to update remote tags:\n')
                if not remote.startswith('hg::'):
                    sys.stderr.write('  git remote update %s\n' % remote)
                else:
                    sys.stderr.write('  git fetch --tags %s\n' % remote)

        elif cmd == 'push':
            if not remote.startswith('hg::'):
                data_pref = 'remote.%s.cinnabar-data' % remote
                data = Git.config(data_pref) or 'phase'
            else:
                data = 'phase'

            if data not in ('never', 'phase', 'always'):
                sys.stderr.write('Invalid value for %s: %s\n' %
                                 (data_pref, data))
                return 1

            refspecs = []
            refspecs.extend(args)
            while True:
                cmd, args = read_cmd(helper)
                if not cmd:
                    break
                assert cmd == 'push'
                refspecs.extend(args)
            pushes = {
                s.lstrip('+'): (d, s.startswith('+'))
                for s, d in (r.split(':', 1) for r in refspecs)
            }
            if isinstance(repo, bundlerepo):
                for source, (dest, force) in pushes.iteritems():
                    helper.write('error %s Cannot push to a bundle file\n' %
                                 dest)
                helper.write('\n')
                helper.flush()
            else:
                repo_heads = branchmap.heads()
                PushStore.adopt(store)
                pushed = push(repo, store, pushes, repo_heads,
                              branchmap.names())

                status = {}
                for source, (dest, _) in pushes.iteritems():
                    if dest.startswith('refs/tags/'):
                        if source:
                            status[dest] = 'Pushing tags is unsupported'
                        else:
                            status[dest] = \
                                'Deleting remote tags is unsupported'
                        continue
                    if not dest.startswith('refs/heads/bookmarks/'):
                        if source:
                            status[dest] = bool(len(pushed))
                        else:
                            status[dest] = \
                                'Deleting remote branches is unsupported'
                        continue
                    name = dest[21:]
                    if source:
                        source = store.hg_changeset(Git.resolve_ref(source)) \
                            or ''
                    status[dest] = repo.pushkey('bookmarks', name,
                                                bookmarks.get(name, ''),
                                                source)

                for source, (dest, force) in pushes.iteritems():
                    if status[dest] is True:
                        helper.write('ok %s\n' % dest)
                    elif status[dest]:
                        helper.write('error %s %s\n' % (dest, status[dest]))
                    else:
                        helper.write('error %s nothing changed on remote\n' %
                                     dest)
                helper.write('\n')
                helper.flush()

                if not pushed:
                    data = False
                elif data == 'always':
                    data = True
                elif data == 'phase':
                    phases = repo.listkeys('phases')
                    drafts = {}
                    if not phases.get('publishing', False):
                        drafts = set(p for p, is_draft in phases.iteritems()
                                     if int(is_draft))
                    if not drafts:
                        data = True
                    else:

                        def draft_commits():
                            for d in drafts:
                                c = store.changeset_ref(d)
                                if c:
                                    yield '^%s^@' % c
                            for h in pushed.heads():
                                yield h

                        args = [
                            'rev-list', '--ancestry-path', '--topo-order',
                            '--stdin'
                        ]

                        pushed_drafts = tuple(
                            Git.iter(*args, stdin=draft_commits()))

                        # Theoretically, we could have commits with no
                        # metadata that the remote declares are public, while
                        # the rest of our push is in a draft state. That is
                        # however so unlikely that it's not worth the effort
                        # to support partial metadata storage.
                        data = not bool(pushed_drafts)
                elif data == 'never':
                    data = False

                store.close(rollback=not data)

    store.close()
コード例 #30
0
    def list(self, arg=None):
        assert not arg or arg == 'for-push'

        fetch = (Git.config('cinnabar.fetch') or '').split()
        if fetch:
            heads = [unhexlify(f) for f in fetch]
            branchmap = {None: heads}
            bookmarks = {}

        elif self._repo.capable('batch'):
            if hasattr(self._repo, 'commandexecutor'):
                with self._repo.commandexecutor() as e:
                    branchmap = e.callcommand('branchmap', {})
                    heads = e.callcommand('heads', {})
                    bookmarks = e.callcommand('listkeys',
                                              {'namespace': 'bookmarks'})
                branchmap = branchmap.result()
                heads = heads.result()
                bookmarks = bookmarks.result()
            elif hasattr(self._repo, 'iterbatch'):
                batch = self._repo.iterbatch()
                batch.branchmap()
                batch.heads()
                batch.listkeys('bookmarks')
                batch.submit()
                branchmap, heads, bookmarks = batch.results()
            else:
                batch = self._repo.batch()
                branchmap = batch.branchmap()
                heads = batch.heads()
                bookmarks = batch.listkeys('bookmarks')
                batch.submit()
                branchmap = branchmap.value
                heads = heads.value
                bookmarks = bookmarks.value
            if heads == ['\0' * 20]:
                heads = []
        else:
            while True:
                branchmap = self._repo.branchmap()
                heads = self._repo.heads()
                if heads == ['\0' * 20]:
                    heads = []
                # Some branch heads can be non-heads topologically, but if
                # some heads don't appear in the branchmap, then something
                # was pushed to the repo between branchmap() and heads()
                if set(heads).issubset(
                        set(chain(*(v for _, v in branchmap.iteritems())))):
                    break
            bookmarks = self._repo.listkeys('bookmarks')

        self._bookmarks = bookmarks
        branchmap = self._branchmap = BranchMap(self._store, branchmap, heads)
        self._has_unknown_heads = bool(self._branchmap.unknown_heads())
        if self._graft and self._has_unknown_heads and not arg:
            self._store.prepare_graft()
            get_heads = set(branchmap.heads()) & branchmap.unknown_heads()
            getbundle(self._repo, self._store, get_heads, branchmap.names())
            # We may have failed to graft all changesets, in which case we
            # skipped them. If that's what happened, we want to create a
            # new branchmap containing all we do know about, so that we can
            # avoid telling git about things we don't know, because if we
            # didn't, it would ask for them, and subsequently fail because
            # they are missing.
            # Since we can't know for sure what the right tips might be for
            # each branch, we won't expose the tips. This means we don't
            # need to care about the order of the heads for the new
            # branchmap.
            self._has_unknown_heads = any(not (self._store.changeset_ref(h))
                                          for h in get_heads)
            if self._has_unknown_heads:
                new_branchmap = {
                    branch: set(h for h in branchmap.heads(branch))
                    for branch in branchmap.names()
                }
                new_branchmap = {
                    branch: set(h for h in branchmap.heads(branch)
                                if h not in branchmap.unknown_heads())
                    for branch in branchmap.names()
                }
                new_heads = set(h for h in branchmap.heads()
                                if h not in branchmap.unknown_heads())
                for status, head, branch in self._store._hgheads.iterchanges():
                    branch_heads = new_branchmap.get(branch)
                    if status == VersionedDict.REMOVED:
                        if branch_heads and head in branch_heads:
                            branch_heads.remove(head)
                        if head in new_heads:
                            new_heads.remove(head)
                    else:
                        if not branch_heads:
                            branch_heads = new_branchmap[branch] = set()
                        branch_heads.add(head)
                        new_heads.add(head)

                branchmap = self._branchmap = BranchMap(
                    self._store, new_branchmap, list(new_heads))

        refs_style = None
        if not fetch and branchmap.heads():
            refs_styles = [
                None,
                '',
                'all',
                'bookmarks',
                'heads',
                'tips',
            ]

            refs_configs = ['cinnabar.refs']
            if arg == 'for-push':
                refs_configs.insert(0, 'cinnabar.pushrefs')

            for refs_config in refs_configs:
                refs_style = Git.config(refs_config,
                                        remote=self._remote.name,
                                        values=refs_styles)
                if refs_style:
                    break

        refs_style = refs_style or 'all'
        self._refs_style = refs_style

        refs = {}
        if refs_style in ('all', 'heads', 'tips'):
            if refs_style == 'all':
                self._head_template = 'refs/heads/branches/{}/{}'
                self._tip_template = 'refs/heads/branches/{}/tip'
            elif refs_style == 'heads':
                self._head_template = 'refs/heads/{}/{}'
            elif refs_style == 'tips':
                self._tip_template = 'refs/heads/{}'

            for branch in sorted(branchmap.names()):
                branch_tip = branchmap.tip(branch)
                if refs_style != 'tips':
                    for head in sorted(branchmap.heads(branch)):
                        if head == branch_tip and refs_style == 'all':
                            continue
                        refs[self._head_template.format(branch, head)] = head
                if branch_tip and refs_style != 'heads':
                    refs[self._tip_template.format(branch)] = branch_tip

        if refs_style in ('all', 'bookmarks'):
            if refs_style == 'all':
                self._bookmark_template = 'refs/heads/bookmarks/{}'
            else:
                self._bookmark_template = 'refs/heads/{}'
            for name, sha1 in sorted(bookmarks.iteritems()):
                if sha1 == NULL_NODE_ID:
                    continue
                ref = self._store.changeset_ref(sha1)
                if self._graft and not ref:
                    continue
                refs[self._bookmark_template.format(name)] = sha1

        for f in fetch:
            refs['hg/revs/%s' % f] = f

        head_ref = None
        if refs_style in ('all', 'bookmarks') and '@' in bookmarks:
            head_ref = self._bookmark_template.format('@')
        elif refs_style in ('all', 'tips'):
            head_ref = self._tip_template.format('default')
        elif refs_style == 'heads':
            head_ref = self._head_template.format('default',
                                                  branchmap.tip('default'))

        if head_ref:
            head = refs.get(head_ref)
            if self._graft and head:
                head = self._store.changeset_ref(head)
            if head:
                refs['HEAD'] = '@{}'.format(head_ref)

        self._refs = {sanitize_branch_name(k): v for k, v in refs.iteritems()}

        head_prefix = strip_suffix((self._head_template or ''), '{}/{}')
        for k, v in sorted(self._refs.iteritems()):
            if head_prefix and k.startswith(head_prefix):
                v = self._store.changeset_ref(v) or self._branchmap.git_sha1(v)
            elif not v.startswith('@'):
                v = self._store.changeset_ref(v) or '?'
            if not self._graft or v != '?':
                self._helper.write('%s %s\n' % (v, k))

        self._helper.write('\n')
        self._helper.flush()