Module pyjallib.perforce
Classes
class Perforce (server, user, workspace=None)
-
Expand source code
class Perforce: """ Perforce 버전 관리 시스템과의 상호작용을 위한 클래스입니다. 이 클래스는 Perforce 명령을 실행하고, 워크스페이스를 관리하며 Perforce 서버와의 연결을 제어하는 기능을 제공합니다. """ def __init__(self, server, user, workspace=None): """ Perforce 클래스의 인스턴스를 초기화합니다. Parameters: server (str, optional): Perforce 서버 주소. 기본값은 환경 변수 P4PORT 또는 "PC-BUILD:1666" user (str, optional): Perforce 사용자 이름. 기본값은 환경 변수 P4USER 또는 "Dev" workspace (str, optional): Perforce 워크스페이스 이름. 기본값은 환경 변수 P4CLIENT """ self.server = server self.user = user self.workspace = workspace if workspace else os.environ.get('P4CLIENT') self.workspaceRoot = None self.localHostName = socket.gethostname() os.environ['P4USER'] = self.user os.environ['P4PORT'] = self.server if self.workspace: os.environ['P4CLIENT'] = self.workspace else: # P4CLIENT가 None이면 환경 변수에서 제거 시도 (선택적) if 'P4CLIENT' in os.environ: del os.environ['P4CLIENT'] # 초기화 시 연결 확인 self._initialize_connection() def _initialize_connection(self): """ Perforce 서버와의 연결을 초기화합니다. 서버 연결을 확인하고 워크스페이스 루트 경로를 설정합니다. Returns: str: Perforce 서버 정보 문자열 Raises: Exception: Perforce 연결 초기화 실패 시 예외 처리 """ result = None try: # 서버 연결 확인 (info 명령은 가볍고 빠르게 실행됨) result = subprocess.run(['p4', 'info'], capture_output=True, text=True, encoding="utf-8") workSpaceRootPathResult = subprocess.run( ['p4', '-F', '%clientRoot%', '-ztag', 'info'], capture_output=True, text=True, encoding="utf-8" ).stdout.strip() self.workspaceRoot = os.path.normpath(workSpaceRootPathResult) if result.returncode != 0: print(f"Perforce 초기화 중 경고: {result.stderr}") except Exception as e: print(f"Perforce 초기화 실패: {e}") return result.stdout.strip() def _run_command(self, inCommands): """ Perforce 명령을 실행하고 결과를 반환합니다. Parameters: inCommands (list): 실행할 Perforce 명령어와 인수들의 리스트 Returns: str: 명령 실행 결과 문자열 """ self._initialize_connection() commands = ['p4'] + inCommands result = subprocess.run(commands, capture_output=True, text=True, encoding="utf-8") return result.stdout.strip() def get_local_hostname(self): """ 현재 로컬 머신의 호스트 이름을 반환합니다. Returns: str: 로컬 머신의 호스트 이름 """ # 현재 로컬 머신의 호스트 이름을 반환합니다. return self.localHostName def get_all_clients(self): """ 모든 Perforce 클라이언트 워크스페이스의 이름 목록을 반환합니다. Returns: list: 클라이언트 워크스페이스 이름 리스트 """ # 모든 클라이언트 워크스페이스의 이름을 반환합니다. result = self._run_command(['clients']) clients = [] if result is None: return clients for line in result.splitlines(): if line.startswith('Client'): parts = line.split() if len(parts) >= 2: clients.append(parts[1]) return clients def get_local_workspaces(self): """ 현재 로컬 머신에 있는 워크스페이스 목록을 반환합니다. 현재 호스트 이름으로 시작하는 모든 클라이언트를 찾습니다. Returns: list: 로컬 머신의 워크스페이스 이름 리스트 """ all_clients = self.get_all_clients() local_clients = [] for client in all_clients: if client.startswith(self.localHostName): local_clients.append(client) return local_clients def set_workspace(self, inWorkspace): """ 주어진 워크스페이스로 현재 작업 환경을 전환합니다. Parameters: inWorkspace (str): 전환할 워크스페이스 이름 Returns: str: 워크스페이스 정보 문자열 Raises: ValueError: 지정된 워크스페이스가 로컬 워크스페이스 목록에 없을 경우 """ # 주어진 워크스페이스로 전환합니다. localWorkSpaces = self.get_local_workspaces() if inWorkspace not in localWorkSpaces: print(f"워크스페이스 '{inWorkspace}'는 로컬 워크스페이스 목록에 없습니다.") return False self.workspace = inWorkspace os.environ['P4CLIENT'] = self.workspace return True def sync(self, inWorkSpace=None, inPaths=None): """ Perforce 워크스페이스를 최신 버전으로 동기화합니다. Parameters: inWorkSpace (str, optional): 동기화할 워크스페이스 이름 inPaths (str or list, optional): 동기화할 특정 경로 또는 경로 목록 Returns: bool: 동기화 성공 여부 """ # 주어진 워크스페이스를 동기화합니다. if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False # 동기화 명령 실행 sync_command = ['sync'] if inPaths: # 문자열로 단일 경로가 주어진 경우 리스트로 변환 if isinstance(inPaths, str): inPaths = [inPaths] valid_paths = [] for path in inPaths: if os.path.exists(path): valid_paths.append(path) else: print(f"경고: 지정된 경로 '{path}'가 로컬에 존재하지 않습니다.") if not valid_paths: print("유효한 경로가 없어 동기화를 진행할 수 없습니다.") return False # 유효한 경로들을 모두 동기화 명령에 추가 sync_command.extend(valid_paths) self._run_command(sync_command) return True def get_changelists(self, inWorkSpace=None): """ 특정 워크스페이스의 pending 상태 체인지 리스트를 가져옵니다. Parameters: inWorkSpace (str, optional): 체인지 리스트를 가져올 워크스페이스 이름 Returns: list: 체인지 리스트 정보 딕셔너리의 리스트 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return [] else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return [] # 체인지 리스트 명령 실행 changes_command = ['changes'] # 항상 pending 상태만 가져오도록 설정 changes_command.extend(['-s', 'pending']) if self.workspace: changes_command.extend(['-c', self.workspace]) result = self._run_command(changes_command) changes = [] for line in result.splitlines(): if line.startswith('Change'): parts = line.split() if len(parts) >= 5: change_id = parts[1] # 설명 부분 추출 desc_start = line.find("'") desc_end = line.rfind("'") description = line[desc_start+1:desc_end] if desc_start != -1 and desc_end != -1 else "" changes.append({ 'id': change_id, 'description': description }) return changes def create_new_changelist(self, inDescription="Created by pyjallib", inWorkSpace=None): """ 새로운 체인지 리스트를 생성합니다. Parameters: inDescription (str): 체인지 리스트 설명 inWorkSpace (str, optional): 체인지 리스트를 생성할 워크스페이스 이름 Returns: dict: 생성된 체인지 리스트 정보 {'id': str, 'description': str} 또는 실패 시 None """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return None else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return None # 체인지 리스트 생성 명령 실행 change_command = ['change', '-o'] result = self._run_command(change_command) # 체인지 스펙 수정 modified_spec = [] for line in result.splitlines(): if line.startswith('Description:'): modified_spec.append(line) # Add the new description, handling potential multi-line descriptions correctly for desc_line in inDescription.splitlines(): modified_spec.append(f"\t{desc_line}") # Skip the default empty tab line if present # This assumes the default spec has an empty line after Description: # If not, this logic might need adjustment based on actual 'p4 change -o' output # We'll rely on the loop structure to handle subsequent lines correctly. continue # Move to the next line in the original spec # Only append lines that are not part of the old description placeholder # This logic assumes the default description is just a placeholder like '<enter description here>' # or similar, often on a single line after 'Description:'. # A more robust approach might be needed if the default spec is complex. if not (line.startswith('\t') and 'Description:' in modified_spec[-1]): modified_spec.append(line) # 수정된 체인지 스펙으로 체인지 리스트 생성 create_result = subprocess.run(['p4', 'change', '-i'], input='\n'.join(modified_spec), capture_output=True, text=True, encoding="utf-8") # 결과에서 체인지 리스트 ID 추출 if create_result.returncode == 0 and create_result.stdout: output = create_result.stdout.strip() # 예: "Change 12345 created." if 'Change' in output and 'created' in output: parts = output.split() if len(parts) >= 2: change_id = parts[1] return {'id': change_id, 'description': inDescription} # Return dictionary print(f"Failed to create changelist. Error: {create_result.stderr}") # Log error if creation failed return None def checkout_files(self, inFiles, inChangelist=None, inWorkSpace=None): """ 지정한 파일들을 체크아웃하고 특정 체인지 리스트에 추가합니다. Parameters: inFiles (list): 체크아웃할 파일 경로 리스트 inChangelist (str, optional): 파일을 추가할 체인지 리스트 ID inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False if not inFiles: return False # 체크아웃 명령 실행 edit_command = ['edit'] target_changelist = inChangelist if not target_changelist: new_changelist_info = self.create_new_changelist(inDescription=f"Auto-checkout for {len(inFiles)} files") if not new_changelist_info: print("Failed to create a new changelist for checkout.") return False target_changelist = new_changelist_info['id'] edit_command.extend(['-c', target_changelist]) edit_command.extend(inFiles) result = self._run_command(edit_command) if len(self.get_changelist_files(target_changelist)) == 0: self.delete_changelist(target_changelist) return True def add_files(self, inFiles, inChangelist=None, inWorkSpace=None): """ 지정한 파일들을 Perforce에 추가합니다. Parameters: inFiles (list): 추가할 파일 경로 리스트 inChangelist (str, optional): 파일을 추가할 체인지 리스트 ID inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False if not inFiles: return False # 파일 추가 명령 실행 add_command = ['add'] target_changelist = inChangelist if not target_changelist: new_changelist_info = self.create_new_changelist(inDescription=f"Auto-add for {len(inFiles)} files") if not new_changelist_info: print("Failed to create a new changelist for add.") return False target_changelist = new_changelist_info['id'] add_command.extend(['-c', target_changelist]) add_command.extend(inFiles) result = self._run_command(add_command) if len(self.get_changelist_files(target_changelist)) == 0: self.delete_changelist(target_changelist) return True def delete_files(self, inFiles, inChangelist=None, inWorkSpace=None): """ 지정한 파일들을 Perforce에서 삭제합니다. Parameters: inFiles (list): 삭제할 파일 경로 리스트 inChangelist (str, optional): 파일 삭제를 추가할 체인지 리스트 ID inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False if not inFiles: return False # 파일 삭제 명령 실행 delete_command = ['delete'] target_changelist = inChangelist if not target_changelist: # If no changelist is specified, create a new one new_changelist_info = self.create_new_changelist(inDescription=f"Auto-delete for {len(inFiles)} files") if not new_changelist_info: print("Failed to create a new changelist for delete.") return False target_changelist = new_changelist_info['id'] delete_command.extend(['-c', target_changelist]) delete_command.extend(inFiles) result = self._run_command(delete_command) if len(self.get_changelist_files(target_changelist)) == 0: self.delete_changelist(target_changelist) return True def revert_changelist(self, inChangelist, inWorkSpace=None): """ 특정 체인지 리스트의 모든 변경 사항을 되돌립니다 (revert). Parameters: inChangelist (str): 되돌릴 체인지 리스트 ID inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False if not inChangelist: print("Error: Changelist ID must be provided for revert operation.") return False # Revert 명령 실행 revert_command = ['revert', '-c', inChangelist, '//...'] # Revert all files in the changelist result = self._run_command(revert_command) # p4 revert might not return an error code even if nothing was reverted. # We'll assume success if the command ran. More robust checking might involve parsing 'result'. print(f"Revert result for CL {inChangelist}:\n{result}") self._run_command(['change', '-d', inChangelist]) return True def submit_changelist(self, inChangelist, inDescription=None, inWorkSpace=None): """ 특정 체인지 리스트를 서버에 제출합니다. Parameters: inChangelist (str): 제출할 체인지 리스트 ID inDescription (str, optional): 제출 설명 (없으면 체인지 리스트의 기존 설명 사용) inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False if not inChangelist: return False if inDescription: # 체인지 리스트 스펙 가져오기 change_spec = self._run_command(['change', '-o', inChangelist]) # 설명 수정 modified_spec = [] description_lines_skipped = False for line in change_spec.splitlines(): if line.startswith('Description:'): modified_spec.append(line) # Add the new description, handling potential multi-line descriptions correctly for desc_line in inDescription.splitlines(): modified_spec.append(f"\t{desc_line}") description_lines_skipped = True # Start skipping old description lines continue # Skip old description lines (indented lines after Description:) if description_lines_skipped: if line.startswith('\t') or line.strip() == '': continue # Skip indented lines or empty lines within the old description else: description_lines_skipped = False # Reached the next field if not description_lines_skipped: modified_spec.append(line) print(modified_spec) # 수정된 스펙 적용 spec_update = subprocess.run(['p4', 'change', '-i'], input='\n'.join(modified_spec), capture_output=True, text=True, encoding="utf-8") if spec_update.returncode != 0: print(f"Error updating changelist spec for {inChangelist}: {spec_update.stderr}") return False self._run_command(['submit', '-c', inChangelist]) self.delete_empty_changelists() return True def get_changelist_files(self, inChangelist, inWorkSpace=None): """ 체인지 리스트에 포함된 파일 목록을 가져옵니다. Parameters: inChangelist (str): 체인지 리스트 ID inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: list: 파일 정보 딕셔너리의 리스트 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return [] else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return [] opened_result = self._run_command(['opened', '-c', inChangelist]) files = [] for line in opened_result.splitlines(): if '#' in line: file_path = line.split('#')[0].strip() action = line.split('for ')[1].split(' ')[0] if 'for ' in line else '' files.append(file_path) return files def delete_changelist(self, inChangelist, inWorkSpace=None): """ 빈 체인지 리스트를 삭제합니다. Parameters: inChangelist (str): 삭제할 체인지 리스트 ID inWorkspace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False if not inChangelist: print("Error: Changelist ID must be provided for delete operation.") return False # 체인지 리스트의 파일 목록을 확인합니다 files = self.get_changelist_files(inChangelist) if files: print(f"Error: Changelist {inChangelist} is not empty. It contains {len(files)} files.") return False # 빈 체인지 리스트를 삭제합니다 delete_result = self._run_command(['change', '-d', inChangelist]) if 'deleted' in delete_result: return True else: return False def delete_empty_changelists(self, inWorkSpace=None): """ 빈 체인지 리스트를 삭제합니다. Parameters: inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False # 모든 체인지 리스트 가져오기 changes = self.get_changelists() for change in changes: if len(self.get_changelist_files(change['id'])) == 0: self.delete_changelist(change['id']) return True def upload_files(self, inFiles, inDescription=None, inWorkSpace=None): """ 지정한 파일들을 Perforce에 Submit 합니다. 만약 파일들이 Depot에 존재하지 않으면 Add, 존재하면 Chekcout을 수행합니다. Parameters: inFiles (list): 업로드할 파일 경로 리스트 inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False if not inFiles: print("업로드할 파일이 지정되지 않았습니다.") return False # 문자열로 단일 경로가 주어진 경우 리스트로 변환 if isinstance(inFiles, str): inFiles = [inFiles] # 새 체인지리스트 생성 description = inDescription if inDescription else f"Auto-upload for {len(inFiles)} files" new_changelist_info = self.create_new_changelist(inDescription=description) if not new_changelist_info: print("Failed to create a new changelist for file upload.") return False target_changelist = new_changelist_info['id'] # 파일들이 이미 디포에 있는지 확인 files_to_add = [] files_to_edit = [] for file_path in inFiles: # 파일 상태 확인 (디포에 있는지 여부) fstat_result = self._run_command(['fstat', file_path]) if 'no such file' in fstat_result.lower() or not fstat_result: # 디포에 없는 파일 - 추가 대상 files_to_add.append(file_path) else: # 디포에 있는 파일 - 체크아웃 대상 files_to_edit.append(file_path) # 파일 추가 (있는 경우) if files_to_add: add_command = ['add', '-c', target_changelist] add_command.extend(files_to_add) self._run_command(add_command) # 파일 체크아웃 (있는 경우) if files_to_edit: edit_command = ['edit', '-c', target_changelist] edit_command.extend(files_to_edit) self._run_command(edit_command) # 체인지리스트에 파일이 제대로 추가되었는지 확인 files_in_changelist = self.get_changelist_files(target_changelist) if not files_in_changelist: # 파일 추가에 실패한 경우 빈 체인지리스트 삭제 self.delete_changelist(target_changelist) print("파일을 체인지리스트에 추가하는 데 실패했습니다.") return False print(f"파일 {len(files_in_changelist)}개가 체인지리스트 {target_changelist}에 추가되었습니다.") return True
Perforce 버전 관리 시스템과의 상호작용을 위한 클래스입니다.
이 클래스는 Perforce 명령을 실행하고, 워크스페이스를 관리하며 Perforce 서버와의 연결을 제어하는 기능을 제공합니다.
Perforce 클래스의 인스턴스를 초기화합니다.
Parameters
server (str, optional): Perforce 서버 주소. 기본값은 환경 변수 P4PORT 또는 "PC-BUILD:1666" user (str, optional): Perforce 사용자 이름. 기본값은 환경 변수 P4USER 또는 "Dev" workspace (str, optional): Perforce 워크스페이스 이름. 기본값은 환경 변수 P4CLIENT
Methods
def add_files(self, inFiles, inChangelist=None, inWorkSpace=None)
-
Expand source code
def add_files(self, inFiles, inChangelist=None, inWorkSpace=None): """ 지정한 파일들을 Perforce에 추가합니다. Parameters: inFiles (list): 추가할 파일 경로 리스트 inChangelist (str, optional): 파일을 추가할 체인지 리스트 ID inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False if not inFiles: return False # 파일 추가 명령 실행 add_command = ['add'] target_changelist = inChangelist if not target_changelist: new_changelist_info = self.create_new_changelist(inDescription=f"Auto-add for {len(inFiles)} files") if not new_changelist_info: print("Failed to create a new changelist for add.") return False target_changelist = new_changelist_info['id'] add_command.extend(['-c', target_changelist]) add_command.extend(inFiles) result = self._run_command(add_command) if len(self.get_changelist_files(target_changelist)) == 0: self.delete_changelist(target_changelist) return True
지정한 파일들을 Perforce에 추가합니다.
Parameters
inFiles (list): 추가할 파일 경로 리스트 inChangelist (str, optional): 파일을 추가할 체인지 리스트 ID inWorkSpace (str, optional): 작업할 워크스페이스 이름
Returns
bool
- 성공 여부
def checkout_files(self, inFiles, inChangelist=None, inWorkSpace=None)
-
Expand source code
def checkout_files(self, inFiles, inChangelist=None, inWorkSpace=None): """ 지정한 파일들을 체크아웃하고 특정 체인지 리스트에 추가합니다. Parameters: inFiles (list): 체크아웃할 파일 경로 리스트 inChangelist (str, optional): 파일을 추가할 체인지 리스트 ID inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False if not inFiles: return False # 체크아웃 명령 실행 edit_command = ['edit'] target_changelist = inChangelist if not target_changelist: new_changelist_info = self.create_new_changelist(inDescription=f"Auto-checkout for {len(inFiles)} files") if not new_changelist_info: print("Failed to create a new changelist for checkout.") return False target_changelist = new_changelist_info['id'] edit_command.extend(['-c', target_changelist]) edit_command.extend(inFiles) result = self._run_command(edit_command) if len(self.get_changelist_files(target_changelist)) == 0: self.delete_changelist(target_changelist) return True
지정한 파일들을 체크아웃하고 특정 체인지 리스트에 추가합니다.
Parameters
inFiles (list): 체크아웃할 파일 경로 리스트 inChangelist (str, optional): 파일을 추가할 체인지 리스트 ID inWorkSpace (str, optional): 작업할 워크스페이스 이름
Returns
bool
- 성공 여부
def create_new_changelist(self, inDescription='Created by pyjallib', inWorkSpace=None)
-
Expand source code
def create_new_changelist(self, inDescription="Created by pyjallib", inWorkSpace=None): """ 새로운 체인지 리스트를 생성합니다. Parameters: inDescription (str): 체인지 리스트 설명 inWorkSpace (str, optional): 체인지 리스트를 생성할 워크스페이스 이름 Returns: dict: 생성된 체인지 리스트 정보 {'id': str, 'description': str} 또는 실패 시 None """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return None else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return None # 체인지 리스트 생성 명령 실행 change_command = ['change', '-o'] result = self._run_command(change_command) # 체인지 스펙 수정 modified_spec = [] for line in result.splitlines(): if line.startswith('Description:'): modified_spec.append(line) # Add the new description, handling potential multi-line descriptions correctly for desc_line in inDescription.splitlines(): modified_spec.append(f"\t{desc_line}") # Skip the default empty tab line if present # This assumes the default spec has an empty line after Description: # If not, this logic might need adjustment based on actual 'p4 change -o' output # We'll rely on the loop structure to handle subsequent lines correctly. continue # Move to the next line in the original spec # Only append lines that are not part of the old description placeholder # This logic assumes the default description is just a placeholder like '<enter description here>' # or similar, often on a single line after 'Description:'. # A more robust approach might be needed if the default spec is complex. if not (line.startswith('\t') and 'Description:' in modified_spec[-1]): modified_spec.append(line) # 수정된 체인지 스펙으로 체인지 리스트 생성 create_result = subprocess.run(['p4', 'change', '-i'], input='\n'.join(modified_spec), capture_output=True, text=True, encoding="utf-8") # 결과에서 체인지 리스트 ID 추출 if create_result.returncode == 0 and create_result.stdout: output = create_result.stdout.strip() # 예: "Change 12345 created." if 'Change' in output and 'created' in output: parts = output.split() if len(parts) >= 2: change_id = parts[1] return {'id': change_id, 'description': inDescription} # Return dictionary print(f"Failed to create changelist. Error: {create_result.stderr}") # Log error if creation failed return None
새로운 체인지 리스트를 생성합니다.
Parameters
inDescription (str): 체인지 리스트 설명 inWorkSpace (str, optional): 체인지 리스트를 생성할 워크스페이스 이름
Returns
dict
- 생성된 체인지 리스트 정보 {'id': str, 'description': str} 또는 실패 시 None
def delete_changelist(self, inChangelist, inWorkSpace=None)
-
Expand source code
def delete_changelist(self, inChangelist, inWorkSpace=None): """ 빈 체인지 리스트를 삭제합니다. Parameters: inChangelist (str): 삭제할 체인지 리스트 ID inWorkspace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False if not inChangelist: print("Error: Changelist ID must be provided for delete operation.") return False # 체인지 리스트의 파일 목록을 확인합니다 files = self.get_changelist_files(inChangelist) if files: print(f"Error: Changelist {inChangelist} is not empty. It contains {len(files)} files.") return False # 빈 체인지 리스트를 삭제합니다 delete_result = self._run_command(['change', '-d', inChangelist]) if 'deleted' in delete_result: return True else: return False
빈 체인지 리스트를 삭제합니다.
Parameters
inChangelist (str): 삭제할 체인지 리스트 ID inWorkspace (str, optional): 작업할 워크스페이스 이름
Returns
bool
- 성공 여부
def delete_empty_changelists(self, inWorkSpace=None)
-
Expand source code
def delete_empty_changelists(self, inWorkSpace=None): """ 빈 체인지 리스트를 삭제합니다. Parameters: inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False # 모든 체인지 리스트 가져오기 changes = self.get_changelists() for change in changes: if len(self.get_changelist_files(change['id'])) == 0: self.delete_changelist(change['id']) return True
빈 체인지 리스트를 삭제합니다.
Parameters
inWorkSpace (str, optional): 작업할 워크스페이스 이름
Returns
bool
- 성공 여부
def delete_files(self, inFiles, inChangelist=None, inWorkSpace=None)
-
Expand source code
def delete_files(self, inFiles, inChangelist=None, inWorkSpace=None): """ 지정한 파일들을 Perforce에서 삭제합니다. Parameters: inFiles (list): 삭제할 파일 경로 리스트 inChangelist (str, optional): 파일 삭제를 추가할 체인지 리스트 ID inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False if not inFiles: return False # 파일 삭제 명령 실행 delete_command = ['delete'] target_changelist = inChangelist if not target_changelist: # If no changelist is specified, create a new one new_changelist_info = self.create_new_changelist(inDescription=f"Auto-delete for {len(inFiles)} files") if not new_changelist_info: print("Failed to create a new changelist for delete.") return False target_changelist = new_changelist_info['id'] delete_command.extend(['-c', target_changelist]) delete_command.extend(inFiles) result = self._run_command(delete_command) if len(self.get_changelist_files(target_changelist)) == 0: self.delete_changelist(target_changelist) return True
지정한 파일들을 Perforce에서 삭제합니다.
Parameters
inFiles (list): 삭제할 파일 경로 리스트 inChangelist (str, optional): 파일 삭제를 추가할 체인지 리스트 ID inWorkSpace (str, optional): 작업할 워크스페이스 이름
Returns
bool
- 성공 여부
def get_all_clients(self)
-
Expand source code
def get_all_clients(self): """ 모든 Perforce 클라이언트 워크스페이스의 이름 목록을 반환합니다. Returns: list: 클라이언트 워크스페이스 이름 리스트 """ # 모든 클라이언트 워크스페이스의 이름을 반환합니다. result = self._run_command(['clients']) clients = [] if result is None: return clients for line in result.splitlines(): if line.startswith('Client'): parts = line.split() if len(parts) >= 2: clients.append(parts[1]) return clients
모든 Perforce 클라이언트 워크스페이스의 이름 목록을 반환합니다.
Returns
list
- 클라이언트 워크스페이스 이름 리스트
def get_changelist_files(self, inChangelist, inWorkSpace=None)
-
Expand source code
def get_changelist_files(self, inChangelist, inWorkSpace=None): """ 체인지 리스트에 포함된 파일 목록을 가져옵니다. Parameters: inChangelist (str): 체인지 리스트 ID inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: list: 파일 정보 딕셔너리의 리스트 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return [] else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return [] opened_result = self._run_command(['opened', '-c', inChangelist]) files = [] for line in opened_result.splitlines(): if '#' in line: file_path = line.split('#')[0].strip() action = line.split('for ')[1].split(' ')[0] if 'for ' in line else '' files.append(file_path) return files
체인지 리스트에 포함된 파일 목록을 가져옵니다.
Parameters
inChangelist (str): 체인지 리스트 ID inWorkSpace (str, optional): 작업할 워크스페이스 이름
Returns
list
- 파일 정보 딕셔너리의 리스트
def get_changelists(self, inWorkSpace=None)
-
Expand source code
def get_changelists(self, inWorkSpace=None): """ 특정 워크스페이스의 pending 상태 체인지 리스트를 가져옵니다. Parameters: inWorkSpace (str, optional): 체인지 리스트를 가져올 워크스페이스 이름 Returns: list: 체인지 리스트 정보 딕셔너리의 리스트 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return [] else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return [] # 체인지 리스트 명령 실행 changes_command = ['changes'] # 항상 pending 상태만 가져오도록 설정 changes_command.extend(['-s', 'pending']) if self.workspace: changes_command.extend(['-c', self.workspace]) result = self._run_command(changes_command) changes = [] for line in result.splitlines(): if line.startswith('Change'): parts = line.split() if len(parts) >= 5: change_id = parts[1] # 설명 부분 추출 desc_start = line.find("'") desc_end = line.rfind("'") description = line[desc_start+1:desc_end] if desc_start != -1 and desc_end != -1 else "" changes.append({ 'id': change_id, 'description': description }) return changes
특정 워크스페이스의 pending 상태 체인지 리스트를 가져옵니다.
Parameters
inWorkSpace (str, optional): 체인지 리스트를 가져올 워크스페이스 이름
Returns
list
- 체인지 리스트 정보 딕셔너리의 리스트
def get_local_hostname(self)
-
Expand source code
def get_local_hostname(self): """ 현재 로컬 머신의 호스트 이름을 반환합니다. Returns: str: 로컬 머신의 호스트 이름 """ # 현재 로컬 머신의 호스트 이름을 반환합니다. return self.localHostName
현재 로컬 머신의 호스트 이름을 반환합니다.
Returns
str
- 로컬 머신의 호스트 이름
def get_local_workspaces(self)
-
Expand source code
def get_local_workspaces(self): """ 현재 로컬 머신에 있는 워크스페이스 목록을 반환합니다. 현재 호스트 이름으로 시작하는 모든 클라이언트를 찾습니다. Returns: list: 로컬 머신의 워크스페이스 이름 리스트 """ all_clients = self.get_all_clients() local_clients = [] for client in all_clients: if client.startswith(self.localHostName): local_clients.append(client) return local_clients
현재 로컬 머신에 있는 워크스페이스 목록을 반환합니다.
현재 호스트 이름으로 시작하는 모든 클라이언트를 찾습니다.
Returns
list
- 로컬 머신의 워크스페이스 이름 리스트
def revert_changelist(self, inChangelist, inWorkSpace=None)
-
Expand source code
def revert_changelist(self, inChangelist, inWorkSpace=None): """ 특정 체인지 리스트의 모든 변경 사항을 되돌립니다 (revert). Parameters: inChangelist (str): 되돌릴 체인지 리스트 ID inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False if not inChangelist: print("Error: Changelist ID must be provided for revert operation.") return False # Revert 명령 실행 revert_command = ['revert', '-c', inChangelist, '//...'] # Revert all files in the changelist result = self._run_command(revert_command) # p4 revert might not return an error code even if nothing was reverted. # We'll assume success if the command ran. More robust checking might involve parsing 'result'. print(f"Revert result for CL {inChangelist}:\n{result}") self._run_command(['change', '-d', inChangelist]) return True
특정 체인지 리스트의 모든 변경 사항을 되돌립니다 (revert).
Parameters
inChangelist (str): 되돌릴 체인지 리스트 ID inWorkSpace (str, optional): 작업할 워크스페이스 이름
Returns
bool
- 성공 여부
def set_workspace(self, inWorkspace)
-
Expand source code
def set_workspace(self, inWorkspace): """ 주어진 워크스페이스로 현재 작업 환경을 전환합니다. Parameters: inWorkspace (str): 전환할 워크스페이스 이름 Returns: str: 워크스페이스 정보 문자열 Raises: ValueError: 지정된 워크스페이스가 로컬 워크스페이스 목록에 없을 경우 """ # 주어진 워크스페이스로 전환합니다. localWorkSpaces = self.get_local_workspaces() if inWorkspace not in localWorkSpaces: print(f"워크스페이스 '{inWorkspace}'는 로컬 워크스페이스 목록에 없습니다.") return False self.workspace = inWorkspace os.environ['P4CLIENT'] = self.workspace return True
주어진 워크스페이스로 현재 작업 환경을 전환합니다.
Parameters
inWorkspace (str): 전환할 워크스페이스 이름
Returns
str
- 워크스페이스 정보 문자열
Raises
ValueError
- 지정된 워크스페이스가 로컬 워크스페이스 목록에 없을 경우
def submit_changelist(self, inChangelist, inDescription=None, inWorkSpace=None)
-
Expand source code
def submit_changelist(self, inChangelist, inDescription=None, inWorkSpace=None): """ 특정 체인지 리스트를 서버에 제출합니다. Parameters: inChangelist (str): 제출할 체인지 리스트 ID inDescription (str, optional): 제출 설명 (없으면 체인지 리스트의 기존 설명 사용) inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False if not inChangelist: return False if inDescription: # 체인지 리스트 스펙 가져오기 change_spec = self._run_command(['change', '-o', inChangelist]) # 설명 수정 modified_spec = [] description_lines_skipped = False for line in change_spec.splitlines(): if line.startswith('Description:'): modified_spec.append(line) # Add the new description, handling potential multi-line descriptions correctly for desc_line in inDescription.splitlines(): modified_spec.append(f"\t{desc_line}") description_lines_skipped = True # Start skipping old description lines continue # Skip old description lines (indented lines after Description:) if description_lines_skipped: if line.startswith('\t') or line.strip() == '': continue # Skip indented lines or empty lines within the old description else: description_lines_skipped = False # Reached the next field if not description_lines_skipped: modified_spec.append(line) print(modified_spec) # 수정된 스펙 적용 spec_update = subprocess.run(['p4', 'change', '-i'], input='\n'.join(modified_spec), capture_output=True, text=True, encoding="utf-8") if spec_update.returncode != 0: print(f"Error updating changelist spec for {inChangelist}: {spec_update.stderr}") return False self._run_command(['submit', '-c', inChangelist]) self.delete_empty_changelists() return True
특정 체인지 리스트를 서버에 제출합니다.
Parameters
inChangelist (str): 제출할 체인지 리스트 ID inDescription (str, optional): 제출 설명 (없으면 체인지 리스트의 기존 설명 사용) inWorkSpace (str, optional): 작업할 워크스페이스 이름
Returns
bool
- 성공 여부
def sync(self, inWorkSpace=None, inPaths=None)
-
Expand source code
def sync(self, inWorkSpace=None, inPaths=None): """ Perforce 워크스페이스를 최신 버전으로 동기화합니다. Parameters: inWorkSpace (str, optional): 동기화할 워크스페이스 이름 inPaths (str or list, optional): 동기화할 특정 경로 또는 경로 목록 Returns: bool: 동기화 성공 여부 """ # 주어진 워크스페이스를 동기화합니다. if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False # 동기화 명령 실행 sync_command = ['sync'] if inPaths: # 문자열로 단일 경로가 주어진 경우 리스트로 변환 if isinstance(inPaths, str): inPaths = [inPaths] valid_paths = [] for path in inPaths: if os.path.exists(path): valid_paths.append(path) else: print(f"경고: 지정된 경로 '{path}'가 로컬에 존재하지 않습니다.") if not valid_paths: print("유효한 경로가 없어 동기화를 진행할 수 없습니다.") return False # 유효한 경로들을 모두 동기화 명령에 추가 sync_command.extend(valid_paths) self._run_command(sync_command) return True
Perforce 워크스페이스를 최신 버전으로 동기화합니다.
Parameters
inWorkSpace (str, optional): 동기화할 워크스페이스 이름 inPaths (str or list, optional): 동기화할 특정 경로 또는 경로 목록
Returns
bool
- 동기화 성공 여부
def upload_files(self, inFiles, inDescription=None, inWorkSpace=None)
-
Expand source code
def upload_files(self, inFiles, inDescription=None, inWorkSpace=None): """ 지정한 파일들을 Perforce에 Submit 합니다. 만약 파일들이 Depot에 존재하지 않으면 Add, 존재하면 Chekcout을 수행합니다. Parameters: inFiles (list): 업로드할 파일 경로 리스트 inWorkSpace (str, optional): 작업할 워크스페이스 이름 Returns: bool: 성공 여부 """ if inWorkSpace == None: if self.workspace == None: print(f"워크스페이스가 없습니다.") return False else: if not self.set_workspace(inWorkSpace): print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.") return False if not inFiles: print("업로드할 파일이 지정되지 않았습니다.") return False # 문자열로 단일 경로가 주어진 경우 리스트로 변환 if isinstance(inFiles, str): inFiles = [inFiles] # 새 체인지리스트 생성 description = inDescription if inDescription else f"Auto-upload for {len(inFiles)} files" new_changelist_info = self.create_new_changelist(inDescription=description) if not new_changelist_info: print("Failed to create a new changelist for file upload.") return False target_changelist = new_changelist_info['id'] # 파일들이 이미 디포에 있는지 확인 files_to_add = [] files_to_edit = [] for file_path in inFiles: # 파일 상태 확인 (디포에 있는지 여부) fstat_result = self._run_command(['fstat', file_path]) if 'no such file' in fstat_result.lower() or not fstat_result: # 디포에 없는 파일 - 추가 대상 files_to_add.append(file_path) else: # 디포에 있는 파일 - 체크아웃 대상 files_to_edit.append(file_path) # 파일 추가 (있는 경우) if files_to_add: add_command = ['add', '-c', target_changelist] add_command.extend(files_to_add) self._run_command(add_command) # 파일 체크아웃 (있는 경우) if files_to_edit: edit_command = ['edit', '-c', target_changelist] edit_command.extend(files_to_edit) self._run_command(edit_command) # 체인지리스트에 파일이 제대로 추가되었는지 확인 files_in_changelist = self.get_changelist_files(target_changelist) if not files_in_changelist: # 파일 추가에 실패한 경우 빈 체인지리스트 삭제 self.delete_changelist(target_changelist) print("파일을 체인지리스트에 추가하는 데 실패했습니다.") return False print(f"파일 {len(files_in_changelist)}개가 체인지리스트 {target_changelist}에 추가되었습니다.") return True
지정한 파일들을 Perforce에 Submit 합니다.
만약 파일들이 Depot에 존재하지 않으면 Add, 존재하면 Chekcout을 수행합니다.
Parameters
inFiles (list): 업로드할 파일 경로 리스트 inWorkSpace (str, optional): 작업할 워크스페이스 이름
Returns
bool
- 성공 여부