summaryrefslogtreecommitdiff
path: root/b4
diff options
context:
space:
mode:
authorKonstantin Ryabitsev <konstantin@linuxfoundation.org>2020-05-25 15:22:30 -0400
committerKonstantin Ryabitsev <konstantin@linuxfoundation.org>2020-05-25 15:22:30 -0400
commit59be08453137a3b9c6a25dc6787b5066a88a84cd (patch)
treedfc5acde52c5cd72895dcd9af7c46260f2a11e1a /b4
parent34bcb457865d0b58e08486b2ceb80e9067717e60 (diff)
downloadb4-59be08453137a3b9c6a25dc6787b5066a88a84cd.tar.gz
Add -3 to "b4 am" to prep for a 3way merge
The original code used for b4 diff was to prepare for a 3-way merge by making sure that all blob indexes exist in the local repo. Add this functionality to "b4 am" and document all the features added in the 0.5.0 branch. Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Diffstat (limited to 'b4')
-rw-r--r--b4/__init__.py115
-rw-r--r--b4/command.py3
-rw-r--r--b4/diff.py112
-rw-r--r--b4/mbox.py23
4 files changed, 135 insertions, 118 deletions
diff --git a/b4/__init__.py b/b4/__init__.py
index b6f25fe..3cdad1c 100644
--- a/b4/__init__.py
+++ b/b4/__init__.py
@@ -587,7 +587,7 @@ class LoreSeries:
return mbx
- def check_applies_clean(self, topdir, when=None):
+ def check_applies_clean(self, gitdir, when=None):
# Go through indexes and see if this series should apply cleanly
mismatches = 0
seenfiles = set()
@@ -603,7 +603,7 @@ class LoreSeries:
if set(bh) == {'0'}:
# New file, will for sure apply clean
continue
- fullpath = os.path.join(topdir, fn)
+ fullpath = os.path.join(gitdir, fn)
if when is None:
if not os.path.exists(fullpath):
mismatches += 1
@@ -611,7 +611,7 @@ class LoreSeries:
cmdargs = ['hash-object', fullpath]
ecode, out = git_run_command(None, cmdargs)
else:
- gitdir = os.path.join(topdir, '.git')
+ gitdir = os.path.join(gitdir, '.git')
logger.debug('Checking hash on %s:%s', when, fn)
# XXX: We should probably pipe the two commands instead of reading into memory,
# so something to consider for the future
@@ -632,6 +632,115 @@ class LoreSeries:
return len(seenfiles), mismatches
+ def make_fake_am_range(self, gitdir):
+ start_commit = end_commit = None
+ # Do we have it in cache already?
+ cachedir = get_cache_dir()
+ # Use the msgid of the first non-None patch in the series
+ msgid = None
+ for lmsg in self.patches:
+ if lmsg is not None:
+ msgid = lmsg.msgid
+ break
+ if msgid is None:
+ logger.critical('Cannot operate on an empty series')
+ return None, None
+ cachefile = os.path.join(cachedir, '%s.fakeam' % urllib.parse.quote_plus(msgid))
+ if os.path.exists(cachefile):
+ stalecache = False
+ with open(cachefile, 'r') as fh:
+ cachedata = fh.read()
+ chunks = cachedata.strip().split()
+ if len(chunks) == 2:
+ start_commit, end_commit = chunks
+ else:
+ stalecache = True
+ if start_commit is not None and end_commit is not None:
+ # Make sure they are still there
+ ecode, out = git_run_command(gitdir, ['cat-file', '-e', start_commit])
+ if ecode > 0:
+ stalecache = True
+ else:
+ ecode, out = git_run_command(gitdir, ['cat-file', '-e', end_commit])
+ if ecode > 0:
+ stalecache = True
+ else:
+ logger.debug('Using previously generated range')
+ return start_commit, end_commit
+
+ if stalecache:
+ logger.debug('Stale cache for [v%s] %s', self.revision, self.subject)
+ os.unlink(cachefile)
+
+ logger.info('Preparing fake-am for v%s: %s', self.revision, self.subject)
+ with git_temp_worktree(gitdir):
+ # We are in a temporary chdir at this time, so writing to a known file should be safe
+ mbxf = '.__git-am__'
+ mbx = mailbox.mbox(mbxf)
+ # Logic largely borrowed from gj_tools
+ seenfiles = set()
+ for lmsg in self.patches[1:]:
+ logger.debug('Looking at %s', lmsg.full_subject)
+ lmsg.load_hashes()
+ if not len(lmsg.blob_indexes):
+ logger.critical('ERROR: some patches do not have indexes')
+ logger.critical(' unable to create a fake-am range')
+ return None, None
+ for fn, fi in lmsg.blob_indexes:
+ if fn in seenfiles:
+ # We already processed this file, so this blob won't match
+ continue
+ seenfiles.add(fn)
+ if set(fi) == {'0'}:
+ # New file creation, nothing to do here
+ logger.debug(' New file: %s', fn)
+ continue
+ # Try to grab full ref_id of this hash
+ ecode, out = git_run_command(gitdir, ['rev-parse', fi])
+ if ecode > 0:
+ logger.critical(' ERROR: Could not find matching blob for %s (%s)', fn, fi)
+ logger.critical(' If you know on which tree this patchset is based,')
+ logger.critical(' add it as a remote and perform "git remote update"')
+ logger.critical(' in order to fetch the missing objects.')
+ return None, None
+ logger.debug(' Found matching blob for: %s', fn)
+ fullref = out.strip()
+ gitargs = ['update-index', '--add', '--cacheinfo', f'0644,{fullref},{fn}']
+ ecode, out = git_run_command(None, gitargs)
+ if ecode > 0:
+ logger.critical(' ERROR: Could not run update-index for %s (%s)', fn, fullref)
+ return None, None
+ mbx.add(lmsg.msg.as_string(policy=emlpolicy).encode('utf-8'))
+
+ mbx.close()
+ ecode, out = git_run_command(None, ['write-tree'])
+ if ecode > 0:
+ logger.critical('ERROR: Could not write fake-am tree')
+ return None, None
+ treeid = out.strip()
+ # At this point we have a worktree with files that should cleanly receive a git am
+ gitargs = ['commit-tree', treeid + '^{tree}', '-F', '-']
+ ecode, out = git_run_command(None, gitargs, stdin='Initial fake commit'.encode('utf-8'))
+ if ecode > 0:
+ logger.critical('ERROR: Could not commit-tree')
+ return None, None
+ start_commit = out.strip()
+ git_run_command(None, ['reset', '--hard', start_commit])
+ ecode, out = git_run_command(None, ['am', mbxf])
+ if ecode > 0:
+ logger.critical('ERROR: Could not fake-am version %s', self.revision)
+ return None, None
+ ecode, out = git_run_command(None, ['rev-parse', 'HEAD'])
+ end_commit = out.strip()
+ logger.info(' range: %.12s..%.12s', start_commit, end_commit)
+
+ with open(cachefile, 'w') as fh:
+ logger.debug('Saving into cache: %s', cachefile)
+ logger.debug(' %s..%s', start_commit, end_commit)
+ fh.write(f'{start_commit} {end_commit}\n')
+
+ return start_commit, end_commit
+
def save_cover(self, outfile):
cover_msg = self.patches[0].get_am_message(add_trailers=False, trailer_order=None)
with open(outfile, 'w') as fh:
diff --git a/b4/command.py b/b4/command.py
index 2964f54..d0d084e 100644
--- a/b4/command.py
+++ b/b4/command.py
@@ -108,6 +108,9 @@ def cmd():
'"-P *globbing*" to match on commit subject)')
sp_am.add_argument('-g', '--guess-base', dest='guessbase', action='store_true', default=False,
help='Try to guess the base of the series (if not specified)')
+ sp_am.add_argument('-3', '--prep-3way', dest='threeway', action='store_true', default=False,
+ help='Prepare for a 3-way merge '
+ '(tries to ensure that all index blobs exist by making a fake commit range)')
sp_am.set_defaults(func=cmd_am)
# b4 attest
diff --git a/b4/diff.py b/b4/diff.py
index 7ff4e47..ab23b0c 100644
--- a/b4/diff.py
+++ b/b4/diff.py
@@ -19,114 +19,6 @@ from tempfile import mkstemp
logger = b4.logger
-def make_fake_commit_range(gitdir, lser):
- start_commit = end_commit = None
- # Do we have it in cache already?
- cachedir = b4.get_cache_dir()
- # Use the msgid of the first non-None patch in the series
- msgid = None
- for lmsg in lser.patches:
- if lmsg is not None:
- msgid = lmsg.msgid
- break
- if msgid is None:
- logger.critical('Cannot operate on an empty series')
- return None, None
- cachefile = os.path.join(cachedir, '%s.fakeam' % urllib.parse.quote_plus(msgid))
- if os.path.exists(cachefile):
- stalecache = False
- with open(cachefile, 'r') as fh:
- cachedata = fh.read()
- chunks = cachedata.strip().split()
- if len(chunks) == 2:
- start_commit, end_commit = chunks
- else:
- stalecache = True
- if start_commit is not None and end_commit is not None:
- # Make sure they are still there
- ecode, out = b4.git_run_command(gitdir, ['cat-file', '-e', start_commit])
- if ecode > 0:
- stalecache = True
- else:
- ecode, out = b4.git_run_command(gitdir, ['cat-file', '-e', end_commit])
- if ecode > 0:
- stalecache = True
- else:
- logger.debug('Using previously generated range')
- return start_commit, end_commit
-
- if stalecache:
- logger.debug('Stale cache for [v%s] %s', lser.revision, lser.subject)
- os.unlink(cachefile)
-
- logger.info('Preparing fake-am for v%s: %s', lser.revision, lser.subject)
- with b4.git_temp_worktree(gitdir):
- # We are in a temporary chdir at this time, so writing to a known file should be safe
- mbxf = '.__git-am__'
- mbx = mailbox.mbox(mbxf)
- # Logic largely borrowed from gj_tools
- seenfiles = set()
- for lmsg in lser.patches[1:]:
- logger.debug('Looking at %s', lmsg.full_subject)
- lmsg.load_hashes()
- if not len(lmsg.blob_indexes):
- logger.critical('ERROR: some patches do not have indexes')
- logger.critical(' automatic range-diff would be misleading')
- return None, None
- for fn, fi in lmsg.blob_indexes:
- if fn in seenfiles:
- # We already processed this file, so this blob won't match
- continue
- seenfiles.add(fn)
- if set(fi) == {'0'}:
- # New file creation, nothing to do here
- logger.debug(' New file: %s', fn)
- continue
- # Try to grab full ref_id of this hash
- ecode, out = b4.git_run_command(gitdir, ['rev-parse', fi])
- if ecode > 0:
- logger.critical(' ERROR: Could not find matching blob for %s (%s)', fn, fi)
- # TODO: better handling
- return None, None
- logger.debug(' Found matching blob for: %s', fn)
- fullref = out.strip()
- gitargs = ['update-index', '--add', '--cacheinfo', f'0644,{fullref},{fn}']
- ecode, out = b4.git_run_command(None, gitargs)
- if ecode > 0:
- logger.critical(' ERROR: Could not run update-index for %s (%s)', fn, fullref)
- return None, None
- mbx.add(lmsg.msg.as_string(policy=b4.emlpolicy).encode('utf-8'))
-
- mbx.close()
- ecode, out = b4.git_run_command(None, ['write-tree'])
- if ecode > 0:
- logger.critical('ERROR: Could not write fake-am tree')
- return None, None
- treeid = out.strip()
- # At this point we have a worktree with files that should cleanly receive a git am
- gitargs = ['commit-tree', treeid + '^{tree}', '-F', '-']
- ecode, out = b4.git_run_command(None, gitargs, stdin='Initial fake commit'.encode('utf-8'))
- if ecode > 0:
- logger.critical('ERROR: Could not commit-tree')
- return None, None
- start_commit = out.strip()
- b4.git_run_command(None, ['reset', '--hard', start_commit])
- ecode, out = b4.git_run_command(None, ['am', mbxf])
- if ecode > 0:
- logger.critical('ERROR: Could not fake-am version %s', lser.revision)
- return None, None
- ecode, out = b4.git_run_command(None, ['rev-parse', 'HEAD'])
- end_commit = out.strip()
- logger.info(' range: %.12s..%.12s', start_commit, end_commit)
-
- with open(cachefile, 'w') as fh:
- logger.debug('Saving into cache: %s', cachefile)
- logger.debug(' %s..%s', start_commit, end_commit)
- fh.write(f'{start_commit} {end_commit}\n')
-
- return start_commit, end_commit
-
-
def diff_same_thread_series(cmdargs):
msgid = b4.get_msgid(cmdargs)
wantvers = cmdargs.wantvers
@@ -221,13 +113,13 @@ def main(cmdargs):
sys.exit(1)
# Prepare the lower fake-am range
- lsc, lec = make_fake_commit_range(cmdargs.gitdir, lser)
+ lsc, lec = lser.make_fake_am_range(gitdir=cmdargs.gitdir)
if lsc is None or lec is None:
logger.critical('---')
logger.critical('Could not create fake-am range for lower series v%s', lser.revision)
sys.exit(1)
# Prepare the upper fake-am range
- usc, uec = make_fake_commit_range(cmdargs.gitdir, user)
+ usc, uec = user.make_fake_am_range(gitdir=cmdargs.gitdir)
if usc is None or uec is None:
logger.critical('---')
logger.critical('Could not create fake-am range for upper series v%s', user.revision)
diff --git a/b4/mbox.py b/b4/mbox.py
index abcad74..50e1471 100644
--- a/b4/mbox.py
+++ b/b4/mbox.py
@@ -123,6 +123,23 @@ def mbox_to_am(mboxfile, cmdargs):
logger.critical(' From: %s <%s>', fname, femail)
logger.critical('NOTE: Rerun with -S to apply them anyway')
+ topdir = None
+ # Are we in a git tree and if so, what is our toplevel?
+ gitargs = ['rev-parse', '--show-toplevel']
+ lines = b4.git_get_command_lines(None, gitargs)
+ if len(lines) == 1:
+ topdir = lines[0]
+
+ if cmdargs.threeway:
+ if not topdir:
+ logger.critical('WARNING: cannot prepare 3-way (not in a git dir)')
+ elif not lser.complete:
+ logger.critical('WARNING: cannot prepare 3-way (series incomplete)')
+ else:
+ rstart, rend = lser.make_fake_am_range(gitdir=None)
+ if rstart and rend:
+ logger.info('Prepared a fake commit range for 3-way merge (%.12s..%.12s)', rstart, rend)
+
logger.critical('---')
if not lser.complete:
logger.critical('WARNING: Thread incomplete!')
@@ -165,11 +182,7 @@ def mbox_to_am(mboxfile, cmdargs):
logger.critical(' git am %s', am_filename)
else:
cleanmsg = ''
- # Are we in a git tree and if so, what is our toplevel?
- gitargs = ['rev-parse', '--show-toplevel']
- lines = b4.git_get_command_lines(None, gitargs)
- if len(lines) == 1:
- topdir = lines[0]
+ if topdir is not None:
checked, mismatches = lser.check_applies_clean(topdir)
if mismatches == 0 and checked != mismatches:
cleanmsg = ' (applies clean to current tree)'