def decide(self, row): """Main entry point for RowDecider.""" self._reset() self.row = row self._raise_if_symlink_in_gdest_path() self.gdest_cell = row.cell_if_col(self.m._gdest_column) self.p4jitfp_cell = row.cell_if_col(self.m._p4jitfp_column) self.git_delta_cell = row.cell_if_col(self.m.git_delta_column) self.integ_dest_cell = row.cell_if_col(self.m._integ_dest_column) if LOG.isEnabledFor(logging.DEBUG2): LOG.debug2('RowDecider.decide() GDEST={gdest}' ' P4JITFP={p4jitfp} git_delta={git_delta}' ' pop_fm={pop_fm} row={row}' .format( row = row.gwt_path , gdest = self._col_index(self.m._gdest_column) , pop_fm = self.m._populate_from_column.index if self.m._populate_from_column else None , p4jitfp = self._col_index(self.m._p4jitfp_column) , git_delta = self._col_index(self.m.git_delta_column) )) self._decide_populate_from(exists_in_git = self.row.exists_in_git()) # Git LFS file? Then it's too complex to try to integ # it from any GPAR(fp)N at the same time we integ from # LFS de-dupe storage. Ignore/bypass GPAR(fp)N. The # only integ we might decide() for an LFS file is a JIT # integ from P4JITFP. lfs_oid = None if self.m.ctx.is_lfs_enabled: lfs_oid = _lfs_oid(self.gdest_cell) if not lfs_oid: # Integrate from each Git parent commit, and possibly # from any fully populated bases for those parent # commits. Using a queue so that we can add GPARFPN # columns later if we need to. self.integ_work_queue = \ deque(col for col in Column.of_col_type( self.m.columns, Column.GPARN)) common.debug3('Row.decide() initial integ_work_queue={col}' , col=[col.index for col in self.integ_work_queue]) while self.integ_work_queue: column = self.integ_work_queue.pop() # GPARN or GPARFPN self._decide_integ_from_column(column) self._decide_gdest_have_delta() # Git says Add/Modify/Delete? Do so. if not self.has_integ: # Apply Git's requested add/edit/delete. self._apply_git_delta() # If add/edit/deleting, do we also need to JIT-branch? self._decide_jit_if() self._remove_duplicate_integ()
def decide(self, row): ''' Main entry point for RowDecider. ''' self._reset() self.row = row self._raise_if_symlink_in_gdest_path() self.gdest_cell = row.cell_if_col(self.m._gdest_column) self.p4jitfp_cell = row.cell_if_col(self.m._p4jitfp_column) self.git_delta_cell = row.cell_if_col(self.m.git_delta_column) self.integ_dest_cell = row.cell_if_col(self.m._integ_dest_column) if LOG.isEnabledFor(logging.DEBUG2): LOG.debug2('RowDecider.decide() GDEST={gdest}' ' P4JITFP={p4jitfp} git_delta={git_delta}' ' pop_fm={pop_fm} row={row}' .format( row = row.gwt_path , gdest = self._col_index(self.m._gdest_column) , pop_fm = self.m._populate_from_column.index if self.m._populate_from_column else None , p4jitfp = self._col_index(self.m._p4jitfp_column) , git_delta = self._col_index(self.m.git_delta_column) )) self._decide_populate_from(exists_in_git = self.row.exists_in_git()) # Integrate from each Git parent commit, and possibly # from any fully populated bases for those parent # commits. Using a queue so that we can add GPARFPN # columns later if we need to. self.integ_work_queue = \ deque(col for col in Column.of_col_type( self.m.columns , Column.GPARN)) common.debug3('Row.decide() initial integ_work_queue={col}' , col=[col.index for col in self.integ_work_queue]) while self.integ_work_queue: column = self.integ_work_queue.pop() # GPARN or GPARFPN self._decide_integ_from_column(column) # Git says Add/Modify/Delete? Do so. if self.git_delta_cell and self.git_delta_cell.discovered: # Apply Git's requested add/edit/delete. self._apply_git_delta() # If add/edit/deleting, do we also need to JIT-branch? self._decide_jit_if() self._remove_duplicate_integ()
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 _after_dest_fp(self, src_cell): """Does src_cell integrate from a Perforce file revision at or after our destination's fully populated basis? Always True if destination has no basis. """ assert src_cell # Even if lightweight, if we have no basis, then all revisions # are permitted as integration sources. if not self.p4jitfp_cell: common.debug3('_after_dest_fp() True no FP basis. ') return True # Integrating from some Perforce path other than our basis? # Go right ahead. fp = self.p4jitfp_cell.discovered # for less typing src = src_cell .discovered fp_from_file = _from_depot_file(fp) src_from_file = _from_depot_file(src) if ( (not fp_from_file) or (not src_from_file) or fp_from_file != src_from_file): common.debug3('_after_dest_fp() True src={src} not from' ' JIT FP basis={fp}. ' .format(src=src_from_file ,fp =fp_from_file)) return True # Integrating a later revision of our basis? fp_rev = self.p4jitfp_cell.rev() src_rev = src_cell.rev() if ( not fp_rev or not src_rev or int(fp_rev) < int(src_rev)): common.debug3('_after_dest_fp() True src={src} > ' ' JIT FP basis={fp}. ' .format( src=src_rev , fp =fp_rev)) return True # Sorry, you're trying to integrate from a source revision that is at # or before lightweight GDEST's fully populated basis P4JITFP's revision # for this file. Can't do that: that would make our lightweight branch # unnecessarily heavyweight. common.debug3('_after_dest_fp() False src={src_d}#{src_r} <= ' ' JIT FP basis={fp_d}#{fp_r}. ' .format( src_d=src.get('depotFile') , src_r=src_rev , fp_d =fp .get('depotFile') , fp_r =fp_rev )) return False
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 _after_dest_fp(self, src_cell): ''' Does src_cell integrate from a Perforce file revision at or after our destination's fully populated basis? Always True if destination has no basis. ''' assert src_cell # Even if lightweight, if we have no basis, then all revisions # are permitted as integration sources. if not self.p4jitfp_cell: common.debug3('_after_dest_fp() True no FP basis. ') return True # Integrating from some Perforce path other than our basis? # Go right ahead. fp = self.p4jitfp_cell.discovered # for less typing src = src_cell .discovered fp_from_file = _from_depot_file(fp) src_from_file = _from_depot_file(src) if ( (not fp_from_file) or (not src_from_file) or fp_from_file != src_from_file): common.debug3('_after_dest_fp() True src={src} not from' ' JIT FP basis={fp}. ' .format(src=src_from_file ,fp =fp_from_file)) return True # Integrating a later revision of our basis? if ( 'rev' not in fp or 'rev' not in src or int(fp['rev']) < int(src['rev'])): common.debug3('_after_dest_fp() True src={src} > ' ' JIT FP basis={fp}. ' .format( src=src.get('rev') , fp =fp .get('rev'))) return True # Sorry, you're trying to integrate from a source revision that is at # or before lightweight GDEST's fully populated basis P4JITFP's revision # for this file. Can't do that: that would make our lightweight branch # unnecessarily heavyweight. common.debug3('_after_dest_fp() True src={src_d}#{src_r} <= ' ' JIT FP basis={fp_d}#{fp_d}. ' .format( src_d=src.get('depotFile') , src_r=src.get('rev') , fp_d =fp .get('depotFile') , fp_r =fp .get('rev') )) return False
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 _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