13
13
import tornado .locks
14
14
import datetime
15
15
16
+ from .log import get_logger
17
+
16
18
17
- # Git configuration options exposed through the REST API
18
- ALLOWED_OPTIONS = ['user.name' , 'user.email' ]
19
19
# Regex pattern to capture (key, value) of Git configuration options.
20
20
# See https://git-scm.com/docs/git-config#_syntax for git var syntax
21
21
CONFIG_PATTERN = re .compile (r"(?:^|\n)([\w\-\.]+)\=" )
22
22
DEFAULT_REMOTE_NAME = "origin"
23
+ # Maximum number of character of command output to print in debug log
24
+ MAX_LOG_OUTPUT = 500 # type: int
23
25
# How long to wait to be executed or finished your execution before timing out
24
26
MAX_WAIT_FOR_EXECUTE_S = 20
25
27
# Ensure on NFS or similar, that we give the .git/index.lock time to be removed
@@ -100,7 +102,7 @@ def call_subprocess(
100
102
101
103
try :
102
104
await execution_lock .acquire (timeout = datetime .timedelta (seconds = MAX_WAIT_FOR_EXECUTE_S ))
103
- except tornado .util .TimeoutError :
105
+ except tornado .util .TimeoutError :
104
106
return (1 , "" , "Unable to get the lock on the directory" )
105
107
106
108
try :
@@ -113,6 +115,7 @@ def call_subprocess(
113
115
114
116
# If the lock still exists at this point, we will likely fail anyway, but let's try anyway
115
117
118
+ get_logger ().debug ("Execute {!s} in {!s}." .format (cmdline , cwd ))
116
119
if username is not None and password is not None :
117
120
code , output , error = await call_subprocess_with_authentication (
118
121
cmdline ,
@@ -126,6 +129,11 @@ def call_subprocess(
126
129
code , output , error = await current_loop .run_in_executor (
127
130
None , call_subprocess , cmdline , cwd , env
128
131
)
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 )
129
137
finally :
130
138
execution_lock .release ()
131
139
@@ -158,9 +166,7 @@ async def config(self, top_repo_path, **kwargs):
158
166
159
167
if len (kwargs ):
160
168
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 ():
164
170
cmd = ["git" , "config" , "--add" , k , v ]
165
171
code , out , err = await execute (cmd , cwd = top_repo_path )
166
172
output .append (out .strip ())
@@ -182,7 +188,7 @@ async def config(self, top_repo_path, **kwargs):
182
188
else :
183
189
raw = output .strip ()
184
190
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 ])}
186
192
187
193
return response
188
194
@@ -841,15 +847,20 @@ async def pull(self, curr_fb_path, auth=None, cancel_on_conflict=False):
841
847
842
848
return response
843
849
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 ):
845
851
"""
846
852
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
+
848
859
env = os .environ .copy ()
849
860
if auth :
850
861
env ["GIT_TERMINAL_PROMPT" ] = "1"
851
862
code , _ , error = await execute (
852
- [ "git" , "push" , remote , branch ] ,
863
+ command ,
853
864
username = auth ["username" ],
854
865
password = auth ["password" ],
855
866
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):
858
869
else :
859
870
env ["GIT_TERMINAL_PROMPT" ] = "0"
860
871
code , _ , error = await execute (
861
- [ "git" , "push" , remote , branch ] ,
872
+ command ,
862
873
env = env ,
863
874
cwd = os .path .join (self .root_dir , curr_fb_path ),
864
875
)
@@ -1125,7 +1136,7 @@ async def _is_binary(self, filename, ref, top_repo_path):
1125
1136
# For binary files, `--numstat` outputs two `-` characters separated by TABs:
1126
1137
return output .startswith ('-\t -\t ' )
1127
1138
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 ):
1129
1140
"""Handle call to `git remote add` command.
1130
1141
1131
1142
top_repo_path: str
@@ -1136,19 +1147,37 @@ def remote_add(self, top_repo_path, url, name=DEFAULT_REMOTE_NAME):
1136
1147
Remote name; default "origin"
1137
1148
"""
1138
1149
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 ,
1144
1153
"command" : " " .join (cmd )
1145
1154
}
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 )
1151
1174
}
1175
+ if code == 0 :
1176
+ response ["remotes" ] = [r .strip () for r in output .splitlines ()]
1177
+ else :
1178
+ response ["message" ] = error
1179
+
1180
+ return response
1152
1181
1153
1182
async def ensure_gitignore (self , top_repo_path ):
1154
1183
"""Handle call to ensure .gitignore file exists and the
0 commit comments