@@ -244,6 +244,62 @@ def rename_local_untracked(self):
244244 os .rename (f , new_file_name )
245245 yield 'Renamed {} to {} to avoid conflict with upstream' .format (f , new_file_name )
246246
247+ def merge (self ):
248+ """
249+ Merges branch from origin into current branch, resolving conflicts when possible.
250+
251+ Resolves conflicts in two ways:
252+
253+ - Passes `-Xours` to git, setting merge-strategy to preserve changes made by the
254+ user whererver possible
255+ - Detect (modify/delete) conflicts, where the user has locally modified something
256+ that was deleted upstream. We just keep the local file.
257+ """
258+ modify_delete_conflict = False
259+ try :
260+ for line in execute_cmd ([
261+ 'git' ,
262+ '-c' , 'user.email=nbgitpuller@nbgitpuller.link' ,
263+ '-c' , 'user.name=nbgitpuller' ,
264+ 'merge' ,
265+ '-Xours' , 'origin/{}' .format (self .branch_name )
266+ ],
267+ cwd = self .repo_dir ):
268+ yield line
269+ # Detect conflict caused by one branch
270+ if line .startswith ("CONFLICT (modify/delete)" ):
271+ modify_delete_conflict = True
272+ except subprocess .CalledProcessError :
273+ if not modify_delete_conflict :
274+ raise
275+
276+ if modify_delete_conflict :
277+ yield "Caught modify/delete conflict, trying to resolve"
278+ # If a file was deleted on one branch, and modified on another,
279+ # we just keep the modified file. This is done by `git add`ing it.
280+ yield from self .commit_all ()
281+
282+ def commit_all (self ):
283+ """
284+ Creates a new commit with all current changes
285+ """
286+ yield from execute_cmd ([
287+ 'git' ,
288+ # We explicitly set user info of the commits we are making, to keep that separate from
289+ # whatever author info is set in system / repo config by the user. We pass '-c' to git
290+ # itself (rather than to 'git commit') to temporarily set config variables. This is
291+ # better than passing --author, since git treats author separately from committer.
292+ '-c' , 'user.email=nbgitpuller@nbgitpuller.link' ,
293+ '-c' , 'user.name=nbgitpuller' ,
294+ 'commit' ,
295+ '-am' , 'Automatic commit by nbgitpuller' ,
296+ # We allow empty commits. On NFS (at least), sometimes repo_is_dirty returns a false
297+ # positive, returning True even when there are no local changes (git diff-files seems to return
298+ # bogus output?). While ideally that would not happen, allowing empty commits keeps us
299+ # resilient to that issue.
300+ '--allow-empty'
301+ ], cwd = self .repo_dir )
302+
247303 def update (self ):
248304 """
249305 Do the pulling if necessary
@@ -266,34 +322,13 @@ def update(self):
266322 yield from execute_cmd (['git' , 'reset' , '--mixed' ], cwd = self .repo_dir )
267323
268324 # If there are local changes, make a commit so we can do merges when pulling
269- # We also allow empty commits. On NFS (at least), sometimes repo_is_dirty returns a false
270- # positive, returning True even when there are no local changes (git diff-files seems to return
271- # bogus output?). While ideally that would not happen, allowing empty commits keeps us
272- # resilient to that issue.
273- # We explicitly set user info of the commits we are making, to keep that separate from
274- # whatever author info is set in system / repo config by the user. We pass '-c' to git
275- # itself (rather than to 'git commit') to temporarily set config variables. This is
276- # better than passing --author, since git treats author separately from committer.
277325 if self .repo_is_dirty ():
278326 yield from self .ensure_lock ()
279- yield from execute_cmd ([
280- 'git' ,
281- '-c' , 'user.email=nbgitpuller@nbgitpuller.link' ,
282- '-c' , 'user.name=nbgitpuller' ,
283- 'commit' ,
284- '-am' , 'Automatic commit by nbgitpuller' ,
285- '--allow-empty'
286- ], cwd = self .repo_dir )
327+ yield from self .commit_all ()
287328
288329 # Merge master into local!
289330 yield from self .ensure_lock ()
290- yield from execute_cmd ([
291- 'git' ,
292- '-c' , 'user.email=nbgitpuller@nbgitpuller.link' ,
293- '-c' , 'user.name=nbgitpuller' ,
294- 'merge' ,
295- '-Xours' , 'origin/{}' .format (self .branch_name )
296- ], cwd = self .repo_dir )
331+ yield from self .merge ()
297332
298333
299334def main ():
0 commit comments