Exemplo n.º 1
0
	def get(self, repo):
		service = self.request.get('service')
		if service:
			handler_cls = DEFAULT_HANDLERS.get(service)
			if not handler_cls:
				self.error(403)
				return
			self.response.headers['Content-type'] = 'application/x-%s-advertisement' % service
			proto = ReceivableProtocol(StringIO().read, self.response.out.write)
			handler = handler_cls(FileSystemBackend(), [ repo ], proto, stateless_rpc = True, advertise_refs = True)
			handler.proto.write_pkt_line('# service=%s\n' % service)
			handler.proto.write_pkt_line(None)
			handler.handle()
		else:
			self.response.headers['Content-type'] = 'text/plain'
			repo = Repo(repo)
			refs = repo.get_refs()
			for name in sorted(refs.iterkeys()):
				if name == 'HEAD':
					continue
				sha = refs[name]
				if not repo[sha]:
					continue
				self.response.out.write('%s\t%s\n' % (sha, name))
				peeled_sha = repo.get_peeled(name)
				if peeled_sha != sha:
					self.response.out.write('%s\t%s^{}\n' % (peeled_sha, name))
Exemplo n.º 2
0
def get_recent_tags(projdir=PROJDIR):
    """
    Get list of recent tags in order from newest to oldest and their datetimes.

    :param projdir: path to ``.git``
    :returns: list of (tag, [datetime, commit, author]) sorted from new to old
    """
    project = Repo(projdir)  # dulwich repository object
    refs = project.get_refs()  # dictionary of refs and their SHA-1 values
    tags = {}  # empty dictionary to hold tags, commits and datetimes
    # iterate over refs in repository
    for key, value in refs.iteritems():
        obj = project.get_object(value)  # dulwich object from SHA-1
        # check if object is tag
        if obj.type_name != 'tag':
            # skip ref if not a tag
            continue
        # strip the leading text from "refs/tag/<tag name>" to get "tag name"
        _, tag = key.rsplit('/', 1)
        # check if tag object is commit, altho it should always be true
        if obj.object[0].type_name == 'commit':
            commit = project.get_object(obj.object[1])  # commit object
            # get tag commit datetime, but dulwich returns seconds since
            # beginning of epoch, so use Python time module to convert it to
            # timetuple then convert to datetime
            tags[tag] = [
                datetime.datetime(*time.gmtime(commit.commit_time)[:6]),
                commit.id,
                commit.author
            ]
            
    # return list of tags sorted by their datetimes from newest to oldest
    return sorted(tags.iteritems(), key=lambda tag: tag[1][0], reverse=True)
Exemplo n.º 3
0
    def list_all_contributors (self):
        tmp = 1
        tot = len(self.repos)
        all_contribs = []
        for repo in self.repos:
            print >> sys.stderr, "[%d/%d Analyzing %s]" % (tmp, tot, repo)
            tmp += 1
            repo = Repo(repo)
            master = repo.get_refs()['refs/heads/master']
            for i in repo.get_walker ([master]):
                if "<" in i.commit.author:
                    split = i.commit.author.split("<")
                    author = split[0]
                    email = split[1]
                    author = author.strip ()
                    email = email.strip ()
                    email = email[:-1]
                else:
                    author = i.commit.author
                    email = ""

                all_contribs.append((author, email))
            del repo

        tmp = []
        for c in all_contribs:
            if c in tmp:
                continue
            tmp.append(c)
        return tmp
Exemplo n.º 4
0
def get_refs(repo: Repo) -> DefaultDict[str, Set[str]]:
    refs: DefaultDict[str, Set[str]] = defaultdict(set)
    for ref, commit in repo.get_refs().items():
        str_commit = _to_str(commit)
        refs[str_commit].add(str_commit)
        refs[str_commit].add(_to_str(ref))
    return refs
Exemplo n.º 5
0
def get_tags(repo: Repo) -> Dict[str, str]:
    tags: Dict[str, str] = {}
    for tag_ref, commit in repo.get_refs().items():
        tag_ref_str = _to_str(tag_ref)
        if tag_ref_str.startswith("refs/tags/") and VALID_TAG.match(
                tag_ref_str[len("refs/tags/"):]):
            tags[_to_str(commit)] = os.path.basename(tag_ref_str)

    return tags
Exemplo n.º 6
0
 def test_simple(self):
     self.build_tree(['remote/foo'])
     self.remote_tree.add("foo")
     revid = self.remote_tree.commit("msg")
     git_sha1 = map_to_git_sha1(self.remote_dir, revid)
     out = self.fetch([(git_sha1, 'HEAD')])
     self.assertEqual(out, b"\n")
     r = Repo('local')
     self.assertTrue(git_sha1 in r.object_store)
     self.assertEqual({}, r.get_refs())
Exemplo n.º 7
0
def repo_dashboard(repo_key, branch, tree_path=''):
    
    #Get repo & branch
    repo = Repo(settings.REPOS[repo_key])
    try:
        branch_or_sha = repo.get_refs()[LOCAL_BRANCH_PREFIX+branch]
    except KeyError:
        #If there is no branch then is a commit sha
        branch_or_sha = branch
    
    branch_name = branch
    
    selected_commit = repo[branch_or_sha]
    
    #Get files
    tree_files={}
    splitted_path = tree_path.split('/')
    #Get filename (maybe we need fo the blob)
    file_name = splitted_path[len(splitted_path)-1]
    #if we are root folder then empty list
    if len(splitted_path) == 1 and splitted_path[0] =='':
        splitted_path = []
    else:    
        splitted_path.reverse()
    
    tree = repo[selected_commit.tree]
    #for tree_file in tree.iteritems():
    #    tree_files[tree_file] = stat.S_ISDIR(tree_file[1])
    
    tree_files = utils.get_repo_files(repo, tree, splitted_path)
    
    
    #Show file content if is a blob (raw file, not dir)
    if isinstance(tree_files, Blob):
        file_code = tree_files.as_raw_string()

        return render_template('file-detail.html', repo_key=repo_key, branch=branch_name,
                                file_code=file_code, file_name=file_name)
    #Show tree if is a empty path (root folder) or a subforlder
    else:
        #Check readme
        readme = ''
        readme_name = ''
        for maybe_readme, directory in tree_files.iteritems():
            if 'readme' in maybe_readme[0].lower():
                readme_name = maybe_readme[0]
                readme = repo[maybe_readme[2]].as_raw_string()
                break
        
        #Little hack for the url generating
        tree_path = tree_path + '/' if tree_path is not '' else tree_path
        
        return render_template('repo-dashboard.html', repo_key=repo_key, branch=branch_name, 
                                tree_files=tree_files, readme=readme, readme_name=readme_name, 
                                tree_path=tree_path)
Exemplo n.º 8
0
 def _find_date_boundaries (self):
     for repo in self.repos:
         repo = Repo(repo)
         master = repo.get_refs()['refs/heads/master']
         for i in repo.get_walker ([master]):
             if self.date_oldest == None or self.date_oldest > i.commit.commit_time:
                 self.date_oldest = i.commit.commit_time
             if self.date_newest == None or self.date_newest < i.commit.commit_time:
                 self.date_newest = i.commit.commit_time
             del i
         del repo
Exemplo n.º 9
0
def commit_history(repo_key, branch):
    
    repo = Repo(settings.REPOS[repo_key])
    
    #get all the branches and set the name branch in a ref list (don't 
    #add the selected one, this will be added sepparetly in the template)
    references = []
    selected_branch = branch
    for ref, sha in repo.get_refs().iteritems():
        #get the name of the branch without the pefix
        if (LOCAL_BRANCH_PREFIX in ref):
            references.append(ref.replace(LOCAL_BRANCH_PREFIX, '', 1))
    
    #Get the branch walker
    walker = repo.get_walker(include = [repo.get_refs()[LOCAL_BRANCH_PREFIX+branch], ])
    
    
    #Start getting all the commits from the branch
    commits = []
    commits_per_day = []
    previous_commit_time = None
    
    #Group commits by day (I use list instead of a dict because the list is ordered already, so I don't need to sort the dict)
    for i in walker:
        
        commit = i.commit
        commit_time = filters.convert_unix_time_filter(commit.commit_time, '%d %b %Y')
        
        #if is new or like the previous one time, then add to the list, if not then save the list and create a new one
        if (previous_commit_time is None) or (commit_time == previous_commit_time):
            commits_per_day.append(commit)
        else:
            commits.append(commits_per_day)
            commits_per_day = [commit,]
        
        previous_commit_time = commit_time
        
    #Add last ones
    commits.append(commits_per_day)
    return render_template('commit-history.html', commits=commits, repo_key=repo_key, 
                            references = references, selected_branch=selected_branch)
Exemplo n.º 10
0
def add_doc(namespace_path, project_path):
    user_repo = os.path.join(namespace_path, project_path)
    repo_path = build_repo_path(user_repo)

    if not os.path.exists(repo_path):
        err_msg = "Repo path {0} not exist".format(repo_path)
        print >> sys.stderr, err_msg
        return

    ix = open_dir(INDEXDIR)
    writer = ix.writer()

    try:
        repo = Repo(repo_path)
    except NotGitRepository:
        err_msg = "No git repository was found at {0}".format(repo_path)
        print >> sys.stderr, err_msg
        return

    try:
        refs = repo.get_refs()

        for ref in refs.keys():
            if ref.startswith('refs/heads') or ref.startswith('refs/tags'):
                obj = repo.get_object(refs[ref])

                if isinstance(obj, Tag):
                    commit = repo[obj.object[1]]
                else:
                    commit = obj

                tree = repo[commit.tree]

                for path, entry in get_entry(repo, tree):
                    filename = os.path.join(ref.rsplit('/')[2],
                                            path, entry.path)
                    blob = repo[entry.sha]

                    writer.add_document(
                        repo=user_repo.decode('UTF-8', 'ignore'),
                        ref=ref.decode('UTF-8', 'ignore'),
                        filename=filename.decode('UTF-8', 'ignore'),
                        content=blob.data.decode('UTF-8', 'ignore'))

        writer.commit()
    except:
        writer.cancel()
        raise
Exemplo n.º 11
0
class GitVCS(object):
    def __init__(self, path):
        self.repo = Repo(path)

    def branches(self):
        return dict(((k.replace('refs/heads/', ''), v) for k, v in self.repo.get_refs().items()
                     if k.startswith('refs/heads/')))

    def get_branch_ref(self, branch):
        branches = self.branches()
        if not branch in branches:
            return None
        return branches[branch]

    def get_object(self, sha1):
        try:
            return self.repo.get_object(sha1)
        except AssertionError:
            return None

    def get_object_by_path(self, ref, path):
        c = self.get_object(self.get_branch_ref(ref) or ref)
        if not c:
            return None
        paths = path.split(os.sep)
        count = len(paths)
        obj = self.get_object(c.tree)
        for i, x in enumerate(paths):
            if not x:
                break
            try:
                _mode, sha1 = obj[x]
            except KeyError:
                obj = None
                break
            obj = self.get_object(sha1)
            if i < count - 1 and not isinstance(obj, Tree):
                obj = None
                break

        if not obj:
            raise ValueError("Bad path")

        print "Result: ", type(obj), obj
        return obj
Exemplo n.º 12
0
    def branches(self, **keywords):
        sha, branch, filename = keywords["sha"], keywords["branch"], keywords["filename"]

        repo = Repo(self.package.path())
        data = repo.get_refs()

        valid_keys = []
        for key in data.keys():
            if key[0:11] == "refs/heads/":
                valid_keys.append((key[11:], data[key]))
        
        content = ""
        for key in valid_keys:
            content = content + "<a href=\"/package/" + self.package.name + ":" + key[0] + "/\">" + key[0] + "</a><br />"

        self.content = content
        self.branch = branch
        self.sha = sha
        return self.respond()
Exemplo n.º 13
0
    def send_pack(self,
                  path,
                  determine_wants,
                  generate_pack_contents,
                  progress=None,
                  write_pack=write_pack_objects):
        """Upload a pack to a remote repository.

        :param path: Repository path
        :param generate_pack_contents: Function that can return a sequence of
            the shas of the objects to upload.
        :param progress: Optional progress function
        :param write_pack: Function called with (file, iterable of objects) to
            write the objects returned by generate_pack_contents to the server.

        :raises SendPackError: if server rejects the pack data
        :raises UpdateRefsError: if the server supports report-status
                                 and rejects ref updates
        """
        from dulwich.repo import Repo

        target = Repo(path)
        old_refs = target.get_refs()
        new_refs = determine_wants(old_refs)

        have = [sha1 for sha1 in old_refs.values() if sha1 != ZERO_SHA]
        want = []
        for refname in set(new_refs.keys() + old_refs.keys()):
            old_sha1 = old_refs.get(refname, ZERO_SHA)
            new_sha1 = new_refs.get(refname, ZERO_SHA)
            if new_sha1 not in have and new_sha1 != ZERO_SHA:
                want.append(new_sha1)

        if not want and old_refs == new_refs:
            return new_refs

        target.object_store.add_objects(generate_pack_contents(have, want))

        for name, sha in new_refs.iteritems():
            target.refs[name] = sha

        return new_refs
Exemplo n.º 14
0
class GitRepository(Repository):
    CommitBuilder = GitCommitBuilder

    def __init__(self, path=None, workdir=None, create=False, bare=False):
        assert path or workdir
        if workdir:
            self.path = workdir.path
        else:
            self.path = path
        if create:
            # XXX: fragile
            path.ensure(dir=1)
            self.repo = Repo.init(path.strpath)
        else:
            assert self.path.check(dir=True)
            try:
                self.repo = Repo(self.path.strpath)
            except NotGitRepository:
                raise NotFoundError('git', self.path)

    def __len__(self):
        # XXX: fragile
        head = self.get_default_head()
        if head is None:
            return 0
        return len(self.repo.revision_history(head.id))

    def push(self):
        # XXX: hell, figure if the remote is empty, push master in that case
        # XXX: use dulwich?
        subprocess.check_call(['git', 'push', '--all'], cwd=self.path)

    def get_default_head(self):
        revs = self.repo.get_refs()
        head = revs.get('HEAD', revs.get('master'))
        if head is not None:
            return GitRevision(self, self.repo.get_object(head))

    def __getitem__(self, id):
        return GitRevision(self, self.repo.get_object(id))
Exemplo n.º 15
0
    def send_pack(self, path, determine_wants, generate_pack_contents,
                  progress=None, write_pack=write_pack_objects):
        """Upload a pack to a remote repository.

        :param path: Repository path
        :param generate_pack_contents: Function that can return a sequence of
            the shas of the objects to upload.
        :param progress: Optional progress function
        :param write_pack: Function called with (file, iterable of objects) to
            write the objects returned by generate_pack_contents to the server.

        :raises SendPackError: if server rejects the pack data
        :raises UpdateRefsError: if the server supports report-status
                                 and rejects ref updates
        """
        from dulwich.repo import Repo

        target = Repo(path)
        old_refs = target.get_refs()
        new_refs = determine_wants(old_refs)

        have = [sha1 for sha1 in old_refs.values() if sha1 != ZERO_SHA]
        want = []
        all_refs = set(new_refs.keys()).union(set(old_refs.keys()))
        for refname in all_refs:
            old_sha1 = old_refs.get(refname, ZERO_SHA)
            new_sha1 = new_refs.get(refname, ZERO_SHA)
            if new_sha1 not in have and new_sha1 != ZERO_SHA:
                want.append(new_sha1)

        if not want and old_refs == new_refs:
            return new_refs

        target.object_store.add_objects(generate_pack_contents(have, want))

        for name, sha in new_refs.items():
            target.refs[name] = sha

        return new_refs
Exemplo n.º 16
0
    def build_stats_by_periods (self, periods, filter_fn=None):
        assert (len(periods) > 0)
        assert (reduce(lambda x,y: x and y, map(lambda x: isinstance(x,int), periods)))

        lower = self.date_oldest
        upper = self.date_newest

        periods.sort()
        periods = dict.fromkeys(periods, [])

        tmp = 1
        tot = len(self.repos)
        for repo in self.repos:
            print >> sys.stderr, "[%d/%d Analyzing %s]" % (tmp, tot, repo)
            tmp += 1
            repo = Repo(repo)
            master = repo.get_refs()['refs/heads/master']
            for i in repo.get_walker ([master]):
                keys = periods.keys()
                keys.sort()
                lower = keys[0]
                upper = keys[-1]
                if i.commit.commit_time < lower or i.commit.commit_time > upper:
                    continue
                if filter_fn != None and not filter_fn (i):
                    continue

                period = self._find_period (periods.keys(), i.commit.commit_time)
                author = i.commit.author.split("<")[0].strip()

                periods[period].append(author)
                del i
            del repo
    
        for period in periods.keys():
            periods[period] = self._plain_to_count(periods[period])

        return periods
Exemplo n.º 17
0
def push(remote_url, repo_path='.'):
    """
    Push to a remote repository
    :param remote_url: <str> url of remote repository
    :param repo_path: <str> path of local repository
    :return refs: <dict> dictionary of ref-sha pairs
    """
    client, path = get_transport_and_path(remote_url)
    r = Repo(repo_path)
    objsto = r.object_store
    refs = r.get_refs()
    def update_refs(old):
        # TODO: Too complicated,  not necessary to find the refs that
        # differ - it's fine to update a ref even if it already exists.
        # TODO: Also error out if there are non-fast forward updates
        same = list(set(refs).intersection(old))
        new = dict([(k,refs[k]) for k in same if refs[k] != old[k]])
        dfky = list(set(refs) - set(new))
        dfrnt = dict([(k,refs[k]) for k in dfky if k != 'HEAD'])
        return dict(new.items() + dfrnt.items())
    return client.send_pack(path,
                            update_refs,
                            objsto.generate_pack_contents,
                            sys.stdout.write)
Exemplo n.º 18
0
def find_lost_commits():
	r=Repo('.')
	o=r.object_store
	
	all_commits=[]
	all_trees=[]
	for sha in o:
		obj=o[sha]
		if isinstance(obj,Commit):
			all_commits.append(obj)
	
	#hide commits that have children, so we get "ends" of chains only
	for c in all_commits:
		for p in c.parents:
			try:
				all_commits.remove(p)
			except ValueError:
				pass
		if c.sha in r.get_refs().values():
			all_commits.remove(c)
	#hide commits that are in branches
	
	for c in sorted(all_commits,key=lambda x:x.commit_time):	
		print('{} {} {}'.format(time.ctime(c.commit_time),sha, c.message))
Exemplo n.º 19
0
def find_lost_commits():
    r = Repo('.')
    o = r.object_store

    all_commits = []
    all_trees = []
    for sha in o:
        obj = o[sha]
        if isinstance(obj, Commit):
            all_commits.append(obj)

    #hide commits that have children, so we get "ends" of chains only
    for c in all_commits:
        for p in c.parents:
            try:
                all_commits.remove(p)
            except ValueError:
                pass
        if c.sha in r.get_refs().values():
            all_commits.remove(c)
    #hide commits that are in branches

    for c in sorted(all_commits, key=lambda x: x.commit_time):
        print('{} {} {}'.format(time.ctime(c.commit_time), sha, c.message))
Exemplo n.º 20
0
class DiskRefsContainerTests(RefsContainerTests, TestCase):
    def setUp(self):
        TestCase.setUp(self)
        self._repo = open_repo('refs.git')
        self.addCleanup(tear_down_repo, self._repo)
        self._refs = self._repo.refs

    def test_get_packed_refs(self):
        self.assertEqual(
            {
                b'refs/heads/packed':
                b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                b'refs/tags/refs-0.1':
                b'df6800012397fb85c56e7418dd4eb9405dee075c',
            }, self._refs.get_packed_refs())

    def test_get_peeled_not_packed(self):
        # not packed
        self.assertEqual(None, self._refs.get_peeled(b'refs/tags/refs-0.2'))
        self.assertEqual(b'3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8',
                         self._refs[b'refs/tags/refs-0.2'])

        # packed, known not peelable
        self.assertEqual(self._refs[b'refs/heads/packed'],
                         self._refs.get_peeled(b'refs/heads/packed'))

        # packed, peeled
        self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                         self._refs.get_peeled(b'refs/tags/refs-0.1'))

    def test_setitem(self):
        RefsContainerTests.test_setitem(self)
        path = os.path.join(self._refs.path, b'refs', b'some', b'ref')
        with open(path, 'rb') as f:
            self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                             f.read()[:40])

        self.assertRaises(OSError, self._refs.__setitem__,
                          b'refs/some/ref/sub',
                          b'42d06bd4b77fed026b154d16493e5deab78f02ec')

    def test_setitem_packed(self):
        with open(os.path.join(self._refs.path, b'packed-refs'), 'w') as f:
            f.write('# pack-refs with: peeled fully-peeled sorted \n')
            f.write(
                '42d06bd4b77fed026b154d16493e5deab78f02ec refs/heads/packed\n')

        # It's allowed to set a new ref on a packed ref, the new ref will be
        # placed outside on refs/
        self._refs[b'refs/heads/packed'] = (
            b'3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8')
        packed_ref_path = os.path.join(self._refs.path, b'refs', b'heads',
                                       b'packed')
        with open(packed_ref_path, 'rb') as f:
            self.assertEqual(b'3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8',
                             f.read()[:40])

        self.assertRaises(OSError, self._refs.__setitem__,
                          b'refs/heads/packed/sub',
                          b'42d06bd4b77fed026b154d16493e5deab78f02ec')

    def test_setitem_symbolic(self):
        ones = b'1' * 40
        self._refs[b'HEAD'] = ones
        self.assertEqual(ones, self._refs[b'HEAD'])

        # ensure HEAD was not modified
        f = open(os.path.join(self._refs.path, b'HEAD'), 'rb')
        v = next(iter(f)).rstrip(b'\n\r')
        f.close()
        self.assertEqual(b'ref: refs/heads/master', v)

        # ensure the symbolic link was written through
        f = open(os.path.join(self._refs.path, b'refs', b'heads', b'master'),
                 'rb')
        self.assertEqual(ones, f.read()[:40])
        f.close()

    def test_set_if_equals(self):
        RefsContainerTests.test_set_if_equals(self)

        # ensure symref was followed
        self.assertEqual(b'9' * 40, self._refs[b'refs/heads/master'])

        # ensure lockfile was deleted
        self.assertFalse(
            os.path.exists(
                os.path.join(self._refs.path, b'refs', b'heads',
                             b'master.lock')))
        self.assertFalse(
            os.path.exists(os.path.join(self._refs.path, b'HEAD.lock')))

    def test_add_if_new_packed(self):
        # don't overwrite packed ref
        self.assertFalse(
            self._refs.add_if_new(b'refs/tags/refs-0.1', b'9' * 40))
        self.assertEqual(b'df6800012397fb85c56e7418dd4eb9405dee075c',
                         self._refs[b'refs/tags/refs-0.1'])

    def test_add_if_new_symbolic(self):
        # Use an empty repo instead of the default.
        repo_dir = os.path.join(tempfile.mkdtemp(), 'test')
        os.makedirs(repo_dir)
        repo = Repo.init(repo_dir)
        self.addCleanup(tear_down_repo, repo)
        refs = repo.refs

        nines = b'9' * 40
        self.assertEqual(b'ref: refs/heads/master', refs.read_ref(b'HEAD'))
        self.assertFalse(b'refs/heads/master' in refs)
        self.assertTrue(refs.add_if_new(b'HEAD', nines))
        self.assertEqual(b'ref: refs/heads/master', refs.read_ref(b'HEAD'))
        self.assertEqual(nines, refs[b'HEAD'])
        self.assertEqual(nines, refs[b'refs/heads/master'])
        self.assertFalse(refs.add_if_new(b'HEAD', b'1' * 40))
        self.assertEqual(nines, refs[b'HEAD'])
        self.assertEqual(nines, refs[b'refs/heads/master'])

    def test_follow(self):
        self.assertEqual(([b'HEAD', b'refs/heads/master'
                           ], b'42d06bd4b77fed026b154d16493e5deab78f02ec'),
                         self._refs.follow(b'HEAD'))
        self.assertEqual(([b'refs/heads/master'
                           ], b'42d06bd4b77fed026b154d16493e5deab78f02ec'),
                         self._refs.follow(b'refs/heads/master'))
        self.assertRaises(KeyError, self._refs.follow, b'refs/heads/loop')

    def test_delitem(self):
        RefsContainerTests.test_delitem(self)
        ref_file = os.path.join(self._refs.path, b'refs', b'heads', b'master')
        self.assertFalse(os.path.exists(ref_file))
        self.assertFalse(b'refs/heads/master' in self._refs.get_packed_refs())

    def test_delitem_symbolic(self):
        self.assertEqual(b'ref: refs/heads/master',
                         self._refs.read_loose_ref(b'HEAD'))
        del self._refs[b'HEAD']
        self.assertRaises(KeyError, lambda: self._refs[b'HEAD'])
        self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                         self._refs[b'refs/heads/master'])
        self.assertFalse(os.path.exists(os.path.join(self._refs.path,
                                                     b'HEAD')))

    def test_remove_if_equals_symref(self):
        # HEAD is a symref, so shouldn't equal its dereferenced value
        self.assertFalse(
            self._refs.remove_if_equals(
                b'HEAD', b'42d06bd4b77fed026b154d16493e5deab78f02ec'))
        self.assertTrue(
            self._refs.remove_if_equals(
                b'refs/heads/master',
                b'42d06bd4b77fed026b154d16493e5deab78f02ec'))
        self.assertRaises(KeyError, lambda: self._refs[b'refs/heads/master'])

        # HEAD is now a broken symref
        self.assertRaises(KeyError, lambda: self._refs[b'HEAD'])
        self.assertEqual(b'ref: refs/heads/master',
                         self._refs.read_loose_ref(b'HEAD'))

        self.assertFalse(
            os.path.exists(
                os.path.join(self._refs.path, b'refs', b'heads',
                             b'master.lock')))
        self.assertFalse(
            os.path.exists(os.path.join(self._refs.path, b'HEAD.lock')))

    def test_remove_packed_without_peeled(self):
        refs_file = os.path.join(self._repo.path, 'packed-refs')
        f = GitFile(refs_file)
        refs_data = f.read()
        f.close()
        f = GitFile(refs_file, 'wb')
        f.write(b'\n'.join(l for l in refs_data.split(b'\n')
                           if not l or l[0] not in b'#^'))
        f.close()
        self._repo = Repo(self._repo.path)
        refs = self._repo.refs
        self.assertTrue(
            refs.remove_if_equals(b'refs/heads/packed',
                                  b'42d06bd4b77fed026b154d16493e5deab78f02ec'))

    def test_remove_if_equals_packed(self):
        # test removing ref that is only packed
        self.assertEqual(b'df6800012397fb85c56e7418dd4eb9405dee075c',
                         self._refs[b'refs/tags/refs-0.1'])
        self.assertTrue(
            self._refs.remove_if_equals(
                b'refs/tags/refs-0.1',
                b'df6800012397fb85c56e7418dd4eb9405dee075c'))
        self.assertRaises(KeyError, lambda: self._refs[b'refs/tags/refs-0.1'])

    def test_remove_parent(self):
        self._refs[b'refs/heads/foo/bar'] = (
            b'df6800012397fb85c56e7418dd4eb9405dee075c')
        del self._refs[b'refs/heads/foo/bar']
        ref_file = os.path.join(
            self._refs.path,
            b'refs',
            b'heads',
            b'foo',
            b'bar',
        )
        self.assertFalse(os.path.exists(ref_file))
        ref_file = os.path.join(self._refs.path, b'refs', b'heads', b'foo')
        self.assertFalse(os.path.exists(ref_file))
        ref_file = os.path.join(self._refs.path, b'refs', b'heads')
        self.assertTrue(os.path.exists(ref_file))
        self._refs[b'refs/heads/foo'] = (
            b'df6800012397fb85c56e7418dd4eb9405dee075c')

    def test_read_ref(self):
        self.assertEqual(b'ref: refs/heads/master',
                         self._refs.read_ref(b'HEAD'))
        self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                         self._refs.read_ref(b'refs/heads/packed'))
        self.assertEqual(None, self._refs.read_ref(b'nonexistant'))

    def test_read_loose_ref(self):
        self._refs[b'refs/heads/foo'] = (
            b'df6800012397fb85c56e7418dd4eb9405dee075c')

        self.assertEqual(None, self._refs.read_ref(b'refs/heads/foo/bar'))

    def test_non_ascii(self):
        try:
            encoded_ref = u'refs/tags/schön'.encode(
                sys.getfilesystemencoding())
        except UnicodeEncodeError:
            raise SkipTest(
                "filesystem encoding doesn't support special character")
        p = os.path.join(self._repo.path.encode(sys.getfilesystemencoding()),
                         encoded_ref)
        with open(p, 'w') as f:
            f.write('00' * 20)

        expected_refs = dict(_TEST_REFS)
        expected_refs[encoded_ref] = b'00' * 20
        del expected_refs[b'refs/heads/loop']

        self.assertEqual(expected_refs, self._repo.get_refs())

    def test_cyrillic(self):
        if sys.platform == 'win32':
            raise SkipTest(
                "filesystem encoding doesn't support arbitrary bytes")
        # reported in https://github.com/dulwich/dulwich/issues/608
        name = b'\xcd\xee\xe2\xe0\xff\xe2\xe5\xf2\xea\xe01'
        encoded_ref = b'refs/heads/' + name
        with open(
                os.path.join(
                    self._repo.path.encode(sys.getfilesystemencoding()),
                    encoded_ref), 'w') as f:
            f.write('00' * 20)

        expected_refs = set(_TEST_REFS.keys())
        expected_refs.add(encoded_ref)

        self.assertEqual(expected_refs, set(self._repo.refs.allkeys()))
        self.assertEqual(
            {
                r[len(b'refs/'):]
                for r in expected_refs if r.startswith(b'refs/')
            }, set(self._repo.refs.subkeys(b'refs/')))
        expected_refs.remove(b'refs/heads/loop')
        expected_refs.add(b'HEAD')
        self.assertEqual(expected_refs, set(self._repo.get_refs().keys()))
Exemplo n.º 21
0
class DiskRefsContainerTests(RefsContainerTests, TestCase):

    def setUp(self):
        TestCase.setUp(self)
        self._repo = open_repo('refs.git')
        self.addCleanup(tear_down_repo, self._repo)
        self._refs = self._repo.refs

    def test_get_packed_refs(self):
        self.assertEqual({
            b'refs/heads/packed': b'42d06bd4b77fed026b154d16493e5deab78f02ec',
            b'refs/tags/refs-0.1': b'df6800012397fb85c56e7418dd4eb9405dee075c',
            }, self._refs.get_packed_refs())

    def test_get_peeled_not_packed(self):
        # not packed
        self.assertEqual(None, self._refs.get_peeled(b'refs/tags/refs-0.2'))
        self.assertEqual(b'3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8',
                         self._refs[b'refs/tags/refs-0.2'])

        # packed, known not peelable
        self.assertEqual(self._refs[b'refs/heads/packed'],
                         self._refs.get_peeled(b'refs/heads/packed'))

        # packed, peeled
        self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                         self._refs.get_peeled(b'refs/tags/refs-0.1'))

    def test_setitem(self):
        RefsContainerTests.test_setitem(self)
        path = os.path.join(self._refs.path, b'refs', b'some', b'ref')
        with open(path, 'rb') as f:
            self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                             f.read()[:40])

        self.assertRaises(
            OSError, self._refs.__setitem__,
            b'refs/some/ref/sub', b'42d06bd4b77fed026b154d16493e5deab78f02ec')

    def test_setitem_packed(self):
        with open(os.path.join(self._refs.path, b'packed-refs'), 'w') as f:
            f.write('# pack-refs with: peeled fully-peeled sorted \n')
            f.write(
                '42d06bd4b77fed026b154d16493e5deab78f02ec refs/heads/packed\n')

        # It's allowed to set a new ref on a packed ref, the new ref will be
        # placed outside on refs/
        self._refs[b'refs/heads/packed'] = (
            b'3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8'
        )
        packed_ref_path = os.path.join(
            self._refs.path, b'refs', b'heads', b'packed')
        with open(packed_ref_path, 'rb') as f:
            self.assertEqual(
                b'3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8',
                f.read()[:40])

        self.assertRaises(
            OSError, self._refs.__setitem__,
            b'refs/heads/packed/sub',
            b'42d06bd4b77fed026b154d16493e5deab78f02ec')

    def test_setitem_symbolic(self):
        ones = b'1' * 40
        self._refs[b'HEAD'] = ones
        self.assertEqual(ones, self._refs[b'HEAD'])

        # ensure HEAD was not modified
        f = open(os.path.join(self._refs.path, b'HEAD'), 'rb')
        v = next(iter(f)).rstrip(b'\n\r')
        f.close()
        self.assertEqual(b'ref: refs/heads/master', v)

        # ensure the symbolic link was written through
        f = open(os.path.join(self._refs.path, b'refs', b'heads', b'master'),
                 'rb')
        self.assertEqual(ones, f.read()[:40])
        f.close()

    def test_set_if_equals(self):
        RefsContainerTests.test_set_if_equals(self)

        # ensure symref was followed
        self.assertEqual(b'9' * 40, self._refs[b'refs/heads/master'])

        # ensure lockfile was deleted
        self.assertFalse(os.path.exists(
            os.path.join(self._refs.path, b'refs', b'heads', b'master.lock')))
        self.assertFalse(os.path.exists(
            os.path.join(self._refs.path, b'HEAD.lock')))

    def test_add_if_new_packed(self):
        # don't overwrite packed ref
        self.assertFalse(self._refs.add_if_new(b'refs/tags/refs-0.1',
                                               b'9' * 40))
        self.assertEqual(b'df6800012397fb85c56e7418dd4eb9405dee075c',
                         self._refs[b'refs/tags/refs-0.1'])

    def test_add_if_new_symbolic(self):
        # Use an empty repo instead of the default.
        repo_dir = os.path.join(tempfile.mkdtemp(), 'test')
        os.makedirs(repo_dir)
        repo = Repo.init(repo_dir)
        self.addCleanup(tear_down_repo, repo)
        refs = repo.refs

        nines = b'9' * 40
        self.assertEqual(b'ref: refs/heads/master', refs.read_ref(b'HEAD'))
        self.assertFalse(b'refs/heads/master' in refs)
        self.assertTrue(refs.add_if_new(b'HEAD', nines))
        self.assertEqual(b'ref: refs/heads/master', refs.read_ref(b'HEAD'))
        self.assertEqual(nines, refs[b'HEAD'])
        self.assertEqual(nines, refs[b'refs/heads/master'])
        self.assertFalse(refs.add_if_new(b'HEAD', b'1' * 40))
        self.assertEqual(nines, refs[b'HEAD'])
        self.assertEqual(nines, refs[b'refs/heads/master'])

    def test_follow(self):
        self.assertEqual(([b'HEAD', b'refs/heads/master'],
                          b'42d06bd4b77fed026b154d16493e5deab78f02ec'),
                         self._refs.follow(b'HEAD'))
        self.assertEqual(([b'refs/heads/master'],
                          b'42d06bd4b77fed026b154d16493e5deab78f02ec'),
                         self._refs.follow(b'refs/heads/master'))
        self.assertRaises(KeyError, self._refs.follow, b'refs/heads/loop')

    def test_delitem(self):
        RefsContainerTests.test_delitem(self)
        ref_file = os.path.join(self._refs.path, b'refs', b'heads', b'master')
        self.assertFalse(os.path.exists(ref_file))
        self.assertFalse(b'refs/heads/master' in self._refs.get_packed_refs())

    def test_delitem_symbolic(self):
        self.assertEqual(b'ref: refs/heads/master',
                         self._refs.read_loose_ref(b'HEAD'))
        del self._refs[b'HEAD']
        self.assertRaises(KeyError, lambda: self._refs[b'HEAD'])
        self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                         self._refs[b'refs/heads/master'])
        self.assertFalse(
            os.path.exists(os.path.join(self._refs.path, b'HEAD')))

    def test_remove_if_equals_symref(self):
        # HEAD is a symref, so shouldn't equal its dereferenced value
        self.assertFalse(self._refs.remove_if_equals(
            b'HEAD', b'42d06bd4b77fed026b154d16493e5deab78f02ec'))
        self.assertTrue(self._refs.remove_if_equals(
            b'refs/heads/master', b'42d06bd4b77fed026b154d16493e5deab78f02ec'))
        self.assertRaises(KeyError, lambda: self._refs[b'refs/heads/master'])

        # HEAD is now a broken symref
        self.assertRaises(KeyError, lambda: self._refs[b'HEAD'])
        self.assertEqual(b'ref: refs/heads/master',
                         self._refs.read_loose_ref(b'HEAD'))

        self.assertFalse(os.path.exists(
            os.path.join(self._refs.path, b'refs', b'heads', b'master.lock')))
        self.assertFalse(os.path.exists(
            os.path.join(self._refs.path, b'HEAD.lock')))

    def test_remove_packed_without_peeled(self):
        refs_file = os.path.join(self._repo.path, 'packed-refs')
        f = GitFile(refs_file)
        refs_data = f.read()
        f.close()
        f = GitFile(refs_file, 'wb')
        f.write(b'\n'.join(l for l in refs_data.split(b'\n')
                           if not l or l[0] not in b'#^'))
        f.close()
        self._repo = Repo(self._repo.path)
        refs = self._repo.refs
        self.assertTrue(refs.remove_if_equals(
            b'refs/heads/packed', b'42d06bd4b77fed026b154d16493e5deab78f02ec'))

    def test_remove_if_equals_packed(self):
        # test removing ref that is only packed
        self.assertEqual(b'df6800012397fb85c56e7418dd4eb9405dee075c',
                         self._refs[b'refs/tags/refs-0.1'])
        self.assertTrue(
            self._refs.remove_if_equals(
                b'refs/tags/refs-0.1',
                b'df6800012397fb85c56e7418dd4eb9405dee075c'))
        self.assertRaises(KeyError, lambda: self._refs[b'refs/tags/refs-0.1'])

    def test_remove_parent(self):
        self._refs[b'refs/heads/foo/bar'] = (
            b'df6800012397fb85c56e7418dd4eb9405dee075c'
        )
        del self._refs[b'refs/heads/foo/bar']
        ref_file = os.path.join(
            self._refs.path, b'refs', b'heads', b'foo', b'bar',
        )
        self.assertFalse(os.path.exists(ref_file))
        ref_file = os.path.join(self._refs.path, b'refs', b'heads', b'foo')
        self.assertFalse(os.path.exists(ref_file))
        ref_file = os.path.join(self._refs.path, b'refs', b'heads')
        self.assertTrue(os.path.exists(ref_file))
        self._refs[b'refs/heads/foo'] = (
            b'df6800012397fb85c56e7418dd4eb9405dee075c'
        )

    def test_read_ref(self):
        self.assertEqual(b'ref: refs/heads/master',
                         self._refs.read_ref(b'HEAD'))
        self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                         self._refs.read_ref(b'refs/heads/packed'))
        self.assertEqual(None, self._refs.read_ref(b'nonexistant'))

    def test_read_loose_ref(self):
        self._refs[b'refs/heads/foo'] = (
            b'df6800012397fb85c56e7418dd4eb9405dee075c'
        )

        self.assertEqual(None, self._refs.read_ref(b'refs/heads/foo/bar'))

    def test_non_ascii(self):
        try:
            encoded_ref = u'refs/tags/schön'.encode(
                    sys.getfilesystemencoding())
        except UnicodeEncodeError:
            raise SkipTest(
                    "filesystem encoding doesn't support special character")
        p = os.path.join(
                self._repo.path.encode(sys.getfilesystemencoding()),
                encoded_ref)
        with open(p, 'w') as f:
            f.write('00' * 20)

        expected_refs = dict(_TEST_REFS)
        expected_refs[encoded_ref] = b'00' * 20
        del expected_refs[b'refs/heads/loop']

        self.assertEqual(expected_refs, self._repo.get_refs())

    def test_cyrillic(self):
        if sys.platform == 'win32':
            raise SkipTest(
                    "filesystem encoding doesn't support arbitrary bytes")
        # reported in https://github.com/dulwich/dulwich/issues/608
        name = b'\xcd\xee\xe2\xe0\xff\xe2\xe5\xf2\xea\xe01'
        encoded_ref = b'refs/heads/' + name
        with open(os.path.join(
            self._repo.path.encode(
                sys.getfilesystemencoding()), encoded_ref), 'w') as f:
            f.write('00' * 20)

        expected_refs = set(_TEST_REFS.keys())
        expected_refs.add(encoded_ref)

        self.assertEqual(expected_refs,
                         set(self._repo.refs.allkeys()))
        self.assertEqual({r[len(b'refs/'):] for r in expected_refs
                          if r.startswith(b'refs/')},
                         set(self._repo.refs.subkeys(b'refs/')))
        expected_refs.remove(b'refs/heads/loop')
        expected_refs.add(b'HEAD')
        self.assertEqual(expected_refs,
                         set(self._repo.get_refs().keys()))
Exemplo n.º 22
0
class GitHandler(object):
    mapfile = 'git-mapfile'
    tagsfile = 'git-tags'

    def __init__(self, dest_repo, ui):
        self.repo = dest_repo
        self.ui = ui

        if ui.configbool('git', 'intree'):
            self.gitdir = self.repo.wjoin('.git')
        else:
            self.gitdir = self.repo.join('git')

        self.init_author_file()

        self.paths = ui.configitems('paths')

        self.branch_bookmark_suffix = ui.config('git', 'branch_bookmark_suffix')

        self._map_git_real = {}
        self._map_hg_real = {}
        self.load_tags()

    @property
    def _map_git(self):
      if not self._map_git_real:
        self.load_map()
      return self._map_git_real

    @property
    def _map_hg(self):
      if not self._map_hg_real:
        self.load_map()
      return self._map_hg_real

    # make the git data directory
    def init_if_missing(self):
        if os.path.exists(self.gitdir):
            self.git = Repo(self.gitdir)
        else:
            os.mkdir(self.gitdir)
            self.git = Repo.init_bare(self.gitdir)

    def init_author_file(self):
        self.author_map = {}
        if self.ui.config('git', 'authors'):
            f = open(self.repo.wjoin(
                self.ui.config('git', 'authors')))
            try:
                for line in f:
                    line = line.strip()
                    if not line or line.startswith('#'):
                        continue
                    from_, to = RE_AUTHOR_FILE.split(line, 2)
                    self.author_map[from_] = to
            finally:
                f.close()

    ## FILE LOAD AND SAVE METHODS

    def map_set(self, gitsha, hgsha):
        self._map_git[gitsha] = hgsha
        self._map_hg[hgsha] = gitsha

    def map_hg_get(self, gitsha):
        return self._map_git.get(gitsha)

    def map_git_get(self, hgsha):
        return self._map_hg.get(hgsha)

    def load_map(self):
        if os.path.exists(self.repo.join(self.mapfile)):
            for line in self.repo.opener(self.mapfile):
                gitsha, hgsha = line.strip().split(' ', 1)
                self._map_git_real[gitsha] = hgsha
                self._map_hg_real[hgsha] = gitsha

    def save_map(self):
        file = self.repo.opener(self.mapfile, 'w+', atomictemp=True)
        for hgsha, gitsha in sorted(self._map_hg.iteritems()):
            file.write("%s %s\n" % (gitsha, hgsha))
        # If this complains that NoneType is not callable, then
        # atomictempfile no longer has either of rename (pre-1.9) or
        # close (post-1.9)
        getattr(file, 'rename', getattr(file, 'close', None))()

    def load_tags(self):
        self.tags = {}
        if os.path.exists(self.repo.join(self.tagsfile)):
            for line in self.repo.opener(self.tagsfile):
                sha, name = line.strip().split(' ', 1)
                self.tags[name] = sha

    def save_tags(self):
        file = self.repo.opener(self.tagsfile, 'w+', atomictemp=True)
        for name, sha in sorted(self.tags.iteritems()):
            if not self.repo.tagtype(name) == 'global':
                file.write("%s %s\n" % (sha, name))
        # If this complains that NoneType is not callable, then
        # atomictempfile no longer has either of rename (pre-1.9) or
        # close (post-1.9)
        getattr(file, 'rename', getattr(file, 'close', None))()

    ## END FILE LOAD AND SAVE METHODS

    ## COMMANDS METHODS

    def import_commits(self, remote_name):
        self.import_git_objects(remote_name)
        self.update_hg_bookmarks(self.git.get_refs())
        self.save_map()

    def fetch(self, remote, heads):
        self.export_commits()
        refs = self.fetch_pack(remote, heads)
        remote_name = self.remote_name(remote)

        oldrefs = self.git.get_refs()
        if refs:
            self.import_git_objects(remote_name, refs)
            self.import_tags(refs)
            self.update_hg_bookmarks(refs)
            if remote_name:
                self.update_remote_branches(remote_name, refs)
            elif not self.paths:
                # intial cloning
                self.update_remote_branches('default', refs)

                # "Activate" a tipmost bookmark.
                bms = getattr(self.repo['tip'], 'bookmarks',
                              lambda : None)()
                if bms:
                    bookmarks.setcurrent(self.repo, bms[0])

        def remoteref(ref):
            rn = remote_name or 'default'
            return 'refs/remotes/' + rn + ref[10:]

        modheads = [refs[k] for k in refs if k.startswith('refs/heads/')
                    and not k.endswith('^{}')
                    and refs[k] != oldrefs.get(remoteref(k))]

        if not modheads:
            self.ui.status(_("no changes found\n"))

        self.save_map()

        return len(modheads)

    def export_commits(self):
        try:
            self.export_git_objects()
            self.export_hg_tags()
            self.update_references()
        finally:
            self.save_map()

    def get_refs(self, remote):
        self.export_commits()
        client, path = self.get_transport_and_path(remote)
        old_refs = {}
        new_refs = {}
        def changed(refs):
            old_refs.update(refs)
            to_push = set(self.local_heads().values() + self.tags.values())
            new_refs.update(self.get_changed_refs(refs, to_push, True))
            return refs # always return the same refs to make the send a no-op

        try:
            client.send_pack(path, changed, lambda have, want: [])

            changed_refs = [ref for ref, sha in new_refs.iteritems()
                            if sha != old_refs.get(ref)]
            new = [bin(self.map_hg_get(new_refs[ref])) for ref in changed_refs]
            old = {}
            for r in old_refs:
                old_ref = self.map_hg_get(old_refs[r])
                if old_ref:
                    old[bin(old_ref)] = 1

            return old, new
        except (HangupException, GitProtocolError), e:
            raise hgutil.Abort(_("git remote error: ") + str(e))
Exemplo n.º 23
0
class Gittle(object):
    """All paths used in Gittle external methods must be paths relative to the git repository
    """

    DEFAULT_COMMIT = "HEAD"
    DEFAULT_BRANCH = "master"
    DEFAULT_REMOTE = "origin"
    DEFAULT_MESSAGE = "**No Message**"
    DEFAULT_USER_INFO = {"name": None, "email": None}

    DIFF_FUNCTIONS = {
        "classic": utils.git.classic_tree_diff,
        "dict": utils.git.dict_tree_diff,
        "changes": utils.git.dict_tree_diff,
    }
    DEFAULT_DIFF_TYPE = "dict"

    HIDDEN_REGEXES = [
        # Hide git directory
        r".*\/\.git\/.*"
    ]

    # References
    REFS_BRANCHES = "refs/heads/"
    REFS_REMOTES = "refs/remotes/"
    REFS_TAGS = "refs/tags/"

    # Name pattern truths
    # Used for detecting if files are :
    # - deleted
    # - added
    # - changed
    PATTERN_ADDED = (False, True)
    PATTERN_REMOVED = (True, False)
    PATTERN_MODIFIED = (True, True)

    # Permissions
    MODE_DIRECTORY = 040000  # Used to tell if a tree entry is a directory

    # Tree depth
    MAX_TREE_DEPTH = 1000

    # Acceptable Root paths
    ROOT_PATHS = (os.path.curdir, os.path.sep)

    def __init__(self, repo_or_path, origin_uri=None, auth=None, report_activity=None, *args, **kwargs):
        if isinstance(repo_or_path, DulwichRepo):
            self.repo = repo_or_path
        elif isinstance(repo_or_path, Gittle):
            self.repo = DulwichRepo(repo_or_path.path)
        elif isinstance(repo_or_path, basestring):
            path = os.path.abspath(repo_or_path)
            self.repo = DulwichRepo(path)
        else:
            logging.warning("Repo is of type %s" % type(repo_or_path))
            raise Exception("Gittle must be initialized with either a dulwich repository or a string to the path")

        # Set path
        self.path = self.repo.path

        # The remote url
        self.origin_uri = origin_uri

        # Report client activty
        self._report_activity = report_activity

        # Build ignore filter
        self.hidden_regexes = copy.copy(self.HIDDEN_REGEXES)
        self.hidden_regexes.extend(self._get_ignore_regexes())
        self.ignore_filter = utils.paths.path_filter_regex(self.hidden_regexes)
        self.filters = [self.ignore_filter]

        # Get authenticator
        if auth:
            self.authenticator = auth
        else:
            self.auth(*args, **kwargs)

    def report_activity(self, *args, **kwargs):
        if not self._report_activity:
            return
        return self._report_activity(*args, **kwargs)

    def _format_author(self, name, email):
        return "%s <%s>" % (name, email)

    def _format_userinfo(self, userinfo):
        name = userinfo.get("name")
        email = userinfo.get("email")
        if name and email:
            return self._format_author(name, email)
        return None

    def _format_ref(self, base, extra):
        return "".join([base, extra])

    def _format_ref_branch(self, branch_name):
        return self._format_ref(self.REFS_BRANCHES, branch_name)

    def _format_ref_remote(self, remote_name):
        return self._format_ref(self.REFS_REMOTES, remote_name)

    def _format_ref_tag(self, tag_name):
        return self._format_ref(self.REFS_TAGS, tag_name)

    @property
    def head(self):
        """Return SHA of the current HEAD
        """
        return self.repo.head()

    @property
    def is_bare(self):
        """Bare repositories have no working directories or indexes
        """
        return self.repo.bare

    @property
    def is_working(self):
        return not (self.is_bare)

    def has_index(self):
        """Opposite of is_bare
        """
        return self.repo.has_index()

    @property
    def has_commits(self):
        """
        If the repository has no HEAD we consider that is has no commits
        """
        try:
            self.repo.head()
        except KeyError:
            return False
        return True

    def ref_walker(self, ref=None):
        """
        Very simple, basic walker
        """
        ref = ref or "HEAD"
        sha = self._commit_sha(ref)
        return self.repo.revision_history(sha)

    def branch_walker(self, branch):
        branch = branch or self.DEFAULT_BRANCH
        ref = self._format_ref_branch(branch)
        return self.ref_walker(ref)

    def commit_info(self, start=0, end=None, branch=None):
        """Return a generator of commits with all their attached information
        """
        if not self.has_commits:
            return []
        commits = [utils.git.commit_info(entry) for entry in self.branch_walker(branch)]
        if not end:
            return commits
        return commits[start:end]

    @funky.uniquify
    def recent_contributors(self, n=None, branch=None):
        n = n or 10
        return funky.pluck(self.commit_info(end=n, branch=branch), "author")

    @property
    def commit_count(self):
        try:
            return len(self.ref_walker())
        except KeyError:
            return 0

    def commits(self):
        """Return a list of SHAs for all the concerned commits
        """
        return [commit["sha"] for commit in self.commit_info()]

    @property
    def git_dir(self):
        return self.repo.controldir()

    def auth(self, *args, **kwargs):
        self.authenticator = GittleAuth(*args, **kwargs)
        return self.authenticator

    # Generate a branch selector (used for pushing)
    def _wants_branch(self, branch_name=None):
        branch_name = branch_name or self.DEFAULT_BRANCH
        refs_key = self._format_ref_branch(branch_name)
        sha = self.branches[branch_name]

        def wants_func(old):
            refs_key = self._format_ref_branch(branch_name)
            return {refs_key: sha}

        return wants_func

    def _get_ignore_regexes(self):
        gitignore_filename = os.path.join(self.path, ".gitignore")
        if not os.path.exists(gitignore_filename):
            return []
        lines = open(gitignore_filename).readlines()
        globers = map(lambda line: line.rstrip(), lines)
        return utils.paths.globers_to_regex(globers)

    # Get the absolute path for a file in the git repo
    def abspath(self, repo_file):
        return os.path.abspath(os.path.join(self.path, repo_file))

    # Get the relative path from the absolute path
    def relpath(self, abspath):
        return os.path.relpath(abspath, self.path)

    @property
    def last_commit(self):
        return self[self.repo.head()]

    @property
    def index(self):
        return self.repo.open_index()

    @classmethod
    def init(cls, path, bare=None, *args, **kwargs):
        """Initialize a repository"""
        mkdir_safe(path)

        # Constructor to use
        if bare:
            constructor = DulwichRepo.init_bare
        else:
            constructor = DulwichRepo.init

        # Create dulwich repo
        repo = constructor(path)

        # Create Gittle repo
        return cls(repo, *args, **kwargs)

    @classmethod
    def init_bare(cls, *args, **kwargs):
        kwargs.setdefault("bare", True)
        return cls.init(*args, **kwargs)

    def get_client(self, origin_uri=None, **kwargs):
        # Get the remote URL
        origin_uri = origin_uri or self.origin_uri

        # Fail if inexistant
        if not origin_uri:
            raise InvalidRemoteUrl()

        client_kwargs = {}
        auth_kwargs = self.authenticator.kwargs()

        client_kwargs.update(auth_kwargs)
        client_kwargs.update(kwargs)
        client_kwargs.update({"report_activity": self.report_activity})

        client, remote_path = get_transport_and_path(origin_uri, **client_kwargs)
        return client, remote_path

    def push_to(self, origin_uri, branch_name=None, progress=None, progress_stderr=None):
        selector = self._wants_branch(branch_name=branch_name)
        client, remote_path = self.get_client(origin_uri, progress_stderr=progress_stderr)
        return client.send_pack(remote_path, selector, self.repo.object_store.generate_pack_contents, progress=progress)

    # Like: git push
    def push(self, origin_uri=None, branch_name=None, progress=None, progress_stderr=None):
        return self.push_to(origin_uri, branch_name, progress, progress_stderr)

    # Not recommended at ALL ... !!!
    def dirty_pull_from(self, origin_uri, branch_name=None):
        # Remove all previously existing data
        rmtree(self.path)
        mkdir_safe(self.path)
        self.repo = DulwichRepo.init(self.path)

        # Fetch brand new copy from remote
        return self.pull_from(origin_uri, branch_name)

    def pull_from(self, origin_uri, branch_name=None):
        return self.fetch(origin_uri)

    # Like: git pull
    def pull(self, origin_uri=None, branch_name=None):
        return self.pull_from(origin_uri, branch_name)

    def fetch_remote(self, origin_uri=None):
        # Get client
        client, remote_path = self.get_client(origin_uri=origin_uri)

        # Fetch data from remote repository
        remote_refs = client.fetch(remote_path, self.repo)

        return remote_refs

    def _setup_fetched_refs(self, refs, origin, bare):
        remote_tags = utils.git.subrefs(refs, "refs/tags")
        remote_heads = utils.git.subrefs(refs, "refs/heads")

        # Filter refs
        clean_remote_tags = utils.git.clean_refs(remote_tags)
        clean_remote_heads = utils.git.clean_refs(remote_heads)

        # Base of new refs
        heads_base = "refs/remotes/" + origin
        if bare:
            heads_base = "refs/heads"

        # Import branches
        self.import_refs(heads_base, clean_remote_heads)

        # Import tags
        self.import_refs("refs/tags", clean_remote_tags)

        # Update HEAD
        self["HEAD"] = refs["HEAD"]

    def fetch(self, origin_uri=None, bare=None, origin=None):
        bare = bare or False
        origin = origin or self.DEFAULT_REMOTE

        # Remote refs
        remote_refs = self.fetch_remote(origin_uri)

        # Update head
        # Hit repo because head doesn't yet exist so
        # print("REFS = %s" % remote_refs)

        # Update refs (branches, tags, HEAD)
        self._setup_fetched_refs(remote_refs, origin, bare)

        # Checkout working directories
        if not bare:
            self.checkout_all()
        else:
            self.update_server_info()

    @classmethod
    def clone(cls, origin_uri, local_path, auth=None, mkdir=True, bare=False, *args, **kwargs):
        """Clone a remote repository"""
        mkdir_safe(local_path)

        # Initialize the local repository
        if bare:
            local_repo = cls.init_bare(local_path)
        else:
            local_repo = cls.init(local_path)

        repo = cls(local_repo, origin_uri=origin_uri, auth=auth, *args, **kwargs)

        repo.fetch(bare=bare)

        # Add origin
        # TODO

        return repo

    @classmethod
    def clone_bare(cls, *args, **kwargs):
        """Same as .clone except clones to a bare repository by default
        """
        kwargs.setdefault("bare", True)
        return cls.clone(*args, **kwargs)

    def _commit(self, committer=None, author=None, message=None, files=None, tree=None, *args, **kwargs):

        if not tree:
            # If no tree then stage files
            modified_files = files or self.modified_files
            logging.warning("STAGING : %s" % modified_files)
            self.add(modified_files)

        # Messages
        message = message or self.DEFAULT_MESSAGE
        author_msg = self._format_userinfo(author)
        committer_msg = self._format_userinfo(committer)

        return self.repo.do_commit(
            message=message, author=author_msg, committer=committer_msg, encoding="UTF-8", tree=tree, *args, **kwargs
        )

    def _tree_from_structure(self, structure):
        # TODO : Support directories
        tree = Tree()

        for file_info in structure:

            # str only
            try:
                data = file_info["data"].encode("ascii")
                name = file_info["name"].encode("ascii")
                mode = file_info["mode"]
            except:
                # Skip file on encoding errors
                continue

            blob = Blob()

            blob.data = data

            # Store file's contents
            self.repo.object_store.add_object(blob)

            # Add blob entry
            tree.add(name, mode, blob.id)

        # Store tree
        self.repo.object_store.add_object(tree)

        return tree.id

    # Like: git commmit -a
    def commit(self, name=None, email=None, message=None, files=None, *args, **kwargs):
        user_info = {"name": name, "email": email}
        return self._commit(committer=user_info, author=user_info, message=message, files=files, *args, **kwargs)

    def commit_structure(self, name=None, email=None, message=None, structure=None, *args, **kwargs):
        """Main use is to do commits directly to bare repositories
        For example doing a first Initial Commit so the repo can be cloned and worked on right away
        """
        if not structure:
            return
        tree = self._tree_from_structure(structure)

        user_info = {"name": name, "email": email}

        return self._commit(committer=user_info, author=user_info, message=message, tree=tree, *args, **kwargs)

    # Push all local commits
    # and pull all remote commits
    def sync(self, origin_uri=None):
        self.push(origin_uri)
        return self.pull(origin_uri)

    def lookup_entry(self, relpath, trackable_files=set()):
        if not relpath in trackable_files:
            raise KeyError

        abspath = self.abspath(relpath)

        with open(abspath, "rb") as git_file:
            data = git_file.read()
            s = sha1()
            s.update("blob %u\0" % len(data))
            s.update(data)
        return (s.hexdigest(), os.stat(abspath).st_mode)

    @property
    @funky.transform(set)
    def tracked_files(self):
        return list(self.index)

    @property
    @funky.transform(set)
    def raw_files(self):
        return utils.paths.subpaths(self.path)

    @property
    @funky.transform(set)
    def ignored_files(self):
        return utils.paths.subpaths(self.path, filters=self.filters)

    @property
    @funky.transform(set)
    def trackable_files(self):
        return self.raw_files - self.ignored_files

    @property
    @funky.transform(set)
    def untracked_files(self):
        return self.trackable_files - self.tracked_files

    """
    @property
    @funky.transform(set)
    def modified_staged_files(self):
        "Checks if the file has changed since last commit"
        timestamp = self.last_commit.commit_time
        index = self.index
        return [
            f
            for f in self.tracked_files
            if index[f][1][0] > timestamp
        ]
    """

    # Return a list of tuples
    # representing the changed elements in the git tree
    def _changed_entries(self, ref=None):
        ref = ref or self.DEFAULT_COMMIT
        if not self.has_commits:
            return []
        obj_sto = self.repo.object_store
        tree_id = self[ref].tree
        names = self.trackable_files

        lookup_func = partial(self.lookup_entry, trackable_files=names)

        # Format = [((old_name, new_name), (old_mode, new_mode), (old_sha, new_sha)), ...]
        tree_diff = changes_from_tree(names, lookup_func, obj_sto, tree_id, want_unchanged=False)
        return list(tree_diff)

    @funky.transform(set)
    def _changed_entries_by_pattern(self, pattern):
        changed_entries = self._changed_entries()
        filtered_paths = [
            funky.first_true(names)
            for names, modes, sha in changed_entries
            if tuple(map(bool, names)) == pattern and funky.first_true(names)
        ]

        return filtered_paths

    @property
    @funky.transform(set)
    def removed_files(self):
        return self._changed_entries_by_pattern(self.PATTERN_REMOVED) - self.ignored_files

    @property
    @funky.transform(set)
    def added_files(self):
        return self._changed_entries_by_pattern(self.PATTERN_ADDED) - self.ignored_files

    @property
    @funky.transform(set)
    def modified_files(self):
        modified_files = self._changed_entries_by_pattern(self.PATTERN_MODIFIED) - self.ignored_files
        return modified_files

    @property
    @funky.transform(set)
    def modified_unstaged_files(self):
        timestamp = self.last_commit.commit_time
        return [f for f in self.tracked_files if os.stat(self.abspath(f)).st_mtime > timestamp]

    @property
    def pending_files(self):
        """
        Returns a list of all files that could be possibly staged
        """
        # Union of both
        return self.modified_files | self.added_files | self.removed_files

    @property
    def pending_files_by_state(self):
        files = {"modified": self.modified_files, "added": self.added_files, "removed": self.removed_files}

        # "Flip" the dictionary
        return {path: state for state, paths in files.items() for path in paths}

    """
    @property
    @funky.transform(set)
    def modified_files(self):
        return self.modified_staged_files | self.modified_unstaged_files
    """

    # Like: git add
    @funky.arglist_method
    def stage(self, files):
        return self.repo.stage(files)

    def add(self, *args, **kwargs):
        return self.stage(*args, **kwargs)

    # Like: git rm
    @funky.arglist_method
    def rm(self, files, force=False):
        index = self.index
        index_files = filter(lambda f: f in index, files)
        for f in index_files:
            del self.index[f]
        return index.write()

    def mv_fs(self, file_pair):
        old_name, new_name = file_pair
        os.rename(old_name, new_name)

    # Like: git mv
    @funky.arglist_method
    def mv(self, files_pair):
        index = self.index
        files_in_index = filter(lambda f: f[0] in index, files_pair)
        map(self.mv_fs, files_in_index)
        old_files = map(funky.first, files_in_index)
        new_files = map(funky.last, files_in_index)
        self.add(new_files)
        self.rm(old_files)
        self.add(old_files)
        return

    @working_only
    def _checkout_tree(self, tree):
        return build_index_from_tree(self.repo.path, self.repo.index_path(), self.repo.object_store, tree)

    def checkout_all(self, commit_sha=None):
        commit_sha = commit_sha or self.head
        commit_tree = self._commit_tree(commit_sha)
        # Rebuild index from the current tree
        return self._checkout_tree(commit_tree)

    def checkout(self, commit_sha=None, files=None):
        """Checkout only a select amount of files
        """
        commit_sha = commit_sha or self.head
        files = files or []

        return self

    @funky.arglist_method
    def reset(self, files, commit="HEAD"):
        pass

    def rm_all(self):
        self.index.clear()
        return self.index.write()

    def _to_commit(self, commit_obj):
        """Allows methods to accept both SHA's or dulwich Commit objects as arguments
        """
        if isinstance(commit_obj, basestring):
            return self.repo[commit_obj]
        return commit_obj

    def _commit_sha(self, commit_obj):
        """Extracts a Dulwich commits SHA
        """
        if utils.git.is_sha(commit_obj):
            return commit_obj
        elif isinstance(commit_obj, basestring):
            # Can't use self[commit_obj] to avoid infinite recursion
            commit_obj = self.repo[commit_obj]
        return commit_obj.id

    def _blob_data(self, sha):
        """Return a blobs content for a given SHA
        """
        return self[sha].data

    # Get the nth parent back for a given commit
    def get_parent_commit(self, commit, n=None):
        """ Recursively gets the nth parent for a given commit
            Warning: Remember that parents aren't the previous commits
        """
        if n is None:
            n = 1
        commit = self._to_commit(commit)
        parents = commit.parents

        if n <= 0 or not parents:
            # Return a SHA
            return self._commit_sha(commit)

        parent_sha = parents[0]
        parent = self[parent_sha]

        # Recur
        return self.get_parent_commit(parent, n - 1)

    def get_previous_commit(self, commit_ref, n=None):
        commit_sha = self._parse_reference(commit_ref)
        n = n or 1
        commits = self.commits()
        return funky.next(commits, commit_sha, n=n, default=commit_sha)

    def _parse_reference(self, ref_string):
        # COMMIT_REF~x
        if "~" in ref_string:
            ref, count = ref_string.split("~")
            count = int(count)
            commit_sha = self._commit_sha(ref)
            return self.get_previous_commit(commit_sha, count)
        return self._commit_sha(ref_string)

    def _commit_tree(self, commit_sha):
        """Return the tree object for a given commit
        """
        return self[commit_sha].tree

    def diff(self, commit_sha, compare_to=None, diff_type=None, filter_binary=True):
        diff_type = diff_type or self.DEFAULT_DIFF_TYPE
        diff_func = self.DIFF_FUNCTIONS[diff_type]

        if not compare_to:
            compare_to = self.get_previous_commit(commit_sha)

        return self._diff_between(compare_to, commit_sha, diff_function=diff_func)

    def diff_working(self, ref=None, filter_binary=True):
        """Diff between the current working directory and the HEAD
        """
        return utils.git.diff_changes_paths(
            self.repo.object_store, self.path, self._changed_entries(ref=ref), filter_binary=filter_binary
        )

    def get_commit_files(self, commit_sha, parent_path=None, is_tree=None, paths=None):
        """Returns a dict of the following Format :
            {
                "directory/filename.txt": {
                    'name': 'filename.txt',
                    'path': "directory/filename.txt",
                    "sha": "xxxxxxxxxxxxxxxxxxxx",
                    "data": "blablabla",
                    "mode": 0xxxxx",
                },
                ...
            }
        """
        # Default values
        context = {}
        is_tree = is_tree or False
        parent_path = parent_path or ""

        if is_tree:
            tree = self[commit_sha]
        else:
            tree = self[self._commit_tree(commit_sha)]

        for mode, path, sha in tree.entries():
            # Check if entry is a directory
            if mode == self.MODE_DIRECTORY:
                context.update(
                    self.get_commit_files(sha, parent_path=os.path.join(parent_path, path), is_tree=True, paths=paths)
                )
                continue

            subpath = os.path.join(parent_path, path)

            # Only add the files we want
            if not (paths is None or subpath in paths):
                continue

            # Add file entry
            context[subpath] = {"name": path, "path": subpath, "mode": mode, "sha": sha, "data": self._blob_data(sha)}
        return context

    def file_versions(self, path):
        """Returns all commits where given file was modified
        """
        versions = []
        commits_info = self.commit_info()
        seen_shas = set()

        for commit in commits_info:
            try:
                files = self.get_commit_files(commit["sha"], paths=[path])
                file_path, file_data = files.items()[0]
            except IndexError:
                continue

            file_sha = file_data["sha"]

            if file_sha in seen_shas:
                continue
            else:
                seen_shas.add(file_sha)

            # Add file info
            commit["file"] = file_data
            versions.append(file_data)
        return versions

    def _diff_between(self, old_commit_sha, new_commit_sha, diff_function=None, filter_binary=True):
        """Internal method for getting a diff between two commits
            Please use .diff method unless you have very speciic needs
        """

        # If commit is first commit (new_commit_sha == old_commit_sha)
        # then compare to an empty tree
        if new_commit_sha == old_commit_sha:
            old_tree = Tree()
        else:
            old_tree = self._commit_tree(old_commit_sha)

        new_tree = self._commit_tree(new_commit_sha)

        return diff_function(self.repo.object_store, old_tree, new_tree, filter_binary=filter_binary)

    def changes(self, *args, **kwargs):
        """ List of changes between two SHAs
            Returns a list of lists of tuples :
            [
                [
                    (oldpath, newpath), (oldmode, newmode), (oldsha, newsha)
                ],
                ...
            ]
        """
        kwargs["diff_type"] = "changes"
        return self.diff(*args, **kwargs)

    def changes_count(self, *args, **kwargs):
        return len(self.changes(*args, **kwargs))

    def _refs_by_pattern(self, pattern):
        refs = self.refs

        def item_filter(key_value):
            """Filter only concered refs"""
            key, value = key_value
            return key.startswith(pattern)

        def item_map(key_value):
            """Rewrite keys"""
            key, value = key_value
            new_key = key[len(pattern) :]
            return (new_key, value)

        return dict(map(item_map, filter(item_filter, refs.items())))

    @property
    def refs(self):
        return self.repo.get_refs()

    def set_refs(refs_dict):
        for k, v in refs_dict.items():
            self.repo[k] = v

    def import_refs(self, base, other):
        return self.repo.refs.import_refs(base, other)

    @property
    def branches(self):
        return self._refs_by_pattern(self.REFS_BRANCHES)

    def _active_branch(self, refs=None, head=None):
        head = head or self.head
        refs = refs or self.branches
        try:
            return {branch: branch_head for branch, branch_head in refs.items() if branch_head == head}.items()[0]
        except IndexError:
            pass
        return (None, None)

    @property
    def active_branch(self):
        return self._active_branch()[0]

    @property
    def active_sha(self):
        return self._active_branch()[1]

    @property
    def remote_branches(self):
        return self._refs_by_pattern(self.REFS_REMOTES)

    @property
    def tags(self):
        return self._refs_by_pattern(self.REFS_TAGS)

    @property
    def remotes(self):
        """ Dict of remotes
        {
            'origin': 'http://friendco.de/some_user/repo.git',
            ...
        }
        """
        config = self.repo.get_config()
        return {keys[1]: values["url"] for keys, values in config.items() if keys[0] == "remote"}

    def add_ref(self, new_ref, old_ref):
        self.repo.refs[new_ref] = self.repo.refs[old_ref]
        self.update_server_info()

    def remove_ref(self, ref_name):
        # Returns False if ref doesn't exist
        if not ref_name in self.repo.refs:
            return False
        del self.repo.refs[ref_name]
        self.update_server_info()
        return True

    def create_branch(self, base_branch, new_branch, tracking=None):
        """Try creating a new branch which tracks the given remote
            if such a branch does not exist then branch off a local branch
        """

        # The remote to track
        tracking = self.DEFAULT_REMOTE

        # Already exists
        if new_branch in self.branches:
            raise Exception("branch %s already exists" % new_branch)

        # Get information about remote_branch
        remote_branch = os.path.sep.join([tracking, base_branch])

        # Fork Local
        if base_branch in self.branches:
            base_ref = self._format_ref_branch(base_branch)
        # Fork remote
        elif remote_branch in self.remote_branches:
            base_ref = self._format_ref_remote(remote_branch)
            # TODO : track
        else:
            raise Exception(
                "Can not find the branch named '%s' to fork either locally or in '%s'" % (base_branch, tracking)
            )

        # Reference of new branch
        new_ref = self._format_ref_branch(new_branch)

        # Copy reference to create branch
        self.add_ref(new_ref, base_ref)

        return new_ref

    def remove_branch(self, branch_name):
        ref = self._format_ref_branch(branch_name)
        return self.remove_ref(ref)

    def switch_branch(self, branch_name, tracking=None, create=None):
        """Changes the current branch
        """
        if create is None:
            create = True

        # Check if branch exists
        if not branch_name in self.branches:
            self.create_branch(branch_name, branch_name, tracking=tracking)

        # Get branch reference
        branch_ref = self._format_ref_branch(branch_name)

        # Change main branch
        self.repo.refs.set_symbolic_ref("HEAD", branch_ref)

        if self.is_working:
            # Remove all files
            self.clean_working()

            # Add files for the current branch
            self.checkout_all()

    def clean(self, force=None, directories=None):
        untracked_files = self.untracked_files
        map(os.remove, untracked_files)
        return untracked_files

    def clean_working(self):
        """Purges all the working (removes everything except .git)
            used by checkout_all to get clean branch switching
        """
        return self.clean()

    def _get_fs_structure(self, tree_sha, depth=None, parent_sha=None):
        tree = self[tree_sha]
        structure = {}
        if depth is None:
            depth = self.MAX_TREE_DEPTH
        elif depth == 0:
            return structure
        for mode, path, sha in tree.entries():
            # tree
            if mode == self.MODE_DIRECTORY:
                # Recur
                structure[path] = self._get_fs_structure(sha, depth=depth - 1, parent_sha=tree_sha)
            # commit
            else:
                structure[path] = sha
        structure["."] = tree_sha
        structure[".."] = parent_sha or tree_sha
        return structure

    def _get_fs_structure_by_path(self, tree_sha, path):
        parts = path.split(os.path.sep)
        depth = len(parts) + 1
        structure = self._get_fs_structure(tree_sha, depth=depth)

        return funky.subkey(structure, parts)

    def commit_ls(self, ref, subpath=None):
        """List a "directory" for a given commit
            using the tree of thqt commit
        """
        tree_sha = self._commit_tree(ref)

        # Root path
        if subpath in self.ROOT_PATHS or not subpath:
            return self._get_fs_structure(tree_sha, depth=1)
        # Any other path
        return self._get_fs_structure_by_path(tree_sha, subpath)

    def commit_file(self, ref, path):
        """Return info on a given file for a given commit
        """
        name, info = self.get_commit_files(ref, paths=[path]).items()[0]
        return info

    def commit_tree(self, ref, *args, **kwargs):
        tree_sha = self._commit_tree(ref)
        return self._get_fs_structure(tree_sha, *args, **kwargs)

    def update_server_info(self):
        if not self.is_bare:
            return
        update_server_info(self.repo)

    def _is_fast_forward(self):
        pass

    def _merge_fast_forward(self):
        pass

    def __hash__(self):
        """This is required otherwise the memoize function will just mess it up
        """
        return hash(self.path)

    def __getitem__(self, key):
        sha = self._parse_reference(key)
        return self.repo[sha]

    def __setitem__(self, key, value):
        self.repo[key] = value

    # Alias to clone_bare
    fork = clone_bare
    log = commit_info
    diff_count = changes_count
    comtributors = recent_contributors
Exemplo n.º 24
0
class Gits3(object):
    def __init__(self, path):
        self.path = path
        self.open_repo(path)

    def open_repo(self, path):
        self.repo = Repo(path)

    def get_id(self, ref):
        return self.repo.get_refs()[ref]

    def get_updates(self, local_ref, tracking_ref):

        refs = self.repo.get_refs()
        for key, value in refs.iteritems():
            print key, value

        local = refs[local_ref]
        try:
            remote = refs[tracking_ref]
        except KeyError:
            remote = None

        if local == remote:
            return None

        local_object = self.repo.get_object(local)

        commits = self.get_commits(local_object, [remote])
        objects = self.get_objects(commits)
        print objects

        if remote:
            remote_object = self.repo.get_object(remote)
            filtered_objects = self.filter_objects(objects, remote_object)
        else:
            filtered_objects = objects

        filtered_objects = set(filtered_objects)
        return filtered_objects

    def filter_objects(self, objects, old_commit):
        filtered = []
        old_treeId = old_commit.tree
        old_objects = self.get_objects_in_tree(old_treeId)
        for object in objects:
            if object not in old_objects:
                filtered.append(object)

        return filtered

    def get_commits(self, interesting, uninteresting):
        commits = [interesting]
        remaining = interesting.parents

        while remaining:
            pId = remaining.pop(0)
            if pId in uninteresting:
                continue
            else:
                parent = self.repo.get_object(pId)
                commits.append(parent)
                parents = parent.parents
                remaining.extend(parents)
        return commits

    def get_objects(self, commits):
        objects = []
        while commits:
            commit = commits.pop(0)
            objects.append(commit)
            objects.extend(self.get_objects_in_tree(commit.tree))
        return objects

    def get_objects_in_tree(self, treeId):
        objects = []
        tree = self.repo.get_object(treeId)
        objects.append(tree)
        for entryId in tree.items():
            # get the entry's sha
            objectId = entryId[2]
            object = self.repo.get_object(objectId)
            if isinstance(object, Tree):
                objects.extend(self.get_objects_in_tree(objectId))
            else:
                objects.append(object)
        return objects

    def generate_pack_name(self, objects):
        m = hashlib.sha1()
        for object in objects:
            sha1 = object.sha().hexdigest()
            #            print sha1
            m.update(sha1)
        file_name = m.hexdigest()
        #        print 'File Name is ', file_name
        return file_name

    def write_pack(self, pack_name, objects):
        write_pack('pack-' + pack_name, [(x, "") for x in objects],
                   len(objects))

    def find_tracking_ref_names(self, fetch, refs):
        if fetch[0] == '+':
            fetch = fetch[1:]
        tmp = fetch.split(':')
        src = tmp[0]
        dst = tmp[1]

        # TODO double check that both src and dst have wild cards, or both don't

        # match the source with refs
        if src.endswith('*') and refs.startswith(src[:-1]):
            return self.expand_from_src(src, dst, refs)
        else:
            return dst

    def expand_from_src(self, src, dst, refs):
        return dst[:-1] + refs[len(src) - 1:]
Exemplo n.º 25
0
class DiskRefsContainerTests(RefsContainerTests, TestCase):
    def setUp(self):
        TestCase.setUp(self)
        self._repo = open_repo('refs.git')
        self.addCleanup(tear_down_repo, self._repo)
        self._refs = self._repo.refs

    def test_get_packed_refs(self):
        self.assertEqual(
            {
                b'refs/heads/packed':
                b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                b'refs/tags/refs-0.1':
                b'df6800012397fb85c56e7418dd4eb9405dee075c',
            }, self._refs.get_packed_refs())

    def test_get_peeled_not_packed(self):
        # not packed
        self.assertEqual(None, self._refs.get_peeled(b'refs/tags/refs-0.2'))
        self.assertEqual(b'3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8',
                         self._refs[b'refs/tags/refs-0.2'])

        # packed, known not peelable
        self.assertEqual(self._refs[b'refs/heads/packed'],
                         self._refs.get_peeled(b'refs/heads/packed'))

        # packed, peeled
        self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                         self._refs.get_peeled(b'refs/tags/refs-0.1'))

    def test_setitem(self):
        RefsContainerTests.test_setitem(self)
        f = open(os.path.join(self._refs.path, 'refs', 'some', 'ref'), 'rb')
        self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                         f.read()[:40])
        f.close()

    def test_setitem_symbolic(self):
        ones = b'1' * 40
        self._refs[b'HEAD'] = ones
        self.assertEqual(ones, self._refs[b'HEAD'])

        # ensure HEAD was not modified
        f = open(os.path.join(self._refs.path, 'HEAD'), 'rb')
        v = next(iter(f)).rstrip(b'\n\r')
        f.close()
        self.assertEqual(b'ref: refs/heads/master', v)

        # ensure the symbolic link was written through
        f = open(os.path.join(self._refs.path, 'refs', 'heads', 'master'),
                 'rb')
        self.assertEqual(ones, f.read()[:40])
        f.close()

    def test_set_if_equals(self):
        RefsContainerTests.test_set_if_equals(self)

        # ensure symref was followed
        self.assertEqual(b'9' * 40, self._refs[b'refs/heads/master'])

        # ensure lockfile was deleted
        self.assertFalse(
            os.path.exists(
                os.path.join(self._refs.path, 'refs', 'heads', 'master.lock')))
        self.assertFalse(
            os.path.exists(os.path.join(self._refs.path, 'HEAD.lock')))

    def test_add_if_new_packed(self):
        # don't overwrite packed ref
        self.assertFalse(
            self._refs.add_if_new(b'refs/tags/refs-0.1', b'9' * 40))
        self.assertEqual(b'df6800012397fb85c56e7418dd4eb9405dee075c',
                         self._refs[b'refs/tags/refs-0.1'])

    def test_add_if_new_symbolic(self):
        # Use an empty repo instead of the default.
        repo_dir = os.path.join(tempfile.mkdtemp(), 'test')
        os.makedirs(repo_dir)
        repo = Repo.init(repo_dir)
        self.addCleanup(tear_down_repo, repo)
        refs = repo.refs

        nines = b'9' * 40
        self.assertEqual(b'ref: refs/heads/master', refs.read_ref(b'HEAD'))
        self.assertFalse(b'refs/heads/master' in refs)
        self.assertTrue(refs.add_if_new(b'HEAD', nines))
        self.assertEqual(b'ref: refs/heads/master', refs.read_ref(b'HEAD'))
        self.assertEqual(nines, refs[b'HEAD'])
        self.assertEqual(nines, refs[b'refs/heads/master'])
        self.assertFalse(refs.add_if_new(b'HEAD', b'1' * 40))
        self.assertEqual(nines, refs[b'HEAD'])
        self.assertEqual(nines, refs[b'refs/heads/master'])

    def test_follow(self):
        self.assertEqual(([b'HEAD', b'refs/heads/master'
                           ], b'42d06bd4b77fed026b154d16493e5deab78f02ec'),
                         self._refs.follow(b'HEAD'))
        self.assertEqual(([b'refs/heads/master'
                           ], b'42d06bd4b77fed026b154d16493e5deab78f02ec'),
                         self._refs.follow(b'refs/heads/master'))
        self.assertRaises(KeyError, self._refs.follow, b'refs/heads/loop')

    def test_delitem(self):
        RefsContainerTests.test_delitem(self)
        ref_file = os.path.join(self._refs.path, 'refs', 'heads', 'master')
        self.assertFalse(os.path.exists(ref_file))
        self.assertFalse(b'refs/heads/master' in self._refs.get_packed_refs())

    def test_delitem_symbolic(self):
        self.assertEqual(b'ref: refs/heads/master',
                         self._refs.read_loose_ref(b'HEAD'))
        del self._refs[b'HEAD']
        self.assertRaises(KeyError, lambda: self._refs[b'HEAD'])
        self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                         self._refs[b'refs/heads/master'])
        self.assertFalse(os.path.exists(os.path.join(self._refs.path, 'HEAD')))

    def test_remove_if_equals_symref(self):
        # HEAD is a symref, so shouldn't equal its dereferenced value
        self.assertFalse(
            self._refs.remove_if_equals(
                b'HEAD', b'42d06bd4b77fed026b154d16493e5deab78f02ec'))
        self.assertTrue(
            self._refs.remove_if_equals(
                b'refs/heads/master',
                b'42d06bd4b77fed026b154d16493e5deab78f02ec'))
        self.assertRaises(KeyError, lambda: self._refs[b'refs/heads/master'])

        # HEAD is now a broken symref
        self.assertRaises(KeyError, lambda: self._refs[b'HEAD'])
        self.assertEqual(b'ref: refs/heads/master',
                         self._refs.read_loose_ref(b'HEAD'))

        self.assertFalse(
            os.path.exists(
                os.path.join(self._refs.path, 'refs', 'heads', 'master.lock')))
        self.assertFalse(
            os.path.exists(os.path.join(self._refs.path, 'HEAD.lock')))

    def test_remove_packed_without_peeled(self):
        refs_file = os.path.join(self._repo.path, 'packed-refs')
        f = GitFile(refs_file)
        refs_data = f.read()
        f.close()
        f = GitFile(refs_file, 'wb')
        f.write(b'\n'.join(l for l in refs_data.split(b'\n')
                           if not l or l[0] not in b'#^'))
        f.close()
        self._repo = Repo(self._repo.path)
        refs = self._repo.refs
        self.assertTrue(
            refs.remove_if_equals(b'refs/heads/packed',
                                  b'42d06bd4b77fed026b154d16493e5deab78f02ec'))

    def test_remove_if_equals_packed(self):
        # test removing ref that is only packed
        self.assertEqual(b'df6800012397fb85c56e7418dd4eb9405dee075c',
                         self._refs[b'refs/tags/refs-0.1'])
        self.assertTrue(
            self._refs.remove_if_equals(
                b'refs/tags/refs-0.1',
                b'df6800012397fb85c56e7418dd4eb9405dee075c'))
        self.assertRaises(KeyError, lambda: self._refs[b'refs/tags/refs-0.1'])

    def test_read_ref(self):
        self.assertEqual(b'ref: refs/heads/master',
                         self._refs.read_ref(b'HEAD'))
        self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                         self._refs.read_ref(b'refs/heads/packed'))
        self.assertEqual(None, self._refs.read_ref(b'nonexistant'))

    def test_non_ascii(self):
        try:
            encoded_ref = u'refs/tags/schön'.encode(
                sys.getfilesystemencoding())
        except UnicodeEncodeError:
            raise SkipTest(
                "filesystem encoding doesn't support special character")
        p = os.path.join(self._repo.path, 'refs', 'tags', u'schön')
        with open(p, 'w') as f:
            f.write('00' * 20)

        expected_refs = dict(_TEST_REFS)
        expected_refs[encoded_ref] = b'00' * 20

        self.assertEqual(expected_refs, self._repo.get_refs())
Exemplo n.º 26
0
class DiskRefsContainerTests(RefsContainerTests, TestCase):
    def setUp(self):
        TestCase.setUp(self)
        self._repo = open_repo("refs.git")
        self.addCleanup(tear_down_repo, self._repo)
        self._refs = self._repo.refs

    def test_get_packed_refs(self):
        self.assertEqual(
            {
                b"refs/heads/packed":
                b"42d06bd4b77fed026b154d16493e5deab78f02ec",
                b"refs/tags/refs-0.1":
                b"df6800012397fb85c56e7418dd4eb9405dee075c",
            },
            self._refs.get_packed_refs(),
        )

    def test_get_peeled_not_packed(self):
        # not packed
        self.assertEqual(None, self._refs.get_peeled(b"refs/tags/refs-0.2"))
        self.assertEqual(
            b"3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8",
            self._refs[b"refs/tags/refs-0.2"],
        )

        # packed, known not peelable
        self.assertEqual(
            self._refs[b"refs/heads/packed"],
            self._refs.get_peeled(b"refs/heads/packed"),
        )

        # packed, peeled
        self.assertEqual(
            b"42d06bd4b77fed026b154d16493e5deab78f02ec",
            self._refs.get_peeled(b"refs/tags/refs-0.1"),
        )

    def test_setitem(self):
        RefsContainerTests.test_setitem(self)
        path = os.path.join(self._refs.path, b"refs", b"some", b"ref")
        with open(path, "rb") as f:
            self.assertEqual(b"42d06bd4b77fed026b154d16493e5deab78f02ec",
                             f.read()[:40])

        self.assertRaises(
            OSError,
            self._refs.__setitem__,
            b"refs/some/ref/sub",
            b"42d06bd4b77fed026b154d16493e5deab78f02ec",
        )

    def test_setitem_packed(self):
        with open(os.path.join(self._refs.path, b"packed-refs"), "w") as f:
            f.write("# pack-refs with: peeled fully-peeled sorted \n")
            f.write(
                "42d06bd4b77fed026b154d16493e5deab78f02ec refs/heads/packed\n")

        # It's allowed to set a new ref on a packed ref, the new ref will be
        # placed outside on refs/
        self._refs[
            b"refs/heads/packed"] = b"3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8"
        packed_ref_path = os.path.join(self._refs.path, b"refs", b"heads",
                                       b"packed")
        with open(packed_ref_path, "rb") as f:
            self.assertEqual(b"3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8",
                             f.read()[:40])

        self.assertRaises(
            OSError,
            self._refs.__setitem__,
            b"refs/heads/packed/sub",
            b"42d06bd4b77fed026b154d16493e5deab78f02ec",
        )

    def test_setitem_symbolic(self):
        ones = b"1" * 40
        self._refs[b"HEAD"] = ones
        self.assertEqual(ones, self._refs[b"HEAD"])

        # ensure HEAD was not modified
        f = open(os.path.join(self._refs.path, b"HEAD"), "rb")
        v = next(iter(f)).rstrip(b"\n\r")
        f.close()
        self.assertEqual(b"ref: refs/heads/master", v)

        # ensure the symbolic link was written through
        f = open(os.path.join(self._refs.path, b"refs", b"heads", b"master"),
                 "rb")
        self.assertEqual(ones, f.read()[:40])
        f.close()

    def test_set_if_equals(self):
        RefsContainerTests.test_set_if_equals(self)

        # ensure symref was followed
        self.assertEqual(b"9" * 40, self._refs[b"refs/heads/master"])

        # ensure lockfile was deleted
        self.assertFalse(
            os.path.exists(
                os.path.join(self._refs.path, b"refs", b"heads",
                             b"master.lock")))
        self.assertFalse(
            os.path.exists(os.path.join(self._refs.path, b"HEAD.lock")))

    def test_add_if_new_packed(self):
        # don't overwrite packed ref
        self.assertFalse(
            self._refs.add_if_new(b"refs/tags/refs-0.1", b"9" * 40))
        self.assertEqual(
            b"df6800012397fb85c56e7418dd4eb9405dee075c",
            self._refs[b"refs/tags/refs-0.1"],
        )

    def test_add_if_new_symbolic(self):
        # Use an empty repo instead of the default.
        repo_dir = os.path.join(tempfile.mkdtemp(), "test")
        os.makedirs(repo_dir)
        repo = Repo.init(repo_dir)
        self.addCleanup(tear_down_repo, repo)
        refs = repo.refs

        nines = b"9" * 40
        self.assertEqual(b"ref: refs/heads/master", refs.read_ref(b"HEAD"))
        self.assertFalse(b"refs/heads/master" in refs)
        self.assertTrue(refs.add_if_new(b"HEAD", nines))
        self.assertEqual(b"ref: refs/heads/master", refs.read_ref(b"HEAD"))
        self.assertEqual(nines, refs[b"HEAD"])
        self.assertEqual(nines, refs[b"refs/heads/master"])
        self.assertFalse(refs.add_if_new(b"HEAD", b"1" * 40))
        self.assertEqual(nines, refs[b"HEAD"])
        self.assertEqual(nines, refs[b"refs/heads/master"])

    def test_follow(self):
        self.assertEqual(
            (
                [b"HEAD", b"refs/heads/master"],
                b"42d06bd4b77fed026b154d16493e5deab78f02ec",
            ),
            self._refs.follow(b"HEAD"),
        )
        self.assertEqual(
            (
                [b"refs/heads/master"],
                b"42d06bd4b77fed026b154d16493e5deab78f02ec",
            ),
            self._refs.follow(b"refs/heads/master"),
        )
        self.assertRaises(KeyError, self._refs.follow, b"refs/heads/loop")

    def test_delitem(self):
        RefsContainerTests.test_delitem(self)
        ref_file = os.path.join(self._refs.path, b"refs", b"heads", b"master")
        self.assertFalse(os.path.exists(ref_file))
        self.assertFalse(b"refs/heads/master" in self._refs.get_packed_refs())

    def test_delitem_symbolic(self):
        self.assertEqual(b"ref: refs/heads/master",
                         self._refs.read_loose_ref(b"HEAD"))
        del self._refs[b"HEAD"]
        self.assertRaises(KeyError, lambda: self._refs[b"HEAD"])
        self.assertEqual(
            b"42d06bd4b77fed026b154d16493e5deab78f02ec",
            self._refs[b"refs/heads/master"],
        )
        self.assertFalse(os.path.exists(os.path.join(self._refs.path,
                                                     b"HEAD")))

    def test_remove_if_equals_symref(self):
        # HEAD is a symref, so shouldn't equal its dereferenced value
        self.assertFalse(
            self._refs.remove_if_equals(
                b"HEAD", b"42d06bd4b77fed026b154d16493e5deab78f02ec"))
        self.assertTrue(
            self._refs.remove_if_equals(
                b"refs/heads/master",
                b"42d06bd4b77fed026b154d16493e5deab78f02ec",
            ))
        self.assertRaises(KeyError, lambda: self._refs[b"refs/heads/master"])

        # HEAD is now a broken symref
        self.assertRaises(KeyError, lambda: self._refs[b"HEAD"])
        self.assertEqual(b"ref: refs/heads/master",
                         self._refs.read_loose_ref(b"HEAD"))

        self.assertFalse(
            os.path.exists(
                os.path.join(self._refs.path, b"refs", b"heads",
                             b"master.lock")))
        self.assertFalse(
            os.path.exists(os.path.join(self._refs.path, b"HEAD.lock")))

    def test_remove_packed_without_peeled(self):
        refs_file = os.path.join(self._repo.path, "packed-refs")
        f = GitFile(refs_file)
        refs_data = f.read()
        f.close()
        f = GitFile(refs_file, "wb")
        f.write(b"\n".join(line for line in refs_data.split(b"\n")
                           if not line or line[0] not in b"#^"))
        f.close()
        self._repo = Repo(self._repo.path)
        refs = self._repo.refs
        self.assertTrue(
            refs.remove_if_equals(
                b"refs/heads/packed",
                b"42d06bd4b77fed026b154d16493e5deab78f02ec",
            ))

    def test_remove_if_equals_packed(self):
        # test removing ref that is only packed
        self.assertEqual(
            b"df6800012397fb85c56e7418dd4eb9405dee075c",
            self._refs[b"refs/tags/refs-0.1"],
        )
        self.assertTrue(
            self._refs.remove_if_equals(
                b"refs/tags/refs-0.1",
                b"df6800012397fb85c56e7418dd4eb9405dee075c",
            ))
        self.assertRaises(KeyError, lambda: self._refs[b"refs/tags/refs-0.1"])

    def test_remove_parent(self):
        self._refs[
            b"refs/heads/foo/bar"] = b"df6800012397fb85c56e7418dd4eb9405dee075c"
        del self._refs[b"refs/heads/foo/bar"]
        ref_file = os.path.join(
            self._refs.path,
            b"refs",
            b"heads",
            b"foo",
            b"bar",
        )
        self.assertFalse(os.path.exists(ref_file))
        ref_file = os.path.join(self._refs.path, b"refs", b"heads", b"foo")
        self.assertFalse(os.path.exists(ref_file))
        ref_file = os.path.join(self._refs.path, b"refs", b"heads")
        self.assertTrue(os.path.exists(ref_file))
        self._refs[
            b"refs/heads/foo"] = b"df6800012397fb85c56e7418dd4eb9405dee075c"

    def test_read_ref(self):
        self.assertEqual(b"ref: refs/heads/master",
                         self._refs.read_ref(b"HEAD"))
        self.assertEqual(
            b"42d06bd4b77fed026b154d16493e5deab78f02ec",
            self._refs.read_ref(b"refs/heads/packed"),
        )
        self.assertEqual(None, self._refs.read_ref(b"nonexistant"))

    def test_read_loose_ref(self):
        self._refs[
            b"refs/heads/foo"] = b"df6800012397fb85c56e7418dd4eb9405dee075c"

        self.assertEqual(None, self._refs.read_ref(b"refs/heads/foo/bar"))

    def test_non_ascii(self):
        try:
            encoded_ref = os.fsencode(u"refs/tags/schön")
        except UnicodeEncodeError:
            raise SkipTest(
                "filesystem encoding doesn't support special character")
        p = os.path.join(os.fsencode(self._repo.path), encoded_ref)
        with open(p, "w") as f:
            f.write("00" * 20)

        expected_refs = dict(_TEST_REFS)
        expected_refs[encoded_ref] = b"00" * 20
        del expected_refs[b"refs/heads/loop"]

        self.assertEqual(expected_refs, self._repo.get_refs())

    def test_cyrillic(self):
        if sys.platform in ("darwin", "win32"):
            raise SkipTest(
                "filesystem encoding doesn't support arbitrary bytes")
        # reported in https://github.com/dulwich/dulwich/issues/608
        name = b"\xcd\xee\xe2\xe0\xff\xe2\xe5\xf2\xea\xe01"
        encoded_ref = b"refs/heads/" + name
        with open(os.path.join(os.fsencode(self._repo.path), encoded_ref),
                  "w") as f:
            f.write("00" * 20)

        expected_refs = set(_TEST_REFS.keys())
        expected_refs.add(encoded_ref)

        self.assertEqual(expected_refs, set(self._repo.refs.allkeys()))
        self.assertEqual(
            {
                r[len(b"refs/"):]
                for r in expected_refs if r.startswith(b"refs/")
            },
            set(self._repo.refs.subkeys(b"refs/")),
        )
        expected_refs.remove(b"refs/heads/loop")
        expected_refs.add(b"HEAD")
        self.assertEqual(expected_refs, set(self._repo.get_refs().keys()))
Exemplo n.º 27
0
class Game(object):
    "A versioned game"
    def __init__(self, name=None, **options):
        self.name = name or uuid.uuid4().hex
        self.options = dict(DEFAULTS, **options)
        self.data = self.options.pop('data').format(name=self.name)
        new = False

        self.repo = None
        if not os.path.exists(self.data):
            if not self.options['create']: raise GameError("Game does not exist")
            os.makedirs(self.data)

        try:
            self.repo = Repo(self.data)
        except dulwich.errors.NotGitRepository:
            if not self.options['create']: raise GameError("Game does not exist")
            self.repo = Repo.init_bare(self.data)
            new = True


        self.board = (new and BoardState()) or self.get_board()

        if new: self.save("New blank board for game: %s" % self.name)

    @property
    def branch(self):
        head = self.repo.refs.read_ref('HEAD')
        if head and head.startswith('ref: '):
            head = head.split(': ')[-1]
            head = head.replace('refs/heads/', '')
            return head
        return 'master'

    def _tree(self, branch=None):
        branch = branch or self.branch
        try: return self.repo[
                      self.repo['refs/heads/%s' % branch].tree
                    ]
        except KeyError: return Tree()

    def signature(self, of=None):
        of = (of and "refs/heads/%s" % of) or "HEAD"
        try: return self.repo.refs[of]
        except KeyError: return None


    def get_board(self, branch=None):
        branch = branch or self.branch
        if branch not in self.branches(): raise GameError("Unknown branch")
        return BoardState.from_json(
            self.repo[
                  [t[2] 
                   for t in self._tree(branch).entries() # [(mode, name, sha)...]
                   if t[1] == 'board.json'].pop()
                 ].data)

    def set_branch(self, new):
        if 'refs/heads/%s' % new in self.repo.get_refs().keys():
            self.repo.refs.set_symbolic_ref('HEAD', 'refs/heads/%s' % new)
            return self.branch
        return False

    def branches(self):
        return sorted([name.replace('refs/heads/', '')
                       for (name, sig) in self.repo.get_refs().items()
                       if name != "HEAD"])

    def make_branch(self, name, back=0):
        if ('refs/heads/%s' % name) in self.repo.get_refs().keys():
            raise GameError("I already have this branch")
        try:
            head = self.repo.head()
            history = self.repo.revision_history(head)
            self.repo.refs['refs/heads/%s' % name] = history[back].id
        except IndexError:
            raise GameError("Trying to go {back} which is further than history".format(back=back))
        return True


    def save(self, message="Forced commit"):
        blob = Blob.from_string(self.board.as_json())
        tree = self._tree()
        tree.add(0100644, 'board.json', blob.id)

        [self.repo.object_store.add_object(it)
         for it in (blob, tree)]

        self.repo.do_commit(message, committer="Game %s" % self.name, tree=tree.id)

    def move(self, x, y):
        player = self.board.player_turn()
        if not self.board.game_over and self.board.move(x, y):
            self.save("{player} moved to ({x}, {y})".format(player=player,
                                                            x=x,
                                                            y=y))
            return player
        return None

    def skip(self):
        player = self.board.player_turn()
        if not self.board.game_over:
            self.board.move(None)
            is_or_isnt = (self.board.game_over and "is") or "is NOT"
            self.save("{player} skipped, game {maybe} over".format(player=player,
                                                                   maybe=is_or_isnt))
        return self.board.game_over or self.board.player_turn()

    def who(self):
        return self.board.player_turn()

    def scores(self):
        return self.board.scores()

    def winner(self):
        return self.board.winner

    def __unicode__(self):
        return "Game: {name} {black} vs {white} on {x}x{y} from {data} :: {board}".format(
                 name=self.name,
                 board=self.board,
                 data=self.data,
                 **self.options
               )
    __str__=__unicode__

    def __repr__(self):
        return "<%s>" % self
Exemplo n.º 28
0
class DiskRefsContainerTests(RefsContainerTests, TestCase):

    def setUp(self):
        TestCase.setUp(self)
        self._repo = open_repo('refs.git')
        self.addCleanup(tear_down_repo, self._repo)
        self._refs = self._repo.refs

    def test_get_packed_refs(self):
        self.assertEqual({
            b'refs/heads/packed': b'42d06bd4b77fed026b154d16493e5deab78f02ec',
            b'refs/tags/refs-0.1': b'df6800012397fb85c56e7418dd4eb9405dee075c',
            }, self._refs.get_packed_refs())

    def test_get_peeled_not_packed(self):
        # not packed
        self.assertEqual(None, self._refs.get_peeled(b'refs/tags/refs-0.2'))
        self.assertEqual(b'3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8',
                         self._refs[b'refs/tags/refs-0.2'])

        # packed, known not peelable
        self.assertEqual(self._refs[b'refs/heads/packed'],
                         self._refs.get_peeled(b'refs/heads/packed'))

        # packed, peeled
        self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                         self._refs.get_peeled(b'refs/tags/refs-0.1'))

    def test_setitem(self):
        RefsContainerTests.test_setitem(self)
        f = open(os.path.join(self._refs.path, 'refs', 'some', 'ref'), 'rb')
        self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                         f.read()[:40])
        f.close()

    def test_setitem_symbolic(self):
        ones = b'1' * 40
        self._refs[b'HEAD'] = ones
        self.assertEqual(ones, self._refs[b'HEAD'])

        # ensure HEAD was not modified
        f = open(os.path.join(self._refs.path, 'HEAD'), 'rb')
        self.assertEqual(b'ref: refs/heads/master', next(iter(f)).rstrip(b'\n'))
        f.close()

        # ensure the symbolic link was written through
        f = open(os.path.join(self._refs.path, 'refs', 'heads', 'master'), 'rb')
        self.assertEqual(ones, f.read()[:40])
        f.close()

    def test_set_if_equals(self):
        RefsContainerTests.test_set_if_equals(self)

        # ensure symref was followed
        self.assertEqual(b'9' * 40, self._refs[b'refs/heads/master'])

        # ensure lockfile was deleted
        self.assertFalse(os.path.exists(
            os.path.join(self._refs.path, 'refs', 'heads', 'master.lock')))
        self.assertFalse(os.path.exists(
            os.path.join(self._refs.path, 'HEAD.lock')))

    def test_add_if_new_packed(self):
        # don't overwrite packed ref
        self.assertFalse(self._refs.add_if_new(b'refs/tags/refs-0.1',
                                               b'9' * 40))
        self.assertEqual(b'df6800012397fb85c56e7418dd4eb9405dee075c',
                         self._refs[b'refs/tags/refs-0.1'])

    def test_add_if_new_symbolic(self):
        # Use an empty repo instead of the default.
        repo_dir = os.path.join(tempfile.mkdtemp(), 'test')
        os.makedirs(repo_dir)
        repo = Repo.init(repo_dir)
        self.addCleanup(tear_down_repo, repo)
        refs = repo.refs

        nines = b'9' * 40
        self.assertEqual(b'ref: refs/heads/master', refs.read_ref(b'HEAD'))
        self.assertFalse(b'refs/heads/master' in refs)
        self.assertTrue(refs.add_if_new(b'HEAD', nines))
        self.assertEqual(b'ref: refs/heads/master', refs.read_ref(b'HEAD'))
        self.assertEqual(nines, refs[b'HEAD'])
        self.assertEqual(nines, refs[b'refs/heads/master'])
        self.assertFalse(refs.add_if_new(b'HEAD', b'1' * 40))
        self.assertEqual(nines, refs[b'HEAD'])
        self.assertEqual(nines, refs[b'refs/heads/master'])

    def test_follow(self):
        self.assertEqual((b'refs/heads/master',
                          b'42d06bd4b77fed026b154d16493e5deab78f02ec'),
                         self._refs._follow(b'HEAD'))
        self.assertEqual((b'refs/heads/master',
                          b'42d06bd4b77fed026b154d16493e5deab78f02ec'),
                         self._refs._follow(b'refs/heads/master'))
        self.assertRaises(KeyError, self._refs._follow, b'refs/heads/loop')

    def test_delitem(self):
        RefsContainerTests.test_delitem(self)
        ref_file = os.path.join(self._refs.path, 'refs', 'heads', 'master')
        self.assertFalse(os.path.exists(ref_file))
        self.assertFalse(b'refs/heads/master' in self._refs.get_packed_refs())

    def test_delitem_symbolic(self):
        self.assertEqual(b'ref: refs/heads/master',
                         self._refs.read_loose_ref(b'HEAD'))
        del self._refs[b'HEAD']
        self.assertRaises(KeyError, lambda: self._refs[b'HEAD'])
        self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                         self._refs[b'refs/heads/master'])
        self.assertFalse(os.path.exists(os.path.join(self._refs.path, 'HEAD')))

    def test_remove_if_equals_symref(self):
        # HEAD is a symref, so shouldn't equal its dereferenced value
        self.assertFalse(self._refs.remove_if_equals(
            b'HEAD', b'42d06bd4b77fed026b154d16493e5deab78f02ec'))
        self.assertTrue(self._refs.remove_if_equals(
            b'refs/heads/master', b'42d06bd4b77fed026b154d16493e5deab78f02ec'))
        self.assertRaises(KeyError, lambda: self._refs[b'refs/heads/master'])

        # HEAD is now a broken symref
        self.assertRaises(KeyError, lambda: self._refs[b'HEAD'])
        self.assertEqual(b'ref: refs/heads/master',
                         self._refs.read_loose_ref(b'HEAD'))

        self.assertFalse(os.path.exists(
            os.path.join(self._refs.path, 'refs', 'heads', 'master.lock')))
        self.assertFalse(os.path.exists(
            os.path.join(self._refs.path, 'HEAD.lock')))

    def test_remove_packed_without_peeled(self):
        refs_file = os.path.join(self._repo.path, 'packed-refs')
        f = GitFile(refs_file)
        refs_data = f.read()
        f.close()
        f = GitFile(refs_file, 'wb')
        f.write(b'\n'.join(l for l in refs_data.split(b'\n')
                           if not l or l[0] not in b'#^'))
        f.close()
        self._repo = Repo(self._repo.path)
        refs = self._repo.refs
        self.assertTrue(refs.remove_if_equals(
            b'refs/heads/packed', b'42d06bd4b77fed026b154d16493e5deab78f02ec'))

    def test_remove_if_equals_packed(self):
        # test removing ref that is only packed
        self.assertEqual(b'df6800012397fb85c56e7418dd4eb9405dee075c',
                         self._refs[b'refs/tags/refs-0.1'])
        self.assertTrue(
            self._refs.remove_if_equals(
                b'refs/tags/refs-0.1',
                b'df6800012397fb85c56e7418dd4eb9405dee075c'))
        self.assertRaises(KeyError, lambda: self._refs[b'refs/tags/refs-0.1'])

    def test_read_ref(self):
        self.assertEqual(b'ref: refs/heads/master', self._refs.read_ref(b'HEAD'))
        self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
                         self._refs.read_ref(b'refs/heads/packed'))
        self.assertEqual(None, self._refs.read_ref(b'nonexistant'))

    def test_non_ascii(self):
        try:
            encoded_ref = u'refs/tags/schön'.encode(sys.getfilesystemencoding())
        except UnicodeEncodeError:
            raise SkipTest("filesystem encoding doesn't support special character")
        p = os.path.join(self._repo.path, 'refs', 'tags', 'schön')
        with open(p, 'w') as f:
            f.write('00' * 20)

        expected_refs = dict(_TEST_REFS)
        expected_refs[encoded_ref] = b'00' * 20

        self.assertEqual(expected_refs, self._repo.get_refs())
Exemplo n.º 29
0
class GitHandler(object):
    mapfile = 'git-mapfile'
    tagsfile = 'git-tags'

    def __init__(self, dest_repo, ui):
        self.repo = dest_repo
        self.ui = ui

        if ui.configbool('git', 'intree'):
            self.gitdir = self.repo.wjoin('.git')
        else:
            self.gitdir = self.repo.join('git')

        self.paths = ui.configitems('paths')

        self.load_map()
        self.load_tags()

    # make the git data directory
    def init_if_missing(self):
        if os.path.exists(self.gitdir):
            self.git = Repo(self.gitdir)
        else:
            os.mkdir(self.gitdir)
            self.git = Repo.init_bare(self.gitdir)

    ## FILE LOAD AND SAVE METHODS

    def map_set(self, gitsha, hgsha):
        self._map_git[gitsha] = hgsha
        self._map_hg[hgsha] = gitsha

    def map_hg_get(self, gitsha):
        return self._map_git.get(gitsha)

    def map_git_get(self, hgsha):
        return self._map_hg.get(hgsha)

    def load_map(self):
        self._map_git = {}
        self._map_hg = {}
        if os.path.exists(self.repo.join(self.mapfile)):
            for line in self.repo.opener(self.mapfile):
                gitsha, hgsha = line.strip().split(' ', 1)
                self._map_git[gitsha] = hgsha
                self._map_hg[hgsha] = gitsha

    def save_map(self):
        file = self.repo.opener(self.mapfile, 'w+', atomictemp=True)
        for hgsha, gitsha in sorted(self._map_hg.iteritems()):
            file.write("%s %s\n" % (gitsha, hgsha))
        # If this complains that NoneType is not callable, then
        # atomictempfile no longer has either of rename (pre-1.9) or
        # close (post-1.9)
        getattr(file, 'rename', getattr(file, 'close', None))()

    def load_tags(self):
        self.tags = {}
        if os.path.exists(self.repo.join(self.tagsfile)):
            for line in self.repo.opener(self.tagsfile):
                sha, name = line.strip().split(' ', 1)
                self.tags[name] = sha

    def save_tags(self):
        file = self.repo.opener(self.tagsfile, 'w+', atomictemp=True)
        for name, sha in sorted(self.tags.iteritems()):
            if not self.repo.tagtype(name) == 'global':
                file.write("%s %s\n" % (sha, name))
        # If this complains that NoneType is not callable, then
        # atomictempfile no longer has either of rename (pre-1.9) or
        # close (post-1.9)
        getattr(file, 'rename', getattr(file, 'close', None))()

    ## END FILE LOAD AND SAVE METHODS

    ## COMMANDS METHODS

    def import_commits(self, remote_name):
        self.import_git_objects(remote_name)
        self.update_hg_bookmarks(self.git.get_refs())
        self.save_map()

    def fetch(self, remote, heads):
        self.export_commits()
        refs = self.fetch_pack(remote, heads)
        remote_name = self.remote_name(remote)

        oldrefs = self.git.get_refs()
        if refs:
            self.import_git_objects(remote_name, refs)
            self.import_tags(refs)
            self.update_hg_bookmarks(refs)
            if remote_name:
                self.update_remote_branches(remote_name, refs)
            elif not self.paths:
                # intial cloning
                self.update_remote_branches('default', refs)

                # "Activate" a tipmost bookmark.
                bms = getattr(self.repo['tip'], 'bookmarks',
                              lambda : None)()
                if bms:
                    bookmarks.setcurrent(self.repo, bms[0])

        def remoteref(ref):
            rn = remote_name or 'default'
            return 'refs/remotes/' + rn + ref[10:]

        modheads = [refs[k] for k in refs if k.startswith('refs/heads/')
                    and not k.endswith('^{}')
                    and refs[k] != oldrefs.get(remoteref(k))]

        if not modheads:
            self.ui.status(_("no changes found\n"))

        self.save_map()

        return len(modheads)

    def export_commits(self):
        try:
            self.export_git_objects()
            self.export_hg_tags()
            self.update_references()
        finally:
            self.save_map()

    def get_refs(self, remote):
        self.export_commits()
        client, path = self.get_transport_and_path(remote)
        old_refs = {}
        new_refs = {}
        def changed(refs):
            old_refs.update(refs)
            to_push = set(self.local_heads().values() + self.tags.values())
            new_refs.update(self.get_changed_refs(refs, to_push, True))
            # don't push anything
            return {}

        try:
            client.send_pack(path, changed, None)

            changed_refs = [ref for ref, sha in new_refs.iteritems()
                            if sha != old_refs.get(ref)]
            new = [bin(self.map_hg_get(new_refs[ref])) for ref in changed_refs]
            old = {}
            for r in old_refs:
                old_ref = self.map_hg_get(old_refs[r])
                if old_ref:
                    old[bin(old_ref)] = 1

            return old, new
        except (HangupException, GitProtocolError), e:
            raise hgutil.Abort(_("git remote error: ") + str(e))
Exemplo n.º 30
0
def cli(metadata_template):
    """Generate the 'metadata.json' file for this app."""
    metadata_dir = Path(__file__).resolve().parent
    output_dir = metadata_dir / 'build'
    root = metadata_dir.parent

    repo = Repo(root)
    versions = {
        parse(get_version_identifier(ref.decode())): ref
        for ref in repo.get_refs()
    }
    requirements = {
        version: get_requirements_for_ref(repo, ref)
        for version, ref in versions.items()
    }

    def get_requirements(spec, version=None):
        spec = SpecifierSet(spec)
        if version is None:
            matching_versions = [
                version for version in sorted(versions) if version in spec
            ]
            matching_requirements = {
                requirements[version]
                for version in matching_versions
            }
            if len(matching_requirements) == 0:
                raise RuntimeError(
                    f"Unable to determine requirements for specifier '{spec}'."
                )
            elif len(matching_requirements) > 1:
                raise RuntimeError(
                    f"Requirements for specifier '{spec}' are not uniform.")
            reqs = matching_requirements.pop()
        else:
            reqs = requirements[parse(version)]

        return json.dumps({str(spec): reqs})[1:-1]

    env = Environment(loader=FileSystemLoader(metadata_dir / 'templates'))
    env.filters['get_requirements'] = get_requirements

    # Writing output...
    output_dir.mkdir(exist_ok=True)

    # index.html
    index_html = output_dir / 'index.html'
    index_html.write_text(env.get_template('index.html').render())
    click.echo(f"Write {index_html.relative_to(Path.cwd())}")

    # metadata.json
    metadata_json = output_dir / 'metadata.json'
    metadata_template = env.get_template(metadata_template)
    try:
        metadata = json.loads(metadata_template.render())
    except json.decoder.JSONDecodeError as error:
        raise RuntimeError(f"{error}\n{metadata_template.render()}")
    validate(
        instance=metadata,
        schema={
            "$ref":
            "https://aiidalab.github.io/aiidalab-registry/schemas/v1/metadata.schema.json"
        })
    metadata_json.write_text(json.dumps(metadata, indent=2))
    click.echo(f"Write {metadata_json.relative_to(Path.cwd())}")

    # QE.jpg
    src = root / 'miscellaneous' / 'logos' / 'QE.jpg'
    dst = output_dir / 'miscellaneous' / 'logos' / 'QE.jpg'
    dst.parent.mkdir(parents=True, exist_ok=True)
    shutil.copyfile(src, dst)
    click.echo(f"Copy {dst.relative_to(Path.cwd())}")
Exemplo n.º 31
0
class GitHandler(object):
    mapfile = 'git-mapfile'
    tagsfile = 'git-tags'

    def __init__(self, dest_repo, ui):
        self.repo = dest_repo
        self.ui = ui

        if ui.configbool('git', 'intree'):
            self.gitdir = self.repo.wjoin('.git')
        else:
            self.gitdir = self.repo.join('git')

        self.paths = ui.configitems('paths')

        self.load_map()
        self.load_tags()

    # make the git data directory
    def init_if_missing(self):
        if os.path.exists(self.gitdir):
            self.git = Repo(self.gitdir)
        else:
            os.mkdir(self.gitdir)
            self.git = Repo.init_bare(self.gitdir)

    ## FILE LOAD AND SAVE METHODS

    def map_set(self, gitsha, hgsha):
        self._map_git[gitsha] = hgsha
        self._map_hg[hgsha] = gitsha

    def map_hg_get(self, gitsha):
        return self._map_git.get(gitsha)

    def map_git_get(self, hgsha):
        return self._map_hg.get(hgsha)

    def load_map(self):
        self._map_git = {}
        self._map_hg = {}
        if os.path.exists(self.repo.join(self.mapfile)):
            for line in self.repo.opener(self.mapfile):
                gitsha, hgsha = line.strip().split(' ', 1)
                self._map_git[gitsha] = hgsha
                self._map_hg[hgsha] = gitsha

    def save_map(self):
        file = self.repo.opener(self.mapfile, 'w+', atomictemp=True)
        for hgsha, gitsha in sorted(self._map_hg.iteritems()):
            file.write("%s %s\n" % (gitsha, hgsha))
        file.rename()

    def load_tags(self):
        self.tags = {}
        if os.path.exists(self.repo.join(self.tagsfile)):
            for line in self.repo.opener(self.tagsfile):
                sha, name = line.strip().split(' ', 1)
                self.tags[name] = sha

    def save_tags(self):
        file = self.repo.opener(self.tagsfile, 'w+', atomictemp=True)
        for name, sha in sorted(self.tags.iteritems()):
            if not self.repo.tagtype(name) == 'global':
                file.write("%s %s\n" % (sha, name))
        file.rename()

    ## END FILE LOAD AND SAVE METHODS

    ## COMMANDS METHODS

    def import_commits(self, remote_name):
        self.import_git_objects(remote_name)
        self.save_map()

    def fetch(self, remote, heads):
        self.export_commits()
        refs = self.fetch_pack(remote, heads)
        remote_name = self.remote_name(remote)

        oldrefs = self.git.get_refs()
        if refs:
            self.import_git_objects(remote_name, refs)
            self.import_tags(refs)
            self.update_hg_bookmarks(refs)
            if remote_name:
                self.update_remote_branches(remote_name, refs)
            elif not self.paths:
                # intial cloning
                self.update_remote_branches('default', refs)

                # "Activate" a tipmost bookmark.
                bms = getattr(self.repo['tip'], 'bookmarks',
                              lambda : None)()
                if bms:
                    bookmarks.setcurrent(self.repo, bms[0])

        def remoteref(ref):
            rn = remote_name or 'default'
            return 'refs/remotes/' + rn + ref[10:]

        modheads = [refs[k] for k in refs if k.startswith('refs/heads/')
                    and not k.endswith('^{}')
                    and refs[k] != oldrefs.get(remoteref(k))]

        if not modheads:
            self.ui.status(_("no changes found\n"))

        self.save_map()

        return len(modheads)

    def export_commits(self):
        try:
            self.export_git_objects()
            self.export_hg_tags()
            self.update_references()
        finally:
            self.save_map()

    def get_refs(self, remote):
        self.export_commits()
        client, path = self.get_transport_and_path(remote)
        old_refs = {}
        new_refs = {}
        def changed(refs):
            old_refs.update(refs)
            to_push = set(self.local_heads().values() + self.tags.values())
            new_refs.update(self.get_changed_refs(refs, to_push, True))
            # don't push anything
            return {}

        try:
            client.send_pack(path, changed, None)

            changed_refs = [ref for ref, sha in new_refs.iteritems()
                            if sha != old_refs.get(ref)]
            new = [bin(self.map_hg_get(new_refs[ref])) for ref in changed_refs]
            old = dict( (bin(self.map_hg_get(old_refs[r])), 1)
                       for r in old_refs)

            return old, new
        except (HangupException, GitProtocolError), e:
            raise hgutil.Abort(_("git remote error: ") + str(e))
Exemplo n.º 32
0
def get_branches(repo_path):
    repo = Repo(repo_path)
    return [ref[11:] for ref in repo.get_refs()
            if ref.startswith('refs/heads/')]
Exemplo n.º 33
0
class Gittle(object):
    """All paths used in Gittle external methods must be paths relative to the git repository
    """
    DEFAULT_COMMIT = 'HEAD'
    DEFAULT_BRANCH = 'master'
    DEFAULT_REMOTE = 'origin'
    DEFAULT_MESSAGE = '**No Message**'
    DEFAULT_USER_INFO = {
        'name': None,
        'email': None,
    }

    DIFF_FUNCTIONS = {
        'classic': utils.git.classic_tree_diff,
        'dict': utils.git.dict_tree_diff,
        'changes': utils.git.dict_tree_diff
    }
    DEFAULT_DIFF_TYPE = 'dict'

    HIDDEN_REGEXES = [
        # Hide git directory
        r'.*\/\.git\/.*',
    ]

    # References
    REFS_BRANCHES = 'refs/heads/'
    REFS_REMOTES = 'refs/remotes/'
    REFS_TAGS = 'refs/tags/'

    # Name pattern truths
    # Used for detecting if files are :
    # - deleted
    # - added
    # - changed
    PATTERN_ADDED = (False, True)
    PATTERN_REMOVED = (True, False)
    PATTERN_MODIFIED = (True, True)

    # Permissions
    MODE_DIRECTORY = 0o40000  # Used to tell if a tree entry is a directory

    # Tree depth
    MAX_TREE_DEPTH = 1000

    # Acceptable Root paths
    ROOT_PATHS = (os.path.curdir, os.path.sep)

    def __init__(self, repo_or_path, origin_uri=None, auth=None, report_activity=None, *args, **kwargs):
        if isinstance(repo_or_path, DulwichRepo):
            self.repo = repo_or_path
        elif isinstance(repo_or_path, Gittle):
            self.repo = DulwichRepo(repo_or_path.path)
        elif isinstance(repo_or_path, basestring):
            path = os.path.abspath(repo_or_path)
            self.repo = DulwichRepo(path)
        else:
            logging.warning('Repo is of type %s' % type(repo_or_path))
            raise Exception('Gittle must be initialized with either a dulwich repository or a string to the path')

        # Set path
        self.path = self.repo.path

        # The remote url
        self.origin_uri = origin_uri

        # Report client activty
        self._report_activity = report_activity

        # Build ignore filter
        self.hidden_regexes = copy.copy(self.HIDDEN_REGEXES)
        self.hidden_regexes.extend(self._get_ignore_regexes())
        self.ignore_filter = utils.paths.path_filter_regex(self.hidden_regexes)
        self.filters = [
            self.ignore_filter,
        ]

        # Get authenticator
        if auth:
            self.authenticator = auth
        else:
            self.auth(*args, **kwargs)

    def report_activity(self, *args, **kwargs):
        if not self._report_activity:
            return
        return self._report_activity(*args, **kwargs)

    def _format_author(self, name, email):
        return "%s <%s>" % (name, email)

    def _format_userinfo(self, userinfo):
        name = userinfo.get('name')
        email = userinfo.get('email')
        if name and email:
            return self._format_author(name, email)
        return None

    def _format_ref(self, base, extra):
        return ''.join([base, extra])

    def _format_ref_branch(self, branch_name):
        return self._format_ref(self.REFS_BRANCHES, branch_name)

    def _format_ref_remote(self, remote_name):
        return self._format_ref(self.REFS_REMOTES, remote_name)

    def _format_ref_tag(self, tag_name):
        return self._format_ref(self.REFS_TAGS, tag_name)

    @property
    def head(self):
        """Return SHA of the current HEAD
        """
        return self.repo.head()

    @property
    def is_bare(self):
        """Bare repositories have no working directories or indexes
        """
        return self.repo.bare

    @property
    def is_working(self):
        return not(self.is_bare)

    def has_index(self):
        """Opposite of is_bare
        """
        return self.repo.has_index()

    @property
    def has_commits(self):
        """
        If the repository has no HEAD we consider that is has no commits
        """
        try:
            self.repo.head()
        except KeyError:
            return False
        return True

    def ref_walker(self, ref=None):
        """
        Very simple, basic walker
        """
        ref = ref or 'HEAD'
        sha = self._commit_sha(ref)
        for entry in self.repo.get_walker(sha):
            yield entry.commit

    def branch_walker(self, branch):
        branch = branch or self.active_branch
        ref = self._format_ref_branch(branch)
        return self.ref_walker(ref)

    def commit_info(self, start=0, end=None, branch=None):
        """Return a generator of commits with all their attached information
        """
        if not self.has_commits:
            return []
        commits = [utils.git.commit_info(entry) for entry in self.branch_walker(branch)]
        if not end:
            return commits
        return commits[start:end]


    @funky.uniquify
    def recent_contributors(self, n=None, branch=None):
        n = n or 10
        return funky.pluck(self.commit_info(end=n, branch=branch), 'author')

    @property
    def commit_count(self):
        try:
            return len(self.ref_walker())
        except KeyError:
            return 0

    def commits(self):
        """Return a list of SHAs for all the concerned commits
        """
        return [commit['sha'] for commit in self.commit_info()]

    @property
    def git_dir(self):
        return self.repo.controldir()

    def auth(self, *args, **kwargs):
        self.authenticator = GittleAuth(*args, **kwargs)
        return self.authenticator

    # Generate a branch selector (used for pushing)
    def _wants_branch(self, branch_name=None):
        branch_name = branch_name or self.active_branch
        refs_key = self._format_ref_branch(branch_name)
        sha = self.branches[branch_name]

        def wants_func(old):
            refs_key = self._format_ref_branch(branch_name)
            return {
                refs_key: sha
            }
        return wants_func

    def _get_ignore_regexes(self):
        gitignore_filename = os.path.join(self.path, '.gitignore')
        if not os.path.exists(gitignore_filename):
            return []
        lines = open(gitignore_filename).readlines()
        globers = map(lambda line: line.rstrip(), lines)
        return utils.paths.globers_to_regex(globers)

    # Get the absolute path for a file in the git repo
    def abspath(self, repo_file):
        return os.path.abspath(
            os.path.join(self.path, repo_file)
        )

    # Get the relative path from the absolute path
    def relpath(self, abspath):
        return os.path.relpath(abspath, self.path)

    @property
    def last_commit(self):
        return self[self.repo.head()]

    @property
    def index(self):
        return self.repo.open_index()

    @classmethod
    def init(cls, path, bare=None, *args, **kwargs):
        """Initialize a repository"""
        mkdir_safe(path)

        # Constructor to use
        if bare:
            constructor = DulwichRepo.init_bare
        else:
            constructor = DulwichRepo.init

        # Create dulwich repo
        repo = constructor(path)

        # Create Gittle repo
        return cls(repo, *args, **kwargs)

    @classmethod
    def init_bare(cls, *args, **kwargs):
        kwargs.setdefault('bare', True)
        return cls.init(*args, **kwargs)

    @classmethod
    def is_repo(cls, path):
        """Returns True if path is a git repository, False if it is not"""
        try:
            repo = Gittle(path)
        except NotGitRepository:
            return False
        else:
            return True

    def get_client(self, origin_uri=None, **kwargs):
        # Get the remote URL
        origin_uri = origin_uri or self.origin_uri

        # Fail if inexistant
        if not origin_uri:
            raise InvalidRemoteUrl()

        client_kwargs = {}
        auth_kwargs = self.authenticator.kwargs()

        client_kwargs.update(auth_kwargs)
        client_kwargs.update(kwargs)
        client_kwargs.update({
            'report_activity': self.report_activity
        })

        client, remote_path = get_transport_and_path(origin_uri, **client_kwargs)
        return client, remote_path

    def push_to(self, origin_uri, branch_name=None, progress=None):
        selector = self._wants_branch(branch_name=branch_name)
        client, remote_path = self.get_client(origin_uri)
        return client.send_pack(
            remote_path,
            selector,
            self.repo.object_store.generate_pack_contents,
            progress=progress
        )

    # Like: git push
    def push(self, origin_uri=None, branch_name=None, progress=None):
        return self.push_to(origin_uri, branch_name, progress)

    # Not recommended at ALL ... !!!
    def dirty_pull_from(self, origin_uri, branch_name=None):
        # Remove all previously existing data
        rmtree(self.path)
        mkdir_safe(self.path)
        self.repo = DulwichRepo.init(self.path)

        # Fetch brand new copy from remote
        return self.pull_from(origin_uri, branch_name)

    def pull_from(self, origin_uri, branch_name=None):
        return self.fetch(origin_uri)

    # Like: git pull
    def pull(self, origin_uri=None, branch_name=None):
        return self.pull_from(origin_uri, branch_name)

    def fetch_remote(self, origin_uri=None):
        # Get client
        client, remote_path = self.get_client(origin_uri=origin_uri)

        # Fetch data from remote repository
        remote_refs = client.fetch(remote_path, self.repo)

        return remote_refs


    def _setup_fetched_refs(self, refs, origin, bare):
        remote_tags = utils.git.subrefs(refs, 'refs/tags')
        remote_heads = utils.git.subrefs(refs, 'refs/heads')

        # Filter refs
        clean_remote_tags = utils.git.clean_refs(remote_tags)
        clean_remote_heads = utils.git.clean_refs(remote_heads)

        # Base of new refs
        heads_base = 'refs/remotes/' + origin
        if bare:
            heads_base = 'refs/heads'

        # Import branches
        self.import_refs(
            heads_base,
            clean_remote_heads
        )

        # Import tags
        self.import_refs(
            'refs/tags',
            clean_remote_tags
        )

        # Update HEAD
        for k, v in utils.git.clean_refs(refs).items():
            self[k] = v


    def fetch(self, origin_uri=None, bare=None, origin=None):
        bare = bare or False
        origin = origin or self.DEFAULT_REMOTE

        # Remote refs
        remote_refs = self.fetch_remote(origin_uri)

        # Update head
        # Hit repo because head doesn't yet exist so
        # print("REFS = %s" % remote_refs)

        # If no refs (empty repository()
        if not remote_refs:
            return

        # Update refs (branches, tags, HEAD)
        self._setup_fetched_refs(remote_refs, origin, bare)

        # Checkout working directories
        if not bare and self.has_commits:
            self.checkout_all()
        else:
            self.update_server_info()


    @classmethod
    def clone(cls, origin_uri, local_path, auth=None, mkdir=True, bare=False, *args, **kwargs):
        """Clone a remote repository"""
        mkdir_safe(local_path)

        # Initialize the local repository
        if bare:
            local_repo = cls.init_bare(local_path)
        else:
            local_repo = cls.init(local_path)

        repo = cls(local_repo, origin_uri=origin_uri, auth=auth, *args, **kwargs)

        repo.fetch(bare=bare)

        # Add origin
        repo.add_remote('origin', origin_uri)

        return repo

    @classmethod
    def clone_bare(cls, *args, **kwargs):
        """Same as .clone except clones to a bare repository by default
        """
        kwargs.setdefault('bare', True)
        return cls.clone(*args, **kwargs)

    def _commit(self, committer=None, author=None, message=None, files=None, tree=None, *args, **kwargs):

        if not tree:
            # If no tree then stage files
            modified_files = files or self.modified_files
            logging.info("STAGING : %s" % modified_files)
            self.repo.stage(modified_files)

        # Messages
        message = message or self.DEFAULT_MESSAGE
        author_msg = self._format_userinfo(author)
        committer_msg = self._format_userinfo(committer)

        return self.repo.do_commit(
            message=message,
            author=author_msg,
            committer=committer_msg,
            encoding='UTF-8',
            tree=tree,
            *args, **kwargs
        )

    def _tree_from_structure(self, structure):
        # TODO : Support directories
        tree = Tree()

        for file_info in structure:

            # str only
            try:
                data = file_info['data'].encode('ascii')
                name = file_info['name'].encode('ascii')
                mode = file_info['mode']
            except:
                # Skip file on encoding errors
                continue

            blob = Blob()

            blob.data = data

            # Store file's contents
            self.repo.object_store.add_object(blob)

            # Add blob entry
            tree.add(
                name,
                mode,
                blob.id
            )

        # Store tree
        self.repo.object_store.add_object(tree)

        return tree.id

    # Like: git commmit -a
    def commit(self, name=None, email=None, message=None, files=None, *args, **kwargs):
        user_info = {
            'name': name,
            'email': email,
        }
        return self._commit(
            committer=user_info,
            author=user_info,
            message=message,
            files=files,
            *args,
            **kwargs
        )

    def commit_structure(self, name=None, email=None, message=None, structure=None, *args, **kwargs):
        """Main use is to do commits directly to bare repositories
        For example doing a first Initial Commit so the repo can be cloned and worked on right away
        """
        if not structure:
            return
        tree = self._tree_from_structure(structure)

        user_info = {
            'name': name,
            'email': email,
        }

        return self._commit(
            committer=user_info,
            author=user_info,
            message=message,
            tree=tree,
            *args,
            **kwargs
        )

    # Push all local commits
    # and pull all remote commits
    def sync(self, origin_uri=None):
        self.push(origin_uri)
        return self.pull(origin_uri)

    def lookup_entry(self, relpath, trackable_files=set()):
        if not relpath in trackable_files:
            raise KeyError

        abspath = self.abspath(relpath)

        with open(abspath, 'rb') as git_file:
            data = git_file.read()
            s = sha1()
            s.update("blob %u\0" % len(data))
            s.update(data)
        return (s.hexdigest(), os.stat(abspath).st_mode)

    @property
    @funky.transform(set)
    def tracked_files(self):
        return list(self.index)

    @property
    @funky.transform(set)
    def raw_files(self):
        return utils.paths.subpaths(self.path)

    @property
    @funky.transform(set)
    def ignored_files(self):
        return utils.paths.subpaths(self.path, filters=self.filters)

    @property
    @funky.transform(set)
    def trackable_files(self):
        return self.raw_files - self.ignored_files

    @property
    @funky.transform(set)
    def untracked_files(self):
        return self.trackable_files - self.tracked_files

    """
    @property
    @funky.transform(set)
    def modified_staged_files(self):
        "Checks if the file has changed since last commit"
        timestamp = self.last_commit.commit_time
        index = self.index
        return [
            f
            for f in self.tracked_files
            if index[f][1][0] > timestamp
        ]
    """

    # Return a list of tuples
    # representing the changed elements in the git tree
    def _changed_entries(self, ref=None):
        ref = ref or self.DEFAULT_COMMIT
        if not self.has_commits:
            return []
        obj_sto = self.repo.object_store
        tree_id = self[ref].tree
        names = self.trackable_files

        lookup_func = partial(self.lookup_entry, trackable_files=names)

        # Format = [((old_name, new_name), (old_mode, new_mode), (old_sha, new_sha)), ...]
        tree_diff = changes_from_tree(names, lookup_func, obj_sto, tree_id, want_unchanged=False)
        return list(tree_diff)

    @funky.transform(set)
    def _changed_entries_by_pattern(self, pattern):
        changed_entries = self._changed_entries()
        filtered_paths = None
         #if the pattern is PATTERN_MODIFIED, should check the sha
        if self.PATTERN_MODIFIED == pattern:
            filtered_paths = [
              funky.first_true(names)
                  for names, modes, sha in changed_entries
                  if tuple(map(bool, names)) == pattern and funky.first_true(names) and sha[0] == sha[1]
            ]
        else :
            filtered_paths = [
               funky.first_true(names)
                 for names, modes, sha in changed_entries
                 if tuple(map(bool, names)) == pattern and funky.first_true(names)
            ]
        return filtered_paths

    @property
    @funky.transform(set)
    def removed_files(self):
        return self._changed_entries_by_pattern(self.PATTERN_REMOVED) - self.ignored_files

    @property
    @funky.transform(set)
    def added_files(self):
        return self._changed_entries_by_pattern(self.PATTERN_ADDED) - self.ignored_files

    @property
    @funky.transform(set)
    def modified_files(self):
        modified_files = self._changed_entries_by_pattern(self.PATTERN_MODIFIED) - self.ignored_files
        return modified_files

    @property
    @funky.transform(set)
    def modified_unstaged_files(self):
        timestamp = self.last_commit.commit_time
        return [
            f
            for f in self.tracked_files
            if os.stat(self.abspath(f)).st_mtime > timestamp
        ]

    @property
    def pending_files(self):
        """
        Returns a list of all files that could be possibly staged
        """
        # Union of both
        return self.modified_files | self.added_files | self.removed_files

    @property
    def pending_files_by_state(self):
        files = {
            'modified': self.modified_files,
            'added': self.added_files,
            'removed': self.removed_files
        }

        # "Flip" the dictionary
        return {
            path: state
            for state, paths in files.items()
            for path in paths
        }

    """
    @property
    @funky.transform(set)
    def modified_files(self):
        return self.modified_staged_files | self.modified_unstaged_files
    """

    # Like: git add
    @funky.arglist_method
    def stage(self, files):
        return self.repo.stage(files)

    def add(self, *args, **kwargs):
        return self.stage(*args, **kwargs)

    # Like: git rm
    @funky.arglist_method
    def rm(self, files, force=False):
        index = self.index
        index_files = filter(lambda f: f in index, files)
        for f in index_files:
            del self.index[f]
        return index.write()

    def mv_fs(self, file_pair):
        old_name, new_name = file_pair
        os.rename(old_name, new_name)

    # Like: git mv
    @funky.arglist_method
    def mv(self, files_pair):
        index = self.index
        files_in_index = filter(lambda f: f[0] in index, files_pair)
        map(self.mv_fs, files_in_index)
        old_files = map(funky.first, files_in_index)
        new_files = map(funky.last, files_in_index)
        self.add(new_files)
        self.rm(old_files)
        self.add(old_files)
        return

    @working_only
    def _checkout_tree(self, tree):
        return build_index_from_tree(
            self.repo.path,
            self.repo.index_path(),
            self.repo.object_store,
            tree
        )

    def checkout_all(self, commit_sha=None):
        commit_sha = commit_sha or self.head
        commit_tree = self._commit_tree(commit_sha)
        # Rebuild index from the current tree
        return self._checkout_tree(commit_tree)

    def checkout(self, ref):
        """Checkout a given ref or SHA
        """
        self.repo.refs.set_symbolic_ref('HEAD', ref)
        commit_tree = self._commit_tree(ref)
        # Rebuild index from the current tree
        return self._checkout_tree(commit_tree)

    @funky.arglist_method
    def reset(self, files, commit='HEAD'):
        pass

    def rm_all(self):
        # if we go at the index via the property, it is reconstructed
        # each time and therefore clear() doesn't have the desired effect,
        # therefore, we cache it in a variable and use that.
        i = self.index
        i.clear()
        return i.write()

    def _to_commit(self, commit_obj):
        """Allows methods to accept both SHA's or dulwich Commit objects as arguments
        """
        if isinstance(commit_obj, basestring):
            return self.repo[commit_obj]
        return commit_obj

    def _commit_sha(self, commit_obj):
        """Extracts a Dulwich commits SHA
        """
        if utils.git.is_sha(commit_obj):
            return commit_obj
        elif isinstance(commit_obj, basestring):
            # Can't use self[commit_obj] to avoid infinite recursion
            commit_obj = self.repo[self.dwim_reference(commit_obj)]
        return commit_obj.id

    def dwim_reference(self, ref):
        """Dwim resolves a short reference to a full reference
        """

        # Formats of refs we want to try in order
        formats = [
            "%s",
            "refs/%s",
            "refs/tags/%s",
            "refs/heads/%s",
            "refs/remotes/%s",
            "refs/remotes/%s/HEAD",
        ]

        for f in formats:
            try:
                fullref = f % ref
                if not fullref in self.repo:
                    continue
                return fullref
            except:
                continue

        raise Exception("Could not resolve ref")

    def blob_data(self, sha):
        """Return a blobs content for a given SHA
        """
        return self[sha].data

    # Get the nth parent back for a given commit
    def get_parent_commit(self, commit, n=None):
        """ Recursively gets the nth parent for a given commit
            Warning: Remember that parents aren't the previous commits
        """
        if n is None:
            n = 1
        commit = self._to_commit(commit)
        parents = commit.parents

        if n <= 0 or not parents:
            # Return a SHA
            return self._commit_sha(commit)

        parent_sha = parents[0]
        parent = self[parent_sha]

        # Recur
        return self.get_parent_commit(parent, n - 1)

    def get_previous_commit(self, commit_ref, n=None):
        commit_sha = self._parse_reference(commit_ref)
        n = n or 1
        commits = self.commits()
        return funky.next(commits, commit_sha, n=n, default=commit_sha)

    def _parse_reference(self, ref_string):
        # COMMIT_REF~x
        if '~' in ref_string:
            ref, count = ref_string.split('~')
            count = int(count)
            commit_sha = self._commit_sha(ref)
            return self.get_previous_commit(commit_sha, count)
        return self._commit_sha(ref_string)

    def _commit_tree(self, commit_sha):
        """Return the tree object for a given commit
        """
        return self[commit_sha].tree

    def diff(self, commit_sha, compare_to=None, diff_type=None, filter_binary=True):
        diff_type = diff_type or self.DEFAULT_DIFF_TYPE
        diff_func = self.DIFF_FUNCTIONS[diff_type]

        if not compare_to:
            compare_to = self.get_previous_commit(commit_sha)

        return self._diff_between(compare_to, commit_sha, diff_function=diff_func)

    def diff_working(self, ref=None, filter_binary=True):
        """Diff between the current working directory and the HEAD
        """
        return utils.git.diff_changes_paths(
            self.repo.object_store,
            self.path,
            self._changed_entries(ref=ref),
            filter_binary=filter_binary
        )

    def get_commit_files(self, commit_sha, parent_path=None, is_tree=None, paths=None):
        """Returns a dict of the following Format :
            {
                "directory/filename.txt": {
                    'name': 'filename.txt',
                    'path': "directory/filename.txt",
                    "sha": "xxxxxxxxxxxxxxxxxxxx",
                    "data": "blablabla",
                    "mode": 0xxxxx",
                },
                ...
            }
        """
        # Default values
        context = {}
        is_tree = is_tree or False
        parent_path = parent_path or ''

        if is_tree:
            tree = self[commit_sha]
        else:
            tree = self[self._commit_tree(commit_sha)]

        for entry in tree.items():
            # Check if entry is a directory
            if entry.mode == self.MODE_DIRECTORY:
                context.update(
                    self.get_commit_files(entry.sha, parent_path=os.path.join(parent_path, entry.path), is_tree=True, paths=paths)
                )
                continue

            subpath = os.path.join(parent_path, entry.path)

            # Only add the files we want
            if not(paths is None or subpath in paths):
                continue

            # Add file entry
            context[subpath] = {
                'name': entry.path,
                'path': subpath,
                'mode': entry.mode,
                'sha': entry.sha,
                'data': self.blob_data(entry.sha),
            }
        return context

    def file_versions(self, path):
        """Returns all commits where given file was modified
        """
        versions = []
        commits_info = self.commit_info()
        seen_shas = set()

        for commit in commits_info:
            try:
                files = self.get_commit_files(commit['sha'], paths=[path])
                file_path, file_data = files.items()[0]
            except IndexError:
                continue

            file_sha = file_data['sha']

            if file_sha in seen_shas:
                continue
            else:
                seen_shas.add(file_sha)

            # Add file info
            commit['file'] = file_data
            versions.append(file_data)
        return versions

    def _diff_between(self, old_commit_sha, new_commit_sha, diff_function=None, filter_binary=True):
        """Internal method for getting a diff between two commits
            Please use .diff method unless you have very specific needs
        """

        # If commit is first commit (new_commit_sha == old_commit_sha)
        # then compare to an empty tree
        if new_commit_sha == old_commit_sha:
            old_tree = Tree()
        else:
            old_tree = self._commit_tree(old_commit_sha)

        new_tree = self._commit_tree(new_commit_sha)

        return diff_function(self.repo.object_store, old_tree, new_tree, filter_binary=filter_binary)

    def changes(self, *args, **kwargs):
        """ List of changes between two SHAs
            Returns a list of lists of tuples :
            [
                [
                    (oldpath, newpath), (oldmode, newmode), (oldsha, newsha)
                ],
                ...
            ]
        """
        kwargs['diff_type'] = 'changes'
        return self.diff(*args, **kwargs)

    def changes_count(self, *args, **kwargs):
        return len(self.changes(*args, **kwargs))

    def _refs_by_pattern(self, pattern):
        refs = self.refs

        def item_filter(key_value):
            """Filter only concered refs"""
            key, value = key_value
            return key.startswith(pattern)

        def item_map(key_value):
            """Rewrite keys"""
            key, value = key_value
            new_key = key[len(pattern):]
            return (new_key, value)

        return dict(
            map(item_map,
                filter(
                    item_filter,
                    refs.items()
                )
            )
        )

    @property
    def refs(self):
        return self.repo.get_refs()

    def set_refs(refs_dict):
        for k, v in refs_dict.items():
            self.repo[k] = v

    def import_refs(self, base, other):
        return self.repo.refs.import_refs(base, other)

    @property
    def branches(self):
        return self._refs_by_pattern(self.REFS_BRANCHES)

    @property
    def active_branch(self):
        """Returns the name of the active branch, or None, if HEAD is detached
        """
        x = self.repo.refs.read_ref('HEAD')
        if not x.startswith(SYMREF):
            return None
        else:
            symref = x[len(SYMREF):]
            if not symref.startswith(self.REFS_BRANCHES):
                return None
            else:
                return symref[len(self.REFS_BRANCHES):]

    @property
    def active_sha(self):
        """Deprecated equivalent to head property
        """
        return self.head

    @property
    def remote_branches(self):
        return self._refs_by_pattern(self.REFS_REMOTES)

    @property
    def tags(self):
        return self._refs_by_pattern(self.REFS_TAGS)

    @property
    def remotes(self):
        """ Dict of remotes
        {
            'origin': 'http://friendco.de/some_user/repo.git',
            ...
        }
        """
        config = self.repo.get_config()
        return {
            keys[1]: values['url']
            for keys, values in config.items()
            if keys[0] == 'remote'
        }

    def add_remote(self, remote_name, remote_url):
        # Get repo's config
        config = self.repo.get_config()

        # Add new entries for remote
        config.set(('remote', remote_name), 'url', remote_url)
        config.set(('remote', remote_name), 'fetch', "+refs/heads/*:refs/remotes/%s/*" % remote_name)

        # Write to disk
        config.write_to_path()

        return remote_name

    def add_ref(self, new_ref, old_ref):
        self.repo.refs[new_ref] = old_ref
        self.update_server_info()

    def remove_ref(self, ref_name):
        # Returns False if ref doesn't exist
        if not ref_name in self.repo.refs:
            return False
        del self.repo.refs[ref_name]
        self.update_server_info()
        return True

    def create_branch(self, base_branch, new_branch, tracking=None):
        """Try creating a new branch which tracks the given remote
            if such a branch does not exist then branch off a local branch
        """

        # The remote to track
        tracking = self.DEFAULT_REMOTE

        # Already exists
        if new_branch in self.branches:
            raise Exception("branch %s already exists" % new_branch)

        # Get information about remote_branch
        remote_branch = os.path.sep.join([tracking, base_branch])

        # Fork Local
        if base_branch in self.branches:
            base_ref = self._format_ref_branch(base_branch)
        # Fork remote
        elif remote_branch in self.remote_branches:
            base_ref = self._format_ref_remote(remote_branch)
            # TODO : track
        else:
            raise Exception("Can not find the branch named '%s' to fork either locally or in '%s'" % (base_branch, tracking))

        # Reference of new branch
        new_ref = self._format_ref_branch(new_branch)

        # Copy reference to create branch
        self.add_ref(new_ref, base_ref)

        return new_ref

    def create_orphan_branch(self, new_branch, empty_index=None):
        """ Create a new branch with no commits in it.
        Technically, just points HEAD to a non-existent branch.  The actual branch will
        only be created if something is committed.  This is equivalent to:

            git checkout --orphan <new_branch>,

        Unless empty_index is set to True, in which case the index will be emptied along
        with the file-tree (which is always emptied).  Against a clean working tree,
        this is equivalent to:

            git checkout --orphan <new_branch>
            git reset --merge
        """
        if new_branch in self.branches:
            raise Exception("branch %s already exists" % new_branch)

        new_ref = self._format_ref_branch(new_branch)
        self.repo.refs.set_symbolic_ref('HEAD', new_ref)

        if self.is_working:
            if empty_index:
               self.rm_all()
            self.clean_working()

        return new_ref

    def remove_branch(self, branch_name):
        ref = self._format_ref_branch(branch_name)
        return self.remove_ref(ref)

    def switch_branch(self, branch_name, tracking=None, create=None):
        """Changes the current branch
        """
        if create is None:
            create = True

        # Check if branch exists
        if not branch_name in self.branches:
            self.create_branch(branch_name, branch_name, tracking=tracking)

        # Get branch reference
        branch_ref = self._format_ref_branch(branch_name)

        # Change main branch
        self.repo.refs.set_symbolic_ref('HEAD', branch_ref)

        if self.is_working:
            # Remove all files
            self.clean_working()

            # Add files for the current branch
            self.checkout_all()

    def create_tag(self, tag_name, target):
        ref = self._format_ref_tag(tag_name)
        return self.add_ref(ref, self._parse_reference(target))

    def remove_tag(self, tag_name):
        ref = self._format_ref_tag(tag_name)
        return self.remove_ref(ref)

    def clean(self, force=None, directories=None):
        untracked_files = self.untracked_files
        map(os.remove, untracked_files)
        return untracked_files

    def clean_working(self):
        """Purges all the working (removes everything except .git)
            used by checkout_all to get clean branch switching
        """
        return self.clean()

    def _get_fs_structure(self, tree_sha, depth=None, parent_sha=None):
        tree = self[tree_sha]
        structure = {}
        if depth is None:
            depth = self.MAX_TREE_DEPTH
        elif depth == 0:
            return structure
        for entry in tree.items():
            # tree
            if entry.mode == self.MODE_DIRECTORY:
                # Recur
                structure[entry.path] = self._get_fs_structure(entry.sha, depth=depth - 1, parent_sha=tree_sha)
            # commit
            else:
                structure[entry.path] = entry.sha
        structure['.'] = tree_sha
        structure['..'] = parent_sha or tree_sha
        return structure

    def _get_fs_structure_by_path(self, tree_sha, path):
        parts = path.split(os.path.sep)
        depth = len(parts) + 1
        structure = self._get_fs_structure(tree_sha, depth=depth)

        return funky.subkey(structure, parts)

    def commit_ls(self, ref, subpath=None):
        """List a "directory" for a given commit
           using the tree of that commit
        """
        tree_sha = self._commit_tree(ref)

        # Root path
        if subpath in self.ROOT_PATHS or not subpath:
            return self._get_fs_structure(tree_sha, depth=1)
        # Any other path
        return self._get_fs_structure_by_path(tree_sha, subpath)

    def commit_file(self, ref, path):
        """Return info on a given file for a given commit
        """
        name, info = self.get_commit_files(ref, paths=[path]).items()[0]
        return info

    def commit_tree(self, ref, *args, **kwargs):
        tree_sha = self._commit_tree(ref)
        return self._get_fs_structure(tree_sha, *args, **kwargs)

    def update_server_info(self):
        if not self.is_bare:
            return
        update_server_info(self.repo)

    def _is_fast_forward(self):
        pass

    def _merge_fast_forward(self):
        pass

    def __hash__(self):
        """This is required otherwise the memoize function will just mess it up
        """
        return hash(self.path)

    def __getitem__(self, key):
        try:
            sha = self._parse_reference(key)
        except:
            raise KeyError(key)
        return self.repo[sha]

    def __setitem__(self, key, value):
        try:
            key = self.dwim_reference(key)
        except:
            pass
        self.repo[key] = value

    def __contains__(self, key):
        try:
            key = self.dwim_reference(key)
        except:
            pass
        return key in self.repo

    def __delitem__(self, key):
        try:
            key = self.dwim_reference(key)
        except:
            raise KeyError(key)
        self.remove_ref(key)


    # Alias to clone_bare
    fork = clone_bare
    log = commit_info
    diff_count = changes_count
    contributors = recent_contributors
Exemplo n.º 34
0
class Backend(BaseBackend):
    default_branch = 'master'
    null_revision = 'null'
    file_mode = 0100644
    directory_mode = 040755

    @property
    def repo(self):
        if not hasattr(self, '_repo'):
            self._repo = Repo(self.path)
        return self._repo

    def _get_commit(self, revision=None, branch=None):
        repo = self.repo
        if not revision:
            try:
                revision = repo.refs['refs/heads/%s' % branch]
            except KeyError:
                raise BranchDoesNotExist(self, branch)
        elif isinstance(revision, DulwichCommit):
            revision = revision.revision
        try:
            commit = repo[revision]
            if not isinstance(commit, Commit):
                raise CommitDoesNotExist(self, revision)
            return commit
        except KeyError:
            raise CommitDoesNotExist(self, revision)

    def _collect(self, tree, path, cache=None):
        result = [(None, None, tree)]
        bits = filter(None, path.split(os.path.sep))
        repo = self.repo
        for i, bit in enumerate(bits):
            found = False
            for mode, name, hexsha in tree.entries():
                if name == bit:
                    found = True
                    if cache and hexsha in cache:
                        tree = cache[hexsha]
                    else:
                        tree = repo[hexsha]
                    result.append((mode, name, tree))
                    break
            if not found:
                result += [(self.directory_mode, bit, Tree())
                           for bit in bits[i:]]
                break
        return result

    def _link(self, seq):
        cache = {}
        for i in xrange(len(seq) - 1, -1, -1):
            mode, name, obj = seq[i]
            cache[obj.id] = obj
            if i > 0:
                seq[i - 1][2][name] = (mode, obj.id)
        return cache

    ### repo ###

    def init_repo(self):
        if os.path.exists(self.path):
            return
        os.mkdir(self.path)
        self._repo = Repo.init_bare(self.path)

    def delete_repo(self):
        shutil.rmtree(self.path)

    ### branches ###

    def has_branch(self, name):
        return 'refs/heads/%s' % name in self.repo.refs

    def create_branch(self, name, revision=None):
        if self.has_branch(name):
            raise BranchDoesAlreadyExist(self, name)
        self.repo.refs['refs/heads/%s' % name] = self._get_commit(
            revision, 'master').id

    def delete_branch(self, name):
        try:
            del self.repo.refs['refs/heads/%s' % name]
        except KeyError:
            raise BranchDoesNotExist(self, name)

    def rename_branch(self, old_name, new_name):
        if old_name == new_name:
            return
        if not self.has_branch(old_name):
            raise BranchDoesNotExist(self, old_name)
        if self.has_branch(new_name):
            raise BranchDoesAlreadyExist(new_name)
        self.create_branch(new_name, 'refs/heads/%s' % old_name)
        self.delete_branch(old_name)

    ### api ###

    def revision(self, revision=None, branch='master'):
        return DulwichCommit(self, self._get_commit(revision, branch))

    def _walk(self, path, tree):
        repo = self.repo
        blobs, subtrees = [], []
        for mode, name, hexsha in tree.entries():
            if stat.S_ISREG(mode):
                blobs.append(name)
            elif stat.S_ISDIR(mode):
                subtrees.append(name)
        yield (path, subtrees, blobs)
        for name in subtrees:
            mode, hexsha = tree[name]
            for t in self._walk(os.path.join(path, name), repo[hexsha]):
                yield t

    def walk(self, path, revision=None, branch='master'):
        root = repo[self._get_commit(revision, branch).tree]
        return self._walk(path, root)

    def history(self,
                path=None,
                revision=None,
                branch='master',
                since_revision=None,
                since=None,
                sort=True):
        if revision == self.null_revision:
            return []

        if path is not None:
            path = clean_path(path)

        if since_revision:
            ctime = self.revision(since_revision).commit_time
            if since:
                since = max(ctime, since)
            else:
                since = ctime

        if revision or branch:
            pending = set([self._get_commit(revision, branch).id])
        else:
            pending = set(self._repo.get_refs().values())

        visited = set()
        result = []
        repo = self.repo

        while pending:
            commit_id = pending.pop()
            commit = self.revision(commit_id)

            if commit_id in visited:
                continue
            visited.add(commit_id)

            if since and since > commit.commit_time:
                continue

            if commit_id != since_revision:
                pending.update(commit._commit.parents)

            if path:
                tree = repo[commit._commit.tree]
                found = False
                parents = commit._commit.parents
                for parent in parents:
                    parent_tree = repo[repo[parent].tree]
                    if not is_same_object(repo, tree, parent_tree, path):
                        found = True
                        break
                if not parents and get_by_path(repo, tree, path):
                    found = True
                if not found:
                    continue
            result.append(commit)
        if sort:
            result.sort(key=attrgetter('commit_time'), reverse=True)
        return result

    def do_read(self, path, revision=None, branch='master'):
        path = clean_path(path)
        repo = self.repo
        if revision == self.null_revision:
            raise FileDoesNotExist(
                self, "'%s' does not exist at revision null" % path)
        c = self._get_commit(revision, branch)
        obj = get_by_path(repo, repo[c.tree], path)
        if not obj:
            raise FileDoesNotExist(self, "'%s' does not exist" % path)
        if not isinstance(obj, Blob):
            raise FileDoesNotExist(self, "'%s' is not a regular file" % path)
        data = obj.as_pretty_string()
        return data

    def do_commit(self,
                  message='',
                  author=None,
                  committer=None,
                  branch='master',
                  parent=None):
        if isinstance(message, unicode):
            message = message.encode('utf-8')
        repo = self.repo
        try:
            parent = self._get_commit(parent, branch)
            root = repo[parent.tree]
        except BranchDoesNotExist:
            if branch == 'master':  # initial commit
                root = Tree()
            else:
                raise

        cache = {}
        paths = set()
        objects = set()

        for path, (action, data) in self.changes.iteritems():
            path = clean_path(path)
            paths.add(path)
            dirname, filename = os.path.split(path)
            trees = self._collect(root, dirname, cache)

            if action == WRITE:
                blob = Blob.from_string(data)
                trees[-1][2][filename] = (self.file_mode, blob.id)
                cache[blob.id] = blob

            elif action == DELETE:
                del trees[-1][2][filename]

            elif action == RENAME:
                old = self._collect(root, data, cache)
                mode, name, obj = old[-1]
                del old[-2][2][name]
                trees[-1][2][filename] = (mode, obj.id)
                cache.update(self._link(old[:-1]))
                paths.add(data)

            cache.update(self._link(trees))
        else:
            objects.add(root)

        # collect all objects that have to be committed
        for path in paths:
            objects.update(
                [obj for mode, name, obj in self._collect(root, path, cache)])

        # create the commit
        c = Commit()
        if parent:
            c.parents = [parent.id]
        c.tree = root.id
        c.committer = committer or self.committer
        c.author = author or c.committer

        t = time.localtime()
        c.commit_time = c.author_time = int(time.mktime(t))
        c.commit_timezone = c.author_timezone = t.tm_isdst * 3600 - time.timezone
        c.encoding = "UTF-8"
        c.message = message
        objects.add(c)

        # write everything to disk
        for obj in objects:
            repo.object_store.add_object(obj)

        repo.refs['refs/heads/%s' % branch] = c.id

        return DulwichCommit(self, c)
Exemplo n.º 35
0
#!/usr/bin/env python2

import os.path
import sys
import glob
from email.utils import formatdate
from dulwich.repo import Repo
from dulwich.objects import Blob, Tree, Commit


repo = Repo(".")

import sys
if len(sys.argv) > 1:
    commit_sha = repo.get_refs()["refs/heads/"+sys.argv[1]]
else:
    commit_sha = repo.head()

commit = repo.get_object(commit_sha)

index = repo.open_index()
assert not list(index.changes_from_tree(repo.object_store, commit.tree)), "uncommited changes"


def add_blob(store, path):
    with open(path, "rb") as f:
        blob = Blob.from_string(f.read())
    store.add_object(blob)
    return blob.id

def add_files(store, tree, files):
Exemplo n.º 36
0
class Backend(BaseBackend):
    default_branch = 'master'
    null_revision = 'null'
    file_mode = 0100644
    directory_mode = 040755

    @property
    def repo(self):
        if not hasattr(self, '_repo'):
            self._repo = Repo(self.path)
        return self._repo
        
    def _get_commit(self, revision=None, branch=None):
        repo = self.repo
        if not revision:
            try:
                revision = repo.refs['refs/heads/%s' % branch]
            except KeyError:
                raise BranchDoesNotExist(self, branch)
        elif isinstance(revision, DulwichCommit):
            revision = revision.revision
        try:
            commit = repo[revision]
            if not isinstance(commit, Commit):
                raise CommitDoesNotExist(self, revision)
            return commit
        except KeyError:
            raise CommitDoesNotExist(self, revision)
    
    def _collect(self, tree, path, cache=None):
        result = [(None, None, tree)]
        bits = filter(None, path.split(os.path.sep))
        repo = self.repo
        for i, bit in enumerate(bits):
            found = False
            for mode, name, hexsha in tree.entries():
                if name == bit:
                    found = True
                    if cache and hexsha in cache:
                        tree = cache[hexsha]
                    else:
                        tree = repo[hexsha]
                    result.append((mode, name, tree))
                    break
            if not found:
                result += [(self.directory_mode, bit, Tree()) for bit in bits[i:]]
                break
        return result
        
    def _link(self, seq):
        cache = {}
        for i in xrange(len(seq) - 1, -1, -1):
            mode, name, obj = seq[i]
            cache[obj.id] = obj
            if i > 0:
                seq[i - 1][2][name] = (mode, obj.id)
        return cache
        
    ### repo ###

    def init_repo(self):
        if os.path.exists(self.path):
            return
        os.mkdir(self.path)
        self._repo = Repo.init_bare(self.path)
        
    def delete_repo(self):
        shutil.rmtree(self.path)
        
    ### branches ###

    def has_branch(self, name):
        return 'refs/heads/%s' % name in self.repo.refs
        
    def create_branch(self, name, revision=None):
        if self.has_branch(name):
            raise BranchDoesAlreadyExist(self, name)
        self.repo.refs['refs/heads/%s' % name] = self._get_commit(revision, 'master').id
        
    def delete_branch(self, name):
        try:
            del self.repo.refs['refs/heads/%s' % name]
        except KeyError:
            raise BranchDoesNotExist(self, name)
        
    def rename_branch(self, old_name, new_name):
        if old_name == new_name:
            return
        if not self.has_branch(old_name):
            raise BranchDoesNotExist(self, old_name)
        if self.has_branch(new_name):
            raise BranchDoesAlreadyExist(new_name)
        self.create_branch(new_name, 'refs/heads/%s' % old_name)
        self.delete_branch(old_name)
    
    ### api ###
    
    def revision(self, revision=None, branch='master'):
        return DulwichCommit(self, self._get_commit(revision, branch))
        
    def _walk(self, path, tree):
        repo = self.repo
        blobs, subtrees = [], []
        for mode, name, hexsha in tree.entries():
            if stat.S_ISREG(mode):
                blobs.append(name)
            elif stat.S_ISDIR(mode):
                subtrees.append(name)
        yield (path, subtrees, blobs)
        for name in subtrees:
            mode, hexsha = tree[name]
            for t in self._walk(os.path.join(path, name), repo[hexsha]):
                yield t
        
    def walk(self, path, revision=None, branch='master'):
        root = repo[self._get_commit(revision, branch).tree]
        return self._walk(path, root)
    
    def history(self, path=None, revision=None, branch='master', since_revision=None, since=None, sort=True):
        if revision == self.null_revision:
            return []

        if path is not None:
            path = clean_path(path)
        
        if since_revision:
            ctime = self.revision(since_revision).commit_time
            if since:
                since = max(ctime, since)
            else:
                since = ctime
                
        if revision or branch:
            pending = set([self._get_commit(revision, branch).id])
        else:
            pending = set(self._repo.get_refs().values())

        visited = set()
        result = []
        repo = self.repo

        while pending:
            commit_id = pending.pop()
            commit = self.revision(commit_id)
            
            if commit_id in visited:
                continue
            visited.add(commit_id)
            
            if since and since > commit.commit_time:
                continue

            if commit_id != since_revision:
                pending.update(commit._commit.parents)
                
            if path:
                tree = repo[commit._commit.tree]
                found = False
                parents = commit._commit.parents
                for parent in parents:
                    parent_tree = repo[repo[parent].tree]
                    if not is_same_object(repo, tree, parent_tree, path):
                        found = True
                        break
                if not parents and get_by_path(repo, tree, path):
                    found = True
                if not found:
                    continue
            result.append(commit)
        if sort:
            result.sort(key=attrgetter('commit_time'), reverse=True)
        return result
        
    def do_read(self, path, revision=None, branch='master'):
        path = clean_path(path)
        repo = self.repo
        if revision == self.null_revision:
            raise FileDoesNotExist(self, "'%s' does not exist at revision null" % path)
        c = self._get_commit(revision, branch)
        obj = get_by_path(repo, repo[c.tree], path)
        if not obj:
            raise FileDoesNotExist(self, "'%s' does not exist" % path)
        if not isinstance(obj, Blob):
            raise FileDoesNotExist(self, "'%s' is not a regular file" % path)
        data = obj.as_pretty_string()
        return data
        
    def do_commit(self, message='', author=None, committer=None, branch='master', parent=None):
        if isinstance(message, unicode):
            message = message.encode('utf-8')
        repo = self.repo
        try:
            parent = self._get_commit(parent, branch)
            root = repo[parent.tree]
        except BranchDoesNotExist:
            if branch == 'master': # initial commit
                root = Tree()
            else:
                raise
            
        cache = {}
        paths = set()
        objects = set()
        
        for path, (action, data) in self.changes.iteritems():
            path = clean_path(path)
            paths.add(path)
            dirname, filename = os.path.split(path)
            trees = self._collect(root, dirname, cache)

            if action == WRITE:
                blob = Blob.from_string(data)
                trees[-1][2][filename] = (self.file_mode, blob.id)
                cache[blob.id] = blob

            elif action == DELETE:
                del trees[-1][2][filename]

            elif action == RENAME:
                old = self._collect(root, data, cache)
                mode, name, obj = old[-1]
                del old[-2][2][name]
                trees[-1][2][filename] = (mode, obj.id)
                cache.update(self._link(old[:-1]))
                paths.add(data)

            cache.update(self._link(trees))
        else:
            objects.add(root)

        # collect all objects that have to be committed
        for path in paths:
            objects.update([obj for mode, name, obj in self._collect(root, path, cache)])
            
        # create the commit
        c = Commit()
        if parent:
            c.parents = [parent.id]
        c.tree = root.id
        c.committer = committer or self.committer
        c.author = author or c.committer

        t = time.localtime()
        c.commit_time = c.author_time = int(time.mktime(t))
        c.commit_timezone = c.author_timezone = t.tm_isdst * 3600 - time.timezone
        c.encoding = "UTF-8"
        c.message = message
        objects.add(c)

        # write everything to disk
        for obj in objects:
            repo.object_store.add_object(obj)

        repo.refs['refs/heads/%s' % branch] = c.id

        return DulwichCommit(self, c)
Exemplo n.º 37
0
class Gits3(object):

    def __init__(self, path):
        self.path = path
        self.open_repo(path)

    def open_repo(self, path):
        self.repo = Repo(path)
  
    def get_id(self, ref):
        return self.repo.get_refs()[ref]
             
    def get_updates(self, local_ref, tracking_ref):        
        
                
        refs = self.repo.get_refs()
        for key, value in refs.iteritems():
           print key, value
        
        local = refs[local_ref]
        try:
            remote = refs[tracking_ref]
        except KeyError:
            remote = None
            
        
        if local == remote:
            return None
        
        local_object = self.repo.get_object(local)
        
        
        commits = self.get_commits(local_object, [remote])
        objects = self.get_objects(commits)
        print objects
        
        if remote:
            remote_object = self.repo.get_object(remote)
            filtered_objects = self.filter_objects(objects, remote_object)
        else:
            filtered_objects = objects
        
        filtered_objects = set(filtered_objects)
        return filtered_objects
        
        
    def filter_objects(self, objects, old_commit):
        filtered = []
        old_treeId = old_commit.tree
        old_objects = self.get_objects_in_tree(old_treeId)
        for object in objects:
            if object not in old_objects:
                filtered.append(object)
                
        return filtered
        
    def get_commits(self, interesting, uninteresting):
        commits = [interesting]
        remaining = interesting.get_parents()
        
        while remaining:
            pId = remaining.pop(0)
            if pId in uninteresting:
                continue    
            else:
                parent = self.repo.get_object(pId)
                commits.append(parent)
                parents = parent.get_parents()
                remaining.extend(parents)
        return commits
    
    
    def get_objects(self, commits):
        objects = []
        while commits:
            commit = commits.pop(0)
            objects.append(commit)
            objects.extend(self.get_objects_in_tree(commit.tree))
        return objects    
            
            
    
    def get_objects_in_tree(self, treeId):
        objects = []
        tree = self.repo.get_object(treeId)
        objects.append(tree)
        entries = tree.entries()
        for entryId in entries:
            # get the entry's sha 
            objectId = entryId[2]
            object = self.repo.get_object(objectId) 
            if isinstance(object, Tree):
                objects.extend(self.get_objects_in_tree(objectId))
            else:
                objects.append(object)    
        return objects
      
    def generate_pack_name(self, objects):
        m = hashlib.sha1()
        for object in objects:
            sha1 = object.sha().hexdigest()
#            print sha1
            m.update(sha1)
        file_name = m.hexdigest()
#        print 'File Name is ', file_name
        return file_name
    
    
    def write_pack(self, pack_name, objects):
         write_pack('pack-' + pack_name, [(x, "") for x in objects], 
            len(objects))
        
    def find_tracking_ref_names(self, fetch, refs):
        if fetch[0] == '+':
            fetch = fetch[1:]
        tmp = fetch.split(':')
        src = tmp[0]
        dst = tmp[1]
        
        # TODO double check that both src and dst have wild cards, or both don't
        
        # match the source with refs
        if src.endswith('*') and refs.startswith(src[:-1]):
            return self.expand_from_src(src, dst, refs)
        else:
            return dst                                         
            
        
    def expand_from_src(self, src, dst, refs):
        return dst[:-1] + refs[len(src)-1:]
Exemplo n.º 38
0
class GitHandler(object):

    def __init__(self, dest_repo, ui):
        self.repo = dest_repo
        self.ui = ui
        self.mapfile = 'git-mapfile'
        self.configfile = 'git-config'

        if ui.config('git', 'intree'):
            self.gitdir = self.repo.wjoin('.git')
        else:
            self.gitdir = self.repo.join('git')

        self.importbranch = ui.config('git', 'importbranch')
        self.exportbranch = ui.config('git', 'exportbranch', 'refs/heads/master')
        self.bookbranch = ui.config('git', 'bookbranch', '')

        self.init_if_missing()
        self.load_git()
        self.load_map()
        self.load_config()

    # make the git data directory
    def init_if_missing(self):
        if not os.path.exists(self.gitdir):
            os.mkdir(self.gitdir)
            Repo.init_bare(self.gitdir)

    def load_git(self):
        self.git = Repo(self.gitdir)

    ## FILE LOAD AND SAVE METHODS

    def map_set(self, gitsha, hgsha):
        self._map_git[gitsha] = hgsha
        self._map_hg[hgsha] = gitsha

    def map_hg_get(self, gitsha):
	return self._map_git.get(gitsha)

    def map_git_get(self, hgsha):
	return self._map_hg.get(hgsha)

    def load_map(self):
        self._map_git = {}
        self._map_hg = {}
        if os.path.exists(self.repo.join(self.mapfile)):
            for line in self.repo.opener(self.mapfile):
                gitsha, hgsha = line.strip().split(' ', 1)
                self._map_git[gitsha] = hgsha
                self._map_hg[hgsha] = gitsha

    def save_map(self):
        file = self.repo.opener(self.mapfile, 'w+', atomictemp=True)
        for gitsha, hgsha in sorted(self._map_git.iteritems()):
            file.write("%s %s\n" % (gitsha, hgsha))
        file.rename()

    def load_config(self):
        self._config = {}
        if os.path.exists(self.repo.join(self.configfile)):
            for line in self.repo.opener(self.configfile):
                key, value = line.strip().split(' ', 1)
                self._config[key] = value

    def save_config(self):
        file = self.repo.opener(self.configfile, 'w+', atomictemp=True)
        for key, value in self._config.iteritems():
            file.write("%s %s\n" % (key, value))
        file.rename()


    ## END FILE LOAD AND SAVE METHODS

    def import_commits(self, remote_name):
        self.import_git_objects(remote_name)
        self.save_map()

    def fetch(self, remote_name):
        self.ui.status(_("fetching from : %s\n") % remote_name)
        self.export_git_objects()
        refs = self.fetch_pack(remote_name)
        if refs:
            self.import_git_objects(remote_name, refs)
            self.import_local_tags(refs)
        self.save_map()

    def export_commits(self):
        self.export_git_objects()
        self.export_hg_tags()
        self.update_references()
        self.save_map()

    def push(self, remote_name):
        self.ui.status(_("pushing to : %s\n") % remote_name)
        self.export_commits()
        self.update_remote_references(remote_name)
        self.upload_pack(remote_name)

    def remote_add(self, remote_name, git_url):
        self._config['remote.' + remote_name + '.url'] = git_url
        self.save_config()

    def remote_remove(self, remote_name):
        key = 'remote.' + remote_name + '.url'
        if key in self._config:
            del self._config[key]
        self.save_config()

    def remote_show(self, remote_name):
        key = 'remote.' + remote_name + '.url'
        if key in self._config:
            name = self._config[key]
            self.ui.status(_("URL for %s : %s\n") % (remote_name, name, ))
        else:
            self.ui.status(_("No remote named : %s\n") % remote_name)
        return

    def remote_list(self):
        for key, value in self._config.iteritems():
            if key[0:6] == 'remote':
                self.ui.status('%s\t%s\n' % (key, value, ))

    def remote_name_to_url(self, remote_name):
        return self._config['remote.' + remote_name + '.url']

    def update_references(self):
        try:
            # We only care about bookmarks of the form 'name',
            # not 'remote/name'.
            def is_local_ref(item): return item[0].count('/') == 0
            bms = bookmarks.parse(self.repo)
            bms = dict(filter(is_local_ref, bms.items()))

            # Create a local Git branch name for each
            # Mercurial bookmark.
            for key in bms:
                hg_sha  = hex(bms[key])
                git_sha = self.map_git_get(hg_sha)
                self.git.set_ref('refs/heads/' + key, git_sha)
        except AttributeError:
            # No bookmarks extension
            pass

        c = self.map_git_get(hex(self.repo.changelog.tip()))
        self.git.set_ref(self.exportbranch, c)

    def export_hg_tags(self):
        for tag, sha in self.repo.tags().iteritems():
            if tag[-3:] == '^{}':
                continue
            if tag == 'tip':
                continue 
            self.git.set_ref('refs/tags/' + tag, self.map_git_get(hex(sha)))

    # Make sure there's a refs/remotes/remote_name/name
    #           for every refs/heads/name
    def update_remote_references(self, remote_name):
        self.git.set_remote_refs(self.local_heads(), remote_name)

    def local_heads(self):
        def is_local_head(item): return item[0].startswith('refs/heads')
        refs = self.git.get_refs()
        return dict(filter(is_local_head, refs.items()))

    def export_git_objects(self):
        self.ui.status(_("importing Hg objects into Git\n"))
        total = len(self.repo.changelog)
        if total:
          magnitude = int(math.log(total, 10)) + 1
        else:
          magnitude = 1
        for i, rev in enumerate(self.repo.changelog):
            if i%100 == 0:
                self.ui.status(_("at: %*d/%d\n") % (magnitude, i, total))
            
            ctx = self.repo.changectx(rev)
            state = ctx.extra().get('hg-git', None)
            if state == 'octopus':
                self.ui.debug("revision %d is a part of octopus explosion\n" % rev)
                continue
            pgit_sha, already_written = self.export_hg_commit(rev)
            if not already_written:
                self.save_map()

    # convert this commit into git objects
    # go through the manifest, convert all blobs/trees we don't have
    # write the commit object (with metadata info)
    def export_hg_commit(self, rev):
        def is_octopus_part(ctx):
            return ctx.extra().get('hg-git', None) in set(['octopus', 'octopus-done'])

        # return if we've already processed this
        node = self.repo.changelog.lookup(rev)
        phgsha = hex(node)
        pgit_sha = self.map_git_get(phgsha)
        if pgit_sha:
            return pgit_sha, True

        self.ui.status(_("converting revision %s\n") % str(rev))

        # make sure parents are converted first
        ctx = self.repo.changectx(rev)
        extra = ctx.extra()

        parents = []
        if extra.get('hg-git', None) == 'octopus-done':
            # implode octopus parents
            part = ctx
            while is_octopus_part(part):
                (p1, p2) = part.parents()
                assert not is_octopus_part(p1)
                parents.append(p1)
                part = p2
            parents.append(p2)
        else:
            parents = ctx.parents()

        for parent in parents:
            p_rev = parent.rev()
            hgsha = hex(parent.node())
            git_sha = self.map_git_get(hgsha)
            if not p_rev == -1:
                if not git_sha:
                    self.export_hg_commit(p_rev)

        tree_sha, renames = self.write_git_tree(ctx)
        
        commit = {}
        commit['tree'] = tree_sha
        (time, timezone) = ctx.date()

        # hg authors might not have emails
        author = ctx.user()
        if not '>' in author: 
            author = author + ' <none@none>'
        commit['author'] = author + ' ' + str(int(time)) + ' ' + format_timezone(-timezone)
        message = ctx.description()
        commit['message'] = ctx.description() + "\n"

        if 'committer' in extra:
            # fixup timezone
            (name_timestamp, timezone) = extra['committer'].rsplit(' ', 1)
            try:
                timezone = format_timezone(-int(timezone))
                commit['committer'] = '%s %s' % (name_timestamp, timezone)
            except ValueError:
                self.ui.warn(_("Ignoring committer in extra, invalid timezone in r%s: '%s'.\n") % (rev, timezone))
        if 'encoding' in extra:
            commit['encoding'] = extra['encoding']

        # HG EXTRA INFORMATION
        add_extras = False
        extra_message = ''
        if not ctx.branch() == 'default':
            add_extras = True
            extra_message += "branch : " + ctx.branch() + "\n"

        if renames:
            add_extras = True
            for oldfile, newfile in renames:
                extra_message += "rename : " + oldfile + " => " + newfile + "\n"

        for key, value in extra.iteritems():
            if key in ['committer', 'encoding', 'branch', 'hg-git', 'git']:
                continue
            else:
                add_extras = True        
                extra_message += "extra : " + key + " : " +  urllib.quote(value) + "\n"

        if add_extras:
            commit['message'] += "\n--HG--\n" + extra_message

        commit['parents'] = []
        for parent in parents:
            hgsha = hex(parent.node())
            git_sha = self.map_git_get(hgsha)
            if git_sha:
                commit['parents'].append(git_sha)

        commit_sha = self.git.write_commit_hash(commit) # writing new blobs to git
        self.map_set(commit_sha, phgsha)
        return commit_sha, False

    def write_git_tree(self, ctx):
        trees = {}
        man = ctx.manifest()
        renames = []
        for filenm in man.keys():
            # write blob if not in our git database
            fctx = ctx.filectx(filenm)
            rename = fctx.renamed()
            if rename:
                filerename, sha = rename
                renames.append((filerename, filenm))
            is_exec = 'x' in fctx.flags()
            is_link = 'l' in fctx.flags()
            file_id = hex(fctx.filenode())
            blob_sha = self.map_git_get(file_id)
            if not blob_sha:
                blob_sha = self.git.write_blob(fctx.data()) # writing new blobs to git
                self.map_set(blob_sha, file_id)

            parts = filenm.split('/')
            if len(parts) > 1:
                # get filename and path for leading subdir
                filepath = parts[-1:][0]
                dirpath = "/".join([v for v in parts[0:-1]]) + '/'

                # get subdir name and path for parent dir
                parpath = '/'
                nparpath = '/'
                for part in parts[0:-1]:
                    if nparpath == '/':
                        nparpath = part + '/'
                    else:
                        nparpath += part + '/'

                    treeentry = ['tree', part + '/', nparpath]

                    if parpath not in trees:
                        trees[parpath] = []
                    if treeentry not in trees[parpath]:
                        trees[parpath].append( treeentry )

                    parpath = nparpath

                # set file entry
                fileentry = ['blob', filepath, blob_sha, is_exec, is_link]
                if dirpath not in trees:
                    trees[dirpath] = []
                trees[dirpath].append(fileentry)

            else:
                fileentry = ['blob', parts[0], blob_sha, is_exec, is_link]
                if '/' not in trees:
                    trees['/'] = []
                trees['/'].append(fileentry)

        dirs = trees.keys()
        if dirs:
            # sort by tree depth, so we write the deepest trees first
            dirs.sort(lambda a, b: len(b.split('/'))-len(a.split('/')))
            dirs.remove('/')
            dirs.append('/')
        else:
            # manifest is empty => make empty root tree
            trees['/'] = []
            dirs = ['/']

        # write all the trees
        tree_sha = None
        tree_shas = {}
        for dirnm in dirs:
            tree_data = []
            for entry in trees[dirnm]:
                # replace tree path with tree SHA
                if entry[0] == 'tree':
                    sha = tree_shas[entry[2]]
                    entry[2] = sha
                tree_data.append(entry)
            tree_sha = self.git.write_tree_array(tree_data) # writing new trees to git
            tree_shas[dirnm] = tree_sha
        
        return (tree_sha, renames) # should be the last root tree sha

    def remote_head(self, remote_name):
        for head, sha in self.git.remote_refs(remote_name).iteritems():
            if head == 'HEAD':
                return self.map_hg_get(sha)
        return None

    def upload_pack(self, remote_name):
        git_url = self.remote_name_to_url(remote_name)
        client, path = self.get_transport_and_path(git_url)
        changed = self.get_changed_refs
        genpack = self.generate_pack_contents
        try:
            self.ui.status(_("creating and sending data\n"))
            changed_refs = client.send_pack(path, changed, genpack)
            if changed_refs:
                new_refs = {}
                for ref, sha in changed_refs.iteritems():
                    self.ui.status("    "+ remote_name + "::" + ref + " => GIT:" + sha[0:8] + "\n")
                    new_refs[ref] = sha
                self.git.set_remote_refs(new_refs, remote_name)
                self.update_hg_bookmarks(remote_name)
        except:
            # TODO : remove try/except or do something useful here
            raise

    # TODO : for now, we'll just push all heads that match remote heads
    #        * we should have specified push, tracking branches and --all
    # takes a dict of refs:shas from the server and returns what should be
    # pushed up
    def get_changed_refs(self, refs):
        keys = refs.keys()

        changed = {}
        if not keys:
            return None

        # TODO : this is a huge hack
        if keys[0] == 'capabilities^{}': # nothing on the server yet - first push
            changed['refs/heads/master'] = self.git.ref('master')

        tags = self.git.get_tags()
        for tag, sha in tags.iteritems():
            tag_name = 'refs/tags/' + tag
            if tag_name not in refs:
                changed[tag_name] = sha

        for ref_name in keys:
            parts = ref_name.split('/')
            if parts[0] == 'refs': # strip off 'refs/heads'
                if parts[1] == 'heads':
                    head = "/".join([v for v in parts[2:]])
                    local_ref = self.git.ref(ref_name)
                    if local_ref:
                        if not local_ref == refs[ref_name]:
                            changed[ref_name] = local_ref
        
        # Also push any local branches not on the server yet
        for head in self.local_heads():
            if not head in refs:
                ref = self.git.ref(head)
                changed[head] = ref

        return changed

    # takes a list of shas the server wants and shas the server has
    # and generates a list of commit shas we need to push up
    def generate_pack_contents(self, want, have):
        graph_walker = SimpleFetchGraphWalker(want, self.git.get_parents)
        next = graph_walker.next()
        shas = set()
        while next:
            if next in have:
                graph_walker.ack(next)
            else:
                shas.add(next)
            next = graph_walker.next()
        
        seen = []
        
        # so now i have the shas, need to turn them into a list of
        # tuples (sha, path) for ALL the objects i'm sending
        # TODO : don't send blobs or trees they already have
        def get_objects(tree, path):
            changes = list()
            changes.append((tree, path))
            for (mode, name, sha) in tree.entries():
                if mode == 0160000: # TODO : properly handle submodules and document what 57344 means
                    continue
                if sha in seen:
                    continue
                    
                obj = self.git.get_object(sha)
                seen.append(sha)
                if isinstance (obj, Blob):
                    changes.append((obj, path + name))
                elif isinstance(obj, Tree):
                    changes.extend(get_objects(obj, path + name + '/'))
            return changes

        objects = []
        for commit_sha in shas:
            commit = self.git.commit(commit_sha)
            objects.append((commit, 'commit'))
            tree = self.git.get_object(commit.tree)
            objects.extend( get_objects(tree, '/') )

        return objects

    def fetch_pack(self, remote_name):
        git_url = self.remote_name_to_url(remote_name)
        client, path = self.get_transport_and_path(git_url)
        graphwalker = SimpleFetchGraphWalker(self.git.heads().values(), self.git.get_parents)
        f, commit = self.git.object_store.add_pack()
        try:
            determine_wants = self.git.object_store.determine_wants_all
            refs = client.fetch_pack(path, determine_wants, graphwalker, f.write, sys.stdout.write)
            f.close()
            commit()
            if refs:
                self.git.set_remote_refs(refs, remote_name)
            else:
                self.ui.status(_("nothing new on the server\n"))
            return refs
        except:
            f.close()
            raise

    # take refs just fetched, add local tags for all tags not in .hgtags
    def import_local_tags(self, refs):
        keys = refs.keys()
        if not keys:
            return None
        for k in keys[0:]:
            ref_name = k
            parts = k.split('/')
            if (parts[0] == 'refs' and parts[1] == 'tags'):
                ref_name = "/".join([v for v in parts[2:]])
                if ref_name[-3:] == '^{}':
                    ref_name = ref_name[:-3]
                if not ref_name in self.repo.tags():
                    obj = self.git.get_object(refs[k])
                    sha = None
                    if isinstance (obj, Commit): # lightweight
                        sha = self.map_hg_get(refs[k])
                    if isinstance (obj, Tag): # annotated
                        (obj_type, obj_sha) = obj.get_object()
                        obj = self.git.get_object(obj_sha)
                        if isinstance (obj, Commit):                
                            sha = self.map_hg_get(obj_sha)
                    if sha:
                        self.repo.tag(ref_name, hex_to_sha(sha), '', True, None, None)
                    
        
    def import_git_objects(self, remote_name=None, refs=None):
        self.ui.status(_("importing Git objects into Hg\n"))
        # import heads and fetched tags as remote references
        todo = []
        done = set()
        convert_list = {}
        self.renames = {}

        # get a list of all the head shas
        if refs: 
          for head, sha in refs.iteritems():
            todo.append(sha)
        else:
          if remote_name:
              todo = self.git.remote_refs(remote_name).values()[:]
          elif self.importbranch:
              branches = self.importbranch.split(',')
              todo = [self.git.ref(i.strip()) for i in branches]
          else:
              todo = self.git.heads().values()[:]

        # traverse the heads getting a list of all the unique commits
        while todo:
            sha = todo.pop()
            assert isinstance(sha, str)
            if sha in done:
                continue
            done.add(sha)
            obj = self.git.get_object(sha)
            if isinstance (obj, Commit):                
                convert_list[sha] = obj
                todo.extend([p for p in obj.parents if p not in done])
            if isinstance(obj, Tag):
                (obj_type, obj_sha) = obj.get_object()
                obj = self.git.get_object(obj_sha)
                if isinstance (obj, Commit):                
                    convert_list[sha] = obj
                    todo.extend([p for p in obj.parents if p not in done])

        # sort the commits
        commits = toposort.TopoSort(convert_list).items()
        
        # import each of the commits, oldest first
        total = len(commits)
        magnitude = int(math.log(total, 10)) + 1 if total else 1
        for i, csha in enumerate(commits):
            if i%100 == 0:
                self.ui.status(_("at: %*d/%d\n") % (magnitude, i, total))
            commit = convert_list[csha]
            if not self.map_hg_get(csha): # it's already here
                self.import_git_commit(commit)
            else:
                # we need to get rename info for further upstream
                self.pseudo_import_git_commit(commit)

        self.update_hg_bookmarks(remote_name)

    def update_hg_bookmarks(self, remote_name):
        try:
            bms = bookmarks.parse(self.repo)
            if remote_name:
                heads = self.git.remote_refs(remote_name)
            else:
                branches = self.bookbranch.split(',')
                heads = dict((i, self.git.ref(i.strip())) for i in branches)

            base_name = (remote_name + '/') if remote_name else '' 

            for head, sha in heads.iteritems():
                if not sha:
                    self.ui.warn(_("Could not resolve head %s.\n") % head)
                    continue
                hgsha = hex_to_sha(self.map_hg_get(sha))
                if not head == 'HEAD':
                    bms[base_name + head] = hgsha
            if heads:
                bookmarks.write(self.repo, bms)

        except AttributeError:
            self.ui.warn(_('creating bookmarks failed, do you have'
                         ' bookmarks enabled?\n'))

    def convert_git_int_mode(self, mode):
	# TODO : make these into constants
        convert = {
         0100644: '',
         0100755: 'x',
         0120000: 'l'}
        if mode in convert:
            return convert[mode]
        return ''

    def extract_hg_metadata(self, message):
        split = message.split("\n\n--HG--\n", 1)
        renames = {}
        extra = {}
        files = []
        branch = False
        if len(split) == 2:
            message, meta = split
            lines = meta.split("\n")
            for line in lines:
                if line == '':
                    continue 
                
                command, data = line.split(" : ", 1)
                
                if command == 'rename':
                    before, after = data.split(" => ", 1)
                    renames[after] = before
                if command == 'branch':
                    branch = data
                if command == 'files':
                    files.append(data)
                if command == 'extra':
                    before, after = data.split(" : ", 1)
                    extra[before] = urllib.unquote(after)
        return (message, renames, branch, files, extra)

    def pseudo_import_git_commit(self, commit):
        (strip_message, hg_renames, hg_branch) = self.extract_hg_metadata(commit.message)
        cs = self.map_hg_get(commit.id)
        p1 = nullid
        p2 = nullid
        if len(commit.parents) > 0:
            sha = commit.parents[0]
            p1 = self.map_hg_get(sha)
        if len(commit.parents) > 1:
            sha = commit.parents[1]
            p2 = self.map_hg_get(sha)
        if len(commit.parents) > 2:
            # TODO : map extra parents to the extras file
            pass
        # saving rename info
        if (not (p2 == nullid) or (p1 == nullid)):
            self.renames[cs] = {}
        else:
            self.renames[cs] = self.renames[p1].copy()

        self.renames[cs].update(hg_renames)
    
    def import_git_commit(self, commit):
        self.ui.debug(_("importing: %s\n") % commit.id)
        # TODO : Do something less coarse-grained than try/except on the
        #        get_file call for removed files

        (strip_message, hg_renames, hg_branch, files, extra) = self.extract_hg_metadata(commit.message)
        
        # get a list of the changed, added, removed files
        files = self.git.get_files_changed(commit)

        date = (commit.author_time, -commit.author_timezone)
        text = strip_message

        def getfilectx(repo, memctx, f):
            try:
                (mode, sha, data) = self.git.get_file(commit, f)
                e = self.convert_git_int_mode(mode)
            except TypeError:
                raise IOError()
            if f in hg_renames:
                copied_path = hg_renames[f]
            else:
                copied_path = None
            return context.memfilectx(f, data, 'l' in e, 'x' in e, copied_path)

        gparents = map(self.map_hg_get, commit.parents)
        p1, p2 = (nullid, nullid)
        octopus = False

        if len(gparents) > 1:
            # merge, possibly octopus
            def commit_octopus(p1, p2):
                ctx = context.memctx(self.repo, (p1, p2), text, files, getfilectx,
                                     commit.author, date, {'hg-git': 'octopus'})
                return hex(self.repo.commitctx(ctx))

            octopus = len(gparents) > 2
            p2 = gparents.pop()
            p1 = gparents.pop()
            while len(gparents) > 0:
                p2 = commit_octopus(p1, p2)
                p1 = gparents.pop()
        else:
            if gparents:
                p1 = gparents.pop()

        # wierd hack for explicit file renames in first but not second branch
        if not (p2 == nullid):
            vals = [item for item in self.renames[p1].values() if not item in self.renames[p2].values()]
            for removefile in vals:
                files.remove(removefile)
        author = commit.author

        extra = {}
        if ' <none@none>' in commit.author:
            author = commit.author[:-12]

        # if named branch, add to extra
        if hg_branch:
            extra['branch'] = hg_branch

        # if committer is different than author, add it to extra
        if not commit._author_raw == commit._committer_raw:
            extra['committer'] = "%s %d %d" % (commit.committer, commit.commit_time, -commit.commit_timezone)

        if commit._encoding:
            extra['encoding'] = commit._encoding

        if hg_branch:
            extra['branch'] = hg_branch

        if octopus:
            extra['hg-git'] ='octopus-done'

        ctx = context.memctx(self.repo, (p1, p2), text, files, getfilectx,
                             author, date, extra)
        node = self.repo.commitctx(ctx)

        # save changeset to mapping file
        cs = hex(node)
        self.map_set(commit.id, cs)
        
        # saving rename info
        if (not (p2 == nullid) or (p1 == nullid)):
            self.renames[cs] = {}
        else:
            self.renames[cs] = self.renames[p1].copy()
            
        self.renames[cs].update(hg_renames)
        

    def check_bookmarks(self):
        if self.ui.config('extensions', 'hgext.bookmarks') is not None:
            self.ui.warn("YOU NEED TO SETUP BOOKMARKS\n")

    def get_transport_and_path(self, uri):
        from dulwich.client import TCPGitClient, SSHGitClient, SubprocessGitClient
        for handler, transport in (("git://", TCPGitClient), ("git@", SSHGitClient), ("git+ssh://", SSHGitClient)):
            if uri.startswith(handler):
                if handler == 'git@':
                    host, path = uri[len(handler):].split(":", 1)
                    host = 'git@' + host
                else:
                    host, path = uri[len(handler):].split("/", 1)
                return transport(host), '/' + path
        # if its not git or git+ssh, try a local url..
        return SubprocessGitClient(), uri

    def clear(self):
        mapfile = self.repo.join(self.mapfile)
        if os.path.exists(self.gitdir):
            for root, dirs, files in os.walk(self.gitdir, topdown=False):
                for name in files:
                    os.remove(os.path.join(root, name))
                for name in dirs:
                    os.rmdir(os.path.join(root, name))
            os.rmdir(self.gitdir)
        if os.path.exists(mapfile):
            os.remove(mapfile)
Exemplo n.º 39
0
def get_branches(repo_path):
    repo = Repo(repo_path)
    return [
        ref[11:] for ref in repo.get_refs() if ref.startswith('refs/heads/')
    ]
Exemplo n.º 40
0
class DiskRefsContainerTests(RefsContainerTests, TestCase):
    def setUp(self):
        TestCase.setUp(self)
        self._repo = open_repo("refs.git")
        self.addCleanup(tear_down_repo, self._repo)
        self._refs = self._repo.refs

    def test_get_packed_refs(self):
        self.assertEqual(
            {
                b"refs/heads/packed": b"42d06bd4b77fed026b154d16493e5deab78f02ec",
                b"refs/tags/refs-0.1": b"df6800012397fb85c56e7418dd4eb9405dee075c",
            },
            self._refs.get_packed_refs(),
        )

    def test_get_peeled_not_packed(self):
        # not packed
        self.assertEqual(None, self._refs.get_peeled(b"refs/tags/refs-0.2"))
        self.assertEqual(b"3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8", self._refs[b"refs/tags/refs-0.2"])

        # packed, known not peelable
        self.assertEqual(self._refs[b"refs/heads/packed"], self._refs.get_peeled(b"refs/heads/packed"))

        # packed, peeled
        self.assertEqual(b"42d06bd4b77fed026b154d16493e5deab78f02ec", self._refs.get_peeled(b"refs/tags/refs-0.1"))

    def test_setitem(self):
        RefsContainerTests.test_setitem(self)
        f = open(os.path.join(self._refs.path, "refs", "some", "ref"), "rb")
        self.assertEqual(b"42d06bd4b77fed026b154d16493e5deab78f02ec", f.read()[:40])
        f.close()

    def test_setitem_symbolic(self):
        ones = b"1" * 40
        self._refs[b"HEAD"] = ones
        self.assertEqual(ones, self._refs[b"HEAD"])

        # ensure HEAD was not modified
        f = open(os.path.join(self._refs.path, "HEAD"), "rb")
        self.assertEqual(b"ref: refs/heads/master", next(iter(f)).rstrip(b"\n"))
        f.close()

        # ensure the symbolic link was written through
        f = open(os.path.join(self._refs.path, "refs", "heads", "master"), "rb")
        self.assertEqual(ones, f.read()[:40])
        f.close()

    def test_set_if_equals(self):
        RefsContainerTests.test_set_if_equals(self)

        # ensure symref was followed
        self.assertEqual(b"9" * 40, self._refs[b"refs/heads/master"])

        # ensure lockfile was deleted
        self.assertFalse(os.path.exists(os.path.join(self._refs.path, "refs", "heads", "master.lock")))
        self.assertFalse(os.path.exists(os.path.join(self._refs.path, "HEAD.lock")))

    def test_add_if_new_packed(self):
        # don't overwrite packed ref
        self.assertFalse(self._refs.add_if_new(b"refs/tags/refs-0.1", b"9" * 40))
        self.assertEqual(b"df6800012397fb85c56e7418dd4eb9405dee075c", self._refs[b"refs/tags/refs-0.1"])

    def test_add_if_new_symbolic(self):
        # Use an empty repo instead of the default.
        repo_dir = os.path.join(tempfile.mkdtemp(), "test")
        os.makedirs(repo_dir)
        repo = Repo.init(repo_dir)
        self.addCleanup(tear_down_repo, repo)
        refs = repo.refs

        nines = b"9" * 40
        self.assertEqual(b"ref: refs/heads/master", refs.read_ref(b"HEAD"))
        self.assertFalse(b"refs/heads/master" in refs)
        self.assertTrue(refs.add_if_new(b"HEAD", nines))
        self.assertEqual(b"ref: refs/heads/master", refs.read_ref(b"HEAD"))
        self.assertEqual(nines, refs[b"HEAD"])
        self.assertEqual(nines, refs[b"refs/heads/master"])
        self.assertFalse(refs.add_if_new(b"HEAD", b"1" * 40))
        self.assertEqual(nines, refs[b"HEAD"])
        self.assertEqual(nines, refs[b"refs/heads/master"])

    def test_follow(self):
        self.assertEqual(
            (b"refs/heads/master", b"42d06bd4b77fed026b154d16493e5deab78f02ec"), self._refs._follow(b"HEAD")
        )
        self.assertEqual(
            (b"refs/heads/master", b"42d06bd4b77fed026b154d16493e5deab78f02ec"),
            self._refs._follow(b"refs/heads/master"),
        )
        self.assertRaises(KeyError, self._refs._follow, b"refs/heads/loop")

    def test_delitem(self):
        RefsContainerTests.test_delitem(self)
        ref_file = os.path.join(self._refs.path, "refs", "heads", "master")
        self.assertFalse(os.path.exists(ref_file))
        self.assertFalse(b"refs/heads/master" in self._refs.get_packed_refs())

    def test_delitem_symbolic(self):
        self.assertEqual(b"ref: refs/heads/master", self._refs.read_loose_ref(b"HEAD"))
        del self._refs[b"HEAD"]
        self.assertRaises(KeyError, lambda: self._refs[b"HEAD"])
        self.assertEqual(b"42d06bd4b77fed026b154d16493e5deab78f02ec", self._refs[b"refs/heads/master"])
        self.assertFalse(os.path.exists(os.path.join(self._refs.path, "HEAD")))

    def test_remove_if_equals_symref(self):
        # HEAD is a symref, so shouldn't equal its dereferenced value
        self.assertFalse(self._refs.remove_if_equals(b"HEAD", b"42d06bd4b77fed026b154d16493e5deab78f02ec"))
        self.assertTrue(self._refs.remove_if_equals(b"refs/heads/master", b"42d06bd4b77fed026b154d16493e5deab78f02ec"))
        self.assertRaises(KeyError, lambda: self._refs[b"refs/heads/master"])

        # HEAD is now a broken symref
        self.assertRaises(KeyError, lambda: self._refs[b"HEAD"])
        self.assertEqual(b"ref: refs/heads/master", self._refs.read_loose_ref(b"HEAD"))

        self.assertFalse(os.path.exists(os.path.join(self._refs.path, "refs", "heads", "master.lock")))
        self.assertFalse(os.path.exists(os.path.join(self._refs.path, "HEAD.lock")))

    def test_remove_packed_without_peeled(self):
        refs_file = os.path.join(self._repo.path, "packed-refs")
        f = GitFile(refs_file)
        refs_data = f.read()
        f.close()
        f = GitFile(refs_file, "wb")
        f.write(b"\n".join(l for l in refs_data.split(b"\n") if not l or l[0] not in b"#^"))
        f.close()
        self._repo = Repo(self._repo.path)
        refs = self._repo.refs
        self.assertTrue(refs.remove_if_equals(b"refs/heads/packed", b"42d06bd4b77fed026b154d16493e5deab78f02ec"))

    def test_remove_if_equals_packed(self):
        # test removing ref that is only packed
        self.assertEqual(b"df6800012397fb85c56e7418dd4eb9405dee075c", self._refs[b"refs/tags/refs-0.1"])
        self.assertTrue(self._refs.remove_if_equals(b"refs/tags/refs-0.1", b"df6800012397fb85c56e7418dd4eb9405dee075c"))
        self.assertRaises(KeyError, lambda: self._refs[b"refs/tags/refs-0.1"])

    def test_read_ref(self):
        self.assertEqual(b"ref: refs/heads/master", self._refs.read_ref(b"HEAD"))
        self.assertEqual(b"42d06bd4b77fed026b154d16493e5deab78f02ec", self._refs.read_ref(b"refs/heads/packed"))
        self.assertEqual(None, self._refs.read_ref(b"nonexistant"))

    def test_non_ascii(self):
        try:
            encoded_ref = u"refs/tags/schön".encode(sys.getfilesystemencoding())
        except UnicodeDecodeError:
            raise SkipTest("filesystem encoding doesn't support special character")
        p = os.path.join(self._repo.path, "refs", "tags", "schön")
        with open(p, "w") as f:
            f.write("00" * 20)

        expected_refs = dict(_TEST_REFS)
        expected_refs[encoded_ref] = b"00" * 20

        self.assertEqual(expected_refs, self._repo.get_refs())