pyjallib 0.1.16__py3-none-any.whl → 0.1.19__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyjallib/__init__.py +4 -1
- pyjallib/exceptions.py +75 -0
- pyjallib/logger.py +288 -0
- pyjallib/max/__init__.py +8 -0
- pyjallib/max/autoClavicle.py +17 -5
- pyjallib/max/bip.py +0 -21
- pyjallib/max/bone.py +21 -1
- pyjallib/max/boneChain.py +2 -0
- pyjallib/max/constraint.py +27 -2
- pyjallib/max/elbow.py +105 -0
- pyjallib/max/groinBone.py +2 -0
- pyjallib/max/header.py +121 -113
- pyjallib/max/hip.py +2 -0
- pyjallib/max/inguinal.py +117 -0
- pyjallib/max/kneeBone.py +2 -0
- pyjallib/max/macro/jal_macro_bone.py +221 -8
- pyjallib/max/macro/jal_macro_constraint.py +30 -0
- pyjallib/max/mirror.py +20 -13
- pyjallib/max/shoulder.py +173 -0
- pyjallib/max/twistBone.py +22 -19
- pyjallib/max/volumeBone.py +12 -1
- pyjallib/max/wrist.py +113 -0
- pyjallib/nameToPath.py +78 -61
- pyjallib/naming.py +4 -1
- pyjallib/perforce.py +196 -347
- pyjallib/progressEvent.py +75 -0
- pyjallib/ue5/ConfigFiles/UE5NamingConfig.json +487 -0
- pyjallib/ue5/__init__.py +170 -0
- pyjallib/ue5/disableInterchangeFrameWork.py +82 -0
- pyjallib/ue5/inUnreal/__init__.py +95 -0
- pyjallib/ue5/inUnreal/animationImporter.py +206 -0
- pyjallib/ue5/inUnreal/baseImporter.py +187 -0
- pyjallib/ue5/inUnreal/importerSettings.py +179 -0
- pyjallib/ue5/inUnreal/skeletalMeshImporter.py +112 -0
- pyjallib/ue5/inUnreal/skeletonImporter.py +105 -0
- pyjallib/ue5/logger.py +241 -0
- pyjallib/ue5/templateProcessor.py +287 -0
- pyjallib/ue5/templates/__init__.py +109 -0
- pyjallib/ue5/templates/animImportTemplate.py +22 -0
- pyjallib/ue5/templates/batchAnimImportTemplate.py +21 -0
- pyjallib/ue5/templates/skeletalMeshImportTemplate.py +22 -0
- pyjallib/ue5/templates/skeletonImportTemplate.py +21 -0
- {pyjallib-0.1.16.dist-info → pyjallib-0.1.19.dist-info}/METADATA +1 -1
- pyjallib-0.1.19.dist-info/RECORD +72 -0
- pyjallib-0.1.16.dist-info/RECORD +0 -49
- {pyjallib-0.1.16.dist-info → pyjallib-0.1.19.dist-info}/WHEEL +0 -0
pyjallib/perforce.py
CHANGED
@@ -9,45 +9,19 @@ P4Python을 사용하는 Perforce 모듈.
|
|
9
9
|
- 파일 동기화 및 업데이트 확인
|
10
10
|
"""
|
11
11
|
|
12
|
-
import logging
|
13
12
|
from P4 import P4, P4Exception
|
14
13
|
import os
|
15
|
-
from
|
16
|
-
|
17
|
-
# 로깅 설정
|
18
|
-
logger = logging.getLogger(__name__)
|
19
|
-
|
20
|
-
# 기본 로그 레벨은 ERROR로 설정 (디버그 모드는 생성자에서 설정)
|
21
|
-
logger.setLevel(logging.ERROR)
|
22
|
-
|
23
|
-
# 사용자 문서 폴더 내 로그 파일 저장
|
24
|
-
log_path = os.path.join(Path.home() / "Documents", 'Perforce.log')
|
25
|
-
file_handler = logging.FileHandler(log_path, encoding='utf-8')
|
26
|
-
file_handler.setLevel(logging.ERROR) # 기본적으로 ERROR 레벨만 기록
|
27
|
-
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
|
28
|
-
logger.addHandler(file_handler)
|
14
|
+
from .exceptions import PerforceError, ValidationError
|
29
15
|
|
30
16
|
|
31
17
|
class Perforce:
|
32
18
|
"""P4Python을 사용하여 Perforce 작업을 수행하는 클래스."""
|
33
19
|
|
34
|
-
def __init__(self
|
35
|
-
"""Perforce 인스턴스를 초기화합니다.
|
36
|
-
|
37
|
-
Args:
|
38
|
-
debug_mode (bool): True로 설정하면 DEBUG 레벨 로그를 활성화합니다.
|
39
|
-
기본값은 False (ERROR 레벨만 기록)
|
40
|
-
"""
|
41
|
-
# 디버그 모드에 따라 로그 레벨 설정
|
42
|
-
if debug_mode:
|
43
|
-
logger.setLevel(logging.DEBUG)
|
44
|
-
file_handler.setLevel(logging.DEBUG)
|
45
|
-
logger.debug("디버그 모드가 활성화되었습니다.")
|
46
|
-
|
20
|
+
def __init__(self):
|
21
|
+
"""Perforce 인스턴스를 초기화합니다."""
|
47
22
|
self.p4 = P4()
|
48
23
|
self.connected = False
|
49
24
|
self.workspaceRoot = r""
|
50
|
-
logger.info("Perforce 인스턴스 생성됨")
|
51
25
|
|
52
26
|
def _is_connected(self) -> bool:
|
53
27
|
"""Perforce 서버 연결 상태를 확인합니다.
|
@@ -56,22 +30,18 @@ class Perforce:
|
|
56
30
|
bool: 연결되어 있으면 True, 아니면 False
|
57
31
|
"""
|
58
32
|
if not self.connected:
|
59
|
-
logger.warning("Perforce 서버에 연결되지 않았습니다.")
|
60
33
|
return False
|
61
34
|
return True
|
62
|
-
|
63
|
-
def
|
64
|
-
"""
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
context_msg (str, optional): 예외가 발생한 컨텍스트 설명
|
35
|
+
|
36
|
+
def _ensure_connected(self) -> None:
|
37
|
+
"""Perforce 서버 연결 상태를 확인하고 연결되지 않은 경우 예외를 발생시킵니다.
|
38
|
+
|
39
|
+
Raises:
|
40
|
+
PerforceError: 연결되지 않은 상태인 경우
|
69
41
|
"""
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
for warn in self.p4.warnings:
|
74
|
-
logger.warning(f" P4 Warning: {warn}")
|
42
|
+
if not self.connected:
|
43
|
+
error_message = "Perforce 서버에 연결되지 않았습니다."
|
44
|
+
raise PerforceError(error_message)
|
75
45
|
|
76
46
|
def connect(self, workspace_name: str) -> bool:
|
77
47
|
"""지정된 워크스페이스에 연결합니다.
|
@@ -82,7 +52,6 @@ class Perforce:
|
|
82
52
|
Returns:
|
83
53
|
bool: 연결 성공 시 True, 실패 시 False
|
84
54
|
"""
|
85
|
-
logger.info(f"'{workspace_name}' 워크스페이스에 연결 시도 중...")
|
86
55
|
try:
|
87
56
|
self.p4.client = workspace_name
|
88
57
|
self.p4.connect()
|
@@ -97,17 +66,14 @@ class Perforce:
|
|
97
66
|
root_path = os.path.normpath(root_path)
|
98
67
|
|
99
68
|
self.workspaceRoot = root_path
|
100
|
-
|
101
|
-
except (IndexError, KeyError) as e:
|
102
|
-
logger.error(f"워크스페이스 루트 경로 가져오기 실패: {e}")
|
69
|
+
except (IndexError, KeyError):
|
103
70
|
self.workspaceRoot = ""
|
104
71
|
|
105
|
-
logger.info(f"'{workspace_name}' 워크스페이스에 성공적으로 연결됨 (User: {self.p4.user}, Port: {self.p4.port})")
|
106
72
|
return True
|
107
73
|
except P4Exception as e:
|
108
74
|
self.connected = False
|
109
|
-
|
110
|
-
|
75
|
+
error_message = f"'{workspace_name}' 워크스페이스 연결 실패 중 P4Exception 발생: {e}"
|
76
|
+
raise PerforceError(error_message)
|
111
77
|
|
112
78
|
def get_pending_change_list(self) -> list:
|
113
79
|
"""워크스페이스의 Pending된 체인지 리스트를 가져옵니다.
|
@@ -115,9 +81,7 @@ class Perforce:
|
|
115
81
|
Returns:
|
116
82
|
list: 체인지 리스트 정보 딕셔너리들의 리스트
|
117
83
|
"""
|
118
|
-
|
119
|
-
return []
|
120
|
-
logger.debug("Pending 체인지 리스트 조회 중...")
|
84
|
+
self._ensure_connected()
|
121
85
|
try:
|
122
86
|
pending_changes = self.p4.run_changes("-s", "pending", "-u", self.p4.user, "-c", self.p4.client)
|
123
87
|
change_numbers = [int(cl['change']) for cl in pending_changes]
|
@@ -129,11 +93,10 @@ class Perforce:
|
|
129
93
|
if cl_info:
|
130
94
|
change_list_info.append(cl_info)
|
131
95
|
|
132
|
-
logger.info(f"Pending 체인지 리스트 {len(change_list_info)}개 조회 완료")
|
133
96
|
return change_list_info
|
134
97
|
except P4Exception as e:
|
135
|
-
|
136
|
-
|
98
|
+
error_message = f"Pending 체인지 리스트 조회 실패 중 P4Exception 발생: {e}"
|
99
|
+
raise PerforceError(error_message)
|
137
100
|
|
138
101
|
def create_change_list(self, description: str) -> dict:
|
139
102
|
"""새로운 체인지 리스트를 생성합니다.
|
@@ -144,22 +107,19 @@ class Perforce:
|
|
144
107
|
Returns:
|
145
108
|
dict: 생성된 체인지 리스트 정보. 실패 시 빈 딕셔너리
|
146
109
|
"""
|
147
|
-
|
148
|
-
return {}
|
149
|
-
logger.info(f"새 체인지 리스트 생성 시도: '{description}'")
|
110
|
+
self._ensure_connected()
|
150
111
|
try:
|
151
112
|
change_spec = self.p4.fetch_change()
|
152
113
|
change_spec["Description"] = description
|
153
114
|
result = self.p4.save_change(change_spec)
|
154
115
|
created_change_number = int(result[0].split()[1])
|
155
|
-
logger.info(f"체인지 리스트 {created_change_number} 생성 완료: '{description}'")
|
156
116
|
return self.get_change_list_by_number(created_change_number)
|
157
117
|
except P4Exception as e:
|
158
|
-
|
159
|
-
|
118
|
+
error_message = f"체인지 리스트 생성 실패 ('{description}') 중 P4Exception 발생: {e}"
|
119
|
+
raise PerforceError(error_message)
|
160
120
|
except (IndexError, ValueError) as e:
|
161
|
-
|
162
|
-
|
121
|
+
error_message = f"체인지 리스트 번호 파싱 오류: {e}"
|
122
|
+
raise PerforceError(error_message)
|
163
123
|
|
164
124
|
def get_change_list_by_number(self, change_list_number: int) -> dict:
|
165
125
|
"""체인지 리스트 번호로 체인지 리스트를 가져옵니다.
|
@@ -170,20 +130,17 @@ class Perforce:
|
|
170
130
|
Returns:
|
171
131
|
dict: 체인지 리스트 정보. 실패 시 빈 딕셔너리
|
172
132
|
"""
|
173
|
-
|
174
|
-
return {}
|
175
|
-
logger.debug(f"체인지 리스트 {change_list_number} 정보 조회 중...")
|
133
|
+
self._ensure_connected()
|
176
134
|
try:
|
177
135
|
cl_info = self.p4.fetch_change(change_list_number)
|
178
136
|
if cl_info:
|
179
|
-
logger.info(f"체인지 리스트 {change_list_number} 정보 조회 완료.")
|
180
137
|
return cl_info
|
181
138
|
else:
|
182
|
-
|
183
|
-
|
139
|
+
error_message = f"체인지 리스트 {change_list_number}를 찾을 수 없습니다."
|
140
|
+
raise PerforceError(error_message)
|
184
141
|
except P4Exception as e:
|
185
|
-
|
186
|
-
|
142
|
+
error_message = f"체인지 리스트 {change_list_number} 정보 조회 실패 중 P4Exception 발생: {e}"
|
143
|
+
raise PerforceError(error_message)
|
187
144
|
|
188
145
|
def get_change_list_by_description(self, description: str) -> dict:
|
189
146
|
"""체인지 리스트 설명으로 체인지 리스트를 가져옵니다.
|
@@ -194,21 +151,17 @@ class Perforce:
|
|
194
151
|
Returns:
|
195
152
|
dict: 체인지 리스트 정보 (일치하는 첫 번째 체인지 리스트)
|
196
153
|
"""
|
197
|
-
|
198
|
-
return {}
|
199
|
-
logger.debug(f"설명으로 체인지 리스트 조회 중: '{description}'")
|
154
|
+
self._ensure_connected()
|
200
155
|
try:
|
201
156
|
pending_changes = self.p4.run_changes("-l", "-s", "pending", "-u", self.p4.user, "-c", self.p4.client)
|
202
157
|
for cl in pending_changes:
|
203
158
|
cl_desc = cl.get('Description', b'').decode('utf-8', 'replace').strip()
|
204
159
|
if cl_desc == description.strip():
|
205
|
-
logger.info(f"설명 '{description}'에 해당하는 체인지 리스트 {cl['change']} 조회 완료.")
|
206
160
|
return self.get_change_list_by_number(int(cl['change']))
|
207
|
-
logger.info(f"설명 '{description}'에 해당하는 Pending 체인지 리스트를 찾을 수 없습니다.")
|
208
161
|
return {}
|
209
162
|
except P4Exception as e:
|
210
|
-
|
211
|
-
|
163
|
+
error_message = f"설명으로 체인지 리스트 조회 실패 ('{description}') 중 P4Exception 발생: {e}"
|
164
|
+
raise PerforceError(error_message)
|
212
165
|
|
213
166
|
def get_change_list_by_description_pattern(self, description_pattern: str, exact_match: bool = False) -> list:
|
214
167
|
"""설명 패턴과 일치하는 Pending 체인지 리스트들을 가져옵니다.
|
@@ -221,12 +174,7 @@ class Perforce:
|
|
221
174
|
Returns:
|
222
175
|
list: 패턴과 일치하는 체인지 리스트 정보들의 리스트
|
223
176
|
"""
|
224
|
-
|
225
|
-
return []
|
226
|
-
|
227
|
-
search_type = "정확히 일치" if exact_match else "패턴 포함"
|
228
|
-
logger.debug(f"설명 패턴으로 체인지 리스트 조회 중 ({search_type}): '{description_pattern}'")
|
229
|
-
|
177
|
+
self._ensure_connected()
|
230
178
|
try:
|
231
179
|
pending_changes = self.p4.run_changes("-l", "-s", "pending", "-u", self.p4.user, "-c", self.p4.client)
|
232
180
|
matching_changes = []
|
@@ -248,17 +196,25 @@ class Perforce:
|
|
248
196
|
change_info = self.get_change_list_by_number(change_number)
|
249
197
|
if change_info:
|
250
198
|
matching_changes.append(change_info)
|
251
|
-
logger.info(f"패턴 '{description_pattern}'에 매칭되는 체인지 리스트 {change_number} 발견: '{cl_desc}'")
|
252
|
-
|
253
|
-
if matching_changes:
|
254
|
-
logger.info(f"패턴 '{description_pattern}'에 매칭되는 체인지 리스트 {len(matching_changes)}개 조회 완료.")
|
255
|
-
else:
|
256
|
-
logger.info(f"패턴 '{description_pattern}'에 매칭되는 Pending 체인지 리스트를 찾을 수 없습니다.")
|
257
199
|
|
258
200
|
return matching_changes
|
259
201
|
except P4Exception as e:
|
260
|
-
|
261
|
-
|
202
|
+
error_message = f"설명 패턴으로 체인지 리스트 조회 실패 ('{description_pattern}') 중 P4Exception 발생: {e}"
|
203
|
+
raise PerforceError(error_message)
|
204
|
+
|
205
|
+
def disconnect(self):
|
206
|
+
"""Perforce 서버와의 연결을 해제합니다."""
|
207
|
+
if self.connected:
|
208
|
+
try:
|
209
|
+
self.p4.disconnect()
|
210
|
+
self.connected = False
|
211
|
+
except P4Exception as e:
|
212
|
+
error_message = f"Perforce 서버 연결 해제 중 P4Exception 발생: {e}"
|
213
|
+
raise PerforceError(error_message)
|
214
|
+
|
215
|
+
def __del__(self):
|
216
|
+
"""객체가 소멸될 때 자동으로 연결을 해제합니다."""
|
217
|
+
self.disconnect()
|
262
218
|
|
263
219
|
def check_files_checked_out(self, file_paths: list) -> dict:
|
264
220
|
"""파일들의 체크아웃 상태를 확인합니다.
|
@@ -278,13 +234,14 @@ class Perforce:
|
|
278
234
|
}
|
279
235
|
}
|
280
236
|
"""
|
281
|
-
|
282
|
-
return {}
|
237
|
+
self._ensure_connected()
|
283
238
|
if not file_paths:
|
284
|
-
logger.debug("체크아웃 상태 확인할 파일 목록이 비어있습니다.")
|
285
239
|
return {}
|
286
240
|
|
287
|
-
|
241
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
242
|
+
if not isinstance(file_paths, list):
|
243
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 is_file_checked_out() 메서드를 사용하세요."
|
244
|
+
raise ValidationError(error_msg)
|
288
245
|
|
289
246
|
result = {}
|
290
247
|
try:
|
@@ -311,31 +268,20 @@ class Perforce:
|
|
311
268
|
file_status['user'] = file_info.get('user', '')
|
312
269
|
file_status['workspace'] = file_info.get('client', '')
|
313
270
|
|
314
|
-
logger.debug(f"파일 '{file_path}' 체크아웃됨: CL {file_status['change_list']}, "
|
315
|
-
f"액션: {file_status['action']}, 사용자: {file_status['user']}, "
|
316
|
-
f"워크스페이스: {file_status['workspace']}")
|
317
|
-
else:
|
318
|
-
# 파일이 체크아웃되지 않음
|
319
|
-
logger.debug(f"파일 '{file_path}' 체크아웃되지 않음")
|
320
|
-
|
321
271
|
except P4Exception as e:
|
322
272
|
# 파일이 perforce에 없거나 접근할 수 없는 경우
|
323
|
-
if any("not opened" in err.lower() or "no such file" in err.lower()
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
self._handle_p4_exception(e, f"파일 '{file_path}' 체크아웃 상태 확인")
|
273
|
+
if not any("not opened" in err.lower() or "no such file" in err.lower()
|
274
|
+
for err in self.p4.errors):
|
275
|
+
error_message = f"파일 '{file_path}' 체크아웃 상태 확인 중 P4Exception 발생: {e}"
|
276
|
+
raise PerforceError(error_message)
|
328
277
|
|
329
278
|
result[file_path] = file_status
|
330
279
|
|
331
|
-
checked_out_count = sum(1 for status in result.values() if status['is_checked_out'])
|
332
|
-
logger.info(f"파일 체크아웃 상태 확인 완료: 전체 {len(file_paths)}개 중 {checked_out_count}개 체크아웃됨")
|
333
|
-
|
334
280
|
return result
|
335
281
|
|
336
282
|
except P4Exception as e:
|
337
|
-
|
338
|
-
|
283
|
+
error_message = f"파일들 체크아웃 상태 확인 ({file_paths}) 중 P4Exception 발생: {e}"
|
284
|
+
raise PerforceError(error_message)
|
339
285
|
|
340
286
|
def is_file_checked_out(self, file_path: str) -> bool:
|
341
287
|
"""단일 파일의 체크아웃 상태를 간단히 확인합니다.
|
@@ -359,11 +305,7 @@ class Perforce:
|
|
359
305
|
Returns:
|
360
306
|
bool: 파일이 해당 체인지 리스트에 있으면 True, 아니면 False
|
361
307
|
"""
|
362
|
-
|
363
|
-
return False
|
364
|
-
|
365
|
-
logger.debug(f"파일 '{file_path}'가 체인지 리스트 {change_list_number}에 있는지 확인 중...")
|
366
|
-
|
308
|
+
self._ensure_connected()
|
367
309
|
try:
|
368
310
|
# 해당 체인지 리스트의 파일들 가져오기
|
369
311
|
opened_files = self.p4.run_opened("-c", change_list_number)
|
@@ -376,16 +318,13 @@ class Perforce:
|
|
376
318
|
normalized_client_file = os.path.normpath(client_file)
|
377
319
|
|
378
320
|
if normalized_client_file == normalized_file_path:
|
379
|
-
logger.debug(f"파일 '{file_path}'가 체인지 리스트 {change_list_number}에서 발견됨 "
|
380
|
-
f"(액션: {file_info.get('action', '')})")
|
381
321
|
return True
|
382
322
|
|
383
|
-
logger.debug(f"파일 '{file_path}'가 체인지 리스트 {change_list_number}에 없음")
|
384
323
|
return False
|
385
324
|
|
386
325
|
except P4Exception as e:
|
387
|
-
|
388
|
-
|
326
|
+
error_message = f"파일 '{file_path}' 체인지 리스트 {change_list_number} 포함 여부 확인 중 P4Exception 발생: {e}"
|
327
|
+
raise PerforceError(error_message)
|
389
328
|
|
390
329
|
def edit_change_list(self, change_list_number: int, description: str = None, add_file_paths: list = None, remove_file_paths: list = None) -> dict:
|
391
330
|
"""체인지 리스트를 편집합니다.
|
@@ -399,9 +338,7 @@ class Perforce:
|
|
399
338
|
Returns:
|
400
339
|
dict: 업데이트된 체인지 리스트 정보
|
401
340
|
"""
|
402
|
-
|
403
|
-
return {}
|
404
|
-
logger.info(f"체인지 리스트 {change_list_number} 편집 시도...")
|
341
|
+
self._ensure_connected()
|
405
342
|
try:
|
406
343
|
if description is not None:
|
407
344
|
change_spec = self.p4.fetch_change(change_list_number)
|
@@ -409,29 +346,28 @@ class Perforce:
|
|
409
346
|
if current_description != description.strip():
|
410
347
|
change_spec['Description'] = description
|
411
348
|
self.p4.save_change(change_spec)
|
412
|
-
logger.info(f"체인지 리스트 {change_list_number} 설명 변경 완료: '{description}'")
|
413
349
|
|
414
350
|
if add_file_paths:
|
415
351
|
for file_path in add_file_paths:
|
416
352
|
try:
|
417
353
|
self.p4.run_reopen("-c", change_list_number, file_path)
|
418
|
-
logger.info(f"파일 '{file_path}'를 체인지 리스트 {change_list_number}로 이동 완료.")
|
419
354
|
except P4Exception as e_reopen:
|
420
|
-
|
355
|
+
error_message = f"파일 '{file_path}'을 CL {change_list_number}로 이동 중 P4Exception 발생: {e_reopen}"
|
356
|
+
raise PerforceError(error_message)
|
421
357
|
|
422
358
|
if remove_file_paths:
|
423
359
|
for file_path in remove_file_paths:
|
424
360
|
try:
|
425
361
|
self.p4.run_revert("-c", change_list_number, file_path)
|
426
|
-
logger.info(f"파일 '{file_path}'를 체인지 리스트 {change_list_number}에서 제거(revert) 완료.")
|
427
362
|
except P4Exception as e_revert:
|
428
|
-
|
363
|
+
error_message = f"파일 '{file_path}'을 CL {change_list_number}에서 제거(revert) 중 P4Exception 발생: {e_revert}"
|
364
|
+
raise PerforceError(error_message)
|
429
365
|
|
430
366
|
return self.get_change_list_by_number(change_list_number)
|
431
367
|
|
432
368
|
except P4Exception as e:
|
433
|
-
|
434
|
-
|
369
|
+
error_message = f"체인지 리스트 {change_list_number} 편집 중 P4Exception 발생: {e}"
|
370
|
+
raise PerforceError(error_message)
|
435
371
|
|
436
372
|
def _file_op(self, command: str, file_path: str, change_list_number: int, op_name: str) -> bool:
|
437
373
|
"""파일 작업을 수행하는 내부 헬퍼 함수입니다.
|
@@ -445,9 +381,7 @@ class Perforce:
|
|
445
381
|
Returns:
|
446
382
|
bool: 작업 성공 시 True, 실패 시 False
|
447
383
|
"""
|
448
|
-
|
449
|
-
return False
|
450
|
-
logger.info(f"파일 '{file_path}'에 대한 '{op_name}' 작업 시도 (CL: {change_list_number})...")
|
384
|
+
self._ensure_connected()
|
451
385
|
try:
|
452
386
|
if command == "edit":
|
453
387
|
self.p4.run_edit("-c", change_list_number, file_path)
|
@@ -456,13 +390,12 @@ class Perforce:
|
|
456
390
|
elif command == "delete":
|
457
391
|
self.p4.run_delete("-c", change_list_number, file_path)
|
458
392
|
else:
|
459
|
-
|
460
|
-
|
461
|
-
logger.info(f"파일 '{file_path}'에 대한 '{op_name}' 작업 성공 (CL: {change_list_number}).")
|
393
|
+
error_message = f"지원되지 않는 파일 작업: {command}"
|
394
|
+
raise ValidationError(error_message)
|
462
395
|
return True
|
463
396
|
except P4Exception as e:
|
464
|
-
|
465
|
-
|
397
|
+
error_message = f"파일 '{file_path}' {op_name} (CL: {change_list_number}) 중 P4Exception 발생: {e}"
|
398
|
+
raise PerforceError(error_message)
|
466
399
|
|
467
400
|
def checkout_file(self, file_path: str, change_list_number: int) -> bool:
|
468
401
|
"""파일을 체크아웃합니다.
|
@@ -487,23 +420,19 @@ class Perforce:
|
|
487
420
|
bool: 모든 파일 체크아웃 성공 시 True, 하나라도 실패 시 False
|
488
421
|
"""
|
489
422
|
if not file_paths:
|
490
|
-
logger.debug("체크아웃할 파일 목록이 비어있습니다.")
|
491
423
|
return True
|
492
|
-
|
493
|
-
logger.info(f"체인지 리스트 {change_list_number}에 {len(file_paths)}개 파일 체크아웃 시도...")
|
494
424
|
|
425
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
426
|
+
if not isinstance(file_paths, list):
|
427
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 checkout_file() 메서드를 사용하세요."
|
428
|
+
raise ValidationError(error_msg)
|
429
|
+
|
495
430
|
all_success = True
|
496
431
|
for file_path in file_paths:
|
497
432
|
success = self.checkout_file(file_path, change_list_number)
|
498
433
|
if not success:
|
499
434
|
all_success = False
|
500
|
-
logger.warning(f"파일 '{file_path}' 체크아웃 실패")
|
501
435
|
|
502
|
-
if all_success:
|
503
|
-
logger.info(f"모든 파일({len(file_paths)}개)을 체인지 리스트 {change_list_number}에 성공적으로 체크아웃했습니다.")
|
504
|
-
else:
|
505
|
-
logger.warning(f"일부 파일을 체인지 리스트 {change_list_number}에 체크아웃하지 못했습니다.")
|
506
|
-
|
507
436
|
return all_success
|
508
437
|
|
509
438
|
def add_file(self, file_path: str, change_list_number: int) -> bool:
|
@@ -529,23 +458,19 @@ class Perforce:
|
|
529
458
|
bool: 모든 파일 추가 성공 시 True, 하나라도 실패 시 False
|
530
459
|
"""
|
531
460
|
if not file_paths:
|
532
|
-
logger.debug("추가할 파일 목록이 비어있습니다.")
|
533
461
|
return True
|
534
|
-
|
535
|
-
logger.info(f"체인지 리스트 {change_list_number}에 {len(file_paths)}개 파일 추가 시도...")
|
536
462
|
|
463
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
464
|
+
if not isinstance(file_paths, list):
|
465
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 add_file() 메서드를 사용하세요."
|
466
|
+
raise ValidationError(error_msg)
|
467
|
+
|
537
468
|
all_success = True
|
538
469
|
for file_path in file_paths:
|
539
470
|
success = self.add_file(file_path, change_list_number)
|
540
471
|
if not success:
|
541
472
|
all_success = False
|
542
|
-
logger.warning(f"파일 '{file_path}' 추가 실패")
|
543
473
|
|
544
|
-
if all_success:
|
545
|
-
logger.info(f"모든 파일({len(file_paths)}개)을 체인지 리스트 {change_list_number}에 성공적으로 추가했습니다.")
|
546
|
-
else:
|
547
|
-
logger.warning(f"일부 파일을 체인지 리스트 {change_list_number}에 추가하지 못했습니다.")
|
548
|
-
|
549
474
|
return all_success
|
550
475
|
|
551
476
|
def delete_file(self, file_path: str, change_list_number: int) -> bool:
|
@@ -571,23 +496,19 @@ class Perforce:
|
|
571
496
|
bool: 모든 파일 삭제 성공 시 True, 하나라도 실패 시 False
|
572
497
|
"""
|
573
498
|
if not file_paths:
|
574
|
-
logger.debug("삭제할 파일 목록이 비어있습니다.")
|
575
499
|
return True
|
576
|
-
|
577
|
-
logger.info(f"체인지 리스트 {change_list_number}에서 {len(file_paths)}개 파일 삭제 시도...")
|
578
500
|
|
501
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
502
|
+
if not isinstance(file_paths, list):
|
503
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 delete_file() 메서드를 사용하세요."
|
504
|
+
raise ValidationError(error_msg)
|
505
|
+
|
579
506
|
all_success = True
|
580
507
|
for file_path in file_paths:
|
581
508
|
success = self.delete_file(file_path, change_list_number)
|
582
509
|
if not success:
|
583
510
|
all_success = False
|
584
|
-
logger.warning(f"파일 '{file_path}' 삭제 실패")
|
585
511
|
|
586
|
-
if all_success:
|
587
|
-
logger.info(f"모든 파일({len(file_paths)}개)을 체인지 리스트 {change_list_number}에서 성공적으로 삭제했습니다.")
|
588
|
-
else:
|
589
|
-
logger.warning(f"일부 파일을 체인지 리스트 {change_list_number}에서 삭제하지 못했습니다.")
|
590
|
-
|
591
512
|
return all_success
|
592
513
|
|
593
514
|
def submit_change_list(self, change_list_number: int, auto_revert_unchanged: bool = True) -> bool:
|
@@ -601,22 +522,20 @@ class Perforce:
|
|
601
522
|
Returns:
|
602
523
|
bool: 제출 성공 시 True, 실패 시 False
|
603
524
|
"""
|
604
|
-
|
605
|
-
return False
|
606
|
-
logger.info(f"체인지 리스트 {change_list_number} 제출 시도...")
|
525
|
+
self._ensure_connected()
|
607
526
|
|
608
527
|
submit_success = False
|
609
528
|
try:
|
610
529
|
self.p4.run_submit("-c", change_list_number)
|
611
|
-
logger.info(f"체인지 리스트 {change_list_number} 제출 성공.")
|
612
530
|
submit_success = True
|
613
531
|
except P4Exception as e:
|
614
|
-
self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 제출")
|
615
532
|
if any("nothing to submit" in err.lower() for err in self.p4.errors):
|
616
|
-
|
617
|
-
|
533
|
+
error_message = f"체인지 리스트 {change_list_number}에 제출할 파일이 없습니다."
|
534
|
+
else:
|
535
|
+
error_message = f"체인지 리스트 {change_list_number} 제출 실패 중 P4Exception 발생: {e}"
|
536
|
+
raise PerforceError(error_message)
|
618
537
|
|
619
|
-
# 제출 성공
|
538
|
+
# 제출 성공 시 후속 작업 실행
|
620
539
|
try:
|
621
540
|
# 제출 후 변경사항이 없는 체크아웃된 파일들을 자동으로 리버트
|
622
541
|
if auto_revert_unchanged:
|
@@ -626,7 +545,8 @@ class Perforce:
|
|
626
545
|
# 빈 체인지 리스트 삭제
|
627
546
|
self.delete_empty_change_list(change_list_number)
|
628
547
|
except Exception as e:
|
629
|
-
|
548
|
+
error_message = f"체인지 리스트 {change_list_number} 제출 후 후속 작업 중 오류 발생: {e}"
|
549
|
+
raise PerforceError(error_message)
|
630
550
|
|
631
551
|
return submit_success
|
632
552
|
|
@@ -636,13 +556,11 @@ class Perforce:
|
|
636
556
|
Args:
|
637
557
|
change_list_number (int): 체인지 리스트 번호
|
638
558
|
"""
|
639
|
-
logger.debug(f"체인지 리스트 {change_list_number}에서 변경사항이 없는 파일들 자동 리버트 시도...")
|
640
559
|
try:
|
641
560
|
# 체인지 리스트에서 체크아웃된 파일들 가져오기
|
642
561
|
opened_files = self.p4.run_opened("-c", change_list_number)
|
643
562
|
|
644
563
|
if not opened_files:
|
645
|
-
logger.debug(f"체인지 리스트 {change_list_number}에 체크아웃된 파일이 없습니다.")
|
646
564
|
return
|
647
565
|
|
648
566
|
unchanged_files = []
|
@@ -659,45 +577,29 @@ class Perforce:
|
|
659
577
|
# diff 결과가 비어있으면 변경사항이 없음
|
660
578
|
if not diff_result:
|
661
579
|
unchanged_files.append(file_path)
|
662
|
-
logger.debug(f"파일 '{file_path}'에 변경사항이 없어 리버트 대상으로 추가")
|
663
|
-
else:
|
664
|
-
logger.debug(f"파일 '{file_path}'에 변경사항이 있어 리버트하지 않음")
|
665
580
|
|
666
|
-
except P4Exception
|
581
|
+
except P4Exception:
|
667
582
|
# diff 명령 실패 시에도 리버트 대상으로 추가 (안전하게 처리)
|
668
583
|
unchanged_files.append(file_path)
|
669
|
-
logger.debug(f"파일 '{file_path}' diff 확인 실패, 리버트 대상으로 추가: {e}")
|
670
|
-
else:
|
671
|
-
logger.debug(f"파일 '{file_path}'는 {action} 액션이므로 리버트하지 않음")
|
672
584
|
|
673
585
|
# 변경사항이 없는 파일들을 리버트
|
674
586
|
if unchanged_files:
|
675
|
-
logger.info(f"체인지 리스트 {change_list_number}에서 변경사항이 없는 파일 {len(unchanged_files)}개 자동 리버트 시도...")
|
676
587
|
for file_path in unchanged_files:
|
677
588
|
try:
|
678
589
|
self.p4.run_revert("-c", change_list_number, file_path)
|
679
|
-
|
680
|
-
|
681
|
-
self._handle_p4_exception(e, f"파일 '{file_path}' 자동 리버트")
|
682
|
-
logger.info(f"체인지 리스트 {change_list_number}에서 변경사항이 없는 파일 {len(unchanged_files)}개 자동 리버트 완료")
|
683
|
-
else:
|
684
|
-
logger.debug(f"체인지 리스트 {change_list_number}에서 변경사항이 없는 파일이 없습니다.")
|
685
|
-
|
686
|
-
# default change list에서도 변경사항이 없는 파일들 처리
|
687
|
-
self._auto_revert_unchanged_files_in_default_changelist()
|
590
|
+
except P4Exception:
|
591
|
+
pass # 개별 파일 리버트 실패는 무시
|
688
592
|
|
689
|
-
except P4Exception
|
690
|
-
|
593
|
+
except P4Exception:
|
594
|
+
pass # 자동 리버트 실패는 무시
|
691
595
|
|
692
596
|
def _auto_revert_unchanged_files_in_default_changelist(self) -> None:
|
693
597
|
"""default change list에서 변경사항이 없는 체크아웃된 파일들을 자동으로 리버트합니다."""
|
694
|
-
logger.debug("default change list에서 변경사항이 없는 파일들 자동 리버트 시도...")
|
695
598
|
try:
|
696
599
|
# get_default_change_list를 사용해서 default change list의 파일들 가져오기
|
697
600
|
default_cl_info = self.get_default_change_list()
|
698
601
|
|
699
602
|
if not default_cl_info or not default_cl_info.get('Files'):
|
700
|
-
logger.debug("default change list에 체크아웃된 파일이 없습니다.")
|
701
603
|
return
|
702
604
|
|
703
605
|
files_list = default_cl_info.get('Files', [])
|
@@ -711,30 +613,21 @@ class Perforce:
|
|
711
613
|
# diff 결과가 비어있으면 변경사항이 없음
|
712
614
|
if not diff_result:
|
713
615
|
unchanged_files.append(file_path)
|
714
|
-
logger.debug(f"default change list의 파일 '{file_path}'에 변경사항이 없어 리버트 대상으로 추가")
|
715
|
-
else:
|
716
|
-
logger.debug(f"default change list의 파일 '{file_path}'에 변경사항이 있어 리버트하지 않음")
|
717
616
|
|
718
|
-
except P4Exception
|
617
|
+
except P4Exception:
|
719
618
|
# diff 명령 실패 시에도 리버트 대상으로 추가 (안전하게 처리)
|
720
619
|
unchanged_files.append(file_path)
|
721
|
-
logger.debug(f"default change list의 파일 '{file_path}' diff 확인 실패, 리버트 대상으로 추가: {e}")
|
722
620
|
|
723
621
|
# 변경사항이 없는 파일들을 리버트
|
724
622
|
if unchanged_files:
|
725
|
-
logger.info(f"default change list에서 변경사항이 없는 파일 {len(unchanged_files)}개 자동 리버트 시도...")
|
726
623
|
for file_path in unchanged_files:
|
727
624
|
try:
|
728
625
|
self.p4.run_revert(file_path)
|
729
|
-
|
730
|
-
|
731
|
-
self._handle_p4_exception(e, f"default change list의 파일 '{file_path}' 자동 리버트")
|
732
|
-
logger.info(f"default change list에서 변경사항이 없는 파일 {len(unchanged_files)}개 자동 리버트 완료")
|
733
|
-
else:
|
734
|
-
logger.debug("default change list에서 변경사항이 없는 파일이 없습니다.")
|
626
|
+
except P4Exception:
|
627
|
+
pass # 개별 파일 리버트 실패는 무시
|
735
628
|
|
736
|
-
except P4Exception
|
737
|
-
|
629
|
+
except P4Exception:
|
630
|
+
pass # 자동 리버트 실패는 무시
|
738
631
|
|
739
632
|
def revert_change_list(self, change_list_number: int) -> bool:
|
740
633
|
"""체인지 리스트를 되돌리고 삭제합니다.
|
@@ -747,27 +640,22 @@ class Perforce:
|
|
747
640
|
Returns:
|
748
641
|
bool: 되돌리기 및 삭제 성공 시 True, 실패 시 False
|
749
642
|
"""
|
750
|
-
|
751
|
-
return False
|
752
|
-
logger.info(f"체인지 리스트 {change_list_number} 전체 되돌리기 및 삭제 시도...")
|
643
|
+
self._ensure_connected()
|
753
644
|
try:
|
754
645
|
# 체인지 리스트의 모든 파일 되돌리기
|
755
646
|
self.p4.run_revert("-c", change_list_number, "//...")
|
756
|
-
logger.info(f"체인지 리스트 {change_list_number} 전체 되돌리기 성공.")
|
757
647
|
|
758
648
|
# 빈 체인지 리스트 삭제
|
759
649
|
try:
|
760
650
|
self.p4.run_change("-d", change_list_number)
|
761
|
-
logger.info(f"체인지 리스트 {change_list_number} 삭제 완료.")
|
762
651
|
except P4Exception as e_delete:
|
763
|
-
|
764
|
-
|
765
|
-
return False
|
652
|
+
error_message = f"체인지 리스트 {change_list_number} 삭제 중 P4Exception 발생: {e_delete}"
|
653
|
+
raise PerforceError(error_message)
|
766
654
|
|
767
655
|
return True
|
768
656
|
except P4Exception as e:
|
769
|
-
|
770
|
-
|
657
|
+
error_message = f"체인지 리스트 {change_list_number} 전체 되돌리기 실패 중 P4Exception 발생: {e}"
|
658
|
+
raise PerforceError(error_message)
|
771
659
|
|
772
660
|
def delete_empty_change_list(self, change_list_number: int) -> bool:
|
773
661
|
"""빈 체인지 리스트를 삭제합니다.
|
@@ -778,26 +666,22 @@ class Perforce:
|
|
778
666
|
Returns:
|
779
667
|
bool: 삭제 성공 시 True, 실패 시 False
|
780
668
|
"""
|
781
|
-
|
782
|
-
return False
|
783
|
-
|
784
|
-
logger.info(f"체인지 리스트 {change_list_number} 삭제 시도 중...")
|
669
|
+
self._ensure_connected()
|
785
670
|
try:
|
786
671
|
# 체인지 리스트 정보 가져오기
|
787
672
|
change_spec = self.p4.fetch_change(change_list_number)
|
788
673
|
|
789
674
|
# 파일이 있는지 확인
|
790
675
|
if change_spec and change_spec.get('Files') and len(change_spec['Files']) > 0:
|
791
|
-
|
792
|
-
|
676
|
+
error_message = f"체인지 리스트 {change_list_number}에 파일이 {len(change_spec['Files'])}개 있어 삭제할 수 없습니다."
|
677
|
+
raise PerforceError(error_message)
|
793
678
|
|
794
679
|
# 빈 체인지 리스트 삭제
|
795
680
|
self.p4.run_change("-d", change_list_number)
|
796
|
-
logger.info(f"빈 체인지 리스트 {change_list_number} 삭제 완료.")
|
797
681
|
return True
|
798
682
|
except P4Exception as e:
|
799
|
-
|
800
|
-
|
683
|
+
error_message = f"체인지 리스트 {change_list_number} 삭제 실패 중 P4Exception 발생: {e}"
|
684
|
+
raise PerforceError(error_message)
|
801
685
|
|
802
686
|
def revert_file(self, file_path: str, change_list_number: int) -> bool:
|
803
687
|
"""체인지 리스트에서 특정 파일을 되돌립니다.
|
@@ -809,17 +693,13 @@ class Perforce:
|
|
809
693
|
Returns:
|
810
694
|
bool: 되돌리기 성공 시 True, 실패 시 False
|
811
695
|
"""
|
812
|
-
|
813
|
-
return False
|
814
|
-
|
815
|
-
logger.info(f"파일 '{file_path}'을 체인지 리스트 {change_list_number}에서 되돌리기 시도...")
|
696
|
+
self._ensure_connected()
|
816
697
|
try:
|
817
698
|
self.p4.run_revert("-c", change_list_number, file_path)
|
818
|
-
logger.info(f"파일 '{file_path}'를 체인지 리스트 {change_list_number}에서 되돌리기 성공.")
|
819
699
|
return True
|
820
700
|
except P4Exception as e:
|
821
|
-
|
822
|
-
|
701
|
+
error_message = f"파일 '{file_path}'를 체인지 리스트 {change_list_number}에서 되돌리기 중 P4Exception 발생: {e}"
|
702
|
+
raise PerforceError(error_message)
|
823
703
|
|
824
704
|
def revert_files(self, change_list_number: int, file_paths: list) -> bool:
|
825
705
|
"""체인지 리스트 내의 특정 파일들을 되돌립니다.
|
@@ -831,26 +711,21 @@ class Perforce:
|
|
831
711
|
Returns:
|
832
712
|
bool: 모든 파일 되돌리기 성공 시 True, 하나라도 실패 시 False
|
833
713
|
"""
|
834
|
-
|
835
|
-
return False
|
714
|
+
self._ensure_connected()
|
836
715
|
if not file_paths:
|
837
|
-
logger.warning("되돌릴 파일 목록이 비어있습니다.")
|
838
716
|
return True
|
839
|
-
|
840
|
-
logger.info(f"체인지 리스트 {change_list_number}에서 {len(file_paths)}개 파일 되돌리기 시도...")
|
841
717
|
|
718
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
719
|
+
if not isinstance(file_paths, list):
|
720
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 revert_file() 메서드를 사용하세요."
|
721
|
+
raise ValidationError(error_msg)
|
722
|
+
|
842
723
|
all_success = True
|
843
724
|
for file_path in file_paths:
|
844
725
|
success = self.revert_file(file_path, change_list_number)
|
845
726
|
if not success:
|
846
727
|
all_success = False
|
847
|
-
logger.warning(f"파일 '{file_path}' 되돌리기 실패")
|
848
728
|
|
849
|
-
if all_success:
|
850
|
-
logger.info(f"모든 파일({len(file_paths)}개)을 체인지 리스트 {change_list_number}에서 성공적으로 되돌렸습니다.")
|
851
|
-
else:
|
852
|
-
logger.warning(f"일부 파일을 체인지 리스트 {change_list_number}에서 되돌리지 못했습니다.")
|
853
|
-
|
854
729
|
return all_success
|
855
730
|
|
856
731
|
def check_update_required(self, file_paths: list) -> bool:
|
@@ -863,23 +738,24 @@ class Perforce:
|
|
863
738
|
Returns:
|
864
739
|
bool: 업데이트가 필요한 파일이 있으면 True, 없으면 False
|
865
740
|
"""
|
866
|
-
|
867
|
-
return False
|
741
|
+
self._ensure_connected()
|
868
742
|
if not file_paths:
|
869
|
-
logger.debug("업데이트 필요 여부 확인할 파일/폴더 목록이 비어있습니다.")
|
870
743
|
return False
|
871
744
|
|
745
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
746
|
+
if not isinstance(file_paths, list):
|
747
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 경로도 리스트로 감싸서 전달하세요: ['{file_paths}']"
|
748
|
+
raise ValidationError(error_msg)
|
749
|
+
|
872
750
|
# 폴더 경로에 재귀적 와일드카드 패턴을 추가
|
873
751
|
processed_paths = []
|
874
752
|
for path in file_paths:
|
875
753
|
if os.path.isdir(path):
|
876
754
|
# 폴더 경로에 '...'(재귀) 패턴을 추가
|
877
755
|
processed_paths.append(os.path.join(path, '...'))
|
878
|
-
logger.debug(f"폴더 경로를 재귀 패턴으로 변환: {path} -> {os.path.join(path, '...')}")
|
879
756
|
else:
|
880
757
|
processed_paths.append(path)
|
881
758
|
|
882
|
-
logger.debug(f"파일/폴더 업데이트 필요 여부 확인 중 (항목 {len(processed_paths)}개): {processed_paths}")
|
883
759
|
try:
|
884
760
|
sync_preview_results = self.p4.run_sync("-n", processed_paths)
|
885
761
|
needs_update = False
|
@@ -889,26 +765,30 @@ class Perforce:
|
|
889
765
|
'no such file(s)' not in result.get('depotFile', ''):
|
890
766
|
if result.get('how') and 'syncing' in result.get('how'):
|
891
767
|
needs_update = True
|
892
|
-
logger.info(f"파일 '{result.get('clientFile', result.get('depotFile'))}' 업데이트 필요: {result.get('how')}")
|
893
768
|
break
|
894
769
|
elif result.get('action') and result.get('action') not in ['checked', 'exists']:
|
895
770
|
needs_update = True
|
896
|
-
logger.info(f"파일 '{result.get('clientFile', result.get('depotFile'))}' 업데이트 필요 (action: {result.get('action')})")
|
897
771
|
break
|
898
772
|
elif isinstance(result, str):
|
899
773
|
if "up-to-date" not in result and "no such file(s)" not in result:
|
900
774
|
needs_update = True
|
901
|
-
logger.info(f"파일 업데이트 필요 (문자열 결과): {result}")
|
902
775
|
break
|
903
776
|
|
904
|
-
if needs_update:
|
905
|
-
logger.info(f"지정된 파일/폴더 중 업데이트가 필요한 파일이 있습니다.")
|
906
|
-
else:
|
907
|
-
logger.info(f"지정된 모든 파일/폴더가 최신 상태입니다.")
|
908
777
|
return needs_update
|
909
778
|
except P4Exception as e:
|
910
|
-
|
911
|
-
|
779
|
+
# "up-to-date" 메시지는 정상적인 응답이므로 에러로 처리하지 않음
|
780
|
+
exception_str = str(e)
|
781
|
+
error_messages = [str(err) for err in self.p4.errors]
|
782
|
+
warning_messages = [str(warn) for warn in self.p4.warnings]
|
783
|
+
|
784
|
+
# P4Exception 자체 메시지나 에러/경고 메시지에서 "up-to-date" 확인
|
785
|
+
if ("up-to-date" in exception_str or
|
786
|
+
any("up-to-date" in msg for msg in error_messages) or
|
787
|
+
any("up-to-date" in msg for msg in warning_messages)):
|
788
|
+
return False
|
789
|
+
else:
|
790
|
+
error_message = f"파일/폴더 업데이트 필요 여부 확인 ({processed_paths}) 중 P4Exception 발생: {e}"
|
791
|
+
raise PerforceError(error_message)
|
912
792
|
|
913
793
|
def is_file_in_perforce(self, file_path: str) -> bool:
|
914
794
|
"""파일이 Perforce에 속하는지 확인합니다.
|
@@ -919,81 +799,82 @@ class Perforce:
|
|
919
799
|
Returns:
|
920
800
|
bool: 파일이 Perforce에 속하면 True, 아니면 False
|
921
801
|
"""
|
922
|
-
|
923
|
-
return False
|
924
|
-
|
925
|
-
logger.debug(f"파일 '{file_path}'가 Perforce에 속하는지 확인 중...")
|
802
|
+
self._ensure_connected()
|
926
803
|
try:
|
927
804
|
# p4 files 명령으로 파일 정보 조회
|
928
805
|
file_info = self.p4.run_files(file_path)
|
929
806
|
|
930
807
|
# 파일 정보가 있고, 'no such file(s)' 오류가 없는 경우
|
931
808
|
if file_info and not any("no such file(s)" in str(err).lower() for err in self.p4.errors):
|
932
|
-
logger.info(f"파일 '{file_path}'가 Perforce에 존재합니다.")
|
933
809
|
return True
|
934
810
|
else:
|
935
|
-
logger.info(f"파일 '{file_path}'가 Perforce에 존재하지 않습니다.")
|
936
811
|
return False
|
937
812
|
|
938
813
|
except P4Exception as e:
|
939
|
-
# 파일이 존재하지 않는 경우는 일반적인 상황이므로
|
814
|
+
# 파일이 존재하지 않는 경우는 일반적인 상황이므로 False 반환
|
940
815
|
if any("no such file(s)" in err.lower() for err in self.p4.errors):
|
941
|
-
logger.info(f"파일 '{file_path}'가 Perforce에 존재하지 않습니다.")
|
942
816
|
return False
|
943
817
|
else:
|
944
|
-
|
945
|
-
|
818
|
+
error_message = f"파일 '{file_path}' Perforce 존재 여부 확인 중 P4Exception 발생: {e}"
|
819
|
+
raise PerforceError(error_message)
|
946
820
|
|
947
821
|
def sync_files(self, file_paths: list) -> bool:
|
948
822
|
"""파일이나 폴더를 동기화합니다.
|
949
823
|
|
950
824
|
Args:
|
951
825
|
file_paths (list): 동기화할 파일 또는 폴더 경로 리스트.
|
952
|
-
|
826
|
+
폴더 경로는 자동으로 재귀적으로 처리됩니다.
|
953
827
|
|
954
828
|
Returns:
|
955
829
|
bool: 동기화 성공 시 True, 실패 시 False
|
956
830
|
"""
|
957
|
-
|
958
|
-
return False
|
831
|
+
self._ensure_connected()
|
959
832
|
if not file_paths:
|
960
|
-
logger.debug("싱크할 파일/폴더 목록이 비어있습니다.")
|
961
833
|
return True
|
962
834
|
|
835
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
836
|
+
if not isinstance(file_paths, list):
|
837
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 경로도 리스트로 감싸서 전달하세요: ['{file_paths}']"
|
838
|
+
raise ValidationError(error_msg)
|
839
|
+
|
963
840
|
# 폴더 경로에 재귀적 와일드카드 패턴을 추가
|
964
841
|
processed_paths = []
|
965
842
|
for path in file_paths:
|
966
843
|
if os.path.isdir(path):
|
967
844
|
# 폴더 경로에 '...'(재귀) 패턴을 추가
|
968
845
|
processed_paths.append(os.path.join(path, '...'))
|
969
|
-
logger.debug(f"폴더 경로를 재귀 패턴으로 변환: {path} -> {os.path.join(path, '...')}")
|
970
846
|
else:
|
971
847
|
processed_paths.append(path)
|
972
848
|
|
973
|
-
logger.info(f"파일/폴더 싱크 시도 (항목 {len(processed_paths)}개): {processed_paths}")
|
974
849
|
try:
|
975
850
|
self.p4.run_sync(processed_paths)
|
976
|
-
logger.info(f"파일/폴더 싱크 완료: {processed_paths}")
|
977
851
|
return True
|
978
852
|
except P4Exception as e:
|
979
|
-
|
980
|
-
|
853
|
+
error_message = f"파일/폴더 싱크 실패 ({processed_paths}) 중 P4Exception 발생: {e}"
|
854
|
+
raise PerforceError(error_message)
|
981
855
|
|
982
|
-
def
|
983
|
-
"""
|
984
|
-
if self.connected:
|
985
|
-
try:
|
986
|
-
self.p4.disconnect()
|
987
|
-
self.connected = False
|
988
|
-
logger.info("Perforce 서버 연결 해제 완료.")
|
989
|
-
except P4Exception as e:
|
990
|
-
self._handle_p4_exception(e, "Perforce 서버 연결 해제")
|
991
|
-
else:
|
992
|
-
logger.debug("Perforce 서버에 이미 연결되지 않은 상태입니다.")
|
856
|
+
def get_default_change_list(self) -> dict:
|
857
|
+
"""default change list의 정보를 가져옵니다.
|
993
858
|
|
994
|
-
|
995
|
-
|
996
|
-
|
859
|
+
Returns:
|
860
|
+
dict: get_change_list_by_number와 동일한 형태의 딕셔너리
|
861
|
+
"""
|
862
|
+
self._ensure_connected()
|
863
|
+
try:
|
864
|
+
opened_files = self.p4.run_opened("-c", "default")
|
865
|
+
files_list = [f.get('clientFile', '') for f in opened_files]
|
866
|
+
result = {
|
867
|
+
'Change': 'default',
|
868
|
+
'Description': 'Default change',
|
869
|
+
'User': getattr(self.p4, 'user', ''),
|
870
|
+
'Client': getattr(self.p4, 'client', ''),
|
871
|
+
'Status': 'pending',
|
872
|
+
'Files': files_list
|
873
|
+
}
|
874
|
+
return result
|
875
|
+
except P4Exception as e:
|
876
|
+
error_message = f"default change list 정보 조회 실패 중 P4Exception 발생: {e}"
|
877
|
+
raise PerforceError(error_message)
|
997
878
|
|
998
879
|
def check_files_checked_out_all_users(self, file_paths: list) -> dict:
|
999
880
|
"""파일들의 체크아웃 상태를 모든 사용자/워크스페이스에서 확인합니다.
|
@@ -1013,13 +894,14 @@ class Perforce:
|
|
1013
894
|
}
|
1014
895
|
}
|
1015
896
|
"""
|
1016
|
-
|
1017
|
-
return {}
|
897
|
+
self._ensure_connected()
|
1018
898
|
if not file_paths:
|
1019
|
-
logger.debug("체크아웃 상태 확인할 파일 목록이 비어있습니다.")
|
1020
899
|
return {}
|
1021
900
|
|
1022
|
-
|
901
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
902
|
+
if not isinstance(file_paths, list):
|
903
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 get_file_checkout_info_all_users() 메서드를 사용하세요."
|
904
|
+
raise ValidationError(error_msg)
|
1023
905
|
|
1024
906
|
result = {}
|
1025
907
|
try:
|
@@ -1046,31 +928,20 @@ class Perforce:
|
|
1046
928
|
file_status['user'] = file_info.get('user', '')
|
1047
929
|
file_status['client'] = file_info.get('client', '')
|
1048
930
|
|
1049
|
-
logger.debug(f"파일 '{file_path}' 체크아웃됨: CL {file_status['change_list']}, "
|
1050
|
-
f"액션: {file_status['action']}, 사용자: {file_status['user']}, "
|
1051
|
-
f"클라이언트: {file_status['client']}")
|
1052
|
-
else:
|
1053
|
-
# 파일이 체크아웃되지 않음
|
1054
|
-
logger.debug(f"파일 '{file_path}' 체크아웃되지 않음 (모든 사용자)")
|
1055
|
-
|
1056
931
|
except P4Exception as e:
|
1057
932
|
# 파일이 perforce에 없거나 접근할 수 없는 경우
|
1058
|
-
if any("not opened" in err.lower() or "no such file" in err.lower()
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
self._handle_p4_exception(e, f"파일 '{file_path}' 체크아웃 상태 확인 (모든 사용자)")
|
933
|
+
if not any("not opened" in err.lower() or "no such file" in err.lower()
|
934
|
+
for err in self.p4.errors):
|
935
|
+
error_message = f"파일 '{file_path}' 체크아웃 상태 확인 (모든 사용자) 중 P4Exception 발생: {e}"
|
936
|
+
raise PerforceError(error_message)
|
1063
937
|
|
1064
938
|
result[file_path] = file_status
|
1065
939
|
|
1066
|
-
checked_out_count = sum(1 for status in result.values() if status['is_checked_out'])
|
1067
|
-
logger.info(f"파일 체크아웃 상태 확인 완료 (모든 사용자): 전체 {len(file_paths)}개 중 {checked_out_count}개 체크아웃됨")
|
1068
|
-
|
1069
940
|
return result
|
1070
941
|
|
1071
942
|
except P4Exception as e:
|
1072
|
-
|
1073
|
-
|
943
|
+
error_message = f"파일들 체크아웃 상태 확인 - 모든 사용자 ({file_paths}) 중 P4Exception 발생: {e}"
|
944
|
+
raise PerforceError(error_message)
|
1074
945
|
|
1075
946
|
def is_file_checked_out_by_others(self, file_path: str) -> bool:
|
1076
947
|
"""단일 파일이 다른 사용자/워크스페이스에 의해 체크아웃되어 있는지 확인합니다.
|
@@ -1156,6 +1027,11 @@ class Perforce:
|
|
1156
1027
|
if not file_paths:
|
1157
1028
|
return []
|
1158
1029
|
|
1030
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
1031
|
+
if not isinstance(file_paths, list):
|
1032
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 is_file_checked_out_by_others() 메서드를 사용하세요."
|
1033
|
+
raise ValidationError(error_msg)
|
1034
|
+
|
1159
1035
|
result = self.check_files_checked_out_all_users(file_paths)
|
1160
1036
|
files_by_others = []
|
1161
1037
|
|
@@ -1177,31 +1053,4 @@ class Perforce:
|
|
1177
1053
|
'action': status.get('action', '')
|
1178
1054
|
})
|
1179
1055
|
|
1180
|
-
|
1181
|
-
return files_by_others
|
1182
|
-
|
1183
|
-
def get_default_change_list(self) -> dict:
|
1184
|
-
"""default change list의 정보를 가져옵니다.
|
1185
|
-
|
1186
|
-
Returns:
|
1187
|
-
dict: get_change_list_by_number와 동일한 형태의 딕셔너리
|
1188
|
-
"""
|
1189
|
-
if not self._is_connected():
|
1190
|
-
return {}
|
1191
|
-
logger.debug("default change list 정보 조회 중...")
|
1192
|
-
try:
|
1193
|
-
opened_files = self.p4.run_opened("-c", "default")
|
1194
|
-
files_list = [f.get('clientFile', '') for f in opened_files]
|
1195
|
-
result = {
|
1196
|
-
'Change': 'default',
|
1197
|
-
'Description': 'Default change',
|
1198
|
-
'User': getattr(self.p4, 'user', ''),
|
1199
|
-
'Client': getattr(self.p4, 'client', ''),
|
1200
|
-
'Status': 'pending',
|
1201
|
-
'Files': files_list
|
1202
|
-
}
|
1203
|
-
logger.info(f"default change list 정보 조회 완료: {len(files_list)}개 파일")
|
1204
|
-
return result
|
1205
|
-
except P4Exception as e:
|
1206
|
-
self._handle_p4_exception(e, "default change list 정보 조회")
|
1207
|
-
return {}
|
1056
|
+
return files_by_others
|