pyjallib 0.1.16__py3-none-any.whl → 0.1.17__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 -2
- pyjallib/logger.py +201 -0
- pyjallib/max/bip.py +0 -21
- pyjallib/naming.py +4 -1
- pyjallib/perforce.py +183 -264
- 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 +114 -0
- pyjallib/ue5/inUnreal/baseImporter.py +184 -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 +200 -0
- pyjallib/ue5/templateProcessor.py +254 -0
- pyjallib/ue5/templates/__init__.py +106 -0
- pyjallib/ue5/templates/animImportTemplate.py +22 -0
- pyjallib/ue5/templates/skeletalMeshImportTemplate.py +22 -0
- pyjallib/ue5/templates/skeletonImportTemplate.py +21 -0
- {pyjallib-0.1.16.dist-info → pyjallib-0.1.17.dist-info}/METADATA +1 -1
- {pyjallib-0.1.16.dist-info → pyjallib-0.1.17.dist-info}/RECORD +24 -7
- {pyjallib-0.1.16.dist-info → pyjallib-0.1.17.dist-info}/WHEEL +0 -0
pyjallib/perforce.py
CHANGED
@@ -9,45 +9,20 @@ P4Python을 사용하는 Perforce 모듈.
|
|
9
9
|
- 파일 동기화 및 업데이트 확인
|
10
10
|
"""
|
11
11
|
|
12
|
-
import logging
|
13
12
|
from P4 import P4, P4Exception
|
14
13
|
import os
|
15
|
-
from pathlib import Path
|
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)
|
29
14
|
|
30
15
|
|
31
16
|
class Perforce:
|
32
17
|
"""P4Python을 사용하여 Perforce 작업을 수행하는 클래스."""
|
33
18
|
|
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
|
-
|
19
|
+
def __init__(self):
|
20
|
+
"""Perforce 인스턴스를 초기화합니다."""
|
47
21
|
self.p4 = P4()
|
48
22
|
self.connected = False
|
49
23
|
self.workspaceRoot = r""
|
50
|
-
|
24
|
+
self.last_error = None
|
25
|
+
self.last_warnings = []
|
51
26
|
|
52
27
|
def _is_connected(self) -> bool:
|
53
28
|
"""Perforce 서버 연결 상태를 확인합니다.
|
@@ -56,22 +31,40 @@ class Perforce:
|
|
56
31
|
bool: 연결되어 있으면 True, 아니면 False
|
57
32
|
"""
|
58
33
|
if not self.connected:
|
59
|
-
|
34
|
+
self.last_error = "Perforce 서버에 연결되지 않았습니다."
|
60
35
|
return False
|
61
36
|
return True
|
62
37
|
|
63
38
|
def _handle_p4_exception(self, e: P4Exception, context_msg: str = "") -> None:
|
64
|
-
"""P4Exception을 처리하고
|
39
|
+
"""P4Exception을 처리하고 에러 정보를 저장합니다.
|
65
40
|
|
66
41
|
Args:
|
67
42
|
e (P4Exception): 발생한 예외
|
68
43
|
context_msg (str, optional): 예외가 발생한 컨텍스트 설명
|
69
44
|
"""
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
45
|
+
self.last_error = f"{context_msg} 중 P4Exception 발생: {e}"
|
46
|
+
self.last_warnings = list(self.p4.warnings) if self.p4.warnings else []
|
47
|
+
|
48
|
+
def get_last_error(self) -> str:
|
49
|
+
"""마지막 에러 메시지를 반환합니다.
|
50
|
+
|
51
|
+
Returns:
|
52
|
+
str: 마지막 에러 메시지. 에러가 없으면 None
|
53
|
+
"""
|
54
|
+
return self.last_error
|
55
|
+
|
56
|
+
def get_last_warnings(self) -> list:
|
57
|
+
"""마지막 경고 메시지들을 반환합니다.
|
58
|
+
|
59
|
+
Returns:
|
60
|
+
list: 마지막 경고 메시지들의 리스트
|
61
|
+
"""
|
62
|
+
return self.last_warnings
|
63
|
+
|
64
|
+
def clear_last_error(self) -> None:
|
65
|
+
"""마지막 에러와 경고 정보를 초기화합니다."""
|
66
|
+
self.last_error = None
|
67
|
+
self.last_warnings = []
|
75
68
|
|
76
69
|
def connect(self, workspace_name: str) -> bool:
|
77
70
|
"""지정된 워크스페이스에 연결합니다.
|
@@ -82,7 +75,7 @@ class Perforce:
|
|
82
75
|
Returns:
|
83
76
|
bool: 연결 성공 시 True, 실패 시 False
|
84
77
|
"""
|
85
|
-
|
78
|
+
self.clear_last_error()
|
86
79
|
try:
|
87
80
|
self.p4.client = workspace_name
|
88
81
|
self.p4.connect()
|
@@ -97,16 +90,14 @@ class Perforce:
|
|
97
90
|
root_path = os.path.normpath(root_path)
|
98
91
|
|
99
92
|
self.workspaceRoot = root_path
|
100
|
-
logger.info(f"워크스페이스 루트 절대 경로: {self.workspaceRoot}")
|
101
93
|
except (IndexError, KeyError) as e:
|
102
|
-
|
94
|
+
self.last_error = f"워크스페이스 루트 경로 가져오기 실패: {e}"
|
103
95
|
self.workspaceRoot = ""
|
104
96
|
|
105
|
-
logger.info(f"'{workspace_name}' 워크스페이스에 성공적으로 연결됨 (User: {self.p4.user}, Port: {self.p4.port})")
|
106
97
|
return True
|
107
98
|
except P4Exception as e:
|
108
99
|
self.connected = False
|
109
|
-
self._handle_p4_exception(e, f"'{workspace_name}' 워크스페이스 연결")
|
100
|
+
self._handle_p4_exception(e, f"'{workspace_name}' 워크스페이스 연결 실패")
|
110
101
|
return False
|
111
102
|
|
112
103
|
def get_pending_change_list(self) -> list:
|
@@ -117,7 +108,7 @@ class Perforce:
|
|
117
108
|
"""
|
118
109
|
if not self._is_connected():
|
119
110
|
return []
|
120
|
-
|
111
|
+
self.clear_last_error()
|
121
112
|
try:
|
122
113
|
pending_changes = self.p4.run_changes("-s", "pending", "-u", self.p4.user, "-c", self.p4.client)
|
123
114
|
change_numbers = [int(cl['change']) for cl in pending_changes]
|
@@ -129,10 +120,9 @@ class Perforce:
|
|
129
120
|
if cl_info:
|
130
121
|
change_list_info.append(cl_info)
|
131
122
|
|
132
|
-
logger.info(f"Pending 체인지 리스트 {len(change_list_info)}개 조회 완료")
|
133
123
|
return change_list_info
|
134
124
|
except P4Exception as e:
|
135
|
-
self._handle_p4_exception(e, "Pending 체인지 리스트 조회")
|
125
|
+
self._handle_p4_exception(e, "Pending 체인지 리스트 조회 실패")
|
136
126
|
return []
|
137
127
|
|
138
128
|
def create_change_list(self, description: str) -> dict:
|
@@ -146,19 +136,18 @@ class Perforce:
|
|
146
136
|
"""
|
147
137
|
if not self._is_connected():
|
148
138
|
return {}
|
149
|
-
|
139
|
+
self.clear_last_error()
|
150
140
|
try:
|
151
141
|
change_spec = self.p4.fetch_change()
|
152
142
|
change_spec["Description"] = description
|
153
143
|
result = self.p4.save_change(change_spec)
|
154
144
|
created_change_number = int(result[0].split()[1])
|
155
|
-
logger.info(f"체인지 리스트 {created_change_number} 생성 완료: '{description}'")
|
156
145
|
return self.get_change_list_by_number(created_change_number)
|
157
146
|
except P4Exception as e:
|
158
|
-
self._handle_p4_exception(e, f"체인지 리스트 생성 ('{description}')")
|
147
|
+
self._handle_p4_exception(e, f"체인지 리스트 생성 실패 ('{description}')")
|
159
148
|
return {}
|
160
149
|
except (IndexError, ValueError) as e:
|
161
|
-
|
150
|
+
self.last_error = f"체인지 리스트 번호 파싱 오류: {e}"
|
162
151
|
return {}
|
163
152
|
|
164
153
|
def get_change_list_by_number(self, change_list_number: int) -> dict:
|
@@ -172,17 +161,16 @@ class Perforce:
|
|
172
161
|
"""
|
173
162
|
if not self._is_connected():
|
174
163
|
return {}
|
175
|
-
|
164
|
+
self.clear_last_error()
|
176
165
|
try:
|
177
166
|
cl_info = self.p4.fetch_change(change_list_number)
|
178
167
|
if cl_info:
|
179
|
-
logger.info(f"체인지 리스트 {change_list_number} 정보 조회 완료.")
|
180
168
|
return cl_info
|
181
169
|
else:
|
182
|
-
|
170
|
+
self.last_error = f"체인지 리스트 {change_list_number}를 찾을 수 없습니다."
|
183
171
|
return {}
|
184
172
|
except P4Exception as e:
|
185
|
-
self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 정보 조회")
|
173
|
+
self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 정보 조회 실패")
|
186
174
|
return {}
|
187
175
|
|
188
176
|
def get_change_list_by_description(self, description: str) -> dict:
|
@@ -196,18 +184,16 @@ class Perforce:
|
|
196
184
|
"""
|
197
185
|
if not self._is_connected():
|
198
186
|
return {}
|
199
|
-
|
187
|
+
self.clear_last_error()
|
200
188
|
try:
|
201
189
|
pending_changes = self.p4.run_changes("-l", "-s", "pending", "-u", self.p4.user, "-c", self.p4.client)
|
202
190
|
for cl in pending_changes:
|
203
191
|
cl_desc = cl.get('Description', b'').decode('utf-8', 'replace').strip()
|
204
192
|
if cl_desc == description.strip():
|
205
|
-
logger.info(f"설명 '{description}'에 해당하는 체인지 리스트 {cl['change']} 조회 완료.")
|
206
193
|
return self.get_change_list_by_number(int(cl['change']))
|
207
|
-
logger.info(f"설명 '{description}'에 해당하는 Pending 체인지 리스트를 찾을 수 없습니다.")
|
208
194
|
return {}
|
209
195
|
except P4Exception as e:
|
210
|
-
self._handle_p4_exception(e, f"설명으로 체인지 리스트 조회 ('{description}')")
|
196
|
+
self._handle_p4_exception(e, f"설명으로 체인지 리스트 조회 실패 ('{description}')")
|
211
197
|
return {}
|
212
198
|
|
213
199
|
def get_change_list_by_description_pattern(self, description_pattern: str, exact_match: bool = False) -> list:
|
@@ -224,9 +210,7 @@ class Perforce:
|
|
224
210
|
if not self._is_connected():
|
225
211
|
return []
|
226
212
|
|
227
|
-
|
228
|
-
logger.debug(f"설명 패턴으로 체인지 리스트 조회 중 ({search_type}): '{description_pattern}'")
|
229
|
-
|
213
|
+
self.clear_last_error()
|
230
214
|
try:
|
231
215
|
pending_changes = self.p4.run_changes("-l", "-s", "pending", "-u", self.p4.user, "-c", self.p4.client)
|
232
216
|
matching_changes = []
|
@@ -248,18 +232,25 @@ class Perforce:
|
|
248
232
|
change_info = self.get_change_list_by_number(change_number)
|
249
233
|
if change_info:
|
250
234
|
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
235
|
|
258
236
|
return matching_changes
|
259
237
|
except P4Exception as e:
|
260
|
-
self._handle_p4_exception(e, f"설명 패턴으로 체인지 리스트 조회 ('{description_pattern}')")
|
238
|
+
self._handle_p4_exception(e, f"설명 패턴으로 체인지 리스트 조회 실패 ('{description_pattern}')")
|
261
239
|
return []
|
262
240
|
|
241
|
+
def disconnect(self):
|
242
|
+
"""Perforce 서버와의 연결을 해제합니다."""
|
243
|
+
if self.connected:
|
244
|
+
try:
|
245
|
+
self.p4.disconnect()
|
246
|
+
self.connected = False
|
247
|
+
except P4Exception as e:
|
248
|
+
self._handle_p4_exception(e, "Perforce 서버 연결 해제")
|
249
|
+
|
250
|
+
def __del__(self):
|
251
|
+
"""객체가 소멸될 때 자동으로 연결을 해제합니다."""
|
252
|
+
self.disconnect()
|
253
|
+
|
263
254
|
def check_files_checked_out(self, file_paths: list) -> dict:
|
264
255
|
"""파일들의 체크아웃 상태를 확인합니다.
|
265
256
|
|
@@ -281,11 +272,15 @@ class Perforce:
|
|
281
272
|
if not self._is_connected():
|
282
273
|
return {}
|
283
274
|
if not file_paths:
|
284
|
-
logger.debug("체크아웃 상태 확인할 파일 목록이 비어있습니다.")
|
285
275
|
return {}
|
286
276
|
|
287
|
-
|
277
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
278
|
+
if not isinstance(file_paths, list):
|
279
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 is_file_checked_out() 메서드를 사용하세요."
|
280
|
+
self.last_error = error_msg
|
281
|
+
raise TypeError(error_msg)
|
288
282
|
|
283
|
+
self.clear_last_error()
|
289
284
|
result = {}
|
290
285
|
try:
|
291
286
|
# 각 파일의 상태 확인
|
@@ -311,26 +306,14 @@ class Perforce:
|
|
311
306
|
file_status['user'] = file_info.get('user', '')
|
312
307
|
file_status['workspace'] = file_info.get('client', '')
|
313
308
|
|
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
309
|
except P4Exception as e:
|
322
310
|
# 파일이 perforce에 없거나 접근할 수 없는 경우
|
323
|
-
if any("not opened" in err.lower() or "no such file" in err.lower()
|
324
|
-
|
325
|
-
logger.debug(f"파일 '{file_path}' 체크아웃되지 않음 (perforce에 없거나 접근 불가)")
|
326
|
-
else:
|
311
|
+
if not any("not opened" in err.lower() or "no such file" in err.lower()
|
312
|
+
for err in self.p4.errors):
|
327
313
|
self._handle_p4_exception(e, f"파일 '{file_path}' 체크아웃 상태 확인")
|
328
314
|
|
329
315
|
result[file_path] = file_status
|
330
316
|
|
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
317
|
return result
|
335
318
|
|
336
319
|
except P4Exception as e:
|
@@ -362,8 +345,7 @@ class Perforce:
|
|
362
345
|
if not self._is_connected():
|
363
346
|
return False
|
364
347
|
|
365
|
-
|
366
|
-
|
348
|
+
self.clear_last_error()
|
367
349
|
try:
|
368
350
|
# 해당 체인지 리스트의 파일들 가져오기
|
369
351
|
opened_files = self.p4.run_opened("-c", change_list_number)
|
@@ -376,11 +358,8 @@ class Perforce:
|
|
376
358
|
normalized_client_file = os.path.normpath(client_file)
|
377
359
|
|
378
360
|
if normalized_client_file == normalized_file_path:
|
379
|
-
logger.debug(f"파일 '{file_path}'가 체인지 리스트 {change_list_number}에서 발견됨 "
|
380
|
-
f"(액션: {file_info.get('action', '')})")
|
381
361
|
return True
|
382
362
|
|
383
|
-
logger.debug(f"파일 '{file_path}'가 체인지 리스트 {change_list_number}에 없음")
|
384
363
|
return False
|
385
364
|
|
386
365
|
except P4Exception as e:
|
@@ -401,7 +380,7 @@ class Perforce:
|
|
401
380
|
"""
|
402
381
|
if not self._is_connected():
|
403
382
|
return {}
|
404
|
-
|
383
|
+
self.clear_last_error()
|
405
384
|
try:
|
406
385
|
if description is not None:
|
407
386
|
change_spec = self.p4.fetch_change(change_list_number)
|
@@ -409,13 +388,11 @@ class Perforce:
|
|
409
388
|
if current_description != description.strip():
|
410
389
|
change_spec['Description'] = description
|
411
390
|
self.p4.save_change(change_spec)
|
412
|
-
logger.info(f"체인지 리스트 {change_list_number} 설명 변경 완료: '{description}'")
|
413
391
|
|
414
392
|
if add_file_paths:
|
415
393
|
for file_path in add_file_paths:
|
416
394
|
try:
|
417
395
|
self.p4.run_reopen("-c", change_list_number, file_path)
|
418
|
-
logger.info(f"파일 '{file_path}'를 체인지 리스트 {change_list_number}로 이동 완료.")
|
419
396
|
except P4Exception as e_reopen:
|
420
397
|
self._handle_p4_exception(e_reopen, f"파일 '{file_path}'을 CL {change_list_number}로 이동")
|
421
398
|
|
@@ -423,7 +400,6 @@ class Perforce:
|
|
423
400
|
for file_path in remove_file_paths:
|
424
401
|
try:
|
425
402
|
self.p4.run_revert("-c", change_list_number, file_path)
|
426
|
-
logger.info(f"파일 '{file_path}'를 체인지 리스트 {change_list_number}에서 제거(revert) 완료.")
|
427
403
|
except P4Exception as e_revert:
|
428
404
|
self._handle_p4_exception(e_revert, f"파일 '{file_path}'을 CL {change_list_number}에서 제거(revert)")
|
429
405
|
|
@@ -447,7 +423,7 @@ class Perforce:
|
|
447
423
|
"""
|
448
424
|
if not self._is_connected():
|
449
425
|
return False
|
450
|
-
|
426
|
+
self.clear_last_error()
|
451
427
|
try:
|
452
428
|
if command == "edit":
|
453
429
|
self.p4.run_edit("-c", change_list_number, file_path)
|
@@ -456,9 +432,8 @@ class Perforce:
|
|
456
432
|
elif command == "delete":
|
457
433
|
self.p4.run_delete("-c", change_list_number, file_path)
|
458
434
|
else:
|
459
|
-
|
435
|
+
self.last_error = f"지원되지 않는 파일 작업: {command}"
|
460
436
|
return False
|
461
|
-
logger.info(f"파일 '{file_path}'에 대한 '{op_name}' 작업 성공 (CL: {change_list_number}).")
|
462
437
|
return True
|
463
438
|
except P4Exception as e:
|
464
439
|
self._handle_p4_exception(e, f"파일 '{file_path}' {op_name} (CL: {change_list_number})")
|
@@ -487,23 +462,20 @@ class Perforce:
|
|
487
462
|
bool: 모든 파일 체크아웃 성공 시 True, 하나라도 실패 시 False
|
488
463
|
"""
|
489
464
|
if not file_paths:
|
490
|
-
logger.debug("체크아웃할 파일 목록이 비어있습니다.")
|
491
465
|
return True
|
492
|
-
|
493
|
-
logger.info(f"체인지 리스트 {change_list_number}에 {len(file_paths)}개 파일 체크아웃 시도...")
|
494
466
|
|
467
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
468
|
+
if not isinstance(file_paths, list):
|
469
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 checkout_file() 메서드를 사용하세요."
|
470
|
+
self.last_error = error_msg
|
471
|
+
raise TypeError(error_msg)
|
472
|
+
|
495
473
|
all_success = True
|
496
474
|
for file_path in file_paths:
|
497
475
|
success = self.checkout_file(file_path, change_list_number)
|
498
476
|
if not success:
|
499
477
|
all_success = False
|
500
|
-
logger.warning(f"파일 '{file_path}' 체크아웃 실패")
|
501
478
|
|
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
479
|
return all_success
|
508
480
|
|
509
481
|
def add_file(self, file_path: str, change_list_number: int) -> bool:
|
@@ -529,23 +501,20 @@ class Perforce:
|
|
529
501
|
bool: 모든 파일 추가 성공 시 True, 하나라도 실패 시 False
|
530
502
|
"""
|
531
503
|
if not file_paths:
|
532
|
-
logger.debug("추가할 파일 목록이 비어있습니다.")
|
533
504
|
return True
|
534
|
-
|
535
|
-
logger.info(f"체인지 리스트 {change_list_number}에 {len(file_paths)}개 파일 추가 시도...")
|
536
505
|
|
506
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
507
|
+
if not isinstance(file_paths, list):
|
508
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 add_file() 메서드를 사용하세요."
|
509
|
+
self.last_error = error_msg
|
510
|
+
raise TypeError(error_msg)
|
511
|
+
|
537
512
|
all_success = True
|
538
513
|
for file_path in file_paths:
|
539
514
|
success = self.add_file(file_path, change_list_number)
|
540
515
|
if not success:
|
541
516
|
all_success = False
|
542
|
-
logger.warning(f"파일 '{file_path}' 추가 실패")
|
543
517
|
|
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
518
|
return all_success
|
550
519
|
|
551
520
|
def delete_file(self, file_path: str, change_list_number: int) -> bool:
|
@@ -571,23 +540,20 @@ class Perforce:
|
|
571
540
|
bool: 모든 파일 삭제 성공 시 True, 하나라도 실패 시 False
|
572
541
|
"""
|
573
542
|
if not file_paths:
|
574
|
-
logger.debug("삭제할 파일 목록이 비어있습니다.")
|
575
543
|
return True
|
576
|
-
|
577
|
-
logger.info(f"체인지 리스트 {change_list_number}에서 {len(file_paths)}개 파일 삭제 시도...")
|
578
544
|
|
545
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
546
|
+
if not isinstance(file_paths, list):
|
547
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 delete_file() 메서드를 사용하세요."
|
548
|
+
self.last_error = error_msg
|
549
|
+
raise TypeError(error_msg)
|
550
|
+
|
579
551
|
all_success = True
|
580
552
|
for file_path in file_paths:
|
581
553
|
success = self.delete_file(file_path, change_list_number)
|
582
554
|
if not success:
|
583
555
|
all_success = False
|
584
|
-
logger.warning(f"파일 '{file_path}' 삭제 실패")
|
585
556
|
|
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
557
|
return all_success
|
592
558
|
|
593
559
|
def submit_change_list(self, change_list_number: int, auto_revert_unchanged: bool = True) -> bool:
|
@@ -603,17 +569,16 @@ class Perforce:
|
|
603
569
|
"""
|
604
570
|
if not self._is_connected():
|
605
571
|
return False
|
606
|
-
|
572
|
+
self.clear_last_error()
|
607
573
|
|
608
574
|
submit_success = False
|
609
575
|
try:
|
610
576
|
self.p4.run_submit("-c", change_list_number)
|
611
|
-
logger.info(f"체인지 리스트 {change_list_number} 제출 성공.")
|
612
577
|
submit_success = True
|
613
578
|
except P4Exception as e:
|
614
|
-
self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 제출")
|
579
|
+
self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 제출 실패")
|
615
580
|
if any("nothing to submit" in err.lower() for err in self.p4.errors):
|
616
|
-
|
581
|
+
self.last_error = f"체인지 리스트 {change_list_number}에 제출할 파일이 없습니다."
|
617
582
|
submit_success = False
|
618
583
|
|
619
584
|
# 제출 성공 여부와 관계없이 후속 작업 실행
|
@@ -626,7 +591,8 @@ class Perforce:
|
|
626
591
|
# 빈 체인지 리스트 삭제
|
627
592
|
self.delete_empty_change_list(change_list_number)
|
628
593
|
except Exception as e:
|
629
|
-
|
594
|
+
if not self.last_error: # 기존 에러가 없는 경우만 설정
|
595
|
+
self.last_error = f"체인지 리스트 {change_list_number} 제출 후 후속 작업 중 오류 발생: {e}"
|
630
596
|
|
631
597
|
return submit_success
|
632
598
|
|
@@ -636,13 +602,11 @@ class Perforce:
|
|
636
602
|
Args:
|
637
603
|
change_list_number (int): 체인지 리스트 번호
|
638
604
|
"""
|
639
|
-
logger.debug(f"체인지 리스트 {change_list_number}에서 변경사항이 없는 파일들 자동 리버트 시도...")
|
640
605
|
try:
|
641
606
|
# 체인지 리스트에서 체크아웃된 파일들 가져오기
|
642
607
|
opened_files = self.p4.run_opened("-c", change_list_number)
|
643
608
|
|
644
609
|
if not opened_files:
|
645
|
-
logger.debug(f"체인지 리스트 {change_list_number}에 체크아웃된 파일이 없습니다.")
|
646
610
|
return
|
647
611
|
|
648
612
|
unchanged_files = []
|
@@ -659,45 +623,29 @@ class Perforce:
|
|
659
623
|
# diff 결과가 비어있으면 변경사항이 없음
|
660
624
|
if not diff_result:
|
661
625
|
unchanged_files.append(file_path)
|
662
|
-
logger.debug(f"파일 '{file_path}'에 변경사항이 없어 리버트 대상으로 추가")
|
663
|
-
else:
|
664
|
-
logger.debug(f"파일 '{file_path}'에 변경사항이 있어 리버트하지 않음")
|
665
626
|
|
666
|
-
except P4Exception
|
627
|
+
except P4Exception:
|
667
628
|
# diff 명령 실패 시에도 리버트 대상으로 추가 (안전하게 처리)
|
668
629
|
unchanged_files.append(file_path)
|
669
|
-
logger.debug(f"파일 '{file_path}' diff 확인 실패, 리버트 대상으로 추가: {e}")
|
670
|
-
else:
|
671
|
-
logger.debug(f"파일 '{file_path}'는 {action} 액션이므로 리버트하지 않음")
|
672
630
|
|
673
631
|
# 변경사항이 없는 파일들을 리버트
|
674
632
|
if unchanged_files:
|
675
|
-
logger.info(f"체인지 리스트 {change_list_number}에서 변경사항이 없는 파일 {len(unchanged_files)}개 자동 리버트 시도...")
|
676
633
|
for file_path in unchanged_files:
|
677
634
|
try:
|
678
635
|
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()
|
636
|
+
except P4Exception:
|
637
|
+
pass # 개별 파일 리버트 실패는 무시
|
688
638
|
|
689
|
-
except P4Exception
|
690
|
-
|
639
|
+
except P4Exception:
|
640
|
+
pass # 자동 리버트 실패는 무시
|
691
641
|
|
692
642
|
def _auto_revert_unchanged_files_in_default_changelist(self) -> None:
|
693
643
|
"""default change list에서 변경사항이 없는 체크아웃된 파일들을 자동으로 리버트합니다."""
|
694
|
-
logger.debug("default change list에서 변경사항이 없는 파일들 자동 리버트 시도...")
|
695
644
|
try:
|
696
645
|
# get_default_change_list를 사용해서 default change list의 파일들 가져오기
|
697
646
|
default_cl_info = self.get_default_change_list()
|
698
647
|
|
699
648
|
if not default_cl_info or not default_cl_info.get('Files'):
|
700
|
-
logger.debug("default change list에 체크아웃된 파일이 없습니다.")
|
701
649
|
return
|
702
650
|
|
703
651
|
files_list = default_cl_info.get('Files', [])
|
@@ -711,30 +659,21 @@ class Perforce:
|
|
711
659
|
# diff 결과가 비어있으면 변경사항이 없음
|
712
660
|
if not diff_result:
|
713
661
|
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
662
|
|
718
|
-
except P4Exception
|
663
|
+
except P4Exception:
|
719
664
|
# diff 명령 실패 시에도 리버트 대상으로 추가 (안전하게 처리)
|
720
665
|
unchanged_files.append(file_path)
|
721
|
-
logger.debug(f"default change list의 파일 '{file_path}' diff 확인 실패, 리버트 대상으로 추가: {e}")
|
722
666
|
|
723
667
|
# 변경사항이 없는 파일들을 리버트
|
724
668
|
if unchanged_files:
|
725
|
-
logger.info(f"default change list에서 변경사항이 없는 파일 {len(unchanged_files)}개 자동 리버트 시도...")
|
726
669
|
for file_path in unchanged_files:
|
727
670
|
try:
|
728
671
|
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에서 변경사항이 없는 파일이 없습니다.")
|
672
|
+
except P4Exception:
|
673
|
+
pass # 개별 파일 리버트 실패는 무시
|
735
674
|
|
736
|
-
except P4Exception
|
737
|
-
|
675
|
+
except P4Exception:
|
676
|
+
pass # 자동 리버트 실패는 무시
|
738
677
|
|
739
678
|
def revert_change_list(self, change_list_number: int) -> bool:
|
740
679
|
"""체인지 리스트를 되돌리고 삭제합니다.
|
@@ -749,24 +688,21 @@ class Perforce:
|
|
749
688
|
"""
|
750
689
|
if not self._is_connected():
|
751
690
|
return False
|
752
|
-
|
691
|
+
self.clear_last_error()
|
753
692
|
try:
|
754
693
|
# 체인지 리스트의 모든 파일 되돌리기
|
755
694
|
self.p4.run_revert("-c", change_list_number, "//...")
|
756
|
-
logger.info(f"체인지 리스트 {change_list_number} 전체 되돌리기 성공.")
|
757
695
|
|
758
696
|
# 빈 체인지 리스트 삭제
|
759
697
|
try:
|
760
698
|
self.p4.run_change("-d", change_list_number)
|
761
|
-
logger.info(f"체인지 리스트 {change_list_number} 삭제 완료.")
|
762
699
|
except P4Exception as e_delete:
|
763
700
|
self._handle_p4_exception(e_delete, f"체인지 리스트 {change_list_number} 삭제")
|
764
|
-
logger.warning(f"파일 되돌리기는 성공했으나 체인지 리스트 {change_list_number} 삭제에 실패했습니다.")
|
765
701
|
return False
|
766
702
|
|
767
703
|
return True
|
768
704
|
except P4Exception as e:
|
769
|
-
self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 전체 되돌리기")
|
705
|
+
self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 전체 되돌리기 실패")
|
770
706
|
return False
|
771
707
|
|
772
708
|
def delete_empty_change_list(self, change_list_number: int) -> bool:
|
@@ -781,22 +717,21 @@ class Perforce:
|
|
781
717
|
if not self._is_connected():
|
782
718
|
return False
|
783
719
|
|
784
|
-
|
720
|
+
self.clear_last_error()
|
785
721
|
try:
|
786
722
|
# 체인지 리스트 정보 가져오기
|
787
723
|
change_spec = self.p4.fetch_change(change_list_number)
|
788
724
|
|
789
725
|
# 파일이 있는지 확인
|
790
726
|
if change_spec and change_spec.get('Files') and len(change_spec['Files']) > 0:
|
791
|
-
|
727
|
+
self.last_error = f"체인지 리스트 {change_list_number}에 파일이 {len(change_spec['Files'])}개 있어 삭제할 수 없습니다."
|
792
728
|
return False
|
793
729
|
|
794
730
|
# 빈 체인지 리스트 삭제
|
795
731
|
self.p4.run_change("-d", change_list_number)
|
796
|
-
logger.info(f"빈 체인지 리스트 {change_list_number} 삭제 완료.")
|
797
732
|
return True
|
798
733
|
except P4Exception as e:
|
799
|
-
self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 삭제")
|
734
|
+
self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 삭제 실패")
|
800
735
|
return False
|
801
736
|
|
802
737
|
def revert_file(self, file_path: str, change_list_number: int) -> bool:
|
@@ -812,10 +747,9 @@ class Perforce:
|
|
812
747
|
if not self._is_connected():
|
813
748
|
return False
|
814
749
|
|
815
|
-
|
750
|
+
self.clear_last_error()
|
816
751
|
try:
|
817
752
|
self.p4.run_revert("-c", change_list_number, file_path)
|
818
|
-
logger.info(f"파일 '{file_path}'를 체인지 리스트 {change_list_number}에서 되돌리기 성공.")
|
819
753
|
return True
|
820
754
|
except P4Exception as e:
|
821
755
|
self._handle_p4_exception(e, f"파일 '{file_path}'를 체인지 리스트 {change_list_number}에서 되돌리기")
|
@@ -834,23 +768,20 @@ class Perforce:
|
|
834
768
|
if not self._is_connected():
|
835
769
|
return False
|
836
770
|
if not file_paths:
|
837
|
-
logger.warning("되돌릴 파일 목록이 비어있습니다.")
|
838
771
|
return True
|
839
|
-
|
840
|
-
logger.info(f"체인지 리스트 {change_list_number}에서 {len(file_paths)}개 파일 되돌리기 시도...")
|
841
772
|
|
773
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
774
|
+
if not isinstance(file_paths, list):
|
775
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 revert_file() 메서드를 사용하세요."
|
776
|
+
self.last_error = error_msg
|
777
|
+
raise TypeError(error_msg)
|
778
|
+
|
842
779
|
all_success = True
|
843
780
|
for file_path in file_paths:
|
844
781
|
success = self.revert_file(file_path, change_list_number)
|
845
782
|
if not success:
|
846
783
|
all_success = False
|
847
|
-
logger.warning(f"파일 '{file_path}' 되돌리기 실패")
|
848
784
|
|
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
785
|
return all_success
|
855
786
|
|
856
787
|
def check_update_required(self, file_paths: list) -> bool:
|
@@ -866,20 +797,24 @@ class Perforce:
|
|
866
797
|
if not self._is_connected():
|
867
798
|
return False
|
868
799
|
if not file_paths:
|
869
|
-
logger.debug("업데이트 필요 여부 확인할 파일/폴더 목록이 비어있습니다.")
|
870
800
|
return False
|
871
801
|
|
802
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
803
|
+
if not isinstance(file_paths, list):
|
804
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 경로도 리스트로 감싸서 전달하세요: ['{file_paths}']"
|
805
|
+
self.last_error = error_msg
|
806
|
+
raise TypeError(error_msg)
|
807
|
+
|
872
808
|
# 폴더 경로에 재귀적 와일드카드 패턴을 추가
|
873
809
|
processed_paths = []
|
874
810
|
for path in file_paths:
|
875
811
|
if os.path.isdir(path):
|
876
812
|
# 폴더 경로에 '...'(재귀) 패턴을 추가
|
877
813
|
processed_paths.append(os.path.join(path, '...'))
|
878
|
-
logger.debug(f"폴더 경로를 재귀 패턴으로 변환: {path} -> {os.path.join(path, '...')}")
|
879
814
|
else:
|
880
815
|
processed_paths.append(path)
|
881
816
|
|
882
|
-
|
817
|
+
self.clear_last_error()
|
883
818
|
try:
|
884
819
|
sync_preview_results = self.p4.run_sync("-n", processed_paths)
|
885
820
|
needs_update = False
|
@@ -889,26 +824,30 @@ class Perforce:
|
|
889
824
|
'no such file(s)' not in result.get('depotFile', ''):
|
890
825
|
if result.get('how') and 'syncing' in result.get('how'):
|
891
826
|
needs_update = True
|
892
|
-
logger.info(f"파일 '{result.get('clientFile', result.get('depotFile'))}' 업데이트 필요: {result.get('how')}")
|
893
827
|
break
|
894
828
|
elif result.get('action') and result.get('action') not in ['checked', 'exists']:
|
895
829
|
needs_update = True
|
896
|
-
logger.info(f"파일 '{result.get('clientFile', result.get('depotFile'))}' 업데이트 필요 (action: {result.get('action')})")
|
897
830
|
break
|
898
831
|
elif isinstance(result, str):
|
899
832
|
if "up-to-date" not in result and "no such file(s)" not in result:
|
900
833
|
needs_update = True
|
901
|
-
logger.info(f"파일 업데이트 필요 (문자열 결과): {result}")
|
902
834
|
break
|
903
835
|
|
904
|
-
if needs_update:
|
905
|
-
logger.info(f"지정된 파일/폴더 중 업데이트가 필요한 파일이 있습니다.")
|
906
|
-
else:
|
907
|
-
logger.info(f"지정된 모든 파일/폴더가 최신 상태입니다.")
|
908
836
|
return needs_update
|
909
837
|
except P4Exception as e:
|
910
|
-
|
911
|
-
|
838
|
+
# "up-to-date" 메시지는 정상적인 응답이므로 에러로 처리하지 않음
|
839
|
+
exception_str = str(e)
|
840
|
+
error_messages = [str(err) for err in self.p4.errors]
|
841
|
+
warning_messages = [str(warn) for warn in self.p4.warnings]
|
842
|
+
|
843
|
+
# P4Exception 자체 메시지나 에러/경고 메시지에서 "up-to-date" 확인
|
844
|
+
if ("up-to-date" in exception_str or
|
845
|
+
any("up-to-date" in msg for msg in error_messages) or
|
846
|
+
any("up-to-date" in msg for msg in warning_messages)):
|
847
|
+
return False
|
848
|
+
else:
|
849
|
+
self._handle_p4_exception(e, f"파일/폴더 업데이트 필요 여부 확인 ({processed_paths})")
|
850
|
+
return False
|
912
851
|
|
913
852
|
def is_file_in_perforce(self, file_path: str) -> bool:
|
914
853
|
"""파일이 Perforce에 속하는지 확인합니다.
|
@@ -922,23 +861,20 @@ class Perforce:
|
|
922
861
|
if not self._is_connected():
|
923
862
|
return False
|
924
863
|
|
925
|
-
|
864
|
+
self.clear_last_error()
|
926
865
|
try:
|
927
866
|
# p4 files 명령으로 파일 정보 조회
|
928
867
|
file_info = self.p4.run_files(file_path)
|
929
868
|
|
930
869
|
# 파일 정보가 있고, 'no such file(s)' 오류가 없는 경우
|
931
870
|
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
871
|
return True
|
934
872
|
else:
|
935
|
-
logger.info(f"파일 '{file_path}'가 Perforce에 존재하지 않습니다.")
|
936
873
|
return False
|
937
874
|
|
938
875
|
except P4Exception as e:
|
939
|
-
# 파일이 존재하지 않는 경우는 일반적인 상황이므로
|
876
|
+
# 파일이 존재하지 않는 경우는 일반적인 상황이므로 False 반환
|
940
877
|
if any("no such file(s)" in err.lower() for err in self.p4.errors):
|
941
|
-
logger.info(f"파일 '{file_path}'가 Perforce에 존재하지 않습니다.")
|
942
878
|
return False
|
943
879
|
else:
|
944
880
|
self._handle_p4_exception(e, f"파일 '{file_path}' Perforce 존재 여부 확인")
|
@@ -949,7 +885,7 @@ class Perforce:
|
|
949
885
|
|
950
886
|
Args:
|
951
887
|
file_paths (list): 동기화할 파일 또는 폴더 경로 리스트.
|
952
|
-
|
888
|
+
폴더 경로는 자동으로 재귀적으로 처리됩니다.
|
953
889
|
|
954
890
|
Returns:
|
955
891
|
bool: 동기화 성공 시 True, 실패 시 False
|
@@ -957,43 +893,55 @@ class Perforce:
|
|
957
893
|
if not self._is_connected():
|
958
894
|
return False
|
959
895
|
if not file_paths:
|
960
|
-
logger.debug("싱크할 파일/폴더 목록이 비어있습니다.")
|
961
896
|
return True
|
962
897
|
|
898
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
899
|
+
if not isinstance(file_paths, list):
|
900
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 경로도 리스트로 감싸서 전달하세요: ['{file_paths}']"
|
901
|
+
self.last_error = error_msg
|
902
|
+
raise TypeError(error_msg)
|
903
|
+
|
963
904
|
# 폴더 경로에 재귀적 와일드카드 패턴을 추가
|
964
905
|
processed_paths = []
|
965
906
|
for path in file_paths:
|
966
907
|
if os.path.isdir(path):
|
967
908
|
# 폴더 경로에 '...'(재귀) 패턴을 추가
|
968
909
|
processed_paths.append(os.path.join(path, '...'))
|
969
|
-
logger.debug(f"폴더 경로를 재귀 패턴으로 변환: {path} -> {os.path.join(path, '...')}")
|
970
910
|
else:
|
971
911
|
processed_paths.append(path)
|
972
912
|
|
973
|
-
|
913
|
+
self.clear_last_error()
|
974
914
|
try:
|
975
915
|
self.p4.run_sync(processed_paths)
|
976
|
-
logger.info(f"파일/폴더 싱크 완료: {processed_paths}")
|
977
916
|
return True
|
978
917
|
except P4Exception as e:
|
979
|
-
self._handle_p4_exception(e, f"파일/폴더 싱크 ({processed_paths})")
|
918
|
+
self._handle_p4_exception(e, f"파일/폴더 싱크 실패 ({processed_paths})")
|
980
919
|
return False
|
981
920
|
|
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 서버에 이미 연결되지 않은 상태입니다.")
|
921
|
+
def get_default_change_list(self) -> dict:
|
922
|
+
"""default change list의 정보를 가져옵니다.
|
993
923
|
|
994
|
-
|
995
|
-
|
996
|
-
|
924
|
+
Returns:
|
925
|
+
dict: get_change_list_by_number와 동일한 형태의 딕셔너리
|
926
|
+
"""
|
927
|
+
if not self._is_connected():
|
928
|
+
return {}
|
929
|
+
self.clear_last_error()
|
930
|
+
try:
|
931
|
+
opened_files = self.p4.run_opened("-c", "default")
|
932
|
+
files_list = [f.get('clientFile', '') for f in opened_files]
|
933
|
+
result = {
|
934
|
+
'Change': 'default',
|
935
|
+
'Description': 'Default change',
|
936
|
+
'User': getattr(self.p4, 'user', ''),
|
937
|
+
'Client': getattr(self.p4, 'client', ''),
|
938
|
+
'Status': 'pending',
|
939
|
+
'Files': files_list
|
940
|
+
}
|
941
|
+
return result
|
942
|
+
except P4Exception as e:
|
943
|
+
self._handle_p4_exception(e, "default change list 정보 조회 실패")
|
944
|
+
return {}
|
997
945
|
|
998
946
|
def check_files_checked_out_all_users(self, file_paths: list) -> dict:
|
999
947
|
"""파일들의 체크아웃 상태를 모든 사용자/워크스페이스에서 확인합니다.
|
@@ -1016,11 +964,15 @@ class Perforce:
|
|
1016
964
|
if not self._is_connected():
|
1017
965
|
return {}
|
1018
966
|
if not file_paths:
|
1019
|
-
logger.debug("체크아웃 상태 확인할 파일 목록이 비어있습니다.")
|
1020
967
|
return {}
|
1021
968
|
|
1022
|
-
|
969
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
970
|
+
if not isinstance(file_paths, list):
|
971
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 get_file_checkout_info_all_users() 메서드를 사용하세요."
|
972
|
+
self.last_error = error_msg
|
973
|
+
raise TypeError(error_msg)
|
1023
974
|
|
975
|
+
self.clear_last_error()
|
1024
976
|
result = {}
|
1025
977
|
try:
|
1026
978
|
# 각 파일의 상태 확인
|
@@ -1046,26 +998,14 @@ class Perforce:
|
|
1046
998
|
file_status['user'] = file_info.get('user', '')
|
1047
999
|
file_status['client'] = file_info.get('client', '')
|
1048
1000
|
|
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
1001
|
except P4Exception as e:
|
1057
1002
|
# 파일이 perforce에 없거나 접근할 수 없는 경우
|
1058
|
-
if any("not opened" in err.lower() or "no such file" in err.lower()
|
1059
|
-
|
1060
|
-
logger.debug(f"파일 '{file_path}' 체크아웃되지 않음 (perforce에 없거나 접근 불가)")
|
1061
|
-
else:
|
1003
|
+
if not any("not opened" in err.lower() or "no such file" in err.lower()
|
1004
|
+
for err in self.p4.errors):
|
1062
1005
|
self._handle_p4_exception(e, f"파일 '{file_path}' 체크아웃 상태 확인 (모든 사용자)")
|
1063
1006
|
|
1064
1007
|
result[file_path] = file_status
|
1065
1008
|
|
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
1009
|
return result
|
1070
1010
|
|
1071
1011
|
except P4Exception as e:
|
@@ -1156,6 +1096,12 @@ class Perforce:
|
|
1156
1096
|
if not file_paths:
|
1157
1097
|
return []
|
1158
1098
|
|
1099
|
+
# 타입 검증: 리스트가 아닌 경우 에러 발생
|
1100
|
+
if not isinstance(file_paths, list):
|
1101
|
+
error_msg = f"file_paths는 리스트여야 합니다. 전달된 타입: {type(file_paths).__name__}. 단일 파일은 is_file_checked_out_by_others() 메서드를 사용하세요."
|
1102
|
+
self.last_error = error_msg
|
1103
|
+
raise TypeError(error_msg)
|
1104
|
+
|
1159
1105
|
result = self.check_files_checked_out_all_users(file_paths)
|
1160
1106
|
files_by_others = []
|
1161
1107
|
|
@@ -1177,31 +1123,4 @@ class Perforce:
|
|
1177
1123
|
'action': status.get('action', '')
|
1178
1124
|
})
|
1179
1125
|
|
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 {}
|
1126
|
+
return files_by_others
|