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
성공 여부