30
30
CHECK_LOCK_INTERVAL_S = 0.1
31
31
# Parse Git version output
32
32
GIT_VERSION_REGEX = re .compile (r"^git\sversion\s(?P<version>\d+(.\d+)*)" )
33
+ # Parse Git branch status
34
+ GIT_BRANCH_STATUS = re .compile (
35
+ r"^## (?P<branch>([\w\-/]+|HEAD \(no branch\)|No commits yet on \w+))(\.\.\.(?P<remote>[\w\-/]+)( \[(ahead (?P<ahead>\d+))?(, )?(behind (?P<behind>\d+))?\])?)?$"
36
+ )
33
37
34
38
execution_lock = tornado .locks .Lock ()
35
39
@@ -272,7 +276,7 @@ async def clone(self, current_path, repo_url, auth=None):
272
276
env = os .environ .copy ()
273
277
if auth :
274
278
env ["GIT_TERMINAL_PROMPT" ] = "1"
275
- code , _ , error = await execute (
279
+ code , output , error = await execute (
276
280
["git" , "clone" , unquote (repo_url ), "-q" ],
277
281
username = auth ["username" ],
278
282
password = auth ["password" ],
@@ -281,28 +285,50 @@ async def clone(self, current_path, repo_url, auth=None):
281
285
)
282
286
else :
283
287
env ["GIT_TERMINAL_PROMPT" ] = "0"
284
- code , _ , error = await execute (
288
+ code , output , error = await execute (
285
289
["git" , "clone" , unquote (repo_url )],
286
290
cwd = os .path .join (self .root_dir , current_path ),
287
291
env = env ,
288
292
)
289
293
290
- response = {"code" : code }
294
+ response = {"code" : code , "message" : output . strip () }
291
295
292
296
if code != 0 :
293
297
response ["message" ] = error .strip ()
294
298
295
299
return response
296
300
301
+ async def fetch (self , current_path ):
302
+ """
303
+ Execute git fetch command
304
+ """
305
+ cwd = os .path .join (self .root_dir , current_path )
306
+ # Start by fetching to get accurate ahead/behind status
307
+ cmd = [
308
+ "git" ,
309
+ "fetch" ,
310
+ "--all" ,
311
+ "--prune" ,
312
+ ] # Run prune by default to help beginners
313
+
314
+ code , _ , fetch_error = await execute (cmd , cwd = cwd )
315
+
316
+ result = {
317
+ "code" : code ,
318
+ }
319
+ if code != 0 :
320
+ result ["command" ] = " " .join (cmd )
321
+ result ["error" ] = fetch_error
322
+
323
+ return result
324
+
297
325
async def status (self , current_path ):
298
326
"""
299
327
Execute git status command & return the result.
300
328
"""
301
- cmd = ["git" , "status" , "--porcelain" , "-u" , "-z" ]
302
- code , my_output , my_error = await execute (
303
- cmd ,
304
- cwd = os .path .join (self .root_dir , current_path ),
305
- )
329
+ cwd = os .path .join (self .root_dir , current_path )
330
+ cmd = ["git" , "status" , "--porcelain" , "-b" , "-u" , "-z" ]
331
+ code , status , my_error = await execute (cmd , cwd = cwd )
306
332
307
333
if code != 0 :
308
334
return {
@@ -320,32 +346,60 @@ async def status(self, current_path):
320
346
"--cached" ,
321
347
"4b825dc642cb6eb9a060e54bf8d69288fbee4904" ,
322
348
]
323
- text_code , text_output , _ = await execute (
324
- command ,
325
- cwd = os .path .join (self .root_dir , current_path ),
326
- )
349
+ text_code , text_output , _ = await execute (command , cwd = cwd )
327
350
328
351
are_binary = dict ()
329
352
if text_code == 0 :
330
353
for line in filter (lambda l : len (l ) > 0 , strip_and_split (text_output )):
331
354
diff , name = line .rsplit ("\t " , maxsplit = 1 )
332
355
are_binary [name ] = diff .startswith ("-\t -" )
333
356
357
+ data = {
358
+ "code" : code ,
359
+ "branch" : None ,
360
+ "remote" : None ,
361
+ "ahead" : 0 ,
362
+ "behind" : 0 ,
363
+ "files" : [],
364
+ }
334
365
result = []
335
- line_iterable = (line for line in strip_and_split (my_output ) if line )
336
- for line in line_iterable :
337
- name = line [3 :]
338
- result .append (
339
- {
340
- "x" : line [0 ],
341
- "y" : line [1 ],
342
- "to" : name ,
343
- # if file was renamed, next line contains original path
344
- "from" : next (line_iterable ) if line [0 ] == "R" else name ,
345
- "is_binary" : are_binary .get (name , None ),
346
- }
347
- )
348
- return {"code" : code , "files" : result }
366
+ line_iterable = (line for line in strip_and_split (status ) if line )
367
+
368
+ try :
369
+ first_line = next (line_iterable )
370
+ # Interpret branch line
371
+ match = GIT_BRANCH_STATUS .match (first_line )
372
+ if match is not None :
373
+ d = match .groupdict ()
374
+ branch = d .get ("branch" )
375
+ if branch == "HEAD (no branch)" :
376
+ branch = "(detached)"
377
+ elif branch .startswith ("No commits yet on " ):
378
+ branch = "(initial)"
379
+ data ["branch" ] = branch
380
+ data ["remote" ] = d .get ("remote" )
381
+ data ["ahead" ] = int (d .get ("ahead" ) or 0 )
382
+ data ["behind" ] = int (d .get ("behind" ) or 0 )
383
+
384
+ # Interpret file lines
385
+ for line in line_iterable :
386
+ name = line [3 :]
387
+ result .append (
388
+ {
389
+ "x" : line [0 ],
390
+ "y" : line [1 ],
391
+ "to" : name ,
392
+ # if file was renamed, next line contains original path
393
+ "from" : next (line_iterable ) if line [0 ] == "R" else name ,
394
+ "is_binary" : are_binary .get (name , None ),
395
+ }
396
+ )
397
+
398
+ data ["files" ] = result
399
+ except StopIteration : # Raised if line_iterable is empty
400
+ pass
401
+
402
+ return data
349
403
350
404
async def log (self , current_path , history_count = 10 ):
351
405
"""
@@ -863,7 +917,7 @@ async def pull(self, curr_fb_path, auth=None, cancel_on_conflict=False):
863
917
cwd = os .path .join (self .root_dir , curr_fb_path ),
864
918
)
865
919
866
- response = {"code" : code }
920
+ response = {"code" : code , "message" : output . strip () }
867
921
868
922
if code != 0 :
869
923
output = output .strip ()
@@ -901,7 +955,7 @@ async def push(self, remote, branch, curr_fb_path, auth=None, set_upstream=False
901
955
env = os .environ .copy ()
902
956
if auth :
903
957
env ["GIT_TERMINAL_PROMPT" ] = "1"
904
- code , _ , error = await execute (
958
+ code , output , error = await execute (
905
959
command ,
906
960
username = auth ["username" ],
907
961
password = auth ["password" ],
@@ -910,13 +964,13 @@ async def push(self, remote, branch, curr_fb_path, auth=None, set_upstream=False
910
964
)
911
965
else :
912
966
env ["GIT_TERMINAL_PROMPT" ] = "0"
913
- code , _ , error = await execute (
967
+ code , output , error = await execute (
914
968
command ,
915
969
env = env ,
916
970
cwd = os .path .join (self .root_dir , curr_fb_path ),
917
971
)
918
972
919
- response = {"code" : code }
973
+ response = {"code" : code , "message" : output . strip () }
920
974
921
975
if code != 0 :
922
976
response ["message" ] = error .strip ()
0 commit comments