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))
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))
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 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)
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])
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])
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))
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]
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("")
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