def decided_(self): ''' Guaranteed to return a Decided instance. Creates one if necessary. ''' if not self.decided: self.decided = Decided() return self.decided
def decided(self, index): """Return a Decided instance, creating Cell and Decided if none yet exists. """ c = self.cell(index) if not c.decided: c.decided = Decided() return c.decided
def _decide_jit_if(self): """Branch file from fully populated basis if necessary and possible. This code runs after any _decide_integ_from_column() and _apply_git_delta(). Prefer p4 'branch' file actions from actual Git parent branches over JIT branch file actions. Store the JIT action we WOULD run if we later decide JIT is required. """ # Not worth attempting to JIT-branch a file that we've already decided # to integrate from at least one other branch. We've got files and # content coming from other integ source branches, and that's good # enough to hold the incoming commit's results. if self.has_integ: common.debug3('_decide_jit_if() no: has_integ') return # If we've got no Git action to apply, there's no reason to # JIT-branch this file. if not ( self.git_delta_cell and self.git_delta_cell.decided and self.git_delta_cell.decided.has_p4_action() ): common.debug3('_decide_jit_if() no: no git delta from prev commit') return # No need to JIT-branch a file that already exists in destination. if common.gdest_cell_exists_in_p4(self.gdest_cell): common.debug3('_decide_jit_if() no: already exists in p4 destination') return # Cannot JIT-branch a file that has no source. if not self._p4jitfp_exists(): common.debug3('_decide_jit_if() no: does not exist in JIT FP basis') return if self.m.symlink_in_path(self.row.gwt_path): common.debug3('_decide_jit_if() no: symlink in path') return # Look up the correct integ, resolve, and fallback action # to take for this file. Sometimes is "do nothing" and that's okay. ri = p4gf_g2p_matrix_integ.to_input( row = self.row , integ_src_cell = self.p4jitfp_cell , integ_dest_cell = self.integ_dest_cell , git_delta_cell = self.git_delta_cell , gdest_cell = self.gdest_cell ) r = p4gf_g2p_matrix_integ.find_row(ri) if ( not r or not ((r.integ_flags is not None) or r.fallback) ): common.debug3('_decide_jit_if() no: integ matrix returned' ' no integ or fallback') return self.p4jitfp_cell.decided = Decided.from_integ_matrix_row(r, ri) common.debug3('_decide_jit_if() JIT: {}'.format(self.p4jitfp_cell.decided)) if r.integ_flags is not None: self.has_integ = True
def _ghost_decide_from_git_lfs_initial_track(self, gparn_cell): """If this is a top-level .gitattributes row that exists in Git but not in Perforce, it's probably one that git-lfs-initial-track inserted into Git history but not Perforce. Time to ghost it into Perforce history, too. Returns a Decided to 'p4 add' it into Perforce history if so, or None if not. """ if ( self.m.ctx.is_lfs_enabled and self.row.gwt_path == p4gf_const.GITATTRIBUTES and gparn_cell and "sha1" in gparn_cell.discovered and not "depotFile" in gparn_cell.discovered ): return Decided( p4_request = 'add' , integ_input = 'lfs_init' ) return None
def _decide_populate_from(self, exists_in_git): """If discovery marked a column for "populate this branch from this column", do so. Infrequent. Occurs only on first changelist on a new Perforce branch. """ if not self._want_populate_from(exists_in_git): return # Yep, we can indeed populate this row's GWT from the # populate_from column. common.debug3( '_decide_populate_from() col={col} integ -Rbd resolve -at' , col=self.m._populate_from_column.index ) self.populate_from_cell.decided \ = Decided( integ_flags = '-Rbd' , resolve_flags = '-at' , on_integ_failure = Decided.RAISE , integ_input = 'populate_from') self.has_integ = True
def apply_git_delta_cell(gdest_cell, git_delta_cell): ''' If git-fast-export or git-diff-tree says to Add/Modify/Delete this GWT path, then do so. Internally only applies to row if we've no integ action. The big integ decision table already factors in Git actions when integrating. ''' git_action = Cell.safe_discovered(git_delta_cell, 'git-action') if not git_action: return exists = ( gdest_cell_exists_in_p4(gdest_cell) and not gdest_cell_deleted_at_head(gdest_cell)) debug3('apply_git_delta_cell() e={e} g={g} '.format(e=exists, g=git_action)) action = GIT_ACTION_AM[exists][git_action] if not action: return if not git_delta_cell.decided: git_delta_cell.decided = Decided() git_delta_cell.decided.add_git_action(action)
def _decide_jit_if(self): ''' Branch file from fully populated basis if necessary and possible. This code runs after any _decide_integ_from_column() and _apply_git_delta(). Prefer p4 'branch' file actions from actual Git parent branches over JIT branch file actions. Store the JIT action we WOULD run if we later decide JIT is required. ### BUG: integ matrix prohibits JIT-for-delete, but we require JIT-for-delete. Make JIT-for-delete work. ''' # Not worth attempting to JIT-branch a file that we've already decided # to integrate from at least one other branch. We've got files and # content coming from other integ source branches, and that's good # enough to hold the incoming commit's results. if self.has_integ: common.debug3('_decide_jit_if() no: has_integ') return # If we've got no Git action to apply, there's no reason to # JIT-branch this file. if not ( self.git_delta_cell and self.git_delta_cell.decided ): common.debug3('_decide_jit_if() no: no git delta from prev commit') return # No need to JIT-branch a file that already exists in destination. if common.gdest_cell_exists_in_p4(self.gdest_cell): common.debug3('_decide_jit_if() no: no already exists in p4 destination') return # Cannot JIT-branch a file that has no source. if not self._p4jitfp_exists(): common.debug3('_decide_jit_if() no: does not exist in JIT FP basis') return if self.m.symlink_in_path(self.row.gwt_path): common.debug3('_decide_jit_if() no: symlink in path') return # Look up the correct integ, resolve, and fallback action # to take for this file. Sometimes is "do nothing" and that's okay. ri = p4gf_g2p_matrix_integ.to_input( row = self.row , integ_src_cell = self.p4jitfp_cell , integ_dest_cell = self.integ_dest_cell , git_delta_cell = self.git_delta_cell , gdest_cell = self.gdest_cell ) r = p4gf_g2p_matrix_integ.find_row(ri) if ( not r or not ((r.integ_flags != None) or r.fallback) ): common.debug3('_decide_jit_if() no: integ matrix returned' ' no integ or fallback') return self.p4jitfp_cell.decided = Decided.from_integ_matrix_row(r, ri) common.debug3('_decide_jit_if() JIT: {}'.format(self.p4jitfp_cell.decided)) if r.integ_flags != None: self.has_integ = True
def _decide_integ_from_column(self, column): ''' If this column has an integration source that we _discover_branches() decide should be used, decide how. If column is GPARN with nothing to integrate, but backed by a GPARFPN, schedule GPARFP as next work_queue item so that we can check to see if it holds something to integrate. This code runs before _decide_jit_if(). Prefer p4 'branch' file actions from actual Git parent branches over JIT branch file actions. ''' common.debug3('Row._decide_integ_from_column() {col}', col=column.index) # Skip any already-integrated population source if column is self.m._populate_from_column: common.debug3( 'Row._decide_integ_from_column() {col} == pop_fm. Skipping.' , col=column.index ) return # Or from our destination branch. if column.branch == self.m.current_branch: common.debug3( 'Row._decide_integ_from_column() {col} == curr. Skipping.' , col=column.index ) return # Nothing to integ? src_cell = self.row.cells[column.index] if not src_cell or not src_cell.discovered: # Even if nothing to integ from GPARN, check its GPARFPN # basis for something to integ. If so, prepend to work queue # to check for something to integ. if ( column.col_type == Column.GPARN and column.fp_counterpart and self.row.cells[column.fp_counterpart.index]): self.integ_work_queue.appendleft(column.fp_counterpart) common.debug3( 'Row._decide_integ_from_column() {col} no src disc.' ' Skipping.' , col=column.index ) return # Nothing to integ. # # 'endFromRev'-not-'rev' OK here, indicates _some_ integ action # that 'rev' does not. if not 'endFromRev' in src_cell.discovered: common.debug3( 'Row._decide_integ_from_column() {col} no src endFromRev.' ' Skipping.' , col=column.index ) return # Don't integ duplicate branch or delete actions. cur_is = _p4_branch_or_delete(src_cell.discovered) if self.is_integ_branch_or_delete and cur_is: common.debug3( 'Row._decide_integ_from_column() {col}' ' double-delete/branch. Skipping.' , col=column.index ) return # Or integs that treat a symlink file as a directory. if self.m.symlink_in_path(self.row.gwt_path): common.debug3( 'Row._decide_integ_from_column() {col} symlink ancestor.' ' Skipping.' , col=column.index) return # Or source file revisions at or before the destination's # fully populated basis # common.debug3('### Row._decide_integ_from_column() {col}' # ' checking FP basis...', col=column.index) if not self._after_dest_fp(src_cell): common.debug3( 'Row._decide_integ_from_column() {col} Not after' ' JIT basis. Skipping.' , col=column.index ) return # Look up the correct integ, resolve, and fallback action # to take for this file. Sometimes is "do nothing" and that's okay. # common.debug3('### Row._decide_integ_from_column() {col}' # ' checking integ matrix...', col=column.index) ri = p4gf_g2p_matrix_integ.to_input( row = self.row , integ_src_cell = src_cell , integ_dest_cell = self.integ_dest_cell , git_delta_cell = self.git_delta_cell , gdest_cell = self.gdest_cell ) r = p4gf_g2p_matrix_integ.find_row(ri) if ( not r or not ((r.integ_flags != None) or r.fallback) ): common.debug3('Row._decide_integ_from_column() {col} {ri} matrix returned' ' no action. Skipping.' , col=column.index , ri = p4gf_g2p_matrix_integ.deb(ri) ) return # I don't _think_ there's a way to gt here on a column we already # decided (such as self._poulate_from_column). assert() to be sure. assert not src_cell.decided src_cell.decided = Decided.from_integ_matrix_row(r, ri) common.debug3( 'Row._decide_integ_from_column() col={col} decided={decided}' , col = column.index , decided = src_cell.decided ) if r.integ_flags != None: self.has_integ = True self.is_integ_branch_or_delete |= cur_is
def ghost_decide(self, row): ''' Main entry point from Matrix.ghost_decide(). What Perforce file action, if any, should we record in a ghost changelist? Store decision in ghost_cell.decided ''' self._reset() self.row = row ghost_cell = row.cell_if_col(self.m.ghost_column) if not ghost_cell: return self.ghost_cell = ghost_cell self.gdest_cell = row.cell_if_col(self.m._gdest_column) self.git_delta_cell = row.cell_if_col(self.m.git_delta_column) self.p4jitfp_cell = row.cell_if_col(self.m._p4jitfp_column) first_parent_col = Column.find_first_parent(self.m.columns) gparn_cell = row.cell_if_col(first_parent_col) if first_parent_col: gparfpn_cell = row.cell_if_col(first_parent_col.fp_counterpart) else: gparfpn_cell = None if not first_parent_col: return None decision_input = p4gf_g2p_matrix2_ghost.to_input( ghost_cell = self.ghost_cell , gdest_cell = self.gdest_cell , gparn_cell = gparn_cell , gparfpn_cell = gparfpn_cell , gdest_column = self.m._gdest_column , gparn_column = first_parent_col ) decision_row = p4gf_g2p_matrix2_ghost.find_row(decision_input) if LOG.isEnabledFor(logging.DEBUG3): LOG.debug3("ghost_decide() {gwt:<10} {input:<47} {output:<6} {comment}" .format( input = p4gf_g2p_matrix2_ghost.deb(decision_input) , output = decision_row.output , gwt = row.gwt_path , comment = decision_row.comment )) decided = None if not decision_row: raise RuntimeError(_('ghost decision input {} matched no known' ' decision table entry {}') .format( p4gf_g2p_matrix2_ghost.deb(decision_input) , row.gwt_path)) if decision_row.output == p4gf_g2p_matrix2_ghost.BRANCH: decided = Decided( integ_flags = '-Rbd' , resolve_flags = '-at' , on_integ_failure = Decided.FALLBACK , integ_fallback = 'add' , p4_request = None , integ_input = decision_input ) # To simplify later code, copy integration source # from GPARN/GPARNFP into GHOST column so that # later code can use GHOST as integ source. _copy_discovered(ghost_cell, gparn_cell, gparfpn_cell) elif decision_row.output in [ p4gf_g2p_matrix2_ghost.EDIT , p4gf_g2p_matrix2_ghost.DELETE ]: decided = Decided( p4_request = decision_row.output , integ_input = decision_input ) elif decision_row.output == p4gf_g2p_matrix2_ghost.NOP: decided = None elif decision_row.output == p4gf_g2p_matrix2_ghost.ADD_DELETE: decided = Decided( p4_request = 'add' , add_delete = True , integ_input = decision_input ) elif decision_row.output == p4gf_g2p_matrix2_ghost.IMPOSSIBLE: LOG.error('IMPOSSIBLE. Check your inputs. {}'.format(row.gwt_path)) p4gf_g2p_matrix2_ghost.LOG.setLevel(logging.DEBUG3) p4gf_g2p_matrix2_ghost.to_input( ghost_cell = self.ghost_cell , gdest_cell = self.gdest_cell , gparn_cell = gparn_cell , gparfpn_cell = gparfpn_cell , gdest_column = self.m._gdest_column , gparn_column = first_parent_col ) raise RuntimeError(_('ghost decision input {} is impossible.' ' gwt_path={}') .format( p4gf_g2p_matrix2_ghost.deb(decision_input) , row.gwt_path)) else: raise RuntimeError(_('ghost decision input {} produced unknown' ' output {}. gwt_path={}') .format( p4gf_g2p_matrix2_ghost.deb(decision_input) , decision_row.output , row.gwt_path)) self.ghost_cell.decided = decided return
def _decide_integ_from_column(self, column): """If this column has an integration source that we _discover_branches() decide should be used, decide how. If column is GPARN with nothing to integrate, but backed by a GPARFPN, schedule GPARFP as next work_queue item so that we can check to see if it holds something to integrate. This code runs before _decide_jit_if(). Prefer p4 'branch' file actions from actual Git parent branches over JIT branch file actions. """ common.debug3('Row._decide_integ_from_column() {col}', col=column.index) # Skip any already-integrated population source if column is self.m._populate_from_column: common.debug3( 'Row._decide_integ_from_column() {col} == pop_fm. Skipping.' , col=column.index ) return # Or from our destination branch. if column.branch == self.m.current_branch: common.debug3( 'Row._decide_integ_from_column() {col} == curr. Skipping.' , col=column.index ) return # Nothing to integ? src_cell = self.row.cells[column.index] if not src_cell or not src_cell.discovered: # Even if nothing to integ from GPARN, check its GPARFPN # basis for something to integ. If so, prepend to work queue # to check for something to integ. if ( column.col_type == Column.GPARN and column.fp_counterpart and self.row.cells[column.fp_counterpart.index]): self.integ_work_queue.appendleft(column.fp_counterpart) common.debug3( 'Row._decide_integ_from_column() {col} no src disc.' ' Skipping.' , col=column.index ) return # Skip integ sources that do not exist at all. if 'depotFile' not in src_cell.discovered: common.debug3('Row._decide_integ_from_column() {col} no src depotFile.' ' Skipping.' , col=column.index ) return # Don't integ duplicate branch or delete actions. ### Zig warns: now that we 'p4 fstat' instead of 'p4 integ', a 'branch' ### action here means "head action on source depot file is 'branch'" ### not "want to branch file from source to dest. This use of ### _p4_branch_or_delete() no longer defends against multiple branch ### actions in a single dest file, single changelist. This appears to ### be okay, because we've never seen multiple branch actions cause a ### problem. It's only multiple deletes that fail. cur_is = _p4_branch_or_delete(src_cell.discovered) if self.is_integ_branch_or_delete and cur_is: common.debug3( 'Row._decide_integ_from_column() {col}' ' double-delete/branch. Skipping.' , col=column.index ) return # Or integs that treat a symlink file as a directory. if self.m.symlink_in_path(self.row.gwt_path): common.debug3( 'Row._decide_integ_from_column() {col} symlink ancestor.' ' Skipping.' , col=column.index) return # Or source file revisions at or before the destination's # fully populated basis # common.debug3('### Row._decide_integ_from_column() {col}' # ' checking FP basis...', col=column.index) if not self._after_dest_fp(src_cell): common.debug3( 'Row._decide_integ_from_column() {col} Not after' ' JIT basis. Skipping.' , col=column.index ) return # Look up the correct integ, resolve, and fallback action # to take for this file. Sometimes is "do nothing" and that's okay. # common.debug3('### Row._decide_integ_from_column() {col}' # ' checking integ matrix...', col=column.index) ri = p4gf_g2p_matrix_integ.to_input( row = self.row , integ_src_cell = src_cell , integ_dest_cell = self.integ_dest_cell , git_delta_cell = self.git_delta_cell , gdest_cell = self.gdest_cell ) r = p4gf_g2p_matrix_integ.find_row(ri) if ( not r or not ((r.integ_flags is not None) or r.fallback) ): common.debug3('Row._decide_integ_from_column() {col} {ri} matrix returned' ' no action. Skipping.' , col=column.index , ri = p4gf_g2p_matrix_integ.deb(ri) ) return # I don't _think_ there's a way to get here on a column we already # decided (such as self._poulate_from_column). assert() to be sure. assert not (src_cell.decided and src_cell.decided.has_p4_action()) src_cell.decided = Decided.from_integ_matrix_row(r, ri) common.debug3( 'Row._decide_integ_from_column() col={col} decided={decided}' , col = column.index , decided = src_cell.decided ) if r.integ_flags is not None: self.has_integ = True self.is_integ_branch_or_delete |= cur_is
def _ghost_decide_from_matrix( self, * , gparn_cell , gparfpn_cell , orig_gparn_col ): """Run a row through the ghost decision matrix. Returns a Decided value if something to do in a ghost changelist, None if nothing to do, or raises exception if surprised. Basically a big Python switch statement, without the clever Python dict wrapper. """ decision_input = p4gf_g2p_matrix2_ghost.to_input( p4jitfp_cell = self.p4jitfp_cell , gdest_cell = self.gdest_cell , gparn_cell = gparn_cell , gparfpn_cell = gparfpn_cell , ghost_cell = self.ghost_cell , gdest_column = self.m._gdest_column , gparn_column = orig_gparn_col ) decision_row = p4gf_g2p_matrix2_ghost.find_row(decision_input) if LOG.isEnabledFor(logging.DEBUG3): LOG.debug3("ghost_decide() {gwt:<15} {input} {output:<6} {comment}" .format( input = p4gf_g2p_matrix2_ghost.deb( decision_input , p4gf_g2p_matrix2_ghost.LONG) , output = decision_row.output , gwt = self.row.gwt_path , comment = decision_row.comment )) decided = None if not decision_row: raise RuntimeError(_('ghost decision input {input} matched no known' ' decision table entry {gwt_path}') .format( input = p4gf_g2p_matrix2_ghost.deb(decision_input) , gwt_path = self.row.gwt_path)) # Branch from GPARN. if decision_row.output == p4gf_g2p_matrix2_ghost.BRANCH: decided = Decided( integ_flags = '-Rbd' , resolve_flags = '-at' , on_integ_failure = Decided.FALLBACK , integ_fallback = 'add' , p4_request = None , integ_input = decision_input ) # To simplify later code, copy integration source # from GPARN/GPARNFP into GHOST column so that # later code can use GHOST as integ source. self._lazy_create_ghost_cell_discovered() _copy_discovered(self.ghost_cell, gparn_cell, gparfpn_cell) # Branch from P4JITFP, submit, # then DELETE in a second GHOST changelist. elif decision_row.output == p4gf_g2p_matrix2_ghost.BRANCH_DELETE: decided = Decided( integ_flags = '-Rbd' , resolve_flags = '-at' , on_integ_failure = Decided.FALLBACK , integ_fallback = 'add' , p4_request = None , integ_input = decision_input , branch_delete = True ) # To simplify later code, copy integration source # from P4JITFP into GHOST column so that # later code can use GHOST as integ source. self._lazy_create_ghost_cell_discovered() _copy_discovered(self.ghost_cell, self.p4jitfp_cell) elif decision_row.output in [ p4gf_g2p_matrix2_ghost.EDIT , p4gf_g2p_matrix2_ghost.DELETE ]: decided = Decided( p4_request = decision_row.output , integ_input = decision_input ) elif decision_row.output == p4gf_g2p_matrix2_ghost.NOP: decided = None elif decision_row.output == p4gf_g2p_matrix2_ghost.IMPOSSIBLE: LOG.error('IMPOSSIBLE ghost. Check your inputs. {}'.format(self.row.gwt_path)) p4gf_g2p_matrix2_ghost.to_input( p4jitfp_cell = self.p4jitfp_cell , gdest_cell = self.gdest_cell , gparn_cell = gparn_cell , gparfpn_cell = gparfpn_cell , ghost_cell = self.ghost_cell , gdest_column = self.m._gdest_column , gparn_column = orig_gparn_col ) raise RuntimeError( _('ghost decision input {input} is impossible. gwt_path={gwt_path}') .format( input = p4gf_g2p_matrix2_ghost.deb( decision_input , fmt=p4gf_g2p_matrix2_ghost.LONG) , gwt_path = self.row.gwt_path)) else: raise RuntimeError(_('ghost decision input {input} produced unknown' ' output {output}. gwt_path={gwt_path}') .format( input = p4gf_g2p_matrix2_ghost.deb(decision_input) , output = decision_row.output , gwt_path = self.row.gwt_path)) return decided
def ghost_decide(self, row): """Main entry point from Matrix.ghost_decide(). What Perforce file action, if any, should we record in a ghost changelist? Store decision in ghost_cell.decided """ # pylint:disable=too-many-branches self._reset() # Never include .p4gf_empty_changelist_placeholder in a # GHOST changelist. Doing so will erroneously trigger # "need to delete this file that isn't in Git" actions, # which in turn leads to ghost changelists that do # nothing but delete a placeholder. Pointless waste. if row.gwt_path == p4gf_const.P4GF_EMPTY_CHANGELIST_PLACEHOLDER: return self.row = row self.ghost_cell = row.cell_if_col(self.m.ghost_column) self.gdest_cell = row.cell_if_col(self.m._gdest_column) self.git_delta_cell = row.cell_if_col(self.m.git_delta_column) self.p4jitfp_cell = row.cell_if_col(self.m._p4jitfp_column) orig_gparn_col = self.m.ghost_orig_gparn_column gparn_cell = row.cell_if_col(orig_gparn_col) if orig_gparn_col: gparfpn_cell = row.cell_if_col(orig_gparn_col.fp_counterpart) else: gparfpn_cell = None # Special case for git-lfs-initial-track # top-level .gitattributes files that do not (yet) # exist in Perforce but do in Git. # # This is rare. decided = self._ghost_decide_from_git_lfs_initial_track(gparn_cell) # Normal decision matrix code. if not decided: decided = self._ghost_decide_from_matrix( gparn_cell = gparn_cell , gparfpn_cell = gparfpn_cell , orig_gparn_col = orig_gparn_col ) # If we need to ghost LFS content into existence, do so # from LFS de-dupe storage, NOT the usual ghost source. if ( decided and decided.p4_request != p4gf_g2p_matrix2_ghost.DELETE and self.ghost_cell.discovered and common.KEY_LFS in self.ghost_cell.discovered ): was_branch_delete = decided.branch_delete d2 = Decided( p4_request = common.P4_REQUEST_LFS_COPY , branch_delete = was_branch_delete , integ_input = decided.integ_input ) decided = d2 # Lazy-create a cell to hold the decision. # +++ Unless that decision is "None". # +++ Don't need a whole cell just to say "None". if decided: self._lazy_create_ghost_cell_discovered() if self.ghost_cell: self.ghost_cell.decided = decided return