diff options
Diffstat (limited to 'scripts')
-rwxr-xr-x | scripts/finddeclarations.pl | 50 | ||||
-rwxr-xr-x | scripts/gen_vimdoc.py | 29 | ||||
-rw-r--r-- | scripts/genvimvim.lua | 9 | ||||
-rw-r--r-- | scripts/lintcommit.lua | 205 | ||||
-rw-r--r-- | scripts/lua2dox.lua | 95 | ||||
-rwxr-xr-x | scripts/pvscheck.sh | 4 | ||||
-rwxr-xr-x | scripts/release.sh | 15 | ||||
-rw-r--r-- | scripts/squash_typos.py | 223 | ||||
-rwxr-xr-x | scripts/vim-patch.sh | 24 | ||||
-rwxr-xr-x | scripts/vimpatch.lua | 4 |
10 files changed, 519 insertions, 139 deletions
diff --git a/scripts/finddeclarations.pl b/scripts/finddeclarations.pl deleted file mode 100755 index 1b1a57b9b7..0000000000 --- a/scripts/finddeclarations.pl +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -if ($ARGV[0] eq '--help') { - print << "EOF"; -Usage: - - $0 definitions.c -EOF - exit; -} - -my ($cfname, $sfname, $gfname, $cpp) = @ARGV; - -my $F; - -open $F, "<", $cfname; - -my $text = join "", <$F>; - -close $F; - -my $s = qr/(?>\s*)/aso; -my $w = qr/(?>\w+)/aso; -my $argname = qr/$w(?:\[(?>\w+)\])?/aso; -my $type_regex = qr/(?:$w$s\**$s)+/aso; -my $arg_regex = qr/(?:$type_regex$s$argname)/aso; - -while ($text =~ / - (?<=\n) # Definition starts at the start of line - $type_regex # Return type - $s$w # Function name - $s\($s - (?: - $arg_regex(?:$s,$s$arg_regex)*+ - ($s,$s\.\.\.)? # varargs function - |void - )? - $s\) - (?:$s FUNC_ATTR_$w(?:\((?>[^)]*)\))?)*+ # Optional attributes - (?=$s;) # Ending semicolon - /axsogp) { - my $match = "${^MATCH}"; - my $s = "${^PREMATCH}"; - $s =~ s/[^\n]++//g; - my $line = 1 + length $s; - print "${cfname}:${line}: $match\n"; -} diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py index 18a2839702..36e01153f1 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -123,11 +123,13 @@ CONFIG = { 'vim.lua', 'shared.lua', 'uri.lua', + 'ui.lua', ], 'files': ' '.join([ os.path.join(base_dir, 'src/nvim/lua/vim.lua'), os.path.join(base_dir, 'runtime/lua/vim/shared.lua'), os.path.join(base_dir, 'runtime/lua/vim/uri.lua'), + os.path.join(base_dir, 'runtime/lua/vim/ui.lua'), ]), 'file_patterns': '*.lua', 'fn_name_prefix': '', @@ -141,6 +143,7 @@ CONFIG = { # `shared` functions are exposed on the `vim` module. 'shared': 'vim', 'uri': 'vim', + 'ui': 'vim.ui', }, 'append_only': [ 'shared.lua', @@ -187,6 +190,23 @@ CONFIG = { 'module_override': {}, 'append_only': [], }, + 'diagnostic': { + 'mode': 'lua', + 'filename': 'diagnostic.txt', + 'section_start_token': '*diagnostic-api*', + 'section_order': [ + 'diagnostic.lua', + ], + 'files': os.path.join(base_dir, 'runtime/lua/vim/diagnostic.lua'), + 'file_patterns': '*.lua', + 'fn_name_prefix': '', + 'section_name': {'diagnostic.lua': 'diagnostic'}, + 'section_fmt': lambda _: 'Lua module: vim.diagnostic', + 'helptag_fmt': lambda _: '*diagnostic-api*', + 'fn_helptag_fmt': lambda fstem, name: f'*vim.{fstem}.{name}()*', + 'module_override': {}, + 'append_only': [], + }, 'treesitter': { 'mode': 'lua', 'filename': 'treesitter.txt', @@ -197,7 +217,6 @@ CONFIG = { 'query.lua', 'highlighter.lua', 'languagetree.lua', - 'health.lua', ], 'files': ' '.join([ os.path.join(base_dir, 'runtime/lua/vim/treesitter.lua'), @@ -950,6 +969,8 @@ def main(config, args): os.remove(mpack_file) output_dir = out_dir.format(target=target) + log.info("Generating documentation for %s in folder %s", + target, output_dir) debug = args.log_level >= logging.DEBUG p = subprocess.Popen( ['doxygen', '-'], @@ -1105,7 +1126,8 @@ def filter_source(filename): def parse_args(): targets = ', '.join(CONFIG.keys()) - ap = argparse.ArgumentParser() + ap = argparse.ArgumentParser( + description="Generate helpdoc from source code") ap.add_argument( "--log-level", "-l", choices=LOG_LEVELS.keys(), default=logging.getLevelName(logging.ERROR), help="Set log verbosity" @@ -1128,7 +1150,7 @@ Doxyfile = textwrap.dedent(''' INPUT_FILTER = "{filter}" EXCLUDE = EXCLUDE_SYMLINKS = NO - EXCLUDE_PATTERNS = */private/* + EXCLUDE_PATTERNS = */private/* */health.lua */_*.lua EXCLUDE_SYMBOLS = EXTENSION_MAPPING = lua=C EXTRACT_PRIVATE = NO @@ -1159,6 +1181,7 @@ if __name__ == "__main__": print("Setting log level to %s" % args.log_level) args.log_level = LOG_LEVELS[args.log_level] log.setLevel(args.log_level) + log.addHandler(logging.StreamHandler()) if len(args.source_filter) > 0: filter_source(args.source_filter[0]) diff --git a/scripts/genvimvim.lua b/scripts/genvimvim.lua index 2c3701bf0c..ff60b6cce7 100644 --- a/scripts/genvimvim.lua +++ b/scripts/genvimvim.lua @@ -1,4 +1,4 @@ -mpack = require('mpack') +local mpack = require('mpack') if arg[1] == '--help' then print('Usage: lua genvimvim.lua src/nvim runtime/syntax/vim/generated.vim') @@ -52,7 +52,7 @@ local function is_autocmd_cmd(cmd) or cmd == 'doautoall') end -vimcmd_start = 'syn keyword vimCommand contained ' +local vimcmd_start = 'syn keyword vimCommand contained ' w(vimcmd_start) local prev_cmd = nil for _, cmd_desc in ipairs(ex_cmds.cmds) do @@ -123,9 +123,8 @@ end w('\n\nsyn case match') local vimfun_start = 'syn keyword vimFuncName contained ' w('\n\n' .. vimfun_start) -funcs = mpack.unpack(io.open(funcs_file, 'rb'):read("*all")) -local started = 0 -for name, def in pairs(funcs) do +local funcs = mpack.unpack(io.open(funcs_file, 'rb'):read("*all")) +for name, _ in pairs(funcs) do if name then if lld.line_length > 850 then w('\n' .. vimfun_start) diff --git a/scripts/lintcommit.lua b/scripts/lintcommit.lua new file mode 100644 index 0000000000..98f9da246c --- /dev/null +++ b/scripts/lintcommit.lua @@ -0,0 +1,205 @@ +-- Usage: +-- # verbose +-- nvim -es +"lua require('scripts.lintcommit').main()" +-- +-- # silent +-- nvim -es +"lua require('scripts.lintcommit').main({trace=false})" +-- +-- # self-test +-- nvim -es +"lua require('scripts.lintcommit')._test()" + +local M = {} + +local _trace = false + +-- Print message +local function p(s) + vim.cmd('set verbose=1') + vim.api.nvim_echo({{s, ''}}, false, {}) + vim.cmd('set verbose=0') +end + +local function die() + p('') + vim.cmd("cquit 1") +end + +-- Executes and returns the output of `cmd`, or nil on failure. +-- +-- Prints `cmd` if `trace` is enabled. +local function run(cmd, or_die) + if _trace then + p('run: '..vim.inspect(cmd)) + end + local rv = vim.trim(vim.fn.system(cmd)) or '' + if vim.v.shell_error ~= 0 then + if or_die then + p(rv) + die() + end + return nil + end + return rv +end + +-- Returns nil if the given commit message is valid, or returns a string +-- message explaining why it is invalid. +local function validate_commit(commit_message) + local commit_split = vim.split(commit_message, ":") + + -- Return true if the type is vim-patch since most of the normal rules don't + -- apply. + if commit_split[1] == "vim-patch" then + return nil + end + + -- Check that message isn't too long. + if commit_message:len() > 80 then + return [[Commit message is too long, a maximum of 80 characters is allowed.]] + end + + + if vim.tbl_count(commit_split) < 2 then + return [[Commit message does not include colons.]] + end + + local before_colon = commit_split[1] + local after_colon = commit_split[2] + + -- Check if commit introduces a breaking change. + if vim.endswith(before_colon, "!") then + before_colon = before_colon:sub(1, -2) + end + + -- Check if type is correct + local type = vim.split(before_colon, "%(")[1] + local allowed_types = {'build', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'test', 'chore', 'vim-patch'} + if not vim.tbl_contains(allowed_types, type) then + return string.format( + 'Invalid commit type "%s". Allowed types are:\n %s', + type, + vim.inspect(allowed_types)) + end + + -- Check if scope is empty + if before_colon:match("%(") then + local scope = vim.trim(before_colon:match("%((.*)%)")) + if scope == '' then + return [[Scope can't be empty.]] + end + end + + -- Check that description doesn't end with a period + if vim.endswith(after_colon, ".") then + return [[Description ends with a period (".").]] + end + + -- Check that description has exactly one whitespace after colon, followed by + -- a lowercase letter and then any number of letters. + if not string.match(after_colon, '^ %l%a*') then + return [[There should be one whitespace after the colon and the first letter should lowercase.]] + end + + return nil +end + +function M.main(opt) + _trace = not opt or not not opt.trace + + local branch = run({'git', 'branch', '--show-current'}, true) + -- TODO(justinmk): check $GITHUB_REF + local ancestor = run({'git', 'merge-base', 'origin/master', branch}) + if not ancestor then + ancestor = run({'git', 'merge-base', 'upstream/master', branch}) + end + local commits_str = run({'git', 'rev-list', ancestor..'..'..branch}, true) + + local commits = {} + for substring in commits_str:gmatch("%S+") do + table.insert(commits, substring) + end + + local failed = 0 + for _, commit_id in ipairs(commits) do + local msg = run({'git', 'show', '-s', '--format=%s' , commit_id}) + if vim.v.shell_error ~= 0 then + p('Invalid commit-id: '..commit_id..'"') + else + local invalid_msg = validate_commit(msg) + if invalid_msg then + failed = failed + 1 + p(string.format([[ +Invalid commit message: "%s" + Commit: %s + %s + See also: + https://github.com/neovim/neovim/blob/master/CONTRIBUTING.md#commit-messages + https://www.conventionalcommits.org/en/v1.0.0/ +]], + msg, + commit_id, + invalid_msg)) + end + end + end + + if failed > 0 then + die() -- Exit with error. + else + p('') + end +end + +function M._test() + -- message:expected_result + local test_cases = { + ['ci: normal message'] = true, + ['build: normal message'] = true, + ['docs: normal message'] = true, + ['feat: normal message'] = true, + ['fix: normal message'] = true, + ['perf: normal message'] = true, + ['refactor: normal message'] = true, + ['revert: normal message'] = true, + ['test: normal message'] = true, + ['chore: normal message'] = true, + ['ci(window): message with scope'] = true, + ['ci!: message with breaking change'] = true, + ['ci(tui)!: message with scope and breaking change'] = true, + ['vim-patch:8.2.3374: Pyret files are not recognized (#15642)'] = true, + ['vim-patch:8.1.1195,8.2.{3417,3419}'] = true, + [':no type before colon 1'] = false, + [' :no type before colon 2'] = false, + [' :no type before colon 3'] = false, + ['ci(empty description):'] = false, + ['ci(whitespace as description): '] = false, + ['docs(multiple whitespaces as description): '] = false, + ['ci no colon after type'] = false, + ['test: extra space after colon'] = false, + ['ci: tab after colon'] = false, + ['ci:no space after colon'] = false, + ['ci :extra space before colon'] = false, + ['refactor(): empty scope'] = false, + ['ci( ): whitespace as scope'] = false, + ['chore: period at end of sentence.'] = false, + ['ci: Starting sentence capitalized'] = false, + ['unknown: using unknown type'] = false, + ['chore: you\'re saying this commit message just goes on and on and on and on and on and on for way too long?'] = false, + } + + local failed = 0 + for message, expected in pairs(test_cases) do + local is_valid = (nil == validate_commit(message)) + if is_valid ~= expected then + failed = failed + 1 + p(string.format('[ FAIL ]: expected=%s, got=%s\n input: "%s"', expected, is_valid, message)) + end + end + + if failed > 0 then + die() -- Exit with error. + end + +end + +return M diff --git a/scripts/lua2dox.lua b/scripts/lua2dox.lua index 0b36a1e061..d110e34c6a 100644 --- a/scripts/lua2dox.lua +++ b/scripts/lua2dox.lua @@ -50,7 +50,7 @@ However I have put in a hack that will insert the "missing" close paren. The effect is that you will get the function documented, but not with the parameter list you might expect. ]] -function class(BaseClass, ClassInitialiser) +local function class(BaseClass, ClassInitialiser) local newClass = {} -- a new class newClass if not ClassInitialiser and type(BaseClass) == 'function' then ClassInitialiser = BaseClass @@ -68,15 +68,14 @@ function class(BaseClass, ClassInitialiser) -- expose a constructor which can be called by <classname>(<args>) local classMetatable = {} - classMetatable.__call = - function(class_tbl, ...) + classMetatable.__call = function(class_tbl, ...) local newInstance = {} setmetatable(newInstance,newClass) --if init then -- init(newInstance,...) if class_tbl.init then class_tbl.init(newInstance,...) - else + else -- make sure that any stuff from the base class is initialized! if BaseClass and BaseClass.init then BaseClass.init(newInstance, ...) @@ -85,10 +84,9 @@ function class(BaseClass, ClassInitialiser) return newInstance end newClass.init = ClassInitialiser - newClass.is_a = - function(this, klass) + newClass.is_a = function(this, klass) local thisMetatable = getmetatable(this) - while thisMetatable do + while thisMetatable do if thisMetatable == klass then return true end @@ -102,12 +100,13 @@ end --! \class TCore_Clock --! \brief a clock -TCore_Clock = class() +local TCore_Clock = class() --! \brief get the current time function TCore_Clock.GetTimeNow() - if os.gettimeofday then - return os.gettimeofday() + local gettimeofday = os.gettimeofday -- luacheck: ignore 143 Accessing an undefined field of a global variable. + if gettimeofday then + return gettimeofday() else return os.time() end @@ -134,20 +133,15 @@ function TCore_Clock.getTimeStamp(this,T0) end ---! \brief io to console ---! ---! pseudo class (no methods, just to keep documentation tidy) -TCore_IO = class() --- --! \brief write to stdout -function TCore_IO_write(Str) +local function TCore_IO_write(Str) if (Str) then io.write(Str) end end --! \brief write to stdout -function TCore_IO_writeln(Str) +local function TCore_IO_writeln(Str) if (Str) then io.write(Str) end @@ -156,16 +150,16 @@ end --! \brief trims a string -function string_trim(Str) +local function string_trim(Str) return Str:match("^%s*(.-)%s*$") end --! \brief split a string ---! +--! --! \param Str --! \param Pattern --! \returns table of string fragments -function string_split(Str, Pattern) +local function string_split(Str, Pattern) local splitStr = {} local fpat = "(.-)" .. Pattern local last_end = 1 @@ -187,7 +181,7 @@ end --! \class TCore_Commandline --! \brief reads/parses commandline -TCore_Commandline = class() +local TCore_Commandline = class() --! \brief constructor function TCore_Commandline.init(this) @@ -207,29 +201,21 @@ end ------------------------------- --! \brief file buffer ---! +--! --! an input file buffer -TStream_Read = class() +local TStream_Read = class() --! \brief get contents of file ---! +--! --! \param Filename name of file to read (or nil == stdin) function TStream_Read.getContents(this,Filename) + assert(Filename) -- get lines from file - local filecontents - if Filename then - -- syphon lines to our table - --TCore_Debug_show_var('Filename',Filename) - filecontents={} - for line in io.lines(Filename) do - table.insert(filecontents,line) - end - else - -- get stuff from stdin as a long string (with crlfs etc) - filecontents=io.read('*a') - -- make it a table of lines - filecontents = TString_split(filecontents,'[\n]') -- note this only works for unix files. - Filename = 'stdin' + -- syphon lines to our table + --TCore_Debug_show_var('Filename',Filename) + local filecontents={} + for line in io.lines(Filename) do + table.insert(filecontents,line) end if filecontents then @@ -278,7 +264,7 @@ function TStream_Read.eof(this) end --! \brief output stream -TStream_Write = class() +local TStream_Write = class() --! \brief constructor function TStream_Write.init(this) @@ -286,17 +272,17 @@ function TStream_Write.init(this) end --! \brief write immediately -function TStream_Write.write(this,Str) +function TStream_Write.write(_,Str) TCore_IO_write(Str) end --! \brief write immediately -function TStream_Write.writeln(this,Str) +function TStream_Write.writeln(_,Str) TCore_IO_writeln(Str) end --! \brief write immediately -function TStream_Write.writelnComment(this,Str) +function TStream_Write.writelnComment(_,Str) TCore_IO_write('// ZZ: ') TCore_IO_writeln(Str) end @@ -311,14 +297,14 @@ end --! \brief outout tail lines function TStream_Write.write_tailLines(this) - for k,line in ipairs(this.tailLine) do + for _,line in ipairs(this.tailLine) do TCore_IO_writeln(line) end TCore_IO_write('// Lua2DoX new eof') end --! \brief input filter -TLua2DoX_filter = class() +local TLua2DoX_filter = class() --! \brief allow us to do errormessages function TLua2DoX_filter.warning(this,Line,LineNo,Legend) @@ -371,12 +357,12 @@ local function checkComment4fn(Fn_magic,MagicLines) local macro,tail - for k,line in ipairs(magicLines) do + for _, line in ipairs(magicLines) do macro,tail = getMagicDirective(line) if macro == 'fn' then fn_magic = tail -- TCore_IO_writeln('// found fn "' .. fn_magic .. '"') - else + --else --TCore_IO_writeln('// not found fn "' .. line .. '"') end end @@ -385,8 +371,6 @@ local function checkComment4fn(Fn_magic,MagicLines) end --! \brief run the filter function TLua2DoX_filter.readfile(this,AppStamp,Filename) - local err - local inStream = TStream_Read() local outStream = TStream_Write() this.outStream = outStream -- save to this obj @@ -401,8 +385,9 @@ function TLua2DoX_filter.readfile(this,AppStamp,Filename) outStream:writelnTail('// #######################') outStream:writelnTail() - local state, offset = '', 0 - while not (err or inStream:eof()) do + local state = '' -- luacheck: ignore 231 variable is set but never accessed. + local offset = 0 + while not (inStream:eof()) do line = string_trim(inStream:getLine()) -- TCore_Debug_show_var('inStream',inStream) -- TCore_Debug_show_var('line',line ) @@ -427,7 +412,7 @@ function TLua2DoX_filter.readfile(this,AppStamp,Filename) line = string.sub(line,5) -- nibble head local comment = '' local closeSquare,hitend,thisComment - while (not err) and (not hitend) and (not inStream:eof()) do + while (not hitend) and (not inStream:eof()) do closeSquare = string.find(line,']]') if not closeSquare then -- need to look on another line thisComment = line .. '\n' @@ -544,7 +529,7 @@ function TLua2DoX_filter.readfile(this,AppStamp,Filename) end --! \brief this application -TApp = class() +local TApp = class() --! \brief constructor function TApp.init(this) @@ -556,16 +541,16 @@ function TApp.init(this) end function TApp.getRunStamp(this) - return this.name .. ' (' .. this.version .. ') ' + return this.name .. ' (' .. this.version .. ') ' .. this.timestamp end function TApp.getVersion(this) - return this.name .. ' (' .. this.version .. ') ' + return this.name .. ' (' .. this.version .. ') ' end function TApp.getCopyright(this) - return this.copyright + return this.copyright end local This_app = TApp() diff --git a/scripts/pvscheck.sh b/scripts/pvscheck.sh index f3371b485e..904ff81700 100755 --- a/scripts/pvscheck.sh +++ b/scripts/pvscheck.sh @@ -373,13 +373,13 @@ run_analysis() {( analyze \ --lic-file PVS-Studio.lic \ --threads "$(get_jobs_num)" \ - --exclude-path src/nvim/xdiff \ + --exclude-path src/xdiff \ --output-file PVS-studio.log \ --file build/compile_commands.json \ --sourcetree-root . || true rm -rf PVS-studio.{xml,err,tsk,html.d} - local plog_args="PVS-studio.log --srcRoot . --excludedCodes V011" + local plog_args="PVS-studio.log --srcRoot . --excludedCodes V011,V1042,V1051,V1074" plog-converter $plog_args --renderTypes xml --output PVS-studio.xml plog-converter $plog_args --renderTypes errorfile --output PVS-studio.err plog-converter $plog_args --renderTypes tasklist --output PVS-studio.tsk diff --git a/scripts/release.sh b/scripts/release.sh index 4d1484b77a..4ec959d697 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -12,6 +12,7 @@ # - CMakeLists.txt: Unset NVIM_VERSION_PRERELEASE # - CMakeLists.txt: Unset NVIM_API_PRERELEASE # - Create test/functional/fixtures/api_level_N.mpack +# - Add date and version to runtime/nvim.appdata.xml # - Tag the commit. # Create the "version bump" commit: # - CMakeLists.txt: Set NVIM_VERSION_PRERELEASE to "-dev" @@ -62,6 +63,10 @@ _do_release_commit() { git add test/functional/fixtures/api_level_$__API_LEVEL.mpack fi + $__sed -i.bk 's,(<releases>),\1\ + <release date="'"${__DATE}"'" version="'"${__VERSION}"'"/>,' runtime/nvim.appdata.xml + git add runtime/nvim.appdata.xml + if ! test "$ARG1" = '--use-current-commit' ; then echo "Building changelog since ${__LAST_TAG}..." @@ -75,14 +80,12 @@ _do_release_commit() { _do_bump_commit() { $__sed -i.bk 's/(NVIM_VERSION_PRERELEASE) ""/\1 "-dev"/' CMakeLists.txt $__sed -i.bk 's/set\((NVIM_VERSION_PATCH) [[:digit:]]/set(\1 ?/' CMakeLists.txt - $__sed -i.bk 's,(<releases>),\1\ - <release date="'"${__DATE}"'" version="xxx"/>,' runtime/nvim.appdata.xml rm CMakeLists.txt.bk rm runtime/nvim.appdata.xml.bk nvim +'/NVIM_VERSION' +1new +'exe "norm! iUpdate version numbers!!!"' \ - -O CMakeLists.txt runtime/nvim.appdata.xml + -O CMakeLists.txt - git add CMakeLists.txt runtime/nvim.appdata.xml + git add CMakeLists.txt git commit -m "$__BUMP_MSG" } @@ -92,11 +95,7 @@ fi _do_bump_commit echo " Next steps: - - Update runtime/nvim.appdata.xml on _master_ - Run tests/CI (version_spec.lua)! - Push the tag: git push --follow-tags - - Update the 'stable' tag: - git push --force upstream HEAD^:refs/tags/stable - git fetch --tags - Update website: index.html" diff --git a/scripts/squash_typos.py b/scripts/squash_typos.py new file mode 100644 index 0000000000..26be6010a2 --- /dev/null +++ b/scripts/squash_typos.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python +""" + +This script squashes a PR tagged with the "typo" label into a single, dedicated +"squash PR". + +""" + +import subprocess +import sys +import os + + +def get_authors_and_emails_from_pr(): + """ + + Return all contributing authors and their emails for the PR on current branch. + This includes co-authors, meaning that if two authors are credited for a + single commit, which is possible with GitHub, then both will get credited. + + """ + + # Get a list of all authors involved in the pull request (including co-authors). + authors = subprocess.check_output( + ["gh", "pr", "view", "--json", "commits", "--jq", ".[][].authors.[].name"], + text=True, + ).splitlines() + + # Get a list of emails of the aforementioned authors. + emails = subprocess.check_output( + ["gh", "pr", "view", "--json", "commits", "--jq", ".[][].authors.[].email"], + text=True, + ).splitlines() + + authors_and_emails_unique = { + (author, mail) for author, mail in zip(authors, emails) + } + + return sorted(authors_and_emails_unique) + + +def rebase_squash_branch_onto_pr(): + """ + + Rebase current branch onto the PR. + + """ + + # Check out the pull request. + subprocess.call(["gh", "pr", "checkout", os.environ["PR_NUMBER"]]) + + # Rebase onto master + default_branch = f"{os.environ['GITHUB_BASE_REF']}" + subprocess.check_call(["git", "rebase", default_branch]) + + # Change back to the original branch. + subprocess.call(["git", "switch", "-"]) + + # Rebase onto the pull request, aka include the commits in the pull request + # in the current branch. Abort with error message if rebase fails. + + try: + subprocess.check_call(["git", "rebase", "-"]) + except subprocess.CalledProcessError: + subprocess.call(["git", "rebase", "--abort"]) + squash_url = subprocess.check_output( + ["gh", "pr", "view", "--json", "url", "--jq", ".url"], text=True + ).strip() + + subprocess.call( + [ + "gh", + "pr", + "comment", + os.environ["PR_NUMBER"], + "--body", + f"Your edit conflicts with an already scheduled fix \ + ({squash_url}). Please check that batch PR whether your fix is \ + already included; if not, then please wait until the batch PR \ + is merged and then rebase your PR on top of master.", + ] + ) + + sys.exit( + f"\n\nERROR: Your edit conflicts with an already scheduled fix \ +{squash_url} \n\n" + ) + + +def rebase_squash_branch_onto_master(): + """ + + Rebase current branch onto the master i.e. make sure current branch is up + to date. Abort on error. + + """ + + default_branch = f"{os.environ['GITHUB_BASE_REF']}" + subprocess.check_call(["git", "rebase", default_branch]) + + +def squash_all_commits(): + """ + + Squash all commits on the PR into a single commit. Credit all authors by + name and email. + + """ + + default_branch = f"{os.environ['GITHUB_BASE_REF']}" + subprocess.call(["git", "reset", "--soft", default_branch]) + + authors_and_emails = get_authors_and_emails_from_pr() + commit_message_coauthors = "\n" + "\n".join( + [f"Co-authored-by: {i[0]} <{i[1]}>" for i in authors_and_emails] + ) + subprocess.call( + ["git", "commit", "-m", "chore: typo fixes", "-m", commit_message_coauthors] + ) + + +def force_push(branch): + """ + + Like the name implies, force push <branch>. + + """ + + gh_actor = os.environ["GITHUB_ACTOR"] + gh_token = os.environ["GITHUB_TOKEN"] + gh_repo = os.environ["GITHUB_REPOSITORY"] + subprocess.call( + [ + "git", + "push", + "--force", + f"https://{gh_actor}:{gh_token}@github.com/{gh_repo}", + branch, + ] + ) + + +def checkout_branch(branch): + """ + + Create and checkout <branch>. Check if branch exists on remote, if so then + sync local branch to remote. + + Return True if remote branch exists, else False. + + """ + + # FIXME I'm not sure why the local branch isn't tracking the remote branch + # automatically. This works but I'm pretty sure it can be done in a more + # "elegant" fashion + + show_ref_output = subprocess.check_output(["git", "show-ref"], text=True).strip() + + if branch in show_ref_output: + subprocess.call(["git", "checkout", "-b", branch, f"origin/{branch}"]) + return True + + subprocess.call(["git", "checkout", "-b", branch]) + return False + + +def get_all_pr_urls(squash_branch_exists): + """ + + Return a list of URLs for the pull requests with the typo fixes. If a + squash branch exists then extract the URLs from the body text. + + """ + + all_pr_urls = "" + if squash_branch_exists: + all_pr_urls += subprocess.check_output( + ["gh", "pr", "view", "--json", "body", "--jq", ".body"], text=True + ) + + all_pr_urls += subprocess.check_output( + ["gh", "pr", "view", os.environ["PR_NUMBER"], "--json", "url", "--jq", ".url"], + text=True, + ).strip() + + return all_pr_urls + + +def main(): + squash_branch = "marvim/squash-typos" + + squash_branch_exists = checkout_branch(squash_branch) + + rebase_squash_branch_onto_master() + force_push(squash_branch) + + rebase_squash_branch_onto_pr() + force_push(squash_branch) + + subprocess.call( + [ + "gh", + "pr", + "create", + "--fill", + "--head", + squash_branch, + "--title", + "chore: typo fixes (automated)", + ] + ) + + squash_all_commits() + force_push(squash_branch) + + all_pr_urls = get_all_pr_urls(squash_branch_exists) + subprocess.call(["gh", "pr", "edit", "--add-label", "typo", "--body", all_pr_urls]) + + subprocess.call(["gh", "pr", "close", os.environ["PR_NUMBER"]]) + + +if __name__ == "__main__": + main() diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 4fd9711619..f4b817dfff 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -190,7 +190,7 @@ preprocess_patch() { 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\S*\<\%(testdir/\)\@<!\%('"${na_src}"'\)@norm! d/\v(^diff)|%$
' +w +q "$file" # Remove unwanted Vim doc files. - local na_doc='channel\.txt\|netbeans\.txt\|os_\w\+\.txt\|term\.txt\|todo\.txt\|version\d\.txt\|sponsor\.txt\|intro\.txt\|tags' + local na_doc='channel\.txt\|netbeans\.txt\|os_\w\+\.txt\|term\.txt\|todo\.txt\|version\d\.txt\|vim9\.txt\|sponsor\.txt\|intro\.txt\|tags' 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/runtime/doc/\<\%('"${na_doc}"'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" # Remove "Last change ..." changes in doc files. @@ -326,12 +326,14 @@ stage_patch() { return $ret } -hub_pr() { - hub pull-request -m "$1" +gh_pr() { + gh pr create --title "$1" --body "$2" } git_hub_pr() { - git hub pull new -m "$1" + local pr_message + pr_message="$(printf '%s\n\n%s\n' "$1" "$2")" + git hub pull new -m "${pr_message}" } # shellcheck disable=SC2015 @@ -341,14 +343,14 @@ submit_pr() { local push_first push_first=1 local submit_fn - if check_executable hub; then - submit_fn="hub_pr" + if check_executable gh; then + submit_fn="gh_pr" elif check_executable git-hub; then push_first=0 submit_fn="git_hub_pr" else - >&2 echo "${BASENAME}: 'hub' or 'git-hub' not found in PATH or not executable." - >&2 echo " Get it here: https://hub.github.com/" + >&2 echo "${BASENAME}: 'gh' or 'git-hub' not found in PATH or not executable." + >&2 echo " Get it here: https://cli.github.com/" exit 1 fi @@ -371,9 +373,7 @@ submit_pr() { patches=(${patches[@]//vim-patch:}) # Remove 'vim-patch:' prefix for each item in array. local pr_title="${patches[*]}" # Create space-separated string from array. pr_title="${pr_title// /,}" # Replace spaces with commas. - - local pr_message - pr_message="$(printf 'vim-patch:%s\n\n%s\n' "${pr_title#,}" "${pr_body}")" + pr_title="$(printf 'vim-patch:%s' "${pr_title#,}")" if [[ $push_first -ne 0 ]]; then echo "Pushing to 'origin/${checked_out_branch}'." @@ -385,7 +385,7 @@ submit_pr() { fi echo "Creating pull request." - output="$(${submit_fn} "${pr_message}" 2>&1)" && + output="$(${submit_fn} "${pr_title}" "${pr_body}" 2>&1)" && msg_ok "${output}" || (msg_err "${output}"; false) diff --git a/scripts/vimpatch.lua b/scripts/vimpatch.lua index 0924f3d718..11eb285462 100755 --- a/scripts/vimpatch.lua +++ b/scripts/vimpatch.lua @@ -5,10 +5,6 @@ local nvim = vim.api -local function pprint(o) - print(nvim.nvim_call_function('string', { o })) -end - local function systemlist(...) local rv = nvim.nvim_call_function('systemlist', ...) local err = nvim.nvim_get_vvar('shell_error') |