pyjallib 0.1.0__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/perforce.py ADDED
@@ -0,0 +1,735 @@
1
+ import os
2
+ import subprocess
3
+ import socket
4
+
5
+ class Perforce:
6
+ """
7
+ Perforce 버전 관리 시스템과의 상호작용을 위한 클래스입니다.
8
+
9
+ 이 클래스는 Perforce 명령을 실행하고, 워크스페이스를 관리하며
10
+ Perforce 서버와의 연결을 제어하는 기능을 제공합니다.
11
+ """
12
+
13
+ def __init__(self, server, user, workspace=None):
14
+ """
15
+ Perforce 클래스의 인스턴스를 초기화합니다.
16
+
17
+ Parameters:
18
+ server (str, optional): Perforce 서버 주소. 기본값은 환경 변수 P4PORT 또는 "PC-BUILD:1666"
19
+ user (str, optional): Perforce 사용자 이름. 기본값은 환경 변수 P4USER 또는 "Dev"
20
+ workspace (str, optional): Perforce 워크스페이스 이름. 기본값은 환경 변수 P4CLIENT
21
+ """
22
+ self.server = server
23
+ self.user = user
24
+ self.workspace = workspace if workspace else os.environ.get('P4CLIENT')
25
+ self.workspaceRoot = None
26
+ self.localHostName = socket.gethostname()
27
+
28
+ os.environ['P4USER'] = self.user
29
+ os.environ['P4PORT'] = self.server
30
+ if self.workspace:
31
+ os.environ['P4CLIENT'] = self.workspace
32
+ else:
33
+ # P4CLIENT가 None이면 환경 변수에서 제거 시도 (선택적)
34
+ if 'P4CLIENT' in os.environ:
35
+ del os.environ['P4CLIENT']
36
+
37
+ # 초기화 시 연결 확인
38
+ self._initialize_connection()
39
+
40
+ def _initialize_connection(self):
41
+ """
42
+ Perforce 서버와의 연결을 초기화합니다.
43
+
44
+ 서버 연결을 확인하고 워크스페이스 루트 경로를 설정합니다.
45
+
46
+ Returns:
47
+ str: Perforce 서버 정보 문자열
48
+
49
+ Raises:
50
+ Exception: Perforce 연결 초기화 실패 시 예외 처리
51
+ """
52
+ result = None
53
+ try:
54
+ # 서버 연결 확인 (info 명령은 가볍고 빠르게 실행됨)
55
+ result = subprocess.run(['p4', 'info'],
56
+ capture_output=True,
57
+ text=True,
58
+ encoding="utf-8")
59
+
60
+ workSpaceRootPathResult = subprocess.run(
61
+ ['p4', '-F', '%clientRoot%', '-ztag', 'info'],
62
+ capture_output=True,
63
+ text=True,
64
+ encoding="utf-8"
65
+ ).stdout.strip()
66
+ self.workspaceRoot = os.path.normpath(workSpaceRootPathResult)
67
+
68
+ if result.returncode != 0:
69
+ print(f"Perforce 초기화 중 경고: {result.stderr}")
70
+ except Exception as e:
71
+ print(f"Perforce 초기화 실패: {e}")
72
+
73
+ return result.stdout.strip()
74
+
75
+ def _run_command(self, inCommands):
76
+ """
77
+ Perforce 명령을 실행하고 결과를 반환합니다.
78
+
79
+ Parameters:
80
+ inCommands (list): 실행할 Perforce 명령어와 인수들의 리스트
81
+
82
+ Returns:
83
+ str: 명령 실행 결과 문자열
84
+ """
85
+ self._initialize_connection()
86
+
87
+ commands = ['p4'] + inCommands
88
+ result = subprocess.run(commands, capture_output=True, text=True, encoding="utf-8")
89
+
90
+ return result.stdout.strip()
91
+
92
+ def get_local_hostname(self):
93
+ """
94
+ 현재 로컬 머신의 호스트 이름을 반환합니다.
95
+
96
+ Returns:
97
+ str: 로컬 머신의 호스트 이름
98
+ """
99
+ # 현재 로컬 머신의 호스트 이름을 반환합니다.
100
+ return self.localHostName
101
+
102
+ def get_all_clients(self):
103
+ """
104
+ 모든 Perforce 클라이언트 워크스페이스의 이름 목록을 반환합니다.
105
+
106
+ Returns:
107
+ list: 클라이언트 워크스페이스 이름 리스트
108
+ """
109
+ # 모든 클라이언트 워크스페이스의 이름을 반환합니다.
110
+ result = self._run_command(['clients'])
111
+ clients = []
112
+
113
+ if result is None:
114
+ return clients
115
+
116
+ for line in result.splitlines():
117
+ if line.startswith('Client'):
118
+ parts = line.split()
119
+ if len(parts) >= 2:
120
+ clients.append(parts[1])
121
+ return clients
122
+
123
+ def get_local_workspaces(self):
124
+ """
125
+ 현재 로컬 머신에 있는 워크스페이스 목록을 반환합니다.
126
+
127
+ 현재 호스트 이름으로 시작하는 모든 클라이언트를 찾습니다.
128
+
129
+ Returns:
130
+ list: 로컬 머신의 워크스페이스 이름 리스트
131
+ """
132
+ all_clients = self.get_all_clients()
133
+ local_clients = []
134
+
135
+ for client in all_clients:
136
+ if client.startswith(self.localHostName):
137
+ local_clients.append(client)
138
+
139
+ return local_clients
140
+
141
+ def set_workspace(self, inWorkspace):
142
+ """
143
+ 주어진 워크스페이스로 현재 작업 환경을 전환합니다.
144
+
145
+ Parameters:
146
+ inWorkspace (str): 전환할 워크스페이스 이름
147
+
148
+ Returns:
149
+ str: 워크스페이스 정보 문자열
150
+
151
+ Raises:
152
+ ValueError: 지정된 워크스페이스가 로컬 워크스페이스 목록에 없을 경우
153
+ """
154
+ # 주어진 워크스페이스로 전환합니다.
155
+ localWorkSpaces = self.get_local_workspaces()
156
+ if inWorkspace not in localWorkSpaces:
157
+ print(f"워크스페이스 '{inWorkspace}'는 로컬 워크스페이스 목록에 없습니다.")
158
+ return False
159
+
160
+ self.workspace = inWorkspace
161
+ os.environ['P4CLIENT'] = self.workspace
162
+
163
+ return True
164
+
165
+ def sync(self, inWorkSpace=None, inPaths=None):
166
+ """
167
+ Perforce 워크스페이스를 최신 버전으로 동기화합니다.
168
+
169
+ Parameters:
170
+ inWorkSpace (str, optional): 동기화할 워크스페이스 이름
171
+ inPaths (str or list, optional): 동기화할 특정 경로 또는 경로 목록
172
+
173
+ Returns:
174
+ bool: 동기화 성공 여부
175
+ """
176
+ # 주어진 워크스페이스를 동기화합니다.
177
+ if inWorkSpace == None:
178
+ if self.workspace == None:
179
+ print(f"워크스페이스가 없습니다.")
180
+ return False
181
+ else:
182
+ if not self.set_workspace(inWorkSpace):
183
+ print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.")
184
+ return False
185
+
186
+ # 동기화 명령 실행
187
+ sync_command = ['sync']
188
+
189
+ if inPaths:
190
+ # 문자열로 단일 경로가 주어진 경우 리스트로 변환
191
+ if isinstance(inPaths, str):
192
+ inPaths = [inPaths]
193
+
194
+ valid_paths = []
195
+ for path in inPaths:
196
+ if os.path.exists(path):
197
+ valid_paths.append(path)
198
+ else:
199
+ print(f"경고: 지정된 경로 '{path}'가 로컬에 존재하지 않습니다.")
200
+
201
+ if not valid_paths:
202
+ print("유효한 경로가 없어 동기화를 진행할 수 없습니다.")
203
+ return False
204
+
205
+ # 유효한 경로들을 모두 동기화 명령에 추가
206
+ sync_command.extend(valid_paths)
207
+
208
+ self._run_command(sync_command)
209
+ return True
210
+
211
+ def get_changelists(self, inWorkSpace=None):
212
+ """
213
+ 특정 워크스페이스의 pending 상태 체인지 리스트를 가져옵니다.
214
+
215
+ Parameters:
216
+ inWorkSpace (str, optional): 체인지 리스트를 가져올 워크스페이스 이름
217
+
218
+ Returns:
219
+ list: 체인지 리스트 정보 딕셔너리의 리스트
220
+ """
221
+ if inWorkSpace == None:
222
+ if self.workspace == None:
223
+ print(f"워크스페이스가 없습니다.")
224
+ return []
225
+ else:
226
+ if not self.set_workspace(inWorkSpace):
227
+ print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.")
228
+ return []
229
+
230
+ # 체인지 리스트 명령 실행
231
+ changes_command = ['changes']
232
+
233
+ # 항상 pending 상태만 가져오도록 설정
234
+ changes_command.extend(['-s', 'pending'])
235
+
236
+ if self.workspace:
237
+ changes_command.extend(['-c', self.workspace])
238
+
239
+ result = self._run_command(changes_command)
240
+ changes = []
241
+
242
+ for line in result.splitlines():
243
+ if line.startswith('Change'):
244
+ parts = line.split()
245
+ if len(parts) >= 5:
246
+ change_id = parts[1]
247
+
248
+ # 설명 부분 추출
249
+ desc_start = line.find("'")
250
+ desc_end = line.rfind("'")
251
+ description = line[desc_start+1:desc_end] if desc_start != -1 and desc_end != -1 else ""
252
+
253
+ changes.append({
254
+ 'id': change_id,
255
+ 'description': description
256
+ })
257
+
258
+ return changes
259
+
260
+ def create_new_changelist(self, inDescription="Created by pyjallib", inWorkSpace=None):
261
+ """
262
+ 새로운 체인지 리스트를 생성합니다.
263
+
264
+ Parameters:
265
+ inDescription (str): 체인지 리스트 설명
266
+ inWorkSpace (str, optional): 체인지 리스트를 생성할 워크스페이스 이름
267
+
268
+ Returns:
269
+ dict: 생성된 체인지 리스트 정보 {'id': str, 'description': str} 또는 실패 시 None
270
+ """
271
+ if inWorkSpace == None:
272
+ if self.workspace == None:
273
+ print(f"워크스페이스가 없습니다.")
274
+ return None
275
+ else:
276
+ if not self.set_workspace(inWorkSpace):
277
+ print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.")
278
+ return None
279
+
280
+ # 체인지 리스트 생성 명령 실행
281
+ change_command = ['change', '-o']
282
+ result = self._run_command(change_command)
283
+
284
+ # 체인지 스펙 수정
285
+ modified_spec = []
286
+ for line in result.splitlines():
287
+ if line.startswith('Description:'):
288
+ modified_spec.append(line)
289
+ # Add the new description, handling potential multi-line descriptions correctly
290
+ for desc_line in inDescription.splitlines():
291
+ modified_spec.append(f"\t{desc_line}")
292
+ # Skip the default empty tab line if present
293
+ # This assumes the default spec has an empty line after Description:
294
+ # If not, this logic might need adjustment based on actual 'p4 change -o' output
295
+ # We'll rely on the loop structure to handle subsequent lines correctly.
296
+ continue # Move to the next line in the original spec
297
+ # Only append lines that are not part of the old description placeholder
298
+ # This logic assumes the default description is just a placeholder like '<enter description here>'
299
+ # or similar, often on a single line after 'Description:'.
300
+ # A more robust approach might be needed if the default spec is complex.
301
+ if not (line.startswith('\t') and 'Description:' in modified_spec[-1]):
302
+ modified_spec.append(line)
303
+
304
+ # 수정된 체인지 스펙으로 체인지 리스트 생성
305
+ create_result = subprocess.run(['p4', 'change', '-i'],
306
+ input='\n'.join(modified_spec),
307
+ capture_output=True,
308
+ text=True,
309
+ encoding="utf-8")
310
+
311
+ # 결과에서 체인지 리스트 ID 추출
312
+ if create_result.returncode == 0 and create_result.stdout:
313
+ output = create_result.stdout.strip()
314
+ # 예: "Change 12345 created."
315
+ if 'Change' in output and 'created' in output:
316
+ parts = output.split()
317
+ if len(parts) >= 2:
318
+ change_id = parts[1]
319
+ return {'id': change_id, 'description': inDescription} # Return dictionary
320
+
321
+ print(f"Failed to create changelist. Error: {create_result.stderr}") # Log error if creation failed
322
+ return None
323
+
324
+ def checkout_files(self, inFiles, inChangelist=None, inWorkSpace=None):
325
+ """
326
+ 지정한 파일들을 체크아웃하고 특정 체인지 리스트에 추가합니다.
327
+
328
+ Parameters:
329
+ inFiles (list): 체크아웃할 파일 경로 리스트
330
+ inChangelist (str, optional): 파일을 추가할 체인지 리스트 ID
331
+ inWorkSpace (str, optional): 작업할 워크스페이스 이름
332
+
333
+ Returns:
334
+ bool: 성공 여부
335
+ """
336
+ if inWorkSpace == None:
337
+ if self.workspace == None:
338
+ print(f"워크스페이스가 없습니다.")
339
+ return False
340
+ else:
341
+ if not self.set_workspace(inWorkSpace):
342
+ print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.")
343
+ return False
344
+
345
+ if not inFiles:
346
+ return False
347
+
348
+ # 체크아웃 명령 실행
349
+ edit_command = ['edit']
350
+
351
+ target_changelist = inChangelist
352
+ if not target_changelist:
353
+ new_changelist_info = self.create_new_changelist(inDescription=f"Auto-checkout for {len(inFiles)} files")
354
+ if not new_changelist_info:
355
+ print("Failed to create a new changelist for checkout.")
356
+ return False
357
+ target_changelist = new_changelist_info['id']
358
+
359
+ edit_command.extend(['-c', target_changelist])
360
+ edit_command.extend(inFiles)
361
+
362
+ result = self._run_command(edit_command)
363
+
364
+ if len(self.get_changelist_files(target_changelist)) == 0:
365
+ self.delete_changelist(target_changelist)
366
+
367
+ return True
368
+
369
+ def add_files(self, inFiles, inChangelist=None, inWorkSpace=None):
370
+ """
371
+ 지정한 파일들을 Perforce에 추가합니다.
372
+
373
+ Parameters:
374
+ inFiles (list): 추가할 파일 경로 리스트
375
+ inChangelist (str, optional): 파일을 추가할 체인지 리스트 ID
376
+ inWorkSpace (str, optional): 작업할 워크스페이스 이름
377
+
378
+ Returns:
379
+ bool: 성공 여부
380
+ """
381
+ if inWorkSpace == None:
382
+ if self.workspace == None:
383
+ print(f"워크스페이스가 없습니다.")
384
+ return False
385
+ else:
386
+ if not self.set_workspace(inWorkSpace):
387
+ print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.")
388
+ return False
389
+
390
+ if not inFiles:
391
+ return False
392
+
393
+ # 파일 추가 명령 실행
394
+ add_command = ['add']
395
+
396
+ target_changelist = inChangelist
397
+ if not target_changelist:
398
+ new_changelist_info = self.create_new_changelist(inDescription=f"Auto-add for {len(inFiles)} files")
399
+ if not new_changelist_info:
400
+ print("Failed to create a new changelist for add.")
401
+ return False
402
+ target_changelist = new_changelist_info['id']
403
+
404
+ add_command.extend(['-c', target_changelist])
405
+ add_command.extend(inFiles)
406
+
407
+ result = self._run_command(add_command)
408
+
409
+ if len(self.get_changelist_files(target_changelist)) == 0:
410
+ self.delete_changelist(target_changelist)
411
+
412
+ return True
413
+
414
+ def delete_files(self, inFiles, inChangelist=None, inWorkSpace=None):
415
+ """
416
+ 지정한 파일들을 Perforce에서 삭제합니다.
417
+
418
+ Parameters:
419
+ inFiles (list): 삭제할 파일 경로 리스트
420
+ inChangelist (str, optional): 파일 삭제를 추가할 체인지 리스트 ID
421
+ inWorkSpace (str, optional): 작업할 워크스페이스 이름
422
+
423
+ Returns:
424
+ bool: 성공 여부
425
+ """
426
+ if inWorkSpace == None:
427
+ if self.workspace == None:
428
+ print(f"워크스페이스가 없습니다.")
429
+ return False
430
+ else:
431
+ if not self.set_workspace(inWorkSpace):
432
+ print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.")
433
+ return False
434
+
435
+ if not inFiles:
436
+ return False
437
+
438
+ # 파일 삭제 명령 실행
439
+ delete_command = ['delete']
440
+
441
+ target_changelist = inChangelist
442
+ if not target_changelist:
443
+ # If no changelist is specified, create a new one
444
+ new_changelist_info = self.create_new_changelist(inDescription=f"Auto-delete for {len(inFiles)} files")
445
+ if not new_changelist_info:
446
+ print("Failed to create a new changelist for delete.")
447
+ return False
448
+ target_changelist = new_changelist_info['id']
449
+
450
+ delete_command.extend(['-c', target_changelist])
451
+ delete_command.extend(inFiles)
452
+
453
+ result = self._run_command(delete_command)
454
+
455
+ if len(self.get_changelist_files(target_changelist)) == 0:
456
+ self.delete_changelist(target_changelist)
457
+
458
+ return True
459
+
460
+ def revert_changelist(self, inChangelist, inWorkSpace=None):
461
+ """
462
+ 특정 체인지 리스트의 모든 변경 사항을 되돌립니다 (revert).
463
+
464
+ Parameters:
465
+ inChangelist (str): 되돌릴 체인지 리스트 ID
466
+ inWorkSpace (str, optional): 작업할 워크스페이스 이름
467
+
468
+ Returns:
469
+ bool: 성공 여부
470
+ """
471
+ if inWorkSpace == None:
472
+ if self.workspace == None:
473
+ print(f"워크스페이스가 없습니다.")
474
+ return False
475
+ else:
476
+ if not self.set_workspace(inWorkSpace):
477
+ print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.")
478
+ return False
479
+
480
+ if not inChangelist:
481
+ print("Error: Changelist ID must be provided for revert operation.")
482
+ return False
483
+
484
+ # Revert 명령 실행
485
+ revert_command = ['revert', '-c', inChangelist, '//...'] # Revert all files in the changelist
486
+
487
+ result = self._run_command(revert_command)
488
+ # p4 revert might not return an error code even if nothing was reverted.
489
+ # We'll assume success if the command ran. More robust checking might involve parsing 'result'.
490
+ print(f"Revert result for CL {inChangelist}:\n{result}")
491
+ self._run_command(['change', '-d', inChangelist])
492
+ return True
493
+
494
+ def submit_changelist(self, inChangelist, inDescription=None, inWorkSpace=None):
495
+ """
496
+ 특정 체인지 리스트를 서버에 제출합니다.
497
+
498
+ Parameters:
499
+ inChangelist (str): 제출할 체인지 리스트 ID
500
+ inDescription (str, optional): 제출 설명 (없으면 체인지 리스트의 기존 설명 사용)
501
+ inWorkSpace (str, optional): 작업할 워크스페이스 이름
502
+
503
+ Returns:
504
+ bool: 성공 여부
505
+ """
506
+ if inWorkSpace == None:
507
+ if self.workspace == None:
508
+ print(f"워크스페이스가 없습니다.")
509
+ return False
510
+ else:
511
+ if not self.set_workspace(inWorkSpace):
512
+ print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.")
513
+ return False
514
+
515
+ if not inChangelist:
516
+ return False
517
+
518
+ if inDescription:
519
+ # 체인지 리스트 스펙 가져오기
520
+ change_spec = self._run_command(['change', '-o', inChangelist])
521
+
522
+ # 설명 수정
523
+ modified_spec = []
524
+ description_lines_skipped = False
525
+ for line in change_spec.splitlines():
526
+ if line.startswith('Description:'):
527
+ modified_spec.append(line)
528
+ # Add the new description, handling potential multi-line descriptions correctly
529
+ for desc_line in inDescription.splitlines():
530
+ modified_spec.append(f"\t{desc_line}")
531
+ description_lines_skipped = True # Start skipping old description lines
532
+ continue
533
+
534
+ # Skip old description lines (indented lines after Description:)
535
+ if description_lines_skipped:
536
+ if line.startswith('\t') or line.strip() == '':
537
+ continue # Skip indented lines or empty lines within the old description
538
+ else:
539
+ description_lines_skipped = False # Reached the next field
540
+
541
+ if not description_lines_skipped:
542
+ modified_spec.append(line)
543
+
544
+ print(modified_spec)
545
+
546
+ # 수정된 스펙 적용
547
+ spec_update = subprocess.run(['p4', 'change', '-i'],
548
+ input='\n'.join(modified_spec),
549
+ capture_output=True,
550
+ text=True,
551
+ encoding="utf-8")
552
+
553
+ if spec_update.returncode != 0:
554
+ print(f"Error updating changelist spec for {inChangelist}: {spec_update.stderr}")
555
+ return False
556
+
557
+ self._run_command(['submit', '-c', inChangelist])
558
+ self.delete_empty_changelists()
559
+ return True
560
+
561
+ def get_changelist_files(self, inChangelist, inWorkSpace=None):
562
+ """
563
+ 체인지 리스트에 포함된 파일 목록을 가져옵니다.
564
+
565
+ Parameters:
566
+ inChangelist (str): 체인지 리스트 ID
567
+ inWorkSpace (str, optional): 작업할 워크스페이스 이름
568
+
569
+ Returns:
570
+ list: 파일 정보 딕셔너리의 리스트
571
+ """
572
+ if inWorkSpace == None:
573
+ if self.workspace == None:
574
+ print(f"워크스페이스가 없습니다.")
575
+ return []
576
+ else:
577
+ if not self.set_workspace(inWorkSpace):
578
+ print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.")
579
+ return []
580
+
581
+ opened_result = self._run_command(['opened', '-c', inChangelist])
582
+ files = []
583
+
584
+ for line in opened_result.splitlines():
585
+ if '#' in line:
586
+ file_path = line.split('#')[0].strip()
587
+ action = line.split('for ')[1].split(' ')[0] if 'for ' in line else ''
588
+
589
+ files.append(file_path)
590
+
591
+ return files
592
+
593
+ def delete_changelist(self, inChangelist, inWorkSpace=None):
594
+ """
595
+ 빈 체인지 리스트를 삭제합니다.
596
+
597
+ Parameters:
598
+ inChangelist (str): 삭제할 체인지 리스트 ID
599
+ inWorkspace (str, optional): 작업할 워크스페이스 이름
600
+
601
+ Returns:
602
+ bool: 성공 여부
603
+ """
604
+ if inWorkSpace == None:
605
+ if self.workspace == None:
606
+ print(f"워크스페이스가 없습니다.")
607
+ return False
608
+ else:
609
+ if not self.set_workspace(inWorkSpace):
610
+ print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.")
611
+ return False
612
+
613
+ if not inChangelist:
614
+ print("Error: Changelist ID must be provided for delete operation.")
615
+ return False
616
+
617
+ # 체인지 리스트의 파일 목록을 확인합니다
618
+ files = self.get_changelist_files(inChangelist)
619
+ if files:
620
+ print(f"Error: Changelist {inChangelist} is not empty. It contains {len(files)} files.")
621
+ return False
622
+
623
+ # 빈 체인지 리스트를 삭제합니다
624
+ delete_result = self._run_command(['change', '-d', inChangelist])
625
+
626
+ if 'deleted' in delete_result:
627
+ return True
628
+ else:
629
+ return False
630
+
631
+ def delete_empty_changelists(self, inWorkSpace=None):
632
+ """
633
+ 빈 체인지 리스트를 삭제합니다.
634
+
635
+ Parameters:
636
+ inWorkSpace (str, optional): 작업할 워크스페이스 이름
637
+
638
+ Returns:
639
+ bool: 성공 여부
640
+ """
641
+ if inWorkSpace == None:
642
+ if self.workspace == None:
643
+ print(f"워크스페이스가 없습니다.")
644
+ return False
645
+ else:
646
+ if not self.set_workspace(inWorkSpace):
647
+ print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.")
648
+ return False
649
+
650
+ # 모든 체인지 리스트 가져오기
651
+ changes = self.get_changelists()
652
+
653
+ for change in changes:
654
+ if len(self.get_changelist_files(change['id'])) == 0:
655
+ self.delete_changelist(change['id'])
656
+
657
+ return True
658
+
659
+ def upload_files(self, inFiles, inDescription=None, inWorkSpace=None):
660
+ """
661
+ 지정한 파일들을 Perforce에 Submit 합니다.
662
+
663
+ 만약 파일들이 Depot에 존재하지 않으면 Add, 존재하면 Chekcout을 수행합니다.
664
+
665
+ Parameters:
666
+ inFiles (list): 업로드할 파일 경로 리스트
667
+ inWorkSpace (str, optional): 작업할 워크스페이스 이름
668
+
669
+ Returns:
670
+ bool: 성공 여부
671
+ """
672
+ if inWorkSpace == None:
673
+ if self.workspace == None:
674
+ print(f"워크스페이스가 없습니다.")
675
+ return False
676
+ else:
677
+ if not self.set_workspace(inWorkSpace):
678
+ print(f"워크스페이스 '{inWorkSpace}'는 로컬 워크스페이스 목록에 없습니다.")
679
+ return False
680
+
681
+ if not inFiles:
682
+ print("업로드할 파일이 지정되지 않았습니다.")
683
+ return False
684
+
685
+ # 문자열로 단일 경로가 주어진 경우 리스트로 변환
686
+ if isinstance(inFiles, str):
687
+ inFiles = [inFiles]
688
+
689
+ # 새 체인지리스트 생성
690
+ description = inDescription if inDescription else f"Auto-upload for {len(inFiles)} files"
691
+ new_changelist_info = self.create_new_changelist(inDescription=description)
692
+ if not new_changelist_info:
693
+ print("Failed to create a new changelist for file upload.")
694
+ return False
695
+
696
+ target_changelist = new_changelist_info['id']
697
+
698
+ # 파일들이 이미 디포에 있는지 확인
699
+ files_to_add = []
700
+ files_to_edit = []
701
+
702
+ for file_path in inFiles:
703
+ # 파일 상태 확인 (디포에 있는지 여부)
704
+ fstat_result = self._run_command(['fstat', file_path])
705
+
706
+ if 'no such file' in fstat_result.lower() or not fstat_result:
707
+ # 디포에 없는 파일 - 추가 대상
708
+ files_to_add.append(file_path)
709
+ else:
710
+ # 디포에 있는 파일 - 체크아웃 대상
711
+ files_to_edit.append(file_path)
712
+
713
+ # 파일 추가 (있는 경우)
714
+ if files_to_add:
715
+ add_command = ['add', '-c', target_changelist]
716
+ add_command.extend(files_to_add)
717
+ self._run_command(add_command)
718
+
719
+ # 파일 체크아웃 (있는 경우)
720
+ if files_to_edit:
721
+ edit_command = ['edit', '-c', target_changelist]
722
+ edit_command.extend(files_to_edit)
723
+ self._run_command(edit_command)
724
+
725
+ # 체인지리스트에 파일이 제대로 추가되었는지 확인
726
+ files_in_changelist = self.get_changelist_files(target_changelist)
727
+
728
+ if not files_in_changelist:
729
+ # 파일 추가에 실패한 경우 빈 체인지리스트 삭제
730
+ self.delete_changelist(target_changelist)
731
+ print("파일을 체인지리스트에 추가하는 데 실패했습니다.")
732
+ return False
733
+
734
+ print(f"파일 {len(files_in_changelist)}개가 체인지리스트 {target_changelist}에 추가되었습니다.")
735
+ return True