Change DWIMery hack for dealing with rewound remote branch

The trick of looking at the reflog for the remote tracking branch
and only going back one commit works some of the time, but not all of
the time.  Its sort of relying on the fact that the user didn't use
`repo sync -n` or `git fetch` to only update the tracking branches
and skip the working directory update.

Doing this right requires looking through the history of the SHA-1
source (what the upstream used to be) and finding a spot where the
DAG diveraged away suddenly, and consider that to be the rewind
point.  That's really difficult to do, as we don't have a clear
picture of what that old point was.

A close approximation is to list all of the commits that are in
HEAD, but not the new upstream, and rebase all of those where the
committer email address is this user's email address.  In most cases,
this will effectively rebase only the user's new original work.

If the user is the project maintainer and rewound the branch
themselves, and they don't want all of the commits they have created
to be rebased onto the new upstream, they should handle the rebase
on their own, after the sync is complete.

Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce 2009-05-29 18:28:25 -07:00
parent d1f70d9929
commit 8ad8a0e61d

View File

@ -708,28 +708,28 @@ class Project(object):
syncbuf.later1(self, _doff) syncbuf.later1(self, _doff)
return return
if merge == rev: # If the upstream switched on us, warn the user.
try: #
old_merge = self.bare_git.rev_parse('%s@{1}' % merge) if merge != rev:
except GitError:
old_merge = merge
if old_merge == '0000000000000000000000000000000000000000' \
or old_merge == '':
old_merge = merge
else:
# The upstream switched on us. Time to cross our fingers
# and pray that the old upstream also wasn't in the habit
# of rebasing itself.
#
syncbuf.info(self, "manifest switched %s...%s", merge, rev) syncbuf.info(self, "manifest switched %s...%s", merge, rev)
old_merge = merge
if rev == old_merge: # Examine the local commits not in the remote. Find the
upstream_lost = [] # last one attributed to this user, if any.
else: #
upstream_lost = self._revlist(not_rev(rev), old_merge) local_changes = self._revlist(
not_rev(merge),
HEAD,
format='%H %ce')
if not upstream_lost and not upstream_gain: last_mine = None
cnt_mine = 0
for commit in local_changes:
commit_id, committer_email = commit.split(' ', 2)
if committer_email == self.UserEmail:
last_mine = commit_id
cnt_mine += 1
if not local_changes and not upstream_gain:
# Trivially no changes caused by the upstream. # Trivially no changes caused by the upstream.
# #
return return
@ -738,25 +738,24 @@ class Project(object):
syncbuf.fail(self, _DirtyError()) syncbuf.fail(self, _DirtyError())
return return
if upstream_lost: if cnt_mine < len(local_changes):
# Upstream rebased. Not everything in HEAD # Upstream rebased. Not everything in HEAD
# may have been caused by the user. # was created by this user.
# #
syncbuf.info(self, syncbuf.info(self,
"discarding %d commits removed from upstream", "discarding %d commits removed from upstream",
len(upstream_lost)) len(local_changes) - cnt_mine)
branch.remote = rem branch.remote = rem
branch.merge = self.revision branch.merge = self.revision
branch.Save() branch.Save()
my_changes = self._revlist(not_rev(old_merge), HEAD) if cnt_mine > 0:
if my_changes:
def _dorebase(): def _dorebase():
self._Rebase(upstream = old_merge, onto = rev) self._Rebase(upstream = '%s^1' % last_mine, onto = rev)
self._CopyFiles() self._CopyFiles()
syncbuf.later2(self, _dorebase) syncbuf.later2(self, _dorebase)
elif upstream_lost: elif local_changes:
try: try:
self._ResetHard(rev) self._ResetHard(rev)
self._CopyFiles() self._CopyFiles()
@ -1141,11 +1140,11 @@ class Project(object):
def _gitdir_path(self, path): def _gitdir_path(self, path):
return os.path.join(self.gitdir, path) return os.path.join(self.gitdir, path)
def _revlist(self, *args): def _revlist(self, *args, **kw):
cmd = [] a = []
cmd.extend(args) a.extend(args)
cmd.append('--') a.append('--')
return self.work_git.rev_list(*args) return self.work_git.rev_list(*a, **kw)
@property @property
def _allrefs(self): def _allrefs(self):
@ -1270,8 +1269,11 @@ class Project(object):
self.update_ref('-d', name, old) self.update_ref('-d', name, old)
self._project.bare_ref.deleted(name) self._project.bare_ref.deleted(name)
def rev_list(self, *args): def rev_list(self, *args, **kw):
cmdv = ['rev-list'] if 'format' in kw:
cmdv = ['log', '--pretty=format:%s' % kw['format']]
else:
cmdv = ['rev-list']
cmdv.extend(args) cmdv.extend(args)
p = GitCommand(self._project, p = GitCommand(self._project,
cmdv, cmdv,
@ -1280,7 +1282,9 @@ class Project(object):
capture_stderr = True) capture_stderr = True)
r = [] r = []
for line in p.process.stdout: for line in p.process.stdout:
r.append(line[:-1]) if line[-1] == '\n':
line = line[:-1]
r.append(line)
if p.Wait() != 0: if p.Wait() != 0:
raise GitError('%s rev-list %s: %s' % ( raise GitError('%s rev-list %s: %s' % (
self._project.name, self._project.name,