1313import tornado .locks
1414import datetime
1515
16+ from .log import get_logger
17+
1618
17- # Git configuration options exposed through the REST API
18- ALLOWED_OPTIONS = ['user.name' , 'user.email' ]
1919# Regex pattern to capture (key, value) of Git configuration options.
2020# See https://git-scm.com/docs/git-config#_syntax for git var syntax
2121CONFIG_PATTERN = re .compile (r"(?:^|\n)([\w\-\.]+)\=" )
2222DEFAULT_REMOTE_NAME = "origin"
23+ # Maximum number of character of command output to print in debug log
24+ MAX_LOG_OUTPUT = 500 # type: int
2325# How long to wait to be executed or finished your execution before timing out
2426MAX_WAIT_FOR_EXECUTE_S = 20
2527# Ensure on NFS or similar, that we give the .git/index.lock time to be removed
@@ -100,7 +102,7 @@ def call_subprocess(
100102
101103 try :
102104 await execution_lock .acquire (timeout = datetime .timedelta (seconds = MAX_WAIT_FOR_EXECUTE_S ))
103- except tornado .util .TimeoutError :
105+ except tornado .util .TimeoutError :
104106 return (1 , "" , "Unable to get the lock on the directory" )
105107
106108 try :
@@ -113,6 +115,7 @@ def call_subprocess(
113115
114116 # If the lock still exists at this point, we will likely fail anyway, but let's try anyway
115117
118+ get_logger ().debug ("Execute {!s} in {!s}." .format (cmdline , cwd ))
116119 if username is not None and password is not None :
117120 code , output , error = await call_subprocess_with_authentication (
118121 cmdline ,
@@ -126,6 +129,11 @@ def call_subprocess(
126129 code , output , error = await current_loop .run_in_executor (
127130 None , call_subprocess , cmdline , cwd , env
128131 )
132+ log_output = output [:MAX_LOG_OUTPUT ] + "..." if len (output ) > MAX_LOG_OUTPUT else output
133+ log_error = error [:MAX_LOG_OUTPUT ] + "..." if len (error ) > MAX_LOG_OUTPUT else error
134+ get_logger ().debug ("Code: {}\n Output: {}\n Error: {}" .format (code , log_output , log_error ))
135+ except BaseException :
136+ get_logger ().warning ("Fail to execute {!s}" .format (cmdline ), exc_info = True )
129137 finally :
130138 execution_lock .release ()
131139
@@ -158,9 +166,7 @@ async def config(self, top_repo_path, **kwargs):
158166
159167 if len (kwargs ):
160168 output = []
161- for k , v in filter (
162- lambda t : True if t [0 ] in ALLOWED_OPTIONS else False , kwargs .items ()
163- ):
169+ for k , v in kwargs .items ():
164170 cmd = ["git" , "config" , "--add" , k , v ]
165171 code , out , err = await execute (cmd , cwd = top_repo_path )
166172 output .append (out .strip ())
@@ -182,7 +188,7 @@ async def config(self, top_repo_path, **kwargs):
182188 else :
183189 raw = output .strip ()
184190 s = CONFIG_PATTERN .split (raw )
185- response ["options" ] = {k :v for k , v in zip (s [1 ::2 ], s [2 ::2 ]) if k in ALLOWED_OPTIONS }
191+ response ["options" ] = {k :v for k , v in zip (s [1 ::2 ], s [2 ::2 ])}
186192
187193 return response
188194
@@ -841,15 +847,20 @@ async def pull(self, curr_fb_path, auth=None, cancel_on_conflict=False):
841847
842848 return response
843849
844- async def push (self , remote , branch , curr_fb_path , auth = None ):
850+ async def push (self , remote , branch , curr_fb_path , auth = None , set_upstream = False ):
845851 """
846852 Execute `git push $UPSTREAM $BRANCH`. The choice of upstream and branch is up to the caller.
847- """
853+ """
854+ command = ["git" , "push" ]
855+ if set_upstream :
856+ command .append ("--set-upstream" )
857+ command .extend ([remote , branch ])
858+
848859 env = os .environ .copy ()
849860 if auth :
850861 env ["GIT_TERMINAL_PROMPT" ] = "1"
851862 code , _ , error = await execute (
852- [ "git" , "push" , remote , branch ] ,
863+ command ,
853864 username = auth ["username" ],
854865 password = auth ["password" ],
855866 cwd = os .path .join (self .root_dir , curr_fb_path ),
@@ -858,7 +869,7 @@ async def push(self, remote, branch, curr_fb_path, auth=None):
858869 else :
859870 env ["GIT_TERMINAL_PROMPT" ] = "0"
860871 code , _ , error = await execute (
861- [ "git" , "push" , remote , branch ] ,
872+ command ,
862873 env = env ,
863874 cwd = os .path .join (self .root_dir , curr_fb_path ),
864875 )
@@ -1125,7 +1136,7 @@ async def _is_binary(self, filename, ref, top_repo_path):
11251136 # For binary files, `--numstat` outputs two `-` characters separated by TABs:
11261137 return output .startswith ('-\t -\t ' )
11271138
1128- def remote_add (self , top_repo_path , url , name = DEFAULT_REMOTE_NAME ):
1139+ async def remote_add (self , top_repo_path , url , name = DEFAULT_REMOTE_NAME ):
11291140 """Handle call to `git remote add` command.
11301141
11311142 top_repo_path: str
@@ -1136,19 +1147,37 @@ def remote_add(self, top_repo_path, url, name=DEFAULT_REMOTE_NAME):
11361147 Remote name; default "origin"
11371148 """
11381149 cmd = ["git" , "remote" , "add" , name , url ]
1139- p = subprocess .Popen (cmd , stdout = subprocess .PIPE , stderr = subprocess .PIPE , cwd = top_repo_path )
1140- _ , my_error = p .communicate ()
1141- if p .returncode == 0 :
1142- return {
1143- "code" : p .returncode ,
1150+ code , _ , error = await execute (cmd , cwd = top_repo_path )
1151+ response = {
1152+ "code" : code ,
11441153 "command" : " " .join (cmd )
11451154 }
1146- else :
1147- return {
1148- "code" : p .returncode ,
1149- "command" : " " .join (cmd ),
1150- "message" : my_error .decode ("utf-8" ).strip ()
1155+
1156+ if code != 0 :
1157+ response ["message" ] = error
1158+
1159+ return response
1160+
1161+ async def remote_show (self , path ):
1162+ """Handle call to `git remote show` command.
1163+ Args:
1164+ path (str): Git repository path
1165+
1166+ Returns:
1167+ List[str]: Known remotes
1168+ """
1169+ command = ["git" , "remote" , "show" ]
1170+ code , output , error = await execute (command , cwd = path )
1171+ response = {
1172+ "code" : code ,
1173+ "command" : " " .join (command )
11511174 }
1175+ if code == 0 :
1176+ response ["remotes" ] = [r .strip () for r in output .splitlines ()]
1177+ else :
1178+ response ["message" ] = error
1179+
1180+ return response
11521181
11531182 async def ensure_gitignore (self , top_repo_path ):
11541183 """Handle call to ensure .gitignore file exists and the
0 commit comments