pyjallib 0.1.10__py3-none-any.whl → 0.1.12__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 +7 -7
- pyjallib/max/__init__.py +24 -25
- pyjallib/max/autoClavicle.py +39 -4
- pyjallib/max/bip.py +69 -2
- pyjallib/max/bone.py +37 -0
- pyjallib/max/boneChain.py +182 -0
- pyjallib/max/groinBone.py +65 -29
- pyjallib/max/hip.py +44 -7
- pyjallib/max/kneeBone.py +61 -26
- pyjallib/max/macro/jal_macro_bone.py +1 -1
- pyjallib/max/twistBone.py +59 -40
- pyjallib/max/volumeBone.py +77 -26
- pyjallib/naming.py +27 -2
- pyjallib/namingConfig.py +3 -2
- pyjallib/perforce.py +489 -9
- {pyjallib-0.1.10.dist-info → pyjallib-0.1.12.dist-info}/METADATA +1 -1
- {pyjallib-0.1.10.dist-info → pyjallib-0.1.12.dist-info}/RECORD +18 -21
- pyjallib/max/autoClavicleChain.py +0 -173
- pyjallib/max/groinBoneChain.py +0 -173
- pyjallib/max/twistBoneChain.py +0 -162
- pyjallib/max/volumeBoneChain.py +0 -363
- {pyjallib-0.1.10.dist-info → pyjallib-0.1.12.dist-info}/WHEEL +0 -0
pyjallib/perforce.py
CHANGED
@@ -195,6 +195,183 @@ class Perforce:
|
|
195
195
|
self._handle_p4_exception(e, f"설명으로 체인지 리스트 조회 ('{description}')")
|
196
196
|
return {}
|
197
197
|
|
198
|
+
def get_change_list_by_description_pattern(self, description_pattern: str, exact_match: bool = False) -> list:
|
199
|
+
"""설명 패턴과 일치하는 Pending 체인지 리스트들을 가져옵니다.
|
200
|
+
|
201
|
+
Args:
|
202
|
+
description_pattern (str): 검색할 설명 패턴
|
203
|
+
exact_match (bool, optional): True면 정확히 일치하는 설명만,
|
204
|
+
False면 패턴이 포함된 설명도 포함. 기본값 False
|
205
|
+
|
206
|
+
Returns:
|
207
|
+
list: 패턴과 일치하는 체인지 리스트 정보들의 리스트
|
208
|
+
"""
|
209
|
+
if not self._is_connected():
|
210
|
+
return []
|
211
|
+
|
212
|
+
search_type = "정확히 일치" if exact_match else "패턴 포함"
|
213
|
+
logger.debug(f"설명 패턴으로 체인지 리스트 조회 중 ({search_type}): '{description_pattern}'")
|
214
|
+
|
215
|
+
try:
|
216
|
+
pending_changes = self.p4.run_changes("-l", "-s", "pending", "-u", self.p4.user, "-c", self.p4.client)
|
217
|
+
matching_changes = []
|
218
|
+
|
219
|
+
for cl in pending_changes:
|
220
|
+
cl_desc = cl.get('Description', b'').decode('utf-8', 'replace').strip()
|
221
|
+
|
222
|
+
# 패턴 매칭 로직
|
223
|
+
is_match = False
|
224
|
+
if exact_match:
|
225
|
+
# 정확한 일치
|
226
|
+
is_match = (cl_desc == description_pattern.strip())
|
227
|
+
else:
|
228
|
+
# 패턴이 포함되어 있는지 확인 (대소문자 구분 없음)
|
229
|
+
is_match = (description_pattern.lower().strip() in cl_desc.lower())
|
230
|
+
|
231
|
+
if is_match:
|
232
|
+
change_number = int(cl['change'])
|
233
|
+
change_info = self.get_change_list_by_number(change_number)
|
234
|
+
if change_info:
|
235
|
+
matching_changes.append(change_info)
|
236
|
+
logger.info(f"패턴 '{description_pattern}'에 매칭되는 체인지 리스트 {change_number} 발견: '{cl_desc}'")
|
237
|
+
|
238
|
+
if matching_changes:
|
239
|
+
logger.info(f"패턴 '{description_pattern}'에 매칭되는 체인지 리스트 {len(matching_changes)}개 조회 완료.")
|
240
|
+
else:
|
241
|
+
logger.info(f"패턴 '{description_pattern}'에 매칭되는 Pending 체인지 리스트를 찾을 수 없습니다.")
|
242
|
+
|
243
|
+
return matching_changes
|
244
|
+
except P4Exception as e:
|
245
|
+
self._handle_p4_exception(e, f"설명 패턴으로 체인지 리스트 조회 ('{description_pattern}')")
|
246
|
+
return []
|
247
|
+
|
248
|
+
def check_files_checked_out(self, file_paths: list) -> dict:
|
249
|
+
"""파일들의 체크아웃 상태를 확인합니다.
|
250
|
+
|
251
|
+
Args:
|
252
|
+
file_paths (list): 확인할 파일 경로 리스트
|
253
|
+
|
254
|
+
Returns:
|
255
|
+
dict: 파일별 체크아웃 상태 정보
|
256
|
+
{
|
257
|
+
'file_path': {
|
258
|
+
'is_checked_out': bool,
|
259
|
+
'change_list': int or None,
|
260
|
+
'action': str or None,
|
261
|
+
'user': str or None,
|
262
|
+
'workspace': str or None
|
263
|
+
}
|
264
|
+
}
|
265
|
+
"""
|
266
|
+
if not self._is_connected():
|
267
|
+
return {}
|
268
|
+
if not file_paths:
|
269
|
+
logger.debug("체크아웃 상태 확인할 파일 목록이 비어있습니다.")
|
270
|
+
return {}
|
271
|
+
|
272
|
+
logger.debug(f"파일 체크아웃 상태 확인 중 (파일 {len(file_paths)}개)")
|
273
|
+
|
274
|
+
result = {}
|
275
|
+
try:
|
276
|
+
# 각 파일의 상태 확인
|
277
|
+
for file_path in file_paths:
|
278
|
+
file_status = {
|
279
|
+
'is_checked_out': False,
|
280
|
+
'change_list': None,
|
281
|
+
'action': None,
|
282
|
+
'user': None,
|
283
|
+
'workspace': None
|
284
|
+
}
|
285
|
+
|
286
|
+
try:
|
287
|
+
# p4 opened 명령으로 파일이 열려있는지 확인
|
288
|
+
opened_files = self.p4.run_opened(file_path)
|
289
|
+
|
290
|
+
if opened_files:
|
291
|
+
# 파일이 체크아웃되어 있음
|
292
|
+
file_info = opened_files[0]
|
293
|
+
file_status['is_checked_out'] = True
|
294
|
+
file_status['change_list'] = int(file_info.get('change', 0))
|
295
|
+
file_status['action'] = file_info.get('action', '')
|
296
|
+
file_status['user'] = file_info.get('user', '')
|
297
|
+
file_status['workspace'] = file_info.get('client', '')
|
298
|
+
|
299
|
+
logger.debug(f"파일 '{file_path}' 체크아웃됨: CL {file_status['change_list']}, "
|
300
|
+
f"액션: {file_status['action']}, 사용자: {file_status['user']}, "
|
301
|
+
f"워크스페이스: {file_status['workspace']}")
|
302
|
+
else:
|
303
|
+
# 파일이 체크아웃되지 않음
|
304
|
+
logger.debug(f"파일 '{file_path}' 체크아웃되지 않음")
|
305
|
+
|
306
|
+
except P4Exception as e:
|
307
|
+
# 파일이 perforce에 없거나 접근할 수 없는 경우
|
308
|
+
if any("not opened" in err.lower() or "no such file" in err.lower()
|
309
|
+
for err in self.p4.errors):
|
310
|
+
logger.debug(f"파일 '{file_path}' 체크아웃되지 않음 (perforce에 없거나 접근 불가)")
|
311
|
+
else:
|
312
|
+
self._handle_p4_exception(e, f"파일 '{file_path}' 체크아웃 상태 확인")
|
313
|
+
|
314
|
+
result[file_path] = file_status
|
315
|
+
|
316
|
+
checked_out_count = sum(1 for status in result.values() if status['is_checked_out'])
|
317
|
+
logger.info(f"파일 체크아웃 상태 확인 완료: 전체 {len(file_paths)}개 중 {checked_out_count}개 체크아웃됨")
|
318
|
+
|
319
|
+
return result
|
320
|
+
|
321
|
+
except P4Exception as e:
|
322
|
+
self._handle_p4_exception(e, f"파일들 체크아웃 상태 확인 ({file_paths})")
|
323
|
+
return {}
|
324
|
+
|
325
|
+
def is_file_checked_out(self, file_path: str) -> bool:
|
326
|
+
"""단일 파일의 체크아웃 상태를 간단히 확인합니다.
|
327
|
+
|
328
|
+
Args:
|
329
|
+
file_path (str): 확인할 파일 경로
|
330
|
+
|
331
|
+
Returns:
|
332
|
+
bool: 체크아웃되어 있으면 True, 아니면 False
|
333
|
+
"""
|
334
|
+
result = self.check_files_checked_out([file_path])
|
335
|
+
return result.get(file_path, {}).get('is_checked_out', False)
|
336
|
+
|
337
|
+
def is_file_in_pending_changelist(self, file_path: str, change_list_number: int) -> bool:
|
338
|
+
"""특정 파일이 지정된 pending 체인지 리스트에 있는지 확인합니다.
|
339
|
+
|
340
|
+
Args:
|
341
|
+
file_path (str): 확인할 파일 경로
|
342
|
+
change_list_number (int): 확인할 체인지 리스트 번호
|
343
|
+
|
344
|
+
Returns:
|
345
|
+
bool: 파일이 해당 체인지 리스트에 있으면 True, 아니면 False
|
346
|
+
"""
|
347
|
+
if not self._is_connected():
|
348
|
+
return False
|
349
|
+
|
350
|
+
logger.debug(f"파일 '{file_path}'가 체인지 리스트 {change_list_number}에 있는지 확인 중...")
|
351
|
+
|
352
|
+
try:
|
353
|
+
# 해당 체인지 리스트의 파일들 가져오기
|
354
|
+
opened_files = self.p4.run_opened("-c", change_list_number)
|
355
|
+
|
356
|
+
# 파일 경로 정규화
|
357
|
+
normalized_file_path = os.path.normpath(file_path)
|
358
|
+
|
359
|
+
for file_info in opened_files:
|
360
|
+
client_file = file_info.get('clientFile', '')
|
361
|
+
normalized_client_file = os.path.normpath(client_file)
|
362
|
+
|
363
|
+
if normalized_client_file == normalized_file_path:
|
364
|
+
logger.debug(f"파일 '{file_path}'가 체인지 리스트 {change_list_number}에서 발견됨 "
|
365
|
+
f"(액션: {file_info.get('action', '')})")
|
366
|
+
return True
|
367
|
+
|
368
|
+
logger.debug(f"파일 '{file_path}'가 체인지 리스트 {change_list_number}에 없음")
|
369
|
+
return False
|
370
|
+
|
371
|
+
except P4Exception as e:
|
372
|
+
self._handle_p4_exception(e, f"파일 '{file_path}' 체인지 리스트 {change_list_number} 포함 여부 확인")
|
373
|
+
return False
|
374
|
+
|
198
375
|
def edit_change_list(self, change_list_number: int, description: str = None, add_file_paths: list = None, remove_file_paths: list = None) -> dict:
|
199
376
|
"""체인지 리스트를 편집합니다.
|
200
377
|
|
@@ -283,6 +460,36 @@ class Perforce:
|
|
283
460
|
bool: 체크아웃 성공 시 True, 실패 시 False
|
284
461
|
"""
|
285
462
|
return self._file_op("edit", file_path, change_list_number, "체크아웃")
|
463
|
+
|
464
|
+
def checkout_files(self, file_paths: list, change_list_number: int) -> bool:
|
465
|
+
"""여러 파일을 한 번에 체크아웃합니다.
|
466
|
+
|
467
|
+
Args:
|
468
|
+
file_paths (list): 체크아웃할 파일 경로 리스트
|
469
|
+
change_list_number (int): 체인지 리스트 번호
|
470
|
+
|
471
|
+
Returns:
|
472
|
+
bool: 모든 파일 체크아웃 성공 시 True, 하나라도 실패 시 False
|
473
|
+
"""
|
474
|
+
if not file_paths:
|
475
|
+
logger.debug("체크아웃할 파일 목록이 비어있습니다.")
|
476
|
+
return True
|
477
|
+
|
478
|
+
logger.info(f"체인지 리스트 {change_list_number}에 {len(file_paths)}개 파일 체크아웃 시도...")
|
479
|
+
|
480
|
+
all_success = True
|
481
|
+
for file_path in file_paths:
|
482
|
+
success = self.checkout_file(file_path, change_list_number)
|
483
|
+
if not success:
|
484
|
+
all_success = False
|
485
|
+
logger.warning(f"파일 '{file_path}' 체크아웃 실패")
|
486
|
+
|
487
|
+
if all_success:
|
488
|
+
logger.info(f"모든 파일({len(file_paths)}개)을 체인지 리스트 {change_list_number}에 성공적으로 체크아웃했습니다.")
|
489
|
+
else:
|
490
|
+
logger.warning(f"일부 파일을 체인지 리스트 {change_list_number}에 체크아웃하지 못했습니다.")
|
491
|
+
|
492
|
+
return all_success
|
286
493
|
|
287
494
|
def add_file(self, file_path: str, change_list_number: int) -> bool:
|
288
495
|
"""파일을 추가합니다.
|
@@ -295,6 +502,36 @@ class Perforce:
|
|
295
502
|
bool: 추가 성공 시 True, 실패 시 False
|
296
503
|
"""
|
297
504
|
return self._file_op("add", file_path, change_list_number, "추가")
|
505
|
+
|
506
|
+
def add_files(self, file_paths: list, change_list_number: int) -> bool:
|
507
|
+
"""여러 파일을 한 번에 추가합니다.
|
508
|
+
|
509
|
+
Args:
|
510
|
+
file_paths (list): 추가할 파일 경로 리스트
|
511
|
+
change_list_number (int): 체인지 리스트 번호
|
512
|
+
|
513
|
+
Returns:
|
514
|
+
bool: 모든 파일 추가 성공 시 True, 하나라도 실패 시 False
|
515
|
+
"""
|
516
|
+
if not file_paths:
|
517
|
+
logger.debug("추가할 파일 목록이 비어있습니다.")
|
518
|
+
return True
|
519
|
+
|
520
|
+
logger.info(f"체인지 리스트 {change_list_number}에 {len(file_paths)}개 파일 추가 시도...")
|
521
|
+
|
522
|
+
all_success = True
|
523
|
+
for file_path in file_paths:
|
524
|
+
success = self.add_file(file_path, change_list_number)
|
525
|
+
if not success:
|
526
|
+
all_success = False
|
527
|
+
logger.warning(f"파일 '{file_path}' 추가 실패")
|
528
|
+
|
529
|
+
if all_success:
|
530
|
+
logger.info(f"모든 파일({len(file_paths)}개)을 체인지 리스트 {change_list_number}에 성공적으로 추가했습니다.")
|
531
|
+
else:
|
532
|
+
logger.warning(f"일부 파일을 체인지 리스트 {change_list_number}에 추가하지 못했습니다.")
|
533
|
+
|
534
|
+
return all_success
|
298
535
|
|
299
536
|
def delete_file(self, file_path: str, change_list_number: int) -> bool:
|
300
537
|
"""파일을 삭제합니다.
|
@@ -307,6 +544,36 @@ class Perforce:
|
|
307
544
|
bool: 삭제 성공 시 True, 실패 시 False
|
308
545
|
"""
|
309
546
|
return self._file_op("delete", file_path, change_list_number, "삭제")
|
547
|
+
|
548
|
+
def delete_files(self, file_paths: list, change_list_number: int) -> bool:
|
549
|
+
"""여러 파일을 한 번에 삭제합니다.
|
550
|
+
|
551
|
+
Args:
|
552
|
+
file_paths (list): 삭제할 파일 경로 리스트
|
553
|
+
change_list_number (int): 체인지 리스트 번호
|
554
|
+
|
555
|
+
Returns:
|
556
|
+
bool: 모든 파일 삭제 성공 시 True, 하나라도 실패 시 False
|
557
|
+
"""
|
558
|
+
if not file_paths:
|
559
|
+
logger.debug("삭제할 파일 목록이 비어있습니다.")
|
560
|
+
return True
|
561
|
+
|
562
|
+
logger.info(f"체인지 리스트 {change_list_number}에서 {len(file_paths)}개 파일 삭제 시도...")
|
563
|
+
|
564
|
+
all_success = True
|
565
|
+
for file_path in file_paths:
|
566
|
+
success = self.delete_file(file_path, change_list_number)
|
567
|
+
if not success:
|
568
|
+
all_success = False
|
569
|
+
logger.warning(f"파일 '{file_path}' 삭제 실패")
|
570
|
+
|
571
|
+
if all_success:
|
572
|
+
logger.info(f"모든 파일({len(file_paths)}개)을 체인지 리스트 {change_list_number}에서 성공적으로 삭제했습니다.")
|
573
|
+
else:
|
574
|
+
logger.warning(f"일부 파일을 체인지 리스트 {change_list_number}에서 삭제하지 못했습니다.")
|
575
|
+
|
576
|
+
return all_success
|
310
577
|
|
311
578
|
def submit_change_list(self, change_list_number: int) -> bool:
|
312
579
|
"""체인지 리스트를 제출합니다.
|
@@ -393,6 +660,28 @@ class Perforce:
|
|
393
660
|
self._handle_p4_exception(e, f"체인지 리스트 {change_list_number} 삭제")
|
394
661
|
return False
|
395
662
|
|
663
|
+
def revert_file(self, file_path: str, change_list_number: int) -> bool:
|
664
|
+
"""체인지 리스트에서 특정 파일을 되돌립니다.
|
665
|
+
|
666
|
+
Args:
|
667
|
+
file_path (str): 되돌릴 파일 경로
|
668
|
+
change_list_number (int): 체인지 리스트 번호
|
669
|
+
|
670
|
+
Returns:
|
671
|
+
bool: 되돌리기 성공 시 True, 실패 시 False
|
672
|
+
"""
|
673
|
+
if not self._is_connected():
|
674
|
+
return False
|
675
|
+
|
676
|
+
logger.info(f"파일 '{file_path}'을 체인지 리스트 {change_list_number}에서 되돌리기 시도...")
|
677
|
+
try:
|
678
|
+
self.p4.run_revert("-c", change_list_number, file_path)
|
679
|
+
logger.info(f"파일 '{file_path}'를 체인지 리스트 {change_list_number}에서 되돌리기 성공.")
|
680
|
+
return True
|
681
|
+
except P4Exception as e:
|
682
|
+
self._handle_p4_exception(e, f"파일 '{file_path}'를 체인지 리스트 {change_list_number}에서 되돌리기")
|
683
|
+
return False
|
684
|
+
|
396
685
|
def revert_files(self, change_list_number: int, file_paths: list) -> bool:
|
397
686
|
"""체인지 리스트 내의 특정 파일들을 되돌립니다.
|
398
687
|
|
@@ -401,7 +690,7 @@ class Perforce:
|
|
401
690
|
file_paths (list): 되돌릴 파일 경로 리스트
|
402
691
|
|
403
692
|
Returns:
|
404
|
-
bool: 되돌리기 성공 시 True, 실패 시 False
|
693
|
+
bool: 모든 파일 되돌리기 성공 시 True, 하나라도 실패 시 False
|
405
694
|
"""
|
406
695
|
if not self._is_connected():
|
407
696
|
return False
|
@@ -410,14 +699,20 @@ class Perforce:
|
|
410
699
|
return True
|
411
700
|
|
412
701
|
logger.info(f"체인지 리스트 {change_list_number}에서 {len(file_paths)}개 파일 되돌리기 시도...")
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
702
|
+
|
703
|
+
all_success = True
|
704
|
+
for file_path in file_paths:
|
705
|
+
success = self.revert_file(file_path, change_list_number)
|
706
|
+
if not success:
|
707
|
+
all_success = False
|
708
|
+
logger.warning(f"파일 '{file_path}' 되돌리기 실패")
|
709
|
+
|
710
|
+
if all_success:
|
711
|
+
logger.info(f"모든 파일({len(file_paths)}개)을 체인지 리스트 {change_list_number}에서 성공적으로 되돌렸습니다.")
|
712
|
+
else:
|
713
|
+
logger.warning(f"일부 파일을 체인지 리스트 {change_list_number}에서 되돌리지 못했습니다.")
|
714
|
+
|
715
|
+
return all_success
|
421
716
|
|
422
717
|
def check_update_required(self, file_paths: list) -> bool:
|
423
718
|
"""파일이나 폴더의 업데이트 필요 여부를 확인합니다.
|
@@ -526,3 +821,188 @@ class Perforce:
|
|
526
821
|
def __del__(self):
|
527
822
|
"""객체가 소멸될 때 자동으로 연결을 해제합니다."""
|
528
823
|
self.disconnect()
|
824
|
+
|
825
|
+
def check_files_checked_out_all_users(self, file_paths: list) -> dict:
|
826
|
+
"""파일들의 체크아웃 상태를 모든 사용자/워크스페이스에서 확인합니다.
|
827
|
+
|
828
|
+
Args:
|
829
|
+
file_paths (list): 확인할 파일 경로 리스트
|
830
|
+
|
831
|
+
Returns:
|
832
|
+
dict: 파일별 체크아웃 상태 정보
|
833
|
+
{
|
834
|
+
'file_path': {
|
835
|
+
'is_checked_out': bool,
|
836
|
+
'change_list': int or None,
|
837
|
+
'action': str or None,
|
838
|
+
'user': str or None,
|
839
|
+
'client': str or None
|
840
|
+
}
|
841
|
+
}
|
842
|
+
"""
|
843
|
+
if not self._is_connected():
|
844
|
+
return {}
|
845
|
+
if not file_paths:
|
846
|
+
logger.debug("체크아웃 상태 확인할 파일 목록이 비어있습니다.")
|
847
|
+
return {}
|
848
|
+
|
849
|
+
logger.debug(f"파일 체크아웃 상태 확인 중 - 모든 사용자 (파일 {len(file_paths)}개)")
|
850
|
+
|
851
|
+
result = {}
|
852
|
+
try:
|
853
|
+
# 각 파일의 상태 확인
|
854
|
+
for file_path in file_paths:
|
855
|
+
file_status = {
|
856
|
+
'is_checked_out': False,
|
857
|
+
'change_list': None,
|
858
|
+
'action': None,
|
859
|
+
'user': None,
|
860
|
+
'client': None
|
861
|
+
}
|
862
|
+
|
863
|
+
try:
|
864
|
+
# p4 opened -a 명령으로 모든 사용자의 파일 체크아웃 상태 확인
|
865
|
+
opened_files = self.p4.run_opened("-a", file_path)
|
866
|
+
|
867
|
+
if opened_files:
|
868
|
+
# 파일이 체크아웃되어 있음 (첫 번째 결과 사용)
|
869
|
+
file_info = opened_files[0]
|
870
|
+
file_status['is_checked_out'] = True
|
871
|
+
file_status['change_list'] = int(file_info.get('change', 0))
|
872
|
+
file_status['action'] = file_info.get('action', '')
|
873
|
+
file_status['user'] = file_info.get('user', '')
|
874
|
+
file_status['client'] = file_info.get('client', '')
|
875
|
+
|
876
|
+
logger.debug(f"파일 '{file_path}' 체크아웃됨: CL {file_status['change_list']}, "
|
877
|
+
f"액션: {file_status['action']}, 사용자: {file_status['user']}, "
|
878
|
+
f"클라이언트: {file_status['client']}")
|
879
|
+
else:
|
880
|
+
# 파일이 체크아웃되지 않음
|
881
|
+
logger.debug(f"파일 '{file_path}' 체크아웃되지 않음 (모든 사용자)")
|
882
|
+
|
883
|
+
except P4Exception as e:
|
884
|
+
# 파일이 perforce에 없거나 접근할 수 없는 경우
|
885
|
+
if any("not opened" in err.lower() or "no such file" in err.lower()
|
886
|
+
for err in self.p4.errors):
|
887
|
+
logger.debug(f"파일 '{file_path}' 체크아웃되지 않음 (perforce에 없거나 접근 불가)")
|
888
|
+
else:
|
889
|
+
self._handle_p4_exception(e, f"파일 '{file_path}' 체크아웃 상태 확인 (모든 사용자)")
|
890
|
+
|
891
|
+
result[file_path] = file_status
|
892
|
+
|
893
|
+
checked_out_count = sum(1 for status in result.values() if status['is_checked_out'])
|
894
|
+
logger.info(f"파일 체크아웃 상태 확인 완료 (모든 사용자): 전체 {len(file_paths)}개 중 {checked_out_count}개 체크아웃됨")
|
895
|
+
|
896
|
+
return result
|
897
|
+
|
898
|
+
except P4Exception as e:
|
899
|
+
self._handle_p4_exception(e, f"파일들 체크아웃 상태 확인 - 모든 사용자 ({file_paths})")
|
900
|
+
return {}
|
901
|
+
|
902
|
+
def is_file_checked_out_by_others(self, file_path: str) -> bool:
|
903
|
+
"""단일 파일이 다른 사용자/워크스페이스에 의해 체크아웃되어 있는지 확인합니다.
|
904
|
+
|
905
|
+
Args:
|
906
|
+
file_path (str): 확인할 파일 경로
|
907
|
+
|
908
|
+
Returns:
|
909
|
+
bool: 다른 사용자에 의해 체크아웃되어 있으면 True, 아니면 False
|
910
|
+
"""
|
911
|
+
result = self.check_files_checked_out_all_users([file_path])
|
912
|
+
file_status = result.get(file_path, {})
|
913
|
+
|
914
|
+
if not file_status.get('is_checked_out', False):
|
915
|
+
return False
|
916
|
+
|
917
|
+
# 현재 사용자와 클라이언트가 아닌 경우 다른 사용자로 간주
|
918
|
+
current_user = self.p4.user
|
919
|
+
current_client = self.p4.client
|
920
|
+
|
921
|
+
file_user = file_status.get('user', '')
|
922
|
+
file_client = file_status.get('client', '')
|
923
|
+
|
924
|
+
return (file_user != current_user) or (file_client != current_client)
|
925
|
+
|
926
|
+
def get_file_checkout_info_all_users(self, file_path: str) -> dict:
|
927
|
+
"""단일 파일의 상세 체크아웃 정보를 모든 사용자에서 가져옵니다.
|
928
|
+
|
929
|
+
Args:
|
930
|
+
file_path (str): 확인할 파일 경로
|
931
|
+
|
932
|
+
Returns:
|
933
|
+
dict: 체크아웃 정보 또는 빈 딕셔너리
|
934
|
+
{
|
935
|
+
'is_checked_out': bool,
|
936
|
+
'change_list': int or None,
|
937
|
+
'action': str or None,
|
938
|
+
'user': str or None,
|
939
|
+
'client': str or None,
|
940
|
+
'is_checked_out_by_current_user': bool,
|
941
|
+
'is_checked_out_by_others': bool
|
942
|
+
}
|
943
|
+
"""
|
944
|
+
result = self.check_files_checked_out_all_users([file_path])
|
945
|
+
file_status = result.get(file_path, {})
|
946
|
+
|
947
|
+
if file_status.get('is_checked_out', False):
|
948
|
+
# 현재 사용자와 클라이언트인지 확인
|
949
|
+
current_user = self.p4.user
|
950
|
+
current_client = self.p4.client
|
951
|
+
|
952
|
+
file_user = file_status.get('user', '')
|
953
|
+
file_client = file_status.get('client', '')
|
954
|
+
|
955
|
+
is_current_user = (file_user == current_user) and (file_client == current_client)
|
956
|
+
|
957
|
+
file_status['is_checked_out_by_current_user'] = is_current_user
|
958
|
+
file_status['is_checked_out_by_others'] = not is_current_user
|
959
|
+
else:
|
960
|
+
file_status['is_checked_out_by_current_user'] = False
|
961
|
+
file_status['is_checked_out_by_others'] = False
|
962
|
+
|
963
|
+
return file_status
|
964
|
+
|
965
|
+
def get_files_checked_out_by_others(self, file_paths: list) -> list:
|
966
|
+
"""파일 목록에서 다른 사용자/워크스페이스에 의해 체크아웃된 파일들을 찾습니다.
|
967
|
+
|
968
|
+
Args:
|
969
|
+
file_paths (list): 확인할 파일 경로 리스트
|
970
|
+
|
971
|
+
Returns:
|
972
|
+
list: 다른 사용자에 의해 체크아웃된 파일 정보 리스트
|
973
|
+
[
|
974
|
+
{
|
975
|
+
'file_path': str,
|
976
|
+
'user': str,
|
977
|
+
'client': str,
|
978
|
+
'change_list': int,
|
979
|
+
'action': str
|
980
|
+
}
|
981
|
+
]
|
982
|
+
"""
|
983
|
+
if not file_paths:
|
984
|
+
return []
|
985
|
+
|
986
|
+
result = self.check_files_checked_out_all_users(file_paths)
|
987
|
+
files_by_others = []
|
988
|
+
|
989
|
+
current_user = self.p4.user
|
990
|
+
current_client = self.p4.client
|
991
|
+
|
992
|
+
for file_path, status in result.items():
|
993
|
+
if status.get('is_checked_out', False):
|
994
|
+
file_user = status.get('user', '')
|
995
|
+
file_client = status.get('client', '')
|
996
|
+
|
997
|
+
# 다른 사용자/클라이언트에 의해 체크아웃된 경우
|
998
|
+
if (file_user != current_user) or (file_client != current_client):
|
999
|
+
files_by_others.append({
|
1000
|
+
'file_path': file_path,
|
1001
|
+
'user': file_user,
|
1002
|
+
'client': file_client,
|
1003
|
+
'change_list': status.get('change_list'),
|
1004
|
+
'action': status.get('action', '')
|
1005
|
+
})
|
1006
|
+
|
1007
|
+
logger.info(f"다른 사용자에 의해 체크아웃된 파일: {len(files_by_others)}개")
|
1008
|
+
return files_by_others
|
@@ -1,26 +1,25 @@
|
|
1
|
-
pyjallib/__init__.py,sha256=
|
1
|
+
pyjallib/__init__.py,sha256=4EmbUX3I3DLJ3DpGz7kBAADCFskvffdUEPd3HIu7-9k,509
|
2
2
|
pyjallib/namePart.py,sha256=lKIiOVkWrtAW-D3nuv--vHmdAnlQeVPaXLYUDhcr8QU,24177
|
3
3
|
pyjallib/nameToPath.py,sha256=aBeezepLYdpv3VYxnQ2c4ZWzz2WjticXjkdbAIlVa1k,4676
|
4
|
-
pyjallib/naming.py,sha256=
|
5
|
-
pyjallib/namingConfig.py,sha256=
|
6
|
-
pyjallib/perforce.py,sha256=
|
4
|
+
pyjallib/naming.py,sha256=b2C-P9VWV4Q2StqkizEwABblYOC5g6sXHzN0KpOZ_Ys,37419
|
5
|
+
pyjallib/namingConfig.py,sha256=QGpK5mCnRiclKqNKz3GJ2PeJO8fbVitAEdqWwnwo8oA,34127
|
6
|
+
pyjallib/perforce.py,sha256=XcF-YG250bh3obOkajPzMNwaJcCmXKIHIlJIEEFw6JE,46459
|
7
7
|
pyjallib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
8
|
pyjallib/reloadModules.py,sha256=RAEG3IxzJ0TlsjvnZwJt56JOkc2j8voqAnRbfQuZ44g,1151
|
9
9
|
pyjallib/ConfigFiles/namingConfig.json,sha256=Ov4bbVJb6qodPaooU63e11YUMGXXPWFAA4AQq1sLBYU,1486
|
10
|
-
pyjallib/max/__init__.py,sha256=
|
10
|
+
pyjallib/max/__init__.py,sha256=sLF07So7OcCccPOWixSjIkAhuye37-buK-1I5Hp8F4U,1537
|
11
11
|
pyjallib/max/align.py,sha256=HKjCViQCuicGmtvHB6xxVv4BEGEBGtV2gO3NvR_6R2A,5183
|
12
12
|
pyjallib/max/anim.py,sha256=QTpR8T047IMpV40wnMMNo080wY9rHMV9k7ISrh4P61I,26083
|
13
|
-
pyjallib/max/autoClavicle.py,sha256=
|
14
|
-
pyjallib/max/
|
15
|
-
pyjallib/max/
|
16
|
-
pyjallib/max/
|
13
|
+
pyjallib/max/autoClavicle.py,sha256=Iga8bWUhRabfFePObdwGJSshp5Gww1Jv1Hwcul_y0V4,9559
|
14
|
+
pyjallib/max/bip.py,sha256=kEneUnYph_GQXY1oojmzpJOV9bAYNBKagE0Ht3EGZ7o,30199
|
15
|
+
pyjallib/max/bone.py,sha256=XVtX6e5UbMcGaOqz5UeoMEpQNMfbyQWDNM-UuS1CCUA,50019
|
16
|
+
pyjallib/max/boneChain.py,sha256=weuOmGk7Y7i-0QNCr7G2hRPOecb5xmErshqpmXtikI0,5660
|
17
17
|
pyjallib/max/constraint.py,sha256=93g-X0aZHtZMKXVKr8xMjIhbKou61yc2b3ubQKJquBs,40589
|
18
|
-
pyjallib/max/groinBone.py,sha256=
|
19
|
-
pyjallib/max/groinBoneChain.py,sha256=E75yHfvse3QJ-Yo__OUBox_dbv_j-bOSzxr_FwetDZA,5604
|
18
|
+
pyjallib/max/groinBone.py,sha256=nLR8rgWl6Vt4qQPZKY_jDgTRNF9RCnst49iR2JdWEMs,9144
|
20
19
|
pyjallib/max/header.py,sha256=nuNCVfm5bfYMS0KxB8IRR67D30CXXHRUXHfFYkLG0jU,4120
|
21
20
|
pyjallib/max/helper.py,sha256=Na3jFRwLsjHh4rz0Tk_r_CwHQxOA6n8LhDRA9x5xcSk,18018
|
22
|
-
pyjallib/max/hip.py,sha256=
|
23
|
-
pyjallib/max/kneeBone.py,sha256=
|
21
|
+
pyjallib/max/hip.py,sha256=OXMS_bBJUYVKT-aZoJ2YCbbS9eQStwMOkXfA22t-PKw,12696
|
22
|
+
pyjallib/max/kneeBone.py,sha256=P5vDX1MFMbDy4DEI1LsT05a2Z62rqgr_FqGBxl2yEeY,24397
|
24
23
|
pyjallib/max/layer.py,sha256=e9Mn8h7xf0oBYST3QIpyBpLMl8qpWTExO9Y6yH6rKc0,8795
|
25
24
|
pyjallib/max/link.py,sha256=J3z9nkP8ZxAh9yYhR16tjQFCJTCYZMSB0MGbSHfA7uI,2592
|
26
25
|
pyjallib/max/mirror.py,sha256=TcbfZXSk-VJQstNqAmD6VGCqYBF9bMuJtFTg-6SiGdQ,14505
|
@@ -28,19 +27,17 @@ pyjallib/max/morph.py,sha256=I8HRYx4NznL6GZL4CbT9iTv05SeaBW_mJJ4PzMxCBkw,12664
|
|
28
27
|
pyjallib/max/name.py,sha256=DcJt2td-N7vfUGyWazdGTD4-0JW-noa7z5nwc6SHm6I,15337
|
29
28
|
pyjallib/max/select.py,sha256=HMJD2WNX3zVBEeYrj0UX2YXM3fHNItfw6UtQSItNsoU,9487
|
30
29
|
pyjallib/max/skin.py,sha256=5mBzG2wSUxoGlkFeb9Ys8uUxOwuZRGeqUMTI9LiWWZU,41937
|
31
|
-
pyjallib/max/twistBone.py,sha256=
|
32
|
-
pyjallib/max/
|
33
|
-
pyjallib/max/volumeBone.py,sha256=fCFJ8YTrjhLkZdpkEekT45VN17DtTJngzJT5uHYZVno,12336
|
34
|
-
pyjallib/max/volumeBoneChain.py,sha256=bS6OwPDSCk0Wrm0Hv9OZ9uGdygikMs98Z-S1koAGrKQ,13481
|
30
|
+
pyjallib/max/twistBone.py,sha256=3fs8EzRH-TTt2Oypm4LGoR9QtNcno9Fe1OV5_gA9p4U,17049
|
31
|
+
pyjallib/max/volumeBone.py,sha256=SfmxxqmozcDinRtfPsjdNOPDMcqDkHJoquaZZOLrn1g,14649
|
35
32
|
pyjallib/max/ConfigFiles/3DSMaxNamingConfig.json,sha256=PBUYawCELG0aLNRdTh6j-Yka4eNdmpF4P8iRyp0Ngpg,3717
|
36
33
|
pyjallib/max/ConfigFiles/Default_3DSMaxNamingConfig.json,sha256=PBUYawCELG0aLNRdTh6j-Yka4eNdmpF4P8iRyp0Ngpg,3717
|
37
34
|
pyjallib/max/macro/jal_macro_align.py,sha256=_Iqwskz0i4AVlP_AhDrqHwhLml6zoDWkVAxPF3AKqEQ,4286
|
38
|
-
pyjallib/max/macro/jal_macro_bone.py,sha256=
|
35
|
+
pyjallib/max/macro/jal_macro_bone.py,sha256=SROjidH4z4rec9jNtWK0Dmkx_-sP6GJiMi0Hf6ipwsI,12418
|
39
36
|
pyjallib/max/macro/jal_macro_constraint.py,sha256=jJnNHCjsFJNYRqaBEPjzW-dnCDJT6JRZXH7dqBPkeiw,4160
|
40
37
|
pyjallib/max/macro/jal_macro_helper.py,sha256=hd8e5x56aq7Qt0g-hP5bY0p-njVy8ja77_qMPZyvDag,12906
|
41
38
|
pyjallib/max/macro/jal_macro_link.py,sha256=E8i3z2xsrQiGDEz4Qoxc75hkpalzS95mOMcIic0J-Fc,1193
|
42
39
|
pyjallib/max/macro/jal_macro_select.py,sha256=jeSFR_mqqudTTrZE1rU6qifJ4g441cYxXWcHPTWh1CU,2289
|
43
40
|
pyjallib/max/ui/Container.py,sha256=QSk3oCqhfiR4aglSSkherRGAyPFqMRUt83L-0ENBz-s,5571
|
44
|
-
pyjallib-0.1.
|
45
|
-
pyjallib-0.1.
|
46
|
-
pyjallib-0.1.
|
41
|
+
pyjallib-0.1.12.dist-info/METADATA,sha256=0jeCReJS2g2RlmWhefBDeSPwlIoFDturmBEqrw_s3Y8,870
|
42
|
+
pyjallib-0.1.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
43
|
+
pyjallib-0.1.12.dist-info/RECORD,,
|