pyjallib 0.1.15__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/max/skeleton.py +1 -8
- pyjallib/naming.py +4 -1
- pyjallib/perforce.py +213 -262
- 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.15.dist-info → pyjallib-0.1.17.dist-info}/METADATA +1 -1
- {pyjallib-0.1.15.dist-info → pyjallib-0.1.17.dist-info}/RECORD +25 -8
- {pyjallib-0.1.15.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,21 +569,32 @@ class Perforce:
|
|
603
569
|
"""
|
604
570
|
if not self._is_connected():
|
605
571
|
return False
|
606
|
-
|
572
|
+
self.clear_last_error()
|
573
|
+
|
574
|
+
submit_success = False
|
607
575
|
try:
|
608
576
|
self.p4.run_submit("-c", change_list_number)
|
609
|
-
|
610
|
-
|
577
|
+
submit_success = True
|
578
|
+
except P4Exception as e:
|
579
|
+
self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 제출 실패")
|
580
|
+
if any("nothing to submit" in err.lower() for err in self.p4.errors):
|
581
|
+
self.last_error = f"체인지 리스트 {change_list_number}에 제출할 파일이 없습니다."
|
582
|
+
submit_success = False
|
583
|
+
|
584
|
+
# 제출 성공 여부와 관계없이 후속 작업 실행
|
585
|
+
try:
|
611
586
|
# 제출 후 변경사항이 없는 체크아웃된 파일들을 자동으로 리버트
|
612
587
|
if auto_revert_unchanged:
|
613
588
|
self._auto_revert_unchanged_files(change_list_number)
|
589
|
+
self._auto_revert_unchanged_files_in_default_changelist()
|
614
590
|
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
if
|
619
|
-
|
620
|
-
|
591
|
+
# 빈 체인지 리스트 삭제
|
592
|
+
self.delete_empty_change_list(change_list_number)
|
593
|
+
except Exception as e:
|
594
|
+
if not self.last_error: # 기존 에러가 없는 경우만 설정
|
595
|
+
self.last_error = f"체인지 리스트 {change_list_number} 제출 후 후속 작업 중 오류 발생: {e}"
|
596
|
+
|
597
|
+
return submit_success
|
621
598
|
|
622
599
|
def _auto_revert_unchanged_files(self, change_list_number: int) -> None:
|
623
600
|
"""제출 후 변경사항이 없는 체크아웃된 파일들을 자동으로 리버트합니다.
|
@@ -625,13 +602,11 @@ class Perforce:
|
|
625
602
|
Args:
|
626
603
|
change_list_number (int): 체인지 리스트 번호
|
627
604
|
"""
|
628
|
-
logger.debug(f"체인지 리스트 {change_list_number}에서 변경사항이 없는 파일들 자동 리버트 시도...")
|
629
605
|
try:
|
630
606
|
# 체인지 리스트에서 체크아웃된 파일들 가져오기
|
631
607
|
opened_files = self.p4.run_opened("-c", change_list_number)
|
632
608
|
|
633
609
|
if not opened_files:
|
634
|
-
logger.debug(f"체인지 리스트 {change_list_number}에 체크아웃된 파일이 없습니다.")
|
635
610
|
return
|
636
611
|
|
637
612
|
unchanged_files = []
|
@@ -648,87 +623,57 @@ class Perforce:
|
|
648
623
|
# diff 결과가 비어있으면 변경사항이 없음
|
649
624
|
if not diff_result:
|
650
625
|
unchanged_files.append(file_path)
|
651
|
-
logger.debug(f"파일 '{file_path}'에 변경사항이 없어 리버트 대상으로 추가")
|
652
|
-
else:
|
653
|
-
logger.debug(f"파일 '{file_path}'에 변경사항이 있어 리버트하지 않음")
|
654
626
|
|
655
|
-
except P4Exception
|
627
|
+
except P4Exception:
|
656
628
|
# diff 명령 실패 시에도 리버트 대상으로 추가 (안전하게 처리)
|
657
629
|
unchanged_files.append(file_path)
|
658
|
-
logger.debug(f"파일 '{file_path}' diff 확인 실패, 리버트 대상으로 추가: {e}")
|
659
|
-
else:
|
660
|
-
logger.debug(f"파일 '{file_path}'는 {action} 액션이므로 리버트하지 않음")
|
661
630
|
|
662
631
|
# 변경사항이 없는 파일들을 리버트
|
663
632
|
if unchanged_files:
|
664
|
-
logger.info(f"체인지 리스트 {change_list_number}에서 변경사항이 없는 파일 {len(unchanged_files)}개 자동 리버트 시도...")
|
665
633
|
for file_path in unchanged_files:
|
666
634
|
try:
|
667
635
|
self.p4.run_revert("-c", change_list_number, file_path)
|
668
|
-
|
669
|
-
|
670
|
-
self._handle_p4_exception(e, f"파일 '{file_path}' 자동 리버트")
|
671
|
-
logger.info(f"체인지 리스트 {change_list_number}에서 변경사항이 없는 파일 {len(unchanged_files)}개 자동 리버트 완료")
|
672
|
-
else:
|
673
|
-
logger.debug(f"체인지 리스트 {change_list_number}에서 변경사항이 없는 파일이 없습니다.")
|
674
|
-
|
675
|
-
# default change list에서도 변경사항이 없는 파일들 처리
|
676
|
-
self._auto_revert_unchanged_files_in_default_changelist()
|
636
|
+
except P4Exception:
|
637
|
+
pass # 개별 파일 리버트 실패는 무시
|
677
638
|
|
678
|
-
except P4Exception
|
679
|
-
|
639
|
+
except P4Exception:
|
640
|
+
pass # 자동 리버트 실패는 무시
|
680
641
|
|
681
642
|
def _auto_revert_unchanged_files_in_default_changelist(self) -> None:
|
682
643
|
"""default change list에서 변경사항이 없는 체크아웃된 파일들을 자동으로 리버트합니다."""
|
683
|
-
logger.debug("default change list에서 변경사항이 없는 파일들 자동 리버트 시도...")
|
684
644
|
try:
|
685
|
-
# default change list
|
686
|
-
|
645
|
+
# get_default_change_list를 사용해서 default change list의 파일들 가져오기
|
646
|
+
default_cl_info = self.get_default_change_list()
|
687
647
|
|
688
|
-
if not
|
689
|
-
logger.debug("default change list에 체크아웃된 파일이 없습니다.")
|
648
|
+
if not default_cl_info or not default_cl_info.get('Files'):
|
690
649
|
return
|
691
650
|
|
651
|
+
files_list = default_cl_info.get('Files', [])
|
692
652
|
unchanged_files = []
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
diff_result = self.p4.run_diff("-sa", file_path)
|
702
|
-
|
703
|
-
# diff 결과가 비어있으면 변경사항이 없음
|
704
|
-
if not diff_result:
|
705
|
-
unchanged_files.append(file_path)
|
706
|
-
logger.debug(f"default change list의 파일 '{file_path}'에 변경사항이 없어 리버트 대상으로 추가")
|
707
|
-
else:
|
708
|
-
logger.debug(f"default change list의 파일 '{file_path}'에 변경사항이 있어 리버트하지 않음")
|
709
|
-
|
710
|
-
except P4Exception as e:
|
711
|
-
# diff 명령 실패 시에도 리버트 대상으로 추가 (안전하게 처리)
|
653
|
+
|
654
|
+
for file_path in files_list:
|
655
|
+
try:
|
656
|
+
# p4 diff 명령으로 파일의 변경사항 확인
|
657
|
+
diff_result = self.p4.run_diff("-sa", file_path)
|
658
|
+
|
659
|
+
# diff 결과가 비어있으면 변경사항이 없음
|
660
|
+
if not diff_result:
|
712
661
|
unchanged_files.append(file_path)
|
713
|
-
|
714
|
-
|
715
|
-
|
662
|
+
|
663
|
+
except P4Exception:
|
664
|
+
# diff 명령 실패 시에도 리버트 대상으로 추가 (안전하게 처리)
|
665
|
+
unchanged_files.append(file_path)
|
716
666
|
|
717
667
|
# 변경사항이 없는 파일들을 리버트
|
718
668
|
if unchanged_files:
|
719
|
-
logger.info(f"default change list에서 변경사항이 없는 파일 {len(unchanged_files)}개 자동 리버트 시도...")
|
720
669
|
for file_path in unchanged_files:
|
721
670
|
try:
|
722
671
|
self.p4.run_revert(file_path)
|
723
|
-
|
724
|
-
|
725
|
-
self._handle_p4_exception(e, f"default change list의 파일 '{file_path}' 자동 리버트")
|
726
|
-
logger.info(f"default change list에서 변경사항이 없는 파일 {len(unchanged_files)}개 자동 리버트 완료")
|
727
|
-
else:
|
728
|
-
logger.debug("default change list에서 변경사항이 없는 파일이 없습니다.")
|
672
|
+
except P4Exception:
|
673
|
+
pass # 개별 파일 리버트 실패는 무시
|
729
674
|
|
730
|
-
except P4Exception
|
731
|
-
|
675
|
+
except P4Exception:
|
676
|
+
pass # 자동 리버트 실패는 무시
|
732
677
|
|
733
678
|
def revert_change_list(self, change_list_number: int) -> bool:
|
734
679
|
"""체인지 리스트를 되돌리고 삭제합니다.
|
@@ -743,24 +688,21 @@ class Perforce:
|
|
743
688
|
"""
|
744
689
|
if not self._is_connected():
|
745
690
|
return False
|
746
|
-
|
691
|
+
self.clear_last_error()
|
747
692
|
try:
|
748
693
|
# 체인지 리스트의 모든 파일 되돌리기
|
749
694
|
self.p4.run_revert("-c", change_list_number, "//...")
|
750
|
-
logger.info(f"체인지 리스트 {change_list_number} 전체 되돌리기 성공.")
|
751
695
|
|
752
696
|
# 빈 체인지 리스트 삭제
|
753
697
|
try:
|
754
698
|
self.p4.run_change("-d", change_list_number)
|
755
|
-
logger.info(f"체인지 리스트 {change_list_number} 삭제 완료.")
|
756
699
|
except P4Exception as e_delete:
|
757
700
|
self._handle_p4_exception(e_delete, f"체인지 리스트 {change_list_number} 삭제")
|
758
|
-
logger.warning(f"파일 되돌리기는 성공했으나 체인지 리스트 {change_list_number} 삭제에 실패했습니다.")
|
759
701
|
return False
|
760
702
|
|
761
703
|
return True
|
762
704
|
except P4Exception as e:
|
763
|
-
self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 전체 되돌리기")
|
705
|
+
self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 전체 되돌리기 실패")
|
764
706
|
return False
|
765
707
|
|
766
708
|
def delete_empty_change_list(self, change_list_number: int) -> bool:
|
@@ -775,22 +717,21 @@ class Perforce:
|
|
775
717
|
if not self._is_connected():
|
776
718
|
return False
|
777
719
|
|
778
|
-
|
720
|
+
self.clear_last_error()
|
779
721
|
try:
|
780
722
|
# 체인지 리스트 정보 가져오기
|
781
723
|
change_spec = self.p4.fetch_change(change_list_number)
|
782
724
|
|
783
725
|
# 파일이 있는지 확인
|
784
726
|
if change_spec and change_spec.get('Files') and len(change_spec['Files']) > 0:
|
785
|
-
|
727
|
+
self.last_error = f"체인지 리스트 {change_list_number}에 파일이 {len(change_spec['Files'])}개 있어 삭제할 수 없습니다."
|
786
728
|
return False
|
787
729
|
|
788
730
|
# 빈 체인지 리스트 삭제
|
789
731
|
self.p4.run_change("-d", change_list_number)
|
790
|
-
logger.info(f"빈 체인지 리스트 {change_list_number} 삭제 완료.")
|
791
732
|
return True
|
792
733
|
except P4Exception as e:
|
793
|
-
self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 삭제")
|
734
|
+
self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 삭제 실패")
|
794
735
|
return False
|
795
736
|
|
796
737
|
def revert_file(self, file_path: str, change_list_number: int) -> bool:
|
@@ -806,10 +747,9 @@ class Perforce:
|
|
806
747
|
if not self._is_connected():
|
807
748
|
return False
|
808
749
|
|
809
|
-
|
750
|
+
self.clear_last_error()
|
810
751
|
try:
|
811
752
|
self.p4.run_revert("-c", change_list_number, file_path)
|
812
|
-
logger.info(f"파일 '{file_path}'를 체인지 리스트 {change_list_number}에서 되돌리기 성공.")
|
813
753
|
return True
|
814
754
|
except P4Exception as e:
|
815
755
|
self._handle_p4_exception(e, f"파일 '{file_path}'를 체인지 리스트 {change_list_number}에서 되돌리기")
|
@@ -828,23 +768,20 @@ class Perforce:
|
|
828
768
|
if not self._is_connected():
|
829
769
|
return False
|
830
770
|
if not file_paths:
|
831
|
-
logger.warning("되돌릴 파일 목록이 비어있습니다.")
|
832
771
|
return True
|
833
|
-
|
834
|
-
logger.info(f"체인지 리스트 {change_list_number}에서 {len(file_paths)}개 파일 되돌리기 시도...")
|
835
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
|
+
|
836
779
|
all_success = True
|
837
780
|
for file_path in file_paths:
|
838
781
|
success = self.revert_file(file_path, change_list_number)
|
839
782
|
if not success:
|
840
783
|
all_success = False
|
841
|
-
logger.warning(f"파일 '{file_path}' 되돌리기 실패")
|
842
784
|
|
843
|
-
if all_success:
|
844
|
-
logger.info(f"모든 파일({len(file_paths)}개)을 체인지 리스트 {change_list_number}에서 성공적으로 되돌렸습니다.")
|
845
|
-
else:
|
846
|
-
logger.warning(f"일부 파일을 체인지 리스트 {change_list_number}에서 되돌리지 못했습니다.")
|
847
|
-
|
848
785
|
return all_success
|
849
786
|
|
850
787
|
def check_update_required(self, file_paths: list) -> bool:
|
@@ -860,20 +797,24 @@ class Perforce:
|
|
860
797
|
if not self._is_connected():
|
861
798
|
return False
|
862
799
|
if not file_paths:
|
863
|
-
logger.debug("업데이트 필요 여부 확인할 파일/폴더 목록이 비어있습니다.")
|
864
800
|
return False
|
865
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
|
+
|
866
808
|
# 폴더 경로에 재귀적 와일드카드 패턴을 추가
|
867
809
|
processed_paths = []
|
868
810
|
for path in file_paths:
|
869
811
|
if os.path.isdir(path):
|
870
812
|
# 폴더 경로에 '...'(재귀) 패턴을 추가
|
871
813
|
processed_paths.append(os.path.join(path, '...'))
|
872
|
-
logger.debug(f"폴더 경로를 재귀 패턴으로 변환: {path} -> {os.path.join(path, '...')}")
|
873
814
|
else:
|
874
815
|
processed_paths.append(path)
|
875
816
|
|
876
|
-
|
817
|
+
self.clear_last_error()
|
877
818
|
try:
|
878
819
|
sync_preview_results = self.p4.run_sync("-n", processed_paths)
|
879
820
|
needs_update = False
|
@@ -883,26 +824,30 @@ class Perforce:
|
|
883
824
|
'no such file(s)' not in result.get('depotFile', ''):
|
884
825
|
if result.get('how') and 'syncing' in result.get('how'):
|
885
826
|
needs_update = True
|
886
|
-
logger.info(f"파일 '{result.get('clientFile', result.get('depotFile'))}' 업데이트 필요: {result.get('how')}")
|
887
827
|
break
|
888
828
|
elif result.get('action') and result.get('action') not in ['checked', 'exists']:
|
889
829
|
needs_update = True
|
890
|
-
logger.info(f"파일 '{result.get('clientFile', result.get('depotFile'))}' 업데이트 필요 (action: {result.get('action')})")
|
891
830
|
break
|
892
831
|
elif isinstance(result, str):
|
893
832
|
if "up-to-date" not in result and "no such file(s)" not in result:
|
894
833
|
needs_update = True
|
895
|
-
logger.info(f"파일 업데이트 필요 (문자열 결과): {result}")
|
896
834
|
break
|
897
835
|
|
898
|
-
if needs_update:
|
899
|
-
logger.info(f"지정된 파일/폴더 중 업데이트가 필요한 파일이 있습니다.")
|
900
|
-
else:
|
901
|
-
logger.info(f"지정된 모든 파일/폴더가 최신 상태입니다.")
|
902
836
|
return needs_update
|
903
837
|
except P4Exception as e:
|
904
|
-
|
905
|
-
|
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
|
906
851
|
|
907
852
|
def is_file_in_perforce(self, file_path: str) -> bool:
|
908
853
|
"""파일이 Perforce에 속하는지 확인합니다.
|
@@ -916,23 +861,20 @@ class Perforce:
|
|
916
861
|
if not self._is_connected():
|
917
862
|
return False
|
918
863
|
|
919
|
-
|
864
|
+
self.clear_last_error()
|
920
865
|
try:
|
921
866
|
# p4 files 명령으로 파일 정보 조회
|
922
867
|
file_info = self.p4.run_files(file_path)
|
923
868
|
|
924
869
|
# 파일 정보가 있고, 'no such file(s)' 오류가 없는 경우
|
925
870
|
if file_info and not any("no such file(s)" in str(err).lower() for err in self.p4.errors):
|
926
|
-
logger.info(f"파일 '{file_path}'가 Perforce에 존재합니다.")
|
927
871
|
return True
|
928
872
|
else:
|
929
|
-
logger.info(f"파일 '{file_path}'가 Perforce에 존재하지 않습니다.")
|
930
873
|
return False
|
931
874
|
|
932
875
|
except P4Exception as e:
|
933
|
-
# 파일이 존재하지 않는 경우는 일반적인 상황이므로
|
876
|
+
# 파일이 존재하지 않는 경우는 일반적인 상황이므로 False 반환
|
934
877
|
if any("no such file(s)" in err.lower() for err in self.p4.errors):
|
935
|
-
logger.info(f"파일 '{file_path}'가 Perforce에 존재하지 않습니다.")
|
936
878
|
return False
|
937
879
|
else:
|
938
880
|
self._handle_p4_exception(e, f"파일 '{file_path}' Perforce 존재 여부 확인")
|
@@ -943,7 +885,7 @@ class Perforce:
|
|
943
885
|
|
944
886
|
Args:
|
945
887
|
file_paths (list): 동기화할 파일 또는 폴더 경로 리스트.
|
946
|
-
|
888
|
+
폴더 경로는 자동으로 재귀적으로 처리됩니다.
|
947
889
|
|
948
890
|
Returns:
|
949
891
|
bool: 동기화 성공 시 True, 실패 시 False
|
@@ -951,43 +893,55 @@ class Perforce:
|
|
951
893
|
if not self._is_connected():
|
952
894
|
return False
|
953
895
|
if not file_paths:
|
954
|
-
logger.debug("싱크할 파일/폴더 목록이 비어있습니다.")
|
955
896
|
return True
|
956
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
|
+
|
957
904
|
# 폴더 경로에 재귀적 와일드카드 패턴을 추가
|
958
905
|
processed_paths = []
|
959
906
|
for path in file_paths:
|
960
907
|
if os.path.isdir(path):
|
961
908
|
# 폴더 경로에 '...'(재귀) 패턴을 추가
|
962
909
|
processed_paths.append(os.path.join(path, '...'))
|
963
|
-
logger.debug(f"폴더 경로를 재귀 패턴으로 변환: {path} -> {os.path.join(path, '...')}")
|
964
910
|
else:
|
965
911
|
processed_paths.append(path)
|
966
912
|
|
967
|
-
|
913
|
+
self.clear_last_error()
|
968
914
|
try:
|
969
915
|
self.p4.run_sync(processed_paths)
|
970
|
-
logger.info(f"파일/폴더 싱크 완료: {processed_paths}")
|
971
916
|
return True
|
972
917
|
except P4Exception as e:
|
973
|
-
self._handle_p4_exception(e, f"파일/폴더 싱크 ({processed_paths})")
|
918
|
+
self._handle_p4_exception(e, f"파일/폴더 싱크 실패 ({processed_paths})")
|
974
919
|
return False
|
975
920
|
|
976
|
-
def
|
977
|
-
"""
|
978
|
-
if self.connected:
|
979
|
-
try:
|
980
|
-
self.p4.disconnect()
|
981
|
-
self.connected = False
|
982
|
-
logger.info("Perforce 서버 연결 해제 완료.")
|
983
|
-
except P4Exception as e:
|
984
|
-
self._handle_p4_exception(e, "Perforce 서버 연결 해제")
|
985
|
-
else:
|
986
|
-
logger.debug("Perforce 서버에 이미 연결되지 않은 상태입니다.")
|
921
|
+
def get_default_change_list(self) -> dict:
|
922
|
+
"""default change list의 정보를 가져옵니다.
|
987
923
|
|
988
|
-
|
989
|
-
|
990
|
-
|
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 {}
|
991
945
|
|
992
946
|
def check_files_checked_out_all_users(self, file_paths: list) -> dict:
|
993
947
|
"""파일들의 체크아웃 상태를 모든 사용자/워크스페이스에서 확인합니다.
|
@@ -1010,11 +964,15 @@ class Perforce:
|
|
1010
964
|
if not self._is_connected():
|
1011
965
|
return {}
|
1012
966
|
if not file_paths:
|
1013
|
-
logger.debug("체크아웃 상태 확인할 파일 목록이 비어있습니다.")
|
1014
967
|
return {}
|
1015
968
|
|
1016
|
-
|
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)
|
1017
974
|
|
975
|
+
self.clear_last_error()
|
1018
976
|
result = {}
|
1019
977
|
try:
|
1020
978
|
# 각 파일의 상태 확인
|
@@ -1040,26 +998,14 @@ class Perforce:
|
|
1040
998
|
file_status['user'] = file_info.get('user', '')
|
1041
999
|
file_status['client'] = file_info.get('client', '')
|
1042
1000
|
|
1043
|
-
logger.debug(f"파일 '{file_path}' 체크아웃됨: CL {file_status['change_list']}, "
|
1044
|
-
f"액션: {file_status['action']}, 사용자: {file_status['user']}, "
|
1045
|
-
f"클라이언트: {file_status['client']}")
|
1046
|
-
else:
|
1047
|
-
# 파일이 체크아웃되지 않음
|
1048
|
-
logger.debug(f"파일 '{file_path}' 체크아웃되지 않음 (모든 사용자)")
|
1049
|
-
|
1050
1001
|
except P4Exception as e:
|
1051
1002
|
# 파일이 perforce에 없거나 접근할 수 없는 경우
|
1052
|
-
if any("not opened" in err.lower() or "no such file" in err.lower()
|
1053
|
-
|
1054
|
-
logger.debug(f"파일 '{file_path}' 체크아웃되지 않음 (perforce에 없거나 접근 불가)")
|
1055
|
-
else:
|
1003
|
+
if not any("not opened" in err.lower() or "no such file" in err.lower()
|
1004
|
+
for err in self.p4.errors):
|
1056
1005
|
self._handle_p4_exception(e, f"파일 '{file_path}' 체크아웃 상태 확인 (모든 사용자)")
|
1057
1006
|
|
1058
1007
|
result[file_path] = file_status
|
1059
1008
|
|
1060
|
-
checked_out_count = sum(1 for status in result.values() if status['is_checked_out'])
|
1061
|
-
logger.info(f"파일 체크아웃 상태 확인 완료 (모든 사용자): 전체 {len(file_paths)}개 중 {checked_out_count}개 체크아웃됨")
|
1062
|
-
|
1063
1009
|
return result
|
1064
1010
|
|
1065
1011
|
except P4Exception as e:
|
@@ -1150,6 +1096,12 @@ class Perforce:
|
|
1150
1096
|
if not file_paths:
|
1151
1097
|
return []
|
1152
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
|
+
|
1153
1105
|
result = self.check_files_checked_out_all_users(file_paths)
|
1154
1106
|
files_by_others = []
|
1155
1107
|
|
@@ -1171,5 +1123,4 @@ class Perforce:
|
|
1171
1123
|
'action': status.get('action', '')
|
1172
1124
|
})
|
1173
1125
|
|
1174
|
-
|
1175
|
-
return files_by_others
|
1126
|
+
return files_by_others
|