def _create_merge_commit(review, prl):
        '''
        Create a new merge commit, merging the pushed commit into
        its destination branch. Return new commit's sha1.

        Leaves all references untouched.

        Knows to scan pushed PreReceiveTuple list for any pushed changes
        to destination branch, use (what will eventually be) post-push head,
        not pre-push, as first-parent of new new merge commit.

        Raises exception if unable to create the merge commit (usually due to
        Git merge conflict, error would be from 'git merge'.
        '''
        LOG.debug('_create_merge_commit() {}'.format(review))

                        # Is the destination branch also being modified as part
                        # of this push? If so, use its eventual post-push head,
                        # not current head, for this merge.
        dest_ref_name = 'refs/heads/' + review.git_branch_name
        LOG.debug3('dest_ref_name={}'.format(dest_ref_name))
        first_parent_sha1     = None
        for prt in prl:
            if prt.ref == dest_ref_name:
                first_parent_sha1 = prt.new_sha1
                LOG.debug3('dest branch part of push, pushed head={}'
                           .format(p4gf_util.abbrev(first_parent_sha1)))
                break
        else:
            first_parent_sha1 = p4gf_util.git_rev_list_1(dest_ref_name)
            LOG.debug3('dest branch not part of push, head={}'
                       .format(p4gf_util.abbrev(first_parent_sha1)))

                        # Check out the raw commit, no branch ref.
                        # That way we don't have to put anything back when
                        # we're done (or if we fail).
        p4gf_util.git_checkout(first_parent_sha1)

                        # Merge in the review head.
                        #
        cmd = [ 'git', NTR('merge')
              , '--no-ff'       # Force a new merge commit, don't just
                                #   fast-forward into the review branch.
              , '--no-commit'   # So that we can set its message via file content.
              , review.sha1]
        p4gf_proc.popen(cmd)

                        # Commit the merge, reusing original commit's message
                        # and authorship.
        cmd = [ 'git', NTR('commit')
              , '--reuse-message', review.sha1]
        p4gf_proc.popen(cmd)

                        # The newly commit is under the HEAD. Use its sha1
                        # as the review's sha1.
        merge_sha1 = p4gf_util.git_rev_list_1('HEAD')
        LOG.debug('Merge commit {sha1} created for review {review}'
                  .format( sha1   = p4gf_util.abbrev(merge_sha1)
                         , review = review ))
        return merge_sha1
    def _enforce_disk_usage(self):
        """Enforce the total and received megabytes push limits, if any are defined.

        Raises a PushLimitException if a limit has been exceeded.
        """
        if not (self.space_limit or self.received_limit):
            return

        pending_mb = self.get_pending_mb()

        if self.space_limit:
            LOG.debug('enforce() measured {0:.2f}M disk usage'.format(self.space_total))
            if (self.space_total + pending_mb) > self.space_limit:
                # Remove the newly introduced, unreferenced, commits so
                # that the next push has a chance of succeeding.
                p4gf_proc.popen(['git', '--git-dir=' + self.repo.path, 'prune'])
                raise PushLimitException(
                    _("Push to repo {repo_name} rejected, space limit exceeded")
                    .format(repo_name=self.repo_name))

        if self.received_limit:
            previous_total = self.get_total_mb()
            space_total = self.space_total
            if space_total == 0:
                # unable to enforce limits, already logged
                return
            recieved_mb = space_total - pending_mb - previous_total
            LOG.debug('enforce() measured {0:.2f}M received'.format(recieved_mb))
            if recieved_mb > self.received_limit:
                # Remove the newly introduced, unreferenced, commits so
                # that the next push has a chance of succeeding.
                p4gf_proc.popen(['git', '--git-dir=' + self.repo.path, 'prune'])
                raise PushLimitException(
                    _("Push to repo {repo_name} rejected, received limit exceeded")
                    .format(repo_name=self.repo_name))
Exemple #3
0
 def move_git_ref(self):
     """Move a Git branch reference to point to the merge commit
     that Git Fusion created, not pushed commit.
     """
     ref = self.new_ref_name()
     cmd = ['git', 'branch', '-f', ref, self.sha1]
     p4gf_proc.popen(cmd)
     LOG.debug('move_git_ref() {} ==> {}'.format(ref, self.sha1))
 def move_git_ref(self):
     '''
     Move a Git branch reference to point to the merge commit
     that Git Fusion created, not pushed commit.
     '''
     ref = self.new_ref_name()
     cmd = ['git', 'branch', '-f', ref, self.sha1]
     p4gf_proc.popen(cmd)
     LOG.debug('move_git_ref() {} ==> {}'
               .format(ref, self.sha1))
Exemple #5
0
    def rename_git_ref(self):
        """Rename a Git branch reference.

        Old reference lacked a review id. Give it one by renaming.
        """
        if not self.needs_rename:
            return

        old_rn = self.old_ref_name()
        new_rn = self.new_ref_name()
        cmd = ['git', 'branch', '-m', old_rn, new_rn]
        p4gf_proc.popen(cmd)
        LOG.debug('rename_git_ref() {} ==> {}'.format(old_rn, new_rn))
Exemple #6
0
    def pre_copy_to_p4(self, prl):
        """Merge all review heads to their destination Git branches,
        move the branch reference forward to point to the merge
        commit, not the pushed head.

        This is a NEW commit that we create, not one pushed by the Git user.
        Git user will not have this commit, must pull to see it.

        Was:
                   dest
                     v
             ... --- D1

             ... --- R1
                     ^
              review/dest/new

        Becomes:
                   dest
                     v
             ... --- D1 == RM
                           /
             ... --- R1 ---
                           ^
                   review/dest/new

        Modifies prl in-place, replacing any review PreReceiveTuple with a new
        tuple that points to the merge commit as its head.
        """
        # Remember our changes for later.
        sha1_changed = False
        with p4gf_git.non_bare_git(self.ctx.repo_dirs.GIT_DIR):
            try:
                for review in self.all_review_list():
                    review.sha1 = self._create_merge_commit(review, prl)
                    review.prt.new_sha1 = review.sha1
                    sha1_changed = True
            except RuntimeError as err:
                with p4gf_git.non_bare_git():
                    # Clean up the repo so future efforts might succeed
                    p4gf_proc.popen(['git', NTR('reset'), '--hard'])
                raise err

        for x in prl:
            LOG.debug('pre_copy_to_p4() prt={}'.format(x))

            # Rebuild index by sha1, since we just changed sha1s.
        if sha1_changed:
            self._sha1_to_review_list = _review_dict(self.ctx, prl)
Exemple #7
0
def set_bare(is_bare):
    '''
    Reconfigure a repo for --bare or not-bare. Assumes current working
    directory is the Git repository to modify.
    '''
    ### this explodes the Python process, in p4api.so, oddly enough
    # path = pygit2.discover_repository('.')
    # repo = pygit2.Repository(path)
    # repo.config['core.bare'] = 'true' if is_bare else 'false'
    if is_bare:
        value = 'true'
    else:
        value = 'false'
    cmd = ['git', 'config', '--replace', 'core.bare', value]
    p4gf_proc.popen(cmd)
    def rename_git_ref(self):
        '''
        Rename a Git branch reference.

        Old reference lacked a review id. Give it one by renaming.
        '''
        if not self.needs_rename:
            return

        old_rn = self.old_ref_name()
        new_rn = self.new_ref_name()
        cmd = ['git', 'branch', '-m', old_rn, new_rn]
        p4gf_proc.popen(cmd)
        LOG.debug('rename_git_ref() {} ==> {}'
                  .format(old_rn, new_rn))
def create_empty_repo_branch(ctx, git_dir):
    '''
    Create and switch to branch empty_repo.

    This avoids Git errors when pushing to a brand-new empty repo which
    prohibits pushes to master.

    We'll switch to master and delete this branch later, when there's
    something in the repo and we can now safely detach HEAD from master.
    '''
    master_ish = p4gf_branch.most_equal(ctx.branch_dict())
    for branch in [
            master_ish.git_branch_name, p4gf_const.P4GF_BRANCH_EMPTY_REPO
    ]:
        p4gf_proc.popen(
            ['git', '--git-dir=' + git_dir, 'checkout', '-b', branch])
Exemple #10
0
def _find_true_parent(repo, head, branch_dict, work_tree):
    """Find the closest parent commit for the given branch reference."""
    if not os.path.exists('.git'):
        # repository not yet initialized
        return head
    branch_names = set()
    # Find all non-deleted branches that Git already knows about...
    for branch in branch_dict.values():
        if branch.git_branch_name and not branch.deleted:
            if repo.lookup_branch(branch.git_branch_name):
                branch_names.add(branch.git_branch_name)
    # ...excluding the branch that is being introduced
    branch_names.discard(head.git_branch_name())
    # Turn all of those into exclusions for git-rev-list
    not_branches = ['^{}'.format(br) for br in branch_names]
    cmd = ['git', 'rev-list', '--date-order', '--parents'] + not_branches
    # Start git-rev-list from the new SHA1 that is being introduced.
    cmd.append(head.new_sha1)
    cwd = os.getcwd()
    os.chdir(work_tree)
    # Initialize p4gf_proc now that we've changed the cwd to the git repo
    # (we lack the functionality to change the cwd after the fact).
    p4gf_proc.init()
    result = p4gf_proc.popen(cmd)
    os.chdir(cwd)
    output = result['out'].strip()
    LOG.debug("_find_true_parent() output: %s", output)
    if len(output) == 0:
        return head
    # Extract the last SHA1 from the git-rev-list output, that is the true
    # parent of this new branch.
    sha1s = output[output.rfind('\n') + 1:].split()
    LOG.debug("_find_true_parent() first parents: %s", sha1s)
    parent_sha1 = sha1s[1] if len(sha1s) > 1 else sha1s[0]
    return PreReceiveTuple(parent_sha1, head.new_sha1, head.ref)
def _run(cmd):
    """Log a shell command."""
    LOG.debug(' '.join(cmd))
    r = p4gf_proc.popen(cmd)
    # pylint:disable=maybe-no-member
    LOG.debug2(pprint.pformat(r))
    return r
def head_restorer():
    """Restore the current working directory's HEAD.

    Also restores the working tree to the sha1 it had when created.

    with p4gf_util.head_restorer():
        ... your code that can raise exceptions...
    """
    sha1 = p4gf_util.git_rev_list_1('HEAD')
    if not sha1:
        logging.getLogger("head_restorer").debug(
            "get_head_sha1() returned None, will not restore")
    try:
        yield
    finally:
        if sha1:
            with non_bare_git():
                p4gf_proc.popen(['git', 'reset', '--hard', sha1])
                p4gf_proc.popen(['git', 'checkout', sha1])
Exemple #13
0
def _test_dump_result_to_stdout(assigner):
    '''
    Dump all assignments to stdout in a format that a test script would enjoy.
    '''
    #print("Commit count: {}".format(len(assigner.rev_list)))
    fmt = NTR("{sha1:<7.7}\t{branch_id}\t{subject}")
    for rev in assigner.rev_list:
        p = p4gf_proc.popen(['git', 'log', '-1', '--pretty=format:%s', rev])
        subject = p['out'].splitlines()[0]
        branch_id = assigner.assign_dict[rev].branch_id_str()
        print(fmt.format(sha1=rev, branch_id=branch_id, subject=subject))
Exemple #14
0
    def rename_git_ref(self, ctx):
        """Change actual Git ref from 'refs/heads/depot-branch/mybranch'to 'refs/heads/mybranch'."""
        if self.prt.ref == self.orig_prt.ref:
            return
        old_name = _strip_refs_heads(self.orig_prt.ref)
        new_name = _strip_refs_heads(self.prt.ref)

        # Ensure the new name does not exist (may be left over from a failed push)
        try:
            ctx.repo.lookup_reference(self.prt.ref)
            cmd = ['git', 'branch', '-D', new_name]
            p4gf_proc.popen(cmd)
        except KeyError:
            pass
        except ValueError:
            pass

        cmd = ['git', 'branch', '-m', old_name, new_name]
        p4gf_proc.popen(cmd)
        LOG.debug('rename_git_ref() {} ==> {}'.format(old_name, new_name))
 def _enforce_overall_usage(self):
     """Enforce the overall disk usage limits, if any are defined."""
     remaining_mb = self.space_remaining
     if remaining_mb <= 0:
         return
     LOG.debug('_enforce_overall_usage() remaining {}'.format(remaining_mb))
     pending_mb = self.get_pending_mb()
     previous_total = self.get_total_mb()
     space_total = self.space_total
     if space_total == 0:
         # unable to enforce limits, already logged
         return
     recieved_mb = space_total - pending_mb - previous_total
     space_pending_mb = self._get_space_pending_mb()
     if (space_pending_mb + recieved_mb) > remaining_mb:
         # Remove the newly introduced, unreferenced, commits so that
         # the next push has a chance of succeeding.
         p4gf_proc.popen(['git', '--git-dir=' + self.repo.path, 'prune'])
         raise PushLimitException(
             _("Push to repo {repo_name} rejected, remaining space exhausted")
             .format(repo_name=self.repo_name))
def git_diff_tree(old_sha1, new_sha1, find_copy_rename_args=None):
    """Run 'git diff-tree -r --name-status <a> <b>' and return the results
    as a list of GitDiffTreeResult <action, gwt_path> tuples.

    See also diff_trees() for a more pygit2/in-process implementation.

    Ideally we would run git diff-tree -r WITHOUT -z or --name-status,
    which would include old/new mode and sha1. But that munges the
    gwt_path, any non-printing chars in the file path are converted
    and the path enquoted. Run 'git ls-tree -r -z' to extract mode and sha1.

    Extracted from Matrix 2
    """
    cmd = ['git', 'diff-tree', '-r',
           '-z']  # -z = machine-readable \0-delimited output
    if find_copy_rename_args:  # use copy_rename argugments if set
        cmd.extend(find_copy_rename_args)
    cmd.extend([old_sha1, new_sha1])
    d = p4gf_proc.popen(cmd)
    # pylint:disable=anomalous-backslash-in-string
    # Anomalous backslash in string: '\0'
    # Known bug in pylint 0.26.0
    #            fixed in 0.27.0
    dtis = DiffTreeItems(d['out'].split('\0'))
    # for pair in p4gf_util.pairwise(d['out'].split('\0')):
    for tupe in dtis:
        # tupe format matches that of output from git diff-tree
        # C/R tupe format: (parts, from_path, path)
        # A/M/D tupe format: (parts, path, None)
        parts = tupe[0].split()
        if parts and parts[0] == ':160000' or parts[1] == '160000':
            # Skip over submodules, cannot process them
            continue
        action = parts[4][0]
        if action in ['C', 'R']:
            # C/R tupe format: (parts, from_path, path)
            yield GitDiffTreeResult(action=action,
                                    gwt_path=tupe[2],
                                    from_path=tupe[1],
                                    old_mode=parts[0],
                                    new_mode=parts[1],
                                    old_sha1=parts[2],
                                    new_sha1=parts[3])
        else:
            # A/M/D tupe format: (parts, path, None)
            yield GitDiffTreeResult(action=action,
                                    gwt_path=tupe[1],
                                    from_path=None,
                                    old_mode=parts[0],
                                    new_mode=parts[1],
                                    old_sha1=parts[2],
                                    new_sha1=parts[3])
def check_process_alive(pid, args):
    '''
    Check if the given process is still alive, comparing the arguments
    given to those of the running process. If a match is found, return
    True, otherwise False.
    '''
    # Check if there is a running process with that ID, and compare the args
    # with those in the heartbeat counter.
    result = p4gf_proc.popen(["ps", "-o", "pid,command"])
    # pylint: disable=E1101
    # Instance of '' has no '' member
    if result['ec'] != 0:
        LOG.error("ps failed, exit status {}".format(result['ec']))
        # Had an error, fall back on the default behavior.
        return True
    for line in result['out'].splitlines()[1:]:
        (fid, fargs) = line.strip().split(' ', 1)
        if fid == pid:
            # Ignore any leading 'python' cruft in the command name since the
            # interpreter strips that from our own command name.
            return not args or fargs.endswith(args)
    # Lock holder appears to be gone, go ahead and steal it.
    return False
def synchronize_git_with_gf_branches(ctx):
    """Synchronize git named refs with named branches in p4gf_config2.

    Remove branches from git which have been deleted by another GF instance.
    Return list of git branch names in GF but not now in git.
    Content from Helix for new these branches will be added to git by p4gf_copy_to_git.

    p4gf_config and p4gf_config contains lists of branch definitions.
    Deleted branches acquires "deleted = True" option.
    Branch definitions for a git branch will have git-branch-name set.
    The same git branch may be re-created with a new branch-id.
    GF retains all branch definitions as
    """
    # Get the list of git branches - and clean them somewhat
    cmd = ['git', 'branch']
    result = p4gf_proc.popen(cmd)
    git_branches = result['out'].replace('*', '').splitlines()
    git_branches = [x.strip() for x in git_branches]
    git_branches = [
        x for x in git_branches if not x.startswith('(no branch)')
        and not x.startswith('(detached from')
    ]
    if not git_branches:  # no git branches - this must be during init repo
        return None
    # Using sets
    bnames_lw_active = {
        b.git_branch_name
        for b in ctx.branch_dict().values()
        if b.git_branch_name and b.is_lightweight and not b.deleted
    }
    bnames_lw_deleted = {
        b.git_branch_name
        for b in ctx.branch_dict().values()
        if b.git_branch_name and b.is_lightweight and b.deleted
    }
    bnames_fp_active = {
        b.git_branch_name
        for b in ctx.branch_dict().values()
        if not b.is_lightweight and not b.deleted
    }
    bnames_fp_deleted = {
        b.git_branch_name
        for b in ctx.branch_dict().values()
        if not b.is_lightweight and b.deleted
    }

    # Branches can be deleted and re-created.
    # All deleted branch definitions are retained after marking with deleted=True.
    # Using set difference.
    bnames_fp_deleted_current = bnames_fp_deleted - bnames_fp_active
    bnames_lw_deleted_current = bnames_lw_deleted - bnames_lw_active
    bnames_all_deleted_current = bnames_fp_deleted_current | bnames_lw_deleted_current
    bnames_all_active_current = bnames_fp_active | bnames_lw_active

    cmd = ['git', 'branch', '-D']
    git_branches_deleted = set()
    for b in git_branches:
        branch_name = b.split()[0]  # first item is branch name
        LOG.debug("synchronize: git branch: {}".format(branch_name))
        if branch_name in bnames_all_deleted_current:
            LOG.debug("Removing branch :{0}: from git".format(branch_name))
            p4gf_proc.popen(cmd + [branch_name])
            git_branches_deleted.add(branch_name)
    # Remove the just deleted branche names from our list of current git branches
    git_branches = set(git_branches) - git_branches_deleted
    # Return list of FP and LW branch names in GF but not in git
    new_branches = [
        b for b in bnames_all_active_current if b not in git_branches
    ]
    return new_branches
def synchronize_git_with_gf_branches(ctx):
    '''
    Synchronize git named refs with named branches in p4config2.
    Remove branches from git which have been deleted by another GF instance.
    Return list of task branches in GF but not in git: branches in this
    list will be added to git by p4gf_copy_to_git.

    p4config2 contains a list of names task branches. If a branch is deleted
    it acquires "deleted = True" option. The same branch may be re-created
    with a new branch-id. We retain both branch definitions as there
    may be dependencies on the deleted branch_id. If one GF instance
    deletes a named task branch, then the current GF instance needs
    to notify git to remove the branch ref. We do this by
    examining the list of non-deleted branch definitions
    and remove from git any that are not found. If p4config2
    has been removed, we cannot determine that a branch
    needs to be deleted from git - so do nothing.
    '''
    cmd = ['git', 'branch']
    d = p4gf_proc.popen(cmd)
    git_branches = d['out'].replace('*', '').splitlines()
    if not git_branches:  # no git branches - this must be during init repo
        return None
    p4_deleted_branch_names = [
        b.git_branch_name for b in ctx.branch_dict().values()
        if b.git_branch_name and b.deleted
    ]
    p4_active_branch_names_lw = [
        b.git_branch_name for b in ctx.branch_dict().values()
        if b.git_branch_name and b.is_lightweight and not b.deleted
    ]
    p4_branch_names_lw = [
        b.git_branch_name for b in ctx.branch_dict().values()
        if b.git_branch_name and b.is_lightweight
    ]
    p4_branch_names_non_lw = [
        b.git_branch_name for b in ctx.branch_dict().values()
        if not b.is_lightweight
    ]
    cmd = ['git', 'branch', '-D']
    git_branches_cleaned = []
    for branch in git_branches:
        if "(no branch)" in branch:
            continue
        branch = branch.split()
        i = 0
        if branch[0] == '*':
            i = 1
        if branch[i] in p4_branch_names_non_lw:
            git_branches_cleaned.append(branch[i])
            continue  # Do not delete non-lightweight
        if (branch[i] in p4_deleted_branch_names
                and not branch[i] in p4_active_branch_names_lw):
            LOG.debug("Removing branch :{0}: from git".format(branch[i]))
            d = p4gf_proc.popen(cmd + [branch[i]])
        else:
            git_branches_cleaned.append(branch[i])
    # which branches are marked as deleted but have not been re-created
    really_deleted = [
        b for b in p4_deleted_branch_names
        if b not in p4_active_branch_names_lw
    ]
    git_branches_cleaned.extend(really_deleted)
    # Return list of LW branches in GF but not in git
    # Adding a new fully populated to p4gf_config needs testing.
    return [b for b in p4_branch_names_lw if b not in git_branches_cleaned]
Exemple #20
0
    def _load_commit_dag(self):
        '''
        Load the Git commit tree into memory. We just need the
        parent/child relationships.
        '''
        # A single call to git-rev-list produces both the commit sha1 list
        # that we need AND the child->parent associations that we need. It's
        # screaming fast: 32,000 commit lines in <1 second.
        with Timer(TIMER_RUN_REV_LIST):
            range_list = [prt.to_range() for prt in self.pre_receive_list]
            cmd        = [ 'git', 'rev-list'
                         , '--date-order', '--parents'] + range_list
            LOG.debug2("DAG: {}".format(' '.join(cmd)))
            d = p4gf_proc.popen(cmd)

        seen_parents = set()

        # Pass 1: Build up a dict of sha1->Assign objects, one per commit.
        with Timer(TIMER_CONSUME_REV_LIST):
            lines = d['out'].splitlines()
            with ProgressReporter.Determinate(len(lines)):
                for line in lines:
                    ProgressReporter.increment(_('Loading commit tree into memory...'))
                    sha1s = line.split()
                    curr_sha1 = sha1s.pop(0)
                    self.rev_list.append(curr_sha1)
                    if LOG.isEnabledFor(logging.DEBUG3):
                        LOG.debug3('DAG: rev_list {} {}'
                                   .format( p4gf_util.abbrev(curr_sha1)
                                          , ' '.join(p4gf_util.abbrev(sha1s))))
                    self.assign_dict[curr_sha1] = Assign(curr_sha1, sha1s)
                    seen_parents.update(sha1s)

        # git-rev-list is awesome in that it gives us only as much as we need
        # for self.rev_list, but unawesome in that this optimization tends to
        # omit paths to branch refs' OLD heads if the old heads are 2+ commits
        # back in time, and that time is ALREADY covered by some OTHER branch.
        # Re-run each pushed branch separately to add enough Assign() nodes
        # to form a full path to its old ref.
        if 2 <= len(self.pre_receive_list):
            for prt in self.pre_receive_list:
                # Skip NEW branch refs: those don't have
                # to connect up to anything.
                if prt.old_sha1 == p4gf_const.NULL_COMMIT_SHA1:
                    continue
                with Timer(TIMER_RUN_REV_LIST):
                    cmd  = [ 'git', 'rev-list'
                           , '--date-order', '--parents', '--reverse', prt.to_range()]
                    LOG.debug2("DAG: {}".format(' '.join(cmd)))
                    d = p4gf_proc.popen(cmd)

                with Timer(TIMER_CONSUME_REV_LIST):
                    for line in d['out'].splitlines():
                        sha1s = line.split()
                        curr_sha1 = sha1s.pop(0)
                        if curr_sha1 in self.assign_dict:
                            break
                        LOG.debug3('DAG: path     {} {}'
                                   .format( p4gf_util.abbrev(curr_sha1)
                                          , ' '.join(p4gf_util.abbrev(sha1s))))
                        self.assign_dict[curr_sha1] = Assign(curr_sha1, sha1s)
                        seen_parents.update(sha1s)

        # Create acting-as-parent-only nodes in dict, too. We don't process
        # these as part of iterating over revs, but we need them when
        # tree walking.
        with Timer(TIMER_CONSUME_REV_LIST):
            parent_only = seen_parents - set(self.assign_dict.keys())
            for curr_sha1 in parent_only:
                if curr_sha1 in self.assign_dict:
                    break
                LOG.debug3('DAG: par only {}'.format( p4gf_util.abbrev(curr_sha1)))
                self.assign_dict[curr_sha1] = Assign(curr_sha1, [])

        # Pass 2: Fill in Assign.children list
        with Timer(TIMER_ASSIGN_CHILDREN):
            with ProgressReporter.Determinate(len(self.assign_dict)):
                for assign in self.assign_dict.values():
                    ProgressReporter.increment(_('Finding child commits...'))
                    for par_sha1 in assign.parents:
                        par_assign = self.assign_dict.get(par_sha1)
                        if par_assign:
                            par_assign.children.add(assign.sha1)
                        else:
                            # Expected and okay: some parents already exist and
                            # are not part of our push/fast-export list.
                            LOG.debug2(
                                "DAG: child {child} -> parent {parent}: parent not part of push"
                                .format(child=assign.sha1[:7], parent=par_sha1[:7]))
def main():
    """Update the disk usage p4 keys for one or more repositories."""
    desc = _("Set/reset the total and pending p4 keys.")
    epilog = _("Without the -y/--reset option, only displays current values.")
    parser = p4gf_util.create_arg_parser(desc, epilog=epilog)
    parser.add_argument('-a', '--all', action='store_true',
                        help=_('process all known Git Fusion repositories'))
    parser.add_argument('-y', '--reset', action='store_true',
                        help=_('perform the reset of the p4 keys'))
    parser.add_argument(NTR('repos'), metavar=NTR('repo'), nargs='*',
                        help=_('name of repository to be updated'))
    args = parser.parse_args()

    # Check that either --all, or 'repos' was specified.
    if not args.all and len(args.repos) == 0:
        sys.stderr.write(_('Missing repo names; try adding --all option.\n'))
        sys.exit(2)
    if args.all and len(args.repos) > 0:
        sys.stderr.write(_('Ambiguous arguments. Choose --all or a repo name.\n'))
        sys.exit(2)

    with p4gf_create_p4.Closer():
        p4 = p4gf_create_p4.create_p4_temp_client()
        if not p4:
            sys.exit(2)
        # Sanity check the connection (e.g. user logged in?) before proceeding.
        try:
            p4.fetch_client()
        except P4.P4Exception as e:
            sys.stderr.write(_('P4 exception occurred: {exception}').format(exception=e))
            sys.exit(1)

        if args.all:
            repos = p4gf_util.repo_config_list(p4)
            if len(repos) == 0:
                print(_('No Git Fusion repositories found, nothing to do.'))
                sys.exit(0)
        else:
            repos = args.repos
        p4gf_create_p4.p4_disconnect(p4)

        for repo in repos:
            repo_name = p4gf_translate.TranslateReponame.git_to_repo(repo)
            print(_("Processing repository {repo_name}... ").format(repo_name=repo_name), end='')
            ctx = p4gf_context.create_context(repo_name)
            with ExitStack() as stack:
                stack.enter_context(ctx)
                ctx.repo_lock = p4gf_lock.RepoLock(ctx.p4gf, repo_name, blocking=False)
                stack.enter_context(ctx.repo_lock)
                limits = PushLimits(ctx)
                if args.reset:
                    # Copy any Perforce changes down to this Git repository.
                    p4gf_copy_p2g.copy_p2g_ctx(ctx)
                    # Attempt to trim any unreferenced objects.
                    p4gf_proc.popen(['git', '--git-dir=' + ctx.repo.path, 'prune'])
                    limits.post_copy()
                # Display current key values and disk usage.
                pending_mb = limits.get_pending_mb()
                total_mb = limits.get_total_mb()
                current_mb = limits.space_total
                print(
                    _('{total_mb:.2f}M total, {pending_mb:.2f}M pending, '
                      '{current_mb:.2f}M current')
                    .format(total_mb=total_mb,
                            pending_mb=pending_mb,
                            current_mb=current_mb), end='')
            print("")
Exemple #22
0
    def _create_merge_commit(review, prl):
        """Create a new merge commit, merging the pushed commit into
        its destination branch. Return new commit's sha1.

        Leaves all references untouched.

        Knows to scan pushed PreReceiveTuple list for any pushed changes
        to destination branch, use (what will eventually be) post-push head,
        not pre-push, as first-parent of new new merge commit.

        Raises exception if unable to create the merge commit (usually due to
        Git merge conflict, error would be from 'git merge'.
        """
        LOG.debug('_create_merge_commit() {}'.format(review))

        # Is the destination branch also being modified as part
        # of this push? If so, use its eventual post-push head,
        # not current head, for this merge.
        dest_ref_name = 'refs/heads/' + review.git_branch_name
        LOG.debug3('dest_ref_name={}'.format(dest_ref_name))
        first_parent_sha1 = None
        for prt in prl:
            if prt.ref == dest_ref_name:
                first_parent_sha1 = prt.new_sha1
                LOG.debug3('dest branch part of push, pushed head={}'.format(
                    p4gf_util.abbrev(first_parent_sha1)))
                break
        else:
            first_parent_sha1 = p4gf_util.git_rev_list_1(dest_ref_name)
            LOG.debug3('dest branch not part of push, head={}'.format(
                p4gf_util.abbrev(first_parent_sha1)))

            # Check out the raw commit, no branch ref.
            # That way we don't have to put anything back when
            # we're done (or if we fail).
        p4gf_git.git_checkout(first_parent_sha1, force=True)

        # Merge in the review head.
        #
        cmd = [
            'git',
            NTR('merge'),
            '--no-ff'  # Force a new merge commit, don't just
            #   fast-forward into the review branch.
            ,
            '--no-commit'  # So that we can set its message via file content.
            ,
            review.sha1
        ]
        p4gf_proc.popen(cmd)

        # Commit the merge, reusing original commit's message
        # and authorship.
        cmd = ['git', NTR('commit'), '--reuse-message', review.sha1]
        p4gf_proc.popen(cmd)

        # The newly commit is under the HEAD. Use its sha1
        # as the review's sha1.
        merge_sha1 = p4gf_util.git_rev_list_1('HEAD')
        LOG.debug('Merge commit {sha1} created for review {review}'.format(
            sha1=p4gf_util.abbrev(merge_sha1), review=review))
        return merge_sha1