def _assert_empty_repo(self, repo): # test all kinds of things with an empty, freshly initialized repo. # It should throw good errors # entries should be empty assert len(repo.index.entries) == 0 # head is accessible assert repo.head assert repo.head.ref assert not repo.head.is_valid() # we can change the head to some other ref head_ref = Head.from_path(repo, Head.to_full_path('some_head')) assert not head_ref.is_valid() repo.head.ref = head_ref # is_dirty can handle all kwargs for args in ((1, 0, 0), (0, 1, 0), (0, 0, 1)): assert not repo.is_dirty(*args) # END for each arg # we can add a file to the index ( if we are not bare ) if not repo.bare: pass
def _assert_empty_repo(self, repo): # test all kinds of things with an empty, freshly initialized repo. # It should throw good errors # entries should be empty self.assertEqual(len(repo.index.entries), 0) # head is accessible assert repo.head assert repo.head.ref assert not repo.head.is_valid() # we can change the head to some other ref head_ref = Head.from_path(repo, Head.to_full_path('some_head')) assert not head_ref.is_valid() repo.head.ref = head_ref # is_dirty can handle all kwargs for args in ((1, 0, 0), (0, 1, 0), (0, 0, 1)): assert not repo.is_dirty(*args) # END for each arg # we can add a file to the index ( if we are not bare ) if not repo.bare: pass
def create_tree(commits, head): try: repo = Repo('tree') except NoSuchPathError: repo = Repo.init('tree') # TODO Only use tree in dev-mode repo.init() index = repo.index delete_files() index.commit("Clearing index") # Easiest way to clear the index is to commit an empty directory # Switch to temp first in case git-gud-construction exists if repo.head.reference.name != 'temp': repo.head.reference = Head(repo, 'refs/heads/temp') # Delete git-gud-construction so we can guarantee it's an orphan try: repo.delete_head('git-gud-construction') except GitCommandError: pass # Branch doesn't exist repo.head.reference = Head(repo, 'refs/heads/git-gud-construction') try: repo.delete_head('temp') except GitCommandError: pass # If temp didn't exist, we only checked it out as an orphan, so it already disappeared for branch in repo.branches: if branch.name != 'git-gud-construction': repo.delete_head(branch, force=True) repo.delete_tag(*repo.tags) index = repo.index commit_objects = {} for name, parents, branches, tags in commits: # commit = (name, parents, branches, tags) parents = [commit_objects[parent] for parent in parents] if parents: repo.active_branch.set_commit(parents[0]) if len(parents) < 2: # Not a merge add_file_to_index(index, name) commit = index.commit(name, author=actor, committer=actor, parent_commits=parents) commit_objects[name] = commit for branch in branches: repo.create_head(branch, commit) for tag in tags: repo.create_tag(tag, commit) # TODO Log commit hash and info # TODO Checkout using name for branch in repo.branches: if branch.name == head: branch.checkout() repo.delete_head('git-gud-construction')
def _create_commit_and_push(self, context: PublisherContext, repository: git.Repo, target_branch: git.Head) -> None: original_branch = repository.active_branch changes_pushed = False try: target_branch.checkout() logger.info("Active branch after checkout: %s", repository.active_branch) feature_file = self._file_manager.produce_feature_file( context=context) logger.info("Untracked files after feature creation: %s", repository.untracked_files) repository.index.add([feature_file.as_posix()]) logger.info("Untracked files after index update: %s", repository.untracked_files) repository.index.commit( (f"Added feature {context.feature.name} " f"(id {context.feature.id}) " f"by {context.test_run.executed_by}"), skip_hooks=True, ) logger.info("Commit successfully created") if repository.active_branch == original_branch: raise CurrentBranchEqualToDefaultError( f"Current branch could not be equal to default: '{original_branch}'!" ) if repository.active_branch != target_branch: raise CurrentBranchNotEqualToTargetError( f"Current branch should be equal to target: '{target_branch}'!" ) logger.info("Remote origin url: %s", repository.remotes.origin.url) repository.git.push("origin", target_branch, force=True) logger.info("Changes successfully pushed to remote repository") changes_pushed = True except (git.GitCommandError, BaseGitVersionPublisherError): logger.exception("Error while trying to commit feature file!") if original_branch != repository.active_branch: logger.info("Current branch is '%s'", repository.active_branch) original_branch.checkout() logger.info("Branch returned to '%s'", repository.active_branch) if not changes_pushed: raise CommitNotCreatedError("Commit has not been created!")
def get_repo(repo_name): directory = os.path.join(test_dir, repo_name) repo = Repo.init(directory) # Ensure the default branch is using a fixed name. # User config could change that, # breaking tests with implicit assumptions further down the line. repo.head.reference = Head(repo, 'refs/heads/master') # We need to synthesize the time stamps of commits to each be a second # apart, otherwise the commits may be at exactly the same second, which # means they won't always sort in order, and thus the merging of identical # metrics in adjacent commits may not happen correctly. base_time = time.time() base_path = os.path.join(base_dir, repo_name) for i in range(num_commits): files_dir = os.path.join(base_path, str(i)) if not os.path.exists(files_dir): break files = os.listdir(files_dir) for filename in files: print("Copying file " + filename) path = os.path.join(base_path, str(i), filename) destination = os.path.join(directory, filename) shutil.copyfile(path, destination) repo.index.add("*") commit_date = datetime.datetime.fromtimestamp(base_time + i).isoformat() commit_date = commit_date[:commit_date.find('.')] repo.index.commit("Commit {index}".format(index=i), commit_date=commit_date) return directory
def checkout_branch(repo, branch_name, no_pull=False): """Checkout a branch and optionally pull updates. :param repo: Repo object :param branch_name: Name of the branch to checkout :param no_pull: (Default: False) If True, don't pull changes to branch :return: Head object for the checked out branch """ base_head = Head(repo, f'refs/heads/{branch_name}') if repo.active_branch != base_head: print(f'Checking out {branch_name}.') base_head.checkout() if not no_pull and base_head.tracking_branch(): print(f'Pulling updates to {branch_name}...') remote_name = base_head.tracking_branch().remote_name remote = Remote(repo, remote_name) base_commit = base_head.commit for fetch_info in remote.pull(): if fetch_info.ref == base_head.tracking_branch(): if fetch_info.commit != base_commit: print( f'Updated {branch_name} to {fetch_info.commit.hexsha}') else: print(f'{branch_name} already up to date.') print('') return base_head
def create_branch (self, repo_name, new_branch, from_revision='master'): """return hexsha of new repo's head """ assert isinstance (repo_name, str) assert isinstance (new_branch, str) repo = self._get_repo (repo_name) self.lock_repo (repo_name) _from = self._get_commit (repo, from_revision) if not _from: self.unlock_repo (repo_name) self._throw_err ("repo '%s' has no revision '%s'" % (repo_name, from_revision)) if self._get_branch (repo, new_branch): self.unlock_repo (repo_name) self._throw_err ("repo '%s' already has branch '%s'" % (repo_name, new_branch)) head = None try: head = Head.create (repo, new_branch, _from) except Exception, e: self.unlock_repo (repo_name) self._throw_err ("repo '%s' cannot create branch '%s' from '%s', %s" % (repo_name, new_branch, from_revision, str(e)))
def _assert_push_and_pull(self, remote, rw_repo, remote_repo): # push our changes lhead = rw_repo.head # assure we are on master and it is checked out where the remote is try: lhead.reference = rw_repo.heads.master except AttributeError: # if the author is on a non-master branch, the clones might not have # a local master yet. We simply create it lhead.reference = rw_repo.create_head('master') # END master handling lhead.reset(remote.refs.master, working_tree=True) # push without spec should fail ( without further configuration ) # well, works nicely # self.failUnlessRaises(GitCommandError, remote.push) # simple file push self._commit_random_file(rw_repo) progress = TestRemoteProgress() res = remote.push(lhead.reference, progress) assert isinstance(res, IterableList) self._do_test_push_result(res, remote) progress.make_assertion() # rejected - undo last commit lhead.reset("HEAD~1") res = remote.push(lhead.reference) assert res[0].flags & PushInfo.ERROR assert res[0].flags & PushInfo.REJECTED self._do_test_push_result(res, remote) # force rejected pull res = remote.push('+%s' % lhead.reference) assert res[0].flags & PushInfo.ERROR == 0 assert res[0].flags & PushInfo.FORCED_UPDATE self._do_test_push_result(res, remote) # invalid refspec self.failUnlessRaises(GitCommandError, remote.push, "hellothere") # push new tags progress = TestRemoteProgress() to_be_updated = "my_tag.1.0RV" new_tag = TagReference.create(rw_repo, to_be_updated) other_tag = TagReference.create(rw_repo, "my_obj_tag.2.1aRV", message="my message") res = remote.push(progress=progress, tags=True) assert res[-1].flags & PushInfo.NEW_TAG progress.make_assertion() self._do_test_push_result(res, remote) # update push new tags # Rejection is default new_tag = TagReference.create(rw_repo, to_be_updated, ref='HEAD~1', force=True) res = remote.push(tags=True) self._do_test_push_result(res, remote) assert res[-1].flags & PushInfo.REJECTED and res[ -1].flags & PushInfo.ERROR # push force this tag res = remote.push("+%s" % new_tag.path) assert res[-1].flags & PushInfo.ERROR == 0 and res[ -1].flags & PushInfo.FORCED_UPDATE # delete tag - have to do it using refspec res = remote.push(":%s" % new_tag.path) self._do_test_push_result(res, remote) assert res[0].flags & PushInfo.DELETED # Currently progress is not properly transferred, especially not using # the git daemon # progress.assert_received_message() # push new branch new_head = Head.create(rw_repo, "my_new_branch") progress = TestRemoteProgress() res = remote.push(new_head, progress) assert len(res) > 0 assert res[0].flags & PushInfo.NEW_HEAD progress.make_assertion() self._do_test_push_result(res, remote) # delete new branch on the remote end and locally res = remote.push(":%s" % new_head.path) self._do_test_push_result(res, remote) Head.delete(rw_repo, new_head) assert res[-1].flags & PushInfo.DELETED # --all res = remote.push(all=True) self._do_test_push_result(res, remote) remote.pull('master') # cleanup - delete created tags and branches as we are in an innerloop on # the same repository TagReference.delete(rw_repo, new_tag, other_tag) remote.push(":%s" % other_tag.path)
def _do_test_fetch(self, remote, rw_repo, remote_repo): # specialized fetch testing to de-clutter the main test self._do_test_fetch_info(rw_repo) def fetch_and_test(remote, **kwargs): progress = TestRemoteProgress() kwargs['progress'] = progress res = remote.fetch(**kwargs) progress.make_assertion() self._do_test_fetch_result(res, remote) return res # END fetch and check def get_info(res, remote, name): return res["%s/%s" % (remote, name)] # put remote head to master as it is guaranteed to exist remote_repo.head.reference = remote_repo.heads.master res = fetch_and_test(remote) # all up to date for info in res: assert info.flags & info.HEAD_UPTODATE # rewind remote head to trigger rejection # index must be false as remote is a bare repo rhead = remote_repo.head remote_commit = rhead.commit rhead.reset("HEAD~2", index=False) res = fetch_and_test(remote) mkey = "%s/%s" % (remote, 'master') master_info = res[mkey] assert master_info.flags & FetchInfo.FORCED_UPDATE and master_info.note is not None # normal fast forward - set head back to previous one rhead.commit = remote_commit res = fetch_and_test(remote) assert res[mkey].flags & FetchInfo.FAST_FORWARD # new remote branch new_remote_branch = Head.create(remote_repo, "new_branch") res = fetch_and_test(remote) new_branch_info = get_info(res, remote, new_remote_branch) assert new_branch_info.flags & FetchInfo.NEW_HEAD # remote branch rename ( causes creation of a new one locally ) new_remote_branch.rename("other_branch_name") res = fetch_and_test(remote) other_branch_info = get_info(res, remote, new_remote_branch) assert other_branch_info.ref.commit == new_branch_info.ref.commit # remove new branch Head.delete(new_remote_branch.repo, new_remote_branch) res = fetch_and_test(remote) # deleted remote will not be fetched self.failUnlessRaises(IndexError, get_info, res, remote, new_remote_branch) # prune stale tracking branches stale_refs = remote.stale_refs assert len(stale_refs) == 2 and isinstance(stale_refs[0], RemoteReference) RemoteReference.delete(rw_repo, *stale_refs) # test single branch fetch with refspec including target remote res = fetch_and_test(remote, refspec="master:refs/remotes/%s/master" % remote) assert len(res) == 1 and get_info(res, remote, 'master') # ... with respec and no target res = fetch_and_test(remote, refspec='master') assert len(res) == 1 # ... multiple refspecs ... works, but git command returns with error if one ref is wrong without # doing anything. This is new in later binaries # res = fetch_and_test(remote, refspec=['master', 'fred']) # assert len(res) == 1 # add new tag reference rtag = TagReference.create(remote_repo, "1.0-RV_hello.there") res = fetch_and_test(remote, tags=True) tinfo = res[str(rtag)] assert isinstance(tinfo.ref, TagReference) and tinfo.ref.commit == rtag.commit assert tinfo.flags & tinfo.NEW_TAG # adjust tag commit Reference.set_object(rtag, rhead.commit.parents[0].parents[0]) res = fetch_and_test(remote, tags=True) tinfo = res[str(rtag)] assert tinfo.commit == rtag.commit assert tinfo.flags & tinfo.TAG_UPDATE # delete remote tag - local one will stay TagReference.delete(remote_repo, rtag) res = fetch_and_test(remote, tags=True) self.failUnlessRaises(IndexError, get_info, res, remote, str(rtag)) # provoke to receive actual objects to see what kind of output we have to # expect. For that we need a remote transport protocol # Create a new UN-shared repo and fetch into it after we pushed a change # to the shared repo other_repo_dir = tempfile.mktemp("other_repo") # must clone with a local path for the repo implementation not to freak out # as it wants local paths only ( which I can understand ) other_repo = remote_repo.clone(other_repo_dir, shared=False) remote_repo_url = "git://localhost:%s%s" % (GIT_DAEMON_PORT, remote_repo.git_dir) # put origin to git-url other_origin = other_repo.remotes.origin other_origin.config_writer.set("url", remote_repo_url) # it automatically creates alternates as remote_repo is shared as well. # It will use the transport though and ignore alternates when fetching # assert not other_repo.alternates # this would fail # assure we are in the right state rw_repo.head.reset(remote.refs.master, working_tree=True) try: self._commit_random_file(rw_repo) remote.push(rw_repo.head.reference) # here I would expect to see remote-information about packing # objects and so on. Unfortunately, this does not happen # if we are redirecting the output - git explicitly checks for this # and only provides progress information to ttys res = fetch_and_test(other_origin) finally: shutil.rmtree(other_repo_dir)
def _assert_push_and_pull(self, remote, rw_repo, remote_repo): # push our changes lhead = rw_repo.head # assure we are on master and it is checked out where the remote is try: lhead.reference = rw_repo.heads.master except AttributeError: # if the author is on a non-master branch, the clones might not have # a local master yet. We simply create it lhead.reference = rw_repo.create_head('master') # END master handling lhead.reset(remote.refs.master, working_tree=True) # push without spec should fail ( without further configuration ) # well, works nicely # self.failUnlessRaises(GitCommandError, remote.push) # simple file push self._commit_random_file(rw_repo) progress = TestRemoteProgress() res = remote.push(lhead.reference, progress) assert isinstance(res, IterableList) self._do_test_push_result(res, remote) progress.make_assertion() # rejected - undo last commit lhead.reset("HEAD~1") res = remote.push(lhead.reference) assert res[0].flags & PushInfo.ERROR assert res[0].flags & PushInfo.REJECTED self._do_test_push_result(res, remote) # force rejected pull res = remote.push('+%s' % lhead.reference) assert res[0].flags & PushInfo.ERROR == 0 assert res[0].flags & PushInfo.FORCED_UPDATE self._do_test_push_result(res, remote) # invalid refspec self.failUnlessRaises(GitCommandError, remote.push, "hellothere") # push new tags progress = TestRemoteProgress() to_be_updated = "my_tag.1.0RV" new_tag = TagReference.create(rw_repo, to_be_updated) other_tag = TagReference.create(rw_repo, "my_obj_tag.2.1aRV", message="my message") res = remote.push(progress=progress, tags=True) assert res[-1].flags & PushInfo.NEW_TAG progress.make_assertion() self._do_test_push_result(res, remote) # update push new tags # Rejection is default new_tag = TagReference.create(rw_repo, to_be_updated, ref='HEAD~1', force=True) res = remote.push(tags=True) self._do_test_push_result(res, remote) assert res[-1].flags & PushInfo.REJECTED and res[-1].flags & PushInfo.ERROR # push force this tag res = remote.push("+%s" % new_tag.path) assert res[-1].flags & PushInfo.ERROR == 0 and res[-1].flags & PushInfo.FORCED_UPDATE # delete tag - have to do it using refspec res = remote.push(":%s" % new_tag.path) self._do_test_push_result(res, remote) assert res[0].flags & PushInfo.DELETED # Currently progress is not properly transferred, especially not using # the git daemon # progress.assert_received_message() # push new branch new_head = Head.create(rw_repo, "my_new_branch") progress = TestRemoteProgress() res = remote.push(new_head, progress) assert len(res) > 0 assert res[0].flags & PushInfo.NEW_HEAD progress.make_assertion() self._do_test_push_result(res, remote) # delete new branch on the remote end and locally res = remote.push(":%s" % new_head.path) self._do_test_push_result(res, remote) Head.delete(rw_repo, new_head) assert res[-1].flags & PushInfo.DELETED # --all res = remote.push(all=True) self._do_test_push_result(res, remote) remote.pull('master') # cleanup - delete created tags and branches as we are in an innerloop on # the same repository TagReference.delete(rw_repo, new_tag, other_tag) remote.push(":%s" % other_tag.path)
def test_heads(self, rwrepo): for head in rwrepo.heads: assert head.name assert head.path assert "refs/heads" in head.path prev_object = head.object cur_object = head.object assert prev_object == cur_object # represent the same git object assert prev_object is not cur_object # but are different instances with head.config_writer() as writer: tv = "testopt" writer.set_value(tv, 1) assert writer.get_value(tv) == 1 assert head.config_reader().get_value(tv) == 1 with head.config_writer() as writer: writer.remove_option(tv) # after the clone, we might still have a tracking branch setup head.set_tracking_branch(None) assert head.tracking_branch() is None remote_ref = rwrepo.remotes[0].refs[0] assert head.set_tracking_branch(remote_ref) is head assert head.tracking_branch() == remote_ref head.set_tracking_branch(None) assert head.tracking_branch() is None # END for each head # verify REFLOG gets altered head = rwrepo.head cur_head = head.ref cur_commit = cur_head.commit pcommit = cur_head.commit.parents[0].parents[0] hlog_len = len(head.log()) blog_len = len(cur_head.log()) assert head.set_reference(pcommit, 'detached head') is head # one new log-entry thlog = head.log() assert len(thlog) == hlog_len + 1 assert thlog[-1].oldhexsha == cur_commit.hexsha assert thlog[-1].newhexsha == pcommit.hexsha # the ref didn't change though assert len(cur_head.log()) == blog_len # head changes once again, cur_head doesn't change head.set_reference(cur_head, 'reattach head') assert len(head.log()) == hlog_len + 2 assert len(cur_head.log()) == blog_len # adjusting the head-ref also adjust the head, so both reflogs are # altered cur_head.set_commit(pcommit, 'changing commit') assert len(cur_head.log()) == blog_len + 1 assert len(head.log()) == hlog_len + 3 # with automatic dereferencing assert head.set_commit(cur_commit, 'change commit once again') is head assert len(head.log()) == hlog_len + 4 assert len(cur_head.log()) == blog_len + 2 # a new branch has just a single entry other_head = Head.create(rwrepo, 'mynewhead', pcommit, logmsg='new head created') log = other_head.log() assert len(log) == 1 assert log[0].oldhexsha == pcommit.NULL_HEX_SHA assert log[0].newhexsha == pcommit.hexsha
def test_heads(self, rwrepo): for head in rwrepo.heads: assert head.name assert head.path assert "refs/heads" in head.path prev_object = head.object cur_object = head.object assert prev_object == cur_object # represent the same git object assert prev_object is not cur_object # but are different instances with head.config_writer() as writer: tv = "testopt" writer.set_value(tv, 1) assert writer.get_value(tv) == 1 assert head.config_reader().get_value(tv) == 1 with head.config_writer() as writer: writer.remove_option(tv) # after the clone, we might still have a tracking branch setup head.set_tracking_branch(None) assert head.tracking_branch() is None remote_ref = rwrepo.remotes[0].refs[0] assert head.set_tracking_branch(remote_ref) is head assert head.tracking_branch() == remote_ref head.set_tracking_branch(None) assert head.tracking_branch() is None special_name = 'feature#123' special_name_remote_ref = SymbolicReference.create(rwrepo, 'refs/remotes/origin/%s' % special_name) gp_tracking_branch = rwrepo.create_head('gp_tracking#123') special_name_remote_ref = rwrepo.remotes[0].refs[special_name] # get correct type gp_tracking_branch.set_tracking_branch(special_name_remote_ref) assert gp_tracking_branch.tracking_branch().path == special_name_remote_ref.path git_tracking_branch = rwrepo.create_head('git_tracking#123') rwrepo.git.branch('-u', special_name_remote_ref.name, git_tracking_branch.name) assert git_tracking_branch.tracking_branch().name == special_name_remote_ref.name # END for each head # verify REFLOG gets altered head = rwrepo.head cur_head = head.ref cur_commit = cur_head.commit pcommit = cur_head.commit.parents[0].parents[0] hlog_len = len(head.log()) blog_len = len(cur_head.log()) assert head.set_reference(pcommit, 'detached head') is head # one new log-entry thlog = head.log() assert len(thlog) == hlog_len + 1 assert thlog[-1].oldhexsha == cur_commit.hexsha assert thlog[-1].newhexsha == pcommit.hexsha # the ref didn't change though assert len(cur_head.log()) == blog_len # head changes once again, cur_head doesn't change head.set_reference(cur_head, 'reattach head') assert len(head.log()) == hlog_len + 2 assert len(cur_head.log()) == blog_len # adjusting the head-ref also adjust the head, so both reflogs are # altered cur_head.set_commit(pcommit, 'changing commit') assert len(cur_head.log()) == blog_len + 1 assert len(head.log()) == hlog_len + 3 # with automatic dereferencing assert head.set_commit(cur_commit, 'change commit once again') is head assert len(head.log()) == hlog_len + 4 assert len(cur_head.log()) == blog_len + 2 # a new branch has just a single entry other_head = Head.create(rwrepo, 'mynewhead', pcommit, logmsg='new head created') log = other_head.log() assert len(log) == 1 assert log[0].oldhexsha == pcommit.NULL_HEX_SHA assert log[0].newhexsha == pcommit.hexsha
def test_head_reset(self, rw_repo): cur_head = rw_repo.head old_head_commit = cur_head.commit new_head_commit = cur_head.ref.commit.parents[0] cur_head.reset(new_head_commit, index=True) # index only assert cur_head.reference.commit == new_head_commit self.failUnlessRaises(ValueError, cur_head.reset, new_head_commit, index=False, working_tree=True) new_head_commit = new_head_commit.parents[0] cur_head.reset(new_head_commit, index=True, working_tree=True) # index + wt assert cur_head.reference.commit == new_head_commit # paths - make sure we have something to do rw_repo.index.reset(old_head_commit.parents[0]) cur_head.reset(cur_head, paths="test") cur_head.reset(new_head_commit, paths="lib") # hard resets with paths don't work, its all or nothing self.failUnlessRaises(GitCommandError, cur_head.reset, new_head_commit, working_tree=True, paths="lib") # we can do a mixed reset, and then checkout from the index though cur_head.reset(new_head_commit) rw_repo.index.checkout(["lib"], force=True) # now that we have a write write repo, change the HEAD reference - its # like git-reset --soft heads = rw_repo.heads assert heads for head in heads: cur_head.reference = head assert cur_head.reference == head assert isinstance(cur_head.reference, Head) assert cur_head.commit == head.commit assert not cur_head.is_detached # END for each head # detach active_head = heads[0] curhead_commit = active_head.commit cur_head.reference = curhead_commit assert cur_head.commit == curhead_commit assert cur_head.is_detached self.failUnlessRaises(TypeError, getattr, cur_head, "reference") # tags are references, hence we can point to them some_tag = rw_repo.tags[0] cur_head.reference = some_tag assert not cur_head.is_detached assert cur_head.commit == some_tag.commit assert isinstance(cur_head.reference, TagReference) # put HEAD back to a real head, otherwise everything else fails cur_head.reference = active_head # type check self.failUnlessRaises(ValueError, setattr, cur_head, "reference", "that") # head handling commit = 'HEAD' prev_head_commit = cur_head.commit for count, new_name in enumerate(("my_new_head", "feature/feature1")): actual_commit = commit + "^" * count new_head = Head.create(rw_repo, new_name, actual_commit) assert new_head.is_detached assert cur_head.commit == prev_head_commit assert isinstance(new_head, Head) # already exists, but has the same value, so its fine Head.create(rw_repo, new_name, new_head.commit) # its not fine with a different value self.failUnlessRaises(OSError, Head.create, rw_repo, new_name, new_head.commit.parents[0]) # force it new_head = Head.create(rw_repo, new_name, actual_commit, force=True) old_path = new_head.path old_name = new_head.name assert new_head.rename("hello").name == "hello" assert new_head.rename("hello/world").name == "hello/world" assert new_head.rename(old_name).name == old_name and new_head.path == old_path # rename with force tmp_head = Head.create(rw_repo, "tmphead") self.failUnlessRaises(GitCommandError, tmp_head.rename, new_head) tmp_head.rename(new_head, force=True) assert tmp_head == new_head and tmp_head.object == new_head.object logfile = RefLog.path(tmp_head) assert osp.isfile(logfile) Head.delete(rw_repo, tmp_head) # deletion removes the log as well assert not osp.isfile(logfile) heads = rw_repo.heads assert tmp_head not in heads and new_head not in heads # force on deletion testing would be missing here, code looks okay though ;) # END for each new head name self.failUnlessRaises(TypeError, RemoteReference.create, rw_repo, "some_name") # tag ref tag_name = "5.0.2" TagReference.create(rw_repo, tag_name) self.failUnlessRaises(GitCommandError, TagReference.create, rw_repo, tag_name) light_tag = TagReference.create(rw_repo, tag_name, "HEAD~1", force=True) assert isinstance(light_tag, TagReference) assert light_tag.name == tag_name assert light_tag.commit == cur_head.commit.parents[0] assert light_tag.tag is None # tag with tag object other_tag_name = "releases/1.0.2RC" msg = "my mighty tag\nsecond line" obj_tag = TagReference.create(rw_repo, other_tag_name, message=msg) assert isinstance(obj_tag, TagReference) assert obj_tag.name == other_tag_name assert obj_tag.commit == cur_head.commit assert obj_tag.tag is not None TagReference.delete(rw_repo, light_tag, obj_tag) tags = rw_repo.tags assert light_tag not in tags and obj_tag not in tags # remote deletion remote_refs_so_far = 0 remotes = rw_repo.remotes assert remotes for remote in remotes: refs = remote.refs # If a HEAD exists, it must be deleted first. Otherwise it might # end up pointing to an invalid ref it the ref was deleted before. remote_head_name = "HEAD" if remote_head_name in refs: RemoteReference.delete(rw_repo, refs[remote_head_name]) del(refs[remote_head_name]) # END handle HEAD deletion RemoteReference.delete(rw_repo, *refs) remote_refs_so_far += len(refs) for ref in refs: assert ref.remote_name == remote.name # END for each ref to delete assert remote_refs_so_far for remote in remotes: # remotes without references should produce an empty list self.assertEqual(remote.refs, []) # END for each remote # change where the active head points to if cur_head.is_detached: cur_head.reference = rw_repo.heads[0] head = cur_head.reference old_commit = head.commit head.commit = old_commit.parents[0] assert head.commit == old_commit.parents[0] assert head.commit == cur_head.commit head.commit = old_commit # setting a non-commit as commit fails, but succeeds as object head_tree = head.commit.tree self.failUnlessRaises(ValueError, setattr, head, 'commit', head_tree) assert head.commit == old_commit # and the ref did not change # we allow heds to point to any object head.object = head_tree assert head.object == head_tree # cannot query tree as commit self.failUnlessRaises(TypeError, getattr, head, 'commit') # set the commit directly using the head. This would never detach the head assert not cur_head.is_detached head.object = old_commit cur_head.reference = head.commit assert cur_head.is_detached parent_commit = head.commit.parents[0] assert cur_head.is_detached cur_head.commit = parent_commit assert cur_head.is_detached and cur_head.commit == parent_commit cur_head.reference = head assert not cur_head.is_detached cur_head.commit = parent_commit assert not cur_head.is_detached assert head.commit == parent_commit # test checkout active_branch = rw_repo.active_branch for head in rw_repo.heads: checked_out_head = head.checkout() assert checked_out_head == head # END for each head to checkout # checkout with branch creation new_head = active_branch.checkout(b="new_head") assert active_branch != rw_repo.active_branch assert new_head == rw_repo.active_branch # checkout with force as we have a changed a file # clear file open(new_head.commit.tree.blobs[-1].abspath, 'w').close() assert len(new_head.commit.diff(None)) # create a new branch that is likely to touch the file we changed far_away_head = rw_repo.create_head("far_head", 'HEAD~100') self.failUnlessRaises(GitCommandError, far_away_head.checkout) assert active_branch == active_branch.checkout(force=True) assert rw_repo.head.reference != far_away_head # test reference creation partial_ref = 'sub/ref' full_ref = 'refs/%s' % partial_ref ref = Reference.create(rw_repo, partial_ref) assert ref.path == full_ref assert ref.object == rw_repo.head.commit self.failUnlessRaises(OSError, Reference.create, rw_repo, full_ref, 'HEAD~20') # it works if it is at the same spot though and points to the same reference assert Reference.create(rw_repo, full_ref, 'HEAD').path == full_ref Reference.delete(rw_repo, full_ref) # recreate the reference using a full_ref ref = Reference.create(rw_repo, full_ref) assert ref.path == full_ref assert ref.object == rw_repo.head.commit # recreate using force ref = Reference.create(rw_repo, partial_ref, 'HEAD~1', force=True) assert ref.path == full_ref assert ref.object == rw_repo.head.commit.parents[0] # rename it orig_obj = ref.object for name in ('refs/absname', 'rela_name', 'feature/rela_name'): ref_new_name = ref.rename(name) assert isinstance(ref_new_name, Reference) assert name in ref_new_name.path assert ref_new_name.object == orig_obj assert ref_new_name == ref # END for each name type # References that don't exist trigger an error if we want to access them self.failUnlessRaises(ValueError, getattr, Reference(rw_repo, "refs/doesntexist"), 'commit') # exists, fail unless we force ex_ref_path = far_away_head.path self.failUnlessRaises(OSError, ref.rename, ex_ref_path) # if it points to the same commit it works far_away_head.commit = ref.commit ref.rename(ex_ref_path) assert ref.path == ex_ref_path and ref.object == orig_obj assert ref.rename(ref.path).path == ex_ref_path # rename to same name # create symbolic refs symref_path = "symrefs/sym" symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference) assert symref.path == symref_path assert symref.reference == cur_head.reference self.failUnlessRaises(OSError, SymbolicReference.create, rw_repo, symref_path, cur_head.reference.commit) # it works if the new ref points to the same reference SymbolicReference.create(rw_repo, symref.path, symref.reference).path == symref.path # @NoEffect SymbolicReference.delete(rw_repo, symref) # would raise if the symref wouldn't have been deletedpbl symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference) # test symbolic references which are not at default locations like HEAD # or FETCH_HEAD - they may also be at spots in refs of course symbol_ref_path = "refs/symbol_ref" symref = SymbolicReference(rw_repo, symbol_ref_path) assert symref.path == symbol_ref_path symbol_ref_abspath = osp.join(rw_repo.git_dir, symref.path) # set it symref.reference = new_head assert symref.reference == new_head assert osp.isfile(symbol_ref_abspath) assert symref.commit == new_head.commit for name in ('absname', 'folder/rela_name'): symref_new_name = symref.rename(name) assert isinstance(symref_new_name, SymbolicReference) assert name in symref_new_name.path assert symref_new_name.reference == new_head assert symref_new_name == symref assert not symref.is_detached # END for each ref # create a new non-head ref just to be sure we handle it even if packed Reference.create(rw_repo, full_ref) # test ref listing - assure we have packed refs rw_repo.git.pack_refs(all=True, prune=True) heads = rw_repo.heads assert heads assert new_head in heads assert active_branch in heads assert rw_repo.tags # we should be able to iterate all symbolic refs as well - in that case # we should expect only symbolic references to be returned for symref in SymbolicReference.iter_items(rw_repo): assert not symref.is_detached # when iterating references, we can get references and symrefs # when deleting all refs, I'd expect them to be gone ! Even from # the packed ones # For this to work, we must not be on any branch rw_repo.head.reference = rw_repo.head.commit deleted_refs = set() for ref in Reference.iter_items(rw_repo): if ref.is_detached: ref.delete(rw_repo, ref) deleted_refs.add(ref) # END delete ref # END for each ref to iterate and to delete assert deleted_refs for ref in Reference.iter_items(rw_repo): if ref.is_detached: assert ref not in deleted_refs # END for each ref # reattach head - head will not be returned if it is not a symbolic # ref rw_repo.head.reference = Head.create(rw_repo, "master") # At least the head should still exist assert osp.isfile(osp.join(rw_repo.git_dir, 'HEAD')) refs = list(SymbolicReference.iter_items(rw_repo)) assert len(refs) == 1 # test creation of new refs from scratch for path in ("basename", "dir/somename", "dir2/subdir/basename"): # REFERENCES ############ fpath = Reference.to_full_path(path) ref_fp = Reference.from_path(rw_repo, fpath) assert not ref_fp.is_valid() ref = Reference(rw_repo, fpath) assert ref == ref_fp # can be created by assigning a commit ref.commit = rw_repo.head.commit assert ref.is_valid() # if the assignment raises, the ref doesn't exist Reference.delete(ref.repo, ref.path) assert not ref.is_valid() self.failUnlessRaises(ValueError, setattr, ref, 'commit', "nonsense") assert not ref.is_valid() # I am sure I had my reason to make it a class method at first, but # now it doesn't make so much sense anymore, want an instance method as well # See http://byronimo.lighthouseapp.com/projects/51787-gitpython/tickets/27 Reference.delete(ref.repo, ref.path) assert not ref.is_valid() ref.object = rw_repo.head.commit assert ref.is_valid() Reference.delete(ref.repo, ref.path) assert not ref.is_valid() self.failUnlessRaises(ValueError, setattr, ref, 'object', "nonsense") assert not ref.is_valid()
print("Updated %s to %s" % (fetch_info.ref, fetch_info.commit)) # Checkout correct branch repo.create_head( branch, repo.remotes[remote].refs[branch] ) # create local branch "master" from remote "master" repo.heads[branch].set_tracking_branch( repo.remotes[remote].refs[branch] ) # set local "master" to track remote "master repo.heads[branch].checkout() repo.remotes[remote].pull() # Delete the renamed branch if branch + "_nn" in repo.heads: Head.delete(repo, branch + "_nn", fource=True) # Run build bp = buildPath + "/" + remote + "/" + branch + "/" + config if not clearOldBuild(bp): print "ERROR: clear old build failed for " + buildPath + "," + remote + "," + branch continue if not createBuildFolder(bp): print "ERROR: create build folder failed for " + buildPath + "," + remote + "," + branch continue if not configureBuild(repoPath, bp, config): print "ERROR: configure failed for " + buildPath + "," + remote + "," + branch continue if not compileBuild(bp): print "ERROR: build failed for " + buildPath + "," + remote + "," + branch continue
def git_create_branch(repo, branch_name): repo.remote().push(Head.create(repo, branch_name))
def test_head_reset(self, rw_repo): cur_head = rw_repo.head old_head_commit = cur_head.commit new_head_commit = cur_head.ref.commit.parents[0] cur_head.reset(new_head_commit, index=True) # index only assert cur_head.reference.commit == new_head_commit self.failUnlessRaises(ValueError, cur_head.reset, new_head_commit, index=False, working_tree=True) new_head_commit = new_head_commit.parents[0] cur_head.reset(new_head_commit, index=True, working_tree=True) # index + wt assert cur_head.reference.commit == new_head_commit # paths - make sure we have something to do rw_repo.index.reset(old_head_commit.parents[0]) cur_head.reset(cur_head, paths="test") cur_head.reset(new_head_commit, paths="lib") # hard resets with paths don't work, its all or nothing self.failUnlessRaises(GitCommandError, cur_head.reset, new_head_commit, working_tree=True, paths="lib") # we can do a mixed reset, and then checkout from the index though cur_head.reset(new_head_commit) rw_repo.index.checkout(["lib"], force=True) # now that we have a write write repo, change the HEAD reference - its # like git-reset --soft heads = rw_repo.heads assert heads for head in heads: cur_head.reference = head assert cur_head.reference == head assert isinstance(cur_head.reference, Head) assert cur_head.commit == head.commit assert not cur_head.is_detached # END for each head # detach active_head = heads[0] curhead_commit = active_head.commit cur_head.reference = curhead_commit assert cur_head.commit == curhead_commit assert cur_head.is_detached self.failUnlessRaises(TypeError, getattr, cur_head, "reference") # tags are references, hence we can point to them some_tag = rw_repo.tags[0] cur_head.reference = some_tag assert not cur_head.is_detached assert cur_head.commit == some_tag.commit assert isinstance(cur_head.reference, TagReference) # put HEAD back to a real head, otherwise everything else fails cur_head.reference = active_head # type check self.failUnlessRaises(ValueError, setattr, cur_head, "reference", "that") # head handling commit = 'HEAD' prev_head_commit = cur_head.commit for count, new_name in enumerate(("my_new_head", "feature/feature1")): actual_commit = commit + "^" * count new_head = Head.create(rw_repo, new_name, actual_commit) assert new_head.is_detached assert cur_head.commit == prev_head_commit assert isinstance(new_head, Head) # already exists, but has the same value, so its fine Head.create(rw_repo, new_name, new_head.commit) # its not fine with a different value self.failUnlessRaises(OSError, Head.create, rw_repo, new_name, new_head.commit.parents[0]) # force it new_head = Head.create(rw_repo, new_name, actual_commit, force=True) old_path = new_head.path old_name = new_head.name assert new_head.rename("hello").name == "hello" assert new_head.rename("hello/world").name == "hello/world" assert new_head.rename(old_name).name == old_name and new_head.path == old_path # rename with force tmp_head = Head.create(rw_repo, "tmphead") self.failUnlessRaises(GitCommandError, tmp_head.rename, new_head) tmp_head.rename(new_head, force=True) assert tmp_head == new_head and tmp_head.object == new_head.object logfile = RefLog.path(tmp_head) assert os.path.isfile(logfile) Head.delete(rw_repo, tmp_head) # deletion removes the log as well assert not os.path.isfile(logfile) heads = rw_repo.heads assert tmp_head not in heads and new_head not in heads # force on deletion testing would be missing here, code looks okay though ;) # END for each new head name self.failUnlessRaises(TypeError, RemoteReference.create, rw_repo, "some_name") # tag ref tag_name = "5.0.2" TagReference.create(rw_repo, tag_name) self.failUnlessRaises(GitCommandError, TagReference.create, rw_repo, tag_name) light_tag = TagReference.create(rw_repo, tag_name, "HEAD~1", force=True) assert isinstance(light_tag, TagReference) assert light_tag.name == tag_name assert light_tag.commit == cur_head.commit.parents[0] assert light_tag.tag is None # tag with tag object other_tag_name = "releases/1.0.2RC" msg = "my mighty tag\nsecond line" obj_tag = TagReference.create(rw_repo, other_tag_name, message=msg) assert isinstance(obj_tag, TagReference) assert obj_tag.name == other_tag_name assert obj_tag.commit == cur_head.commit assert obj_tag.tag is not None TagReference.delete(rw_repo, light_tag, obj_tag) tags = rw_repo.tags assert light_tag not in tags and obj_tag not in tags # remote deletion remote_refs_so_far = 0 remotes = rw_repo.remotes assert remotes for remote in remotes: refs = remote.refs # If a HEAD exists, it must be deleted first. Otherwise it might # end up pointing to an invalid ref it the ref was deleted before. remote_head_name = "HEAD" if remote_head_name in refs: RemoteReference.delete(rw_repo, refs[remote_head_name]) del(refs[remote_head_name]) # END handle HEAD deletion RemoteReference.delete(rw_repo, *refs) remote_refs_so_far += len(refs) for ref in refs: assert ref.remote_name == remote.name # END for each ref to delete assert remote_refs_so_far for remote in remotes: # remotes without references should produce an empty list self.assertEqual(remote.refs, []) # END for each remote # change where the active head points to if cur_head.is_detached: cur_head.reference = rw_repo.heads[0] head = cur_head.reference old_commit = head.commit head.commit = old_commit.parents[0] assert head.commit == old_commit.parents[0] assert head.commit == cur_head.commit head.commit = old_commit # setting a non-commit as commit fails, but succeeds as object head_tree = head.commit.tree self.failUnlessRaises(ValueError, setattr, head, 'commit', head_tree) assert head.commit == old_commit # and the ref did not change # we allow heds to point to any object head.object = head_tree assert head.object == head_tree # cannot query tree as commit self.failUnlessRaises(TypeError, getattr, head, 'commit') # set the commit directly using the head. This would never detach the head assert not cur_head.is_detached head.object = old_commit cur_head.reference = head.commit assert cur_head.is_detached parent_commit = head.commit.parents[0] assert cur_head.is_detached cur_head.commit = parent_commit assert cur_head.is_detached and cur_head.commit == parent_commit cur_head.reference = head assert not cur_head.is_detached cur_head.commit = parent_commit assert not cur_head.is_detached assert head.commit == parent_commit # test checkout active_branch = rw_repo.active_branch for head in rw_repo.heads: checked_out_head = head.checkout() assert checked_out_head == head # END for each head to checkout # checkout with branch creation new_head = active_branch.checkout(b="new_head") assert active_branch != rw_repo.active_branch assert new_head == rw_repo.active_branch # checkout with force as we have a changed a file # clear file open(new_head.commit.tree.blobs[-1].abspath, 'w').close() assert len(new_head.commit.diff(None)) # create a new branch that is likely to touch the file we changed far_away_head = rw_repo.create_head("far_head", 'HEAD~100') self.failUnlessRaises(GitCommandError, far_away_head.checkout) assert active_branch == active_branch.checkout(force=True) assert rw_repo.head.reference != far_away_head # test reference creation partial_ref = 'sub/ref' full_ref = 'refs/%s' % partial_ref ref = Reference.create(rw_repo, partial_ref) assert ref.path == full_ref assert ref.object == rw_repo.head.commit self.failUnlessRaises(OSError, Reference.create, rw_repo, full_ref, 'HEAD~20') # it works if it is at the same spot though and points to the same reference assert Reference.create(rw_repo, full_ref, 'HEAD').path == full_ref Reference.delete(rw_repo, full_ref) # recreate the reference using a full_ref ref = Reference.create(rw_repo, full_ref) assert ref.path == full_ref assert ref.object == rw_repo.head.commit # recreate using force ref = Reference.create(rw_repo, partial_ref, 'HEAD~1', force=True) assert ref.path == full_ref assert ref.object == rw_repo.head.commit.parents[0] # rename it orig_obj = ref.object for name in ('refs/absname', 'rela_name', 'feature/rela_name'): ref_new_name = ref.rename(name) assert isinstance(ref_new_name, Reference) assert name in ref_new_name.path assert ref_new_name.object == orig_obj assert ref_new_name == ref # END for each name type # References that don't exist trigger an error if we want to access them self.failUnlessRaises(ValueError, getattr, Reference(rw_repo, "refs/doesntexist"), 'commit') # exists, fail unless we force ex_ref_path = far_away_head.path self.failUnlessRaises(OSError, ref.rename, ex_ref_path) # if it points to the same commit it works far_away_head.commit = ref.commit ref.rename(ex_ref_path) assert ref.path == ex_ref_path and ref.object == orig_obj assert ref.rename(ref.path).path == ex_ref_path # rename to same name # create symbolic refs symref_path = "symrefs/sym" symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference) assert symref.path == symref_path assert symref.reference == cur_head.reference self.failUnlessRaises(OSError, SymbolicReference.create, rw_repo, symref_path, cur_head.reference.commit) # it works if the new ref points to the same reference SymbolicReference.create(rw_repo, symref.path, symref.reference).path == symref.path # @NoEffect SymbolicReference.delete(rw_repo, symref) # would raise if the symref wouldn't have been deletedpbl symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference) # test symbolic references which are not at default locations like HEAD # or FETCH_HEAD - they may also be at spots in refs of course symbol_ref_path = "refs/symbol_ref" symref = SymbolicReference(rw_repo, symbol_ref_path) assert symref.path == symbol_ref_path symbol_ref_abspath = os.path.join(rw_repo.git_dir, symref.path) # set it symref.reference = new_head assert symref.reference == new_head assert os.path.isfile(symbol_ref_abspath) assert symref.commit == new_head.commit for name in ('absname', 'folder/rela_name'): symref_new_name = symref.rename(name) assert isinstance(symref_new_name, SymbolicReference) assert name in symref_new_name.path assert symref_new_name.reference == new_head assert symref_new_name == symref assert not symref.is_detached # END for each ref # create a new non-head ref just to be sure we handle it even if packed Reference.create(rw_repo, full_ref) # test ref listing - assure we have packed refs rw_repo.git.pack_refs(all=True, prune=True) heads = rw_repo.heads assert heads assert new_head in heads assert active_branch in heads assert rw_repo.tags # we should be able to iterate all symbolic refs as well - in that case # we should expect only symbolic references to be returned for symref in SymbolicReference.iter_items(rw_repo): assert not symref.is_detached # when iterating references, we can get references and symrefs # when deleting all refs, I'd expect them to be gone ! Even from # the packed ones # For this to work, we must not be on any branch rw_repo.head.reference = rw_repo.head.commit deleted_refs = set() for ref in Reference.iter_items(rw_repo): if ref.is_detached: ref.delete(rw_repo, ref) deleted_refs.add(ref) # END delete ref # END for each ref to iterate and to delete assert deleted_refs for ref in Reference.iter_items(rw_repo): if ref.is_detached: assert ref not in deleted_refs # END for each ref # reattach head - head will not be returned if it is not a symbolic # ref rw_repo.head.reference = Head.create(rw_repo, "master") # At least the head should still exist assert os.path.isfile(os.path.join(rw_repo.git_dir, 'HEAD')) refs = list(SymbolicReference.iter_items(rw_repo)) assert len(refs) == 1 # test creation of new refs from scratch for path in ("basename", "dir/somename", "dir2/subdir/basename"): # REFERENCES ############ fpath = Reference.to_full_path(path) ref_fp = Reference.from_path(rw_repo, fpath) assert not ref_fp.is_valid() ref = Reference(rw_repo, fpath) assert ref == ref_fp # can be created by assigning a commit ref.commit = rw_repo.head.commit assert ref.is_valid() # if the assignment raises, the ref doesn't exist Reference.delete(ref.repo, ref.path) assert not ref.is_valid() self.failUnlessRaises(ValueError, setattr, ref, 'commit', "nonsense") assert not ref.is_valid() # I am sure I had my reason to make it a class method at first, but # now it doesn't make so much sense anymore, want an instance method as well # See http://byronimo.lighthouseapp.com/projects/51787-gitpython/tickets/27 Reference.delete(ref.repo, ref.path) assert not ref.is_valid() ref.object = rw_repo.head.commit assert ref.is_valid() Reference.delete(ref.repo, ref.path) assert not ref.is_valid() self.failUnlessRaises(ValueError, setattr, ref, 'object', "nonsense") assert not ref.is_valid()
def __init__(self, git_object=None, change_ids=list(), upstream_branch=None, force=False, *args, **kwargs): # make sure to correctly initialize inherited objects before performing # any computation super(Supersede, self).__init__(*args, **kwargs) # test commit parameter if not git_object: raise SupersedeError("Commit should be provided") # test that we can use this git repo if self.is_detached(): raise SupersedeError("In 'detached HEAD' state") # To Do: check if it possible and useful. if self.repo.bare: raise SupersedeError("Cannot add notes in bare repositories") if not upstream_branch: raise SupersedeError("Missing upstream_branch parameter") try: # test commit "id" presence self._commit = self.repo.commit(git_object) except (BadName, BadObject): raise SupersedeError("Commit '%s' not found (or ambiguous)" % git_object) # test change_ids parameter if len(change_ids) == 0: raise SupersedeError("At least one change id should be provided") self._upstream_branch = upstream_branch self._change_ids = change_ids git_branch = Head(self.repo, 'refs/heads/%s' % upstream_branch) for change_id in change_ids: # Check change id format if not re.match(Supersede.CHANGE_ID_REGEX, change_id, re.IGNORECASE): raise SupersedeError("Invalid Change Id '%s'" % change_id) # Check if change id is actually present in some commit # reachable from <upstream_branch> try: change_commit = CommitMessageSearcher( repo=self.repo, branch=git_branch, pattern=Supersede.CHANGE_ID_HEADER_REGEX_FMT % change_id).find() self.log.debug("Change-id '%s' found in commit '%s'" % (change_id, change_commit)) except RuntimeError: if force: self.log.warn("Warning: change-id '%s' not found in '%s'" % (change_id, upstream_branch)) else: raise SupersedeError( "Change-Id '%s' not found in branch '%s'" % (change_id, upstream_branch))