Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,2 @@
blank_issues_enabled: false

contact_links:
- name: Questions or discussions about Midnight Commander
url: https://github.com/MidnightCommander/mc/discussions
about: Please ask for support and answer questions here

- name: List for Midnight Commander users
url: https://lists.midnight-commander.org/mailman/listinfo/mc
about: If you prefer mailing lists, post your questions to the users list
- name: List for Midnight Commander developers
url: https://lists.midnight-commander.org/mailman/listinfo/mc-devel
about: If you prefer mailing lists, post development-related messages to the developers list
blank_issues_enabled: true
contact_links: []
2 changes: 1 addition & 1 deletion .github/workflows/issue-label-approve.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- run: |
gh issue edit \
${{ inputs.issue_number != '' && inputs.issue_number || github.event.issue.number }} \
--remove-label "state: in review"
--remove-label "state: in review" || true

gh issue comment \
${{ inputs.issue_number != '' && inputs.issue_number || github.event.issue.number }} \
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/issue-label-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- run: |
gh issue edit \
${{ inputs.issue_number != '' && inputs.issue_number || github.event.issue.number }} \
--remove-label "state: approved"
--remove-label "state: approved" || true

gh issue comment \
${{ inputs.issue_number != '' && inputs.issue_number || github.event.issue.number }} \
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-label-approve.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:

gh pr edit \
${{ inputs.issue_number != '' && inputs.issue_number || github.event.pull_request.number }} \
--remove-label "state: in review"
--remove-label "state: in review" || true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
111 changes: 111 additions & 0 deletions .github/workflows/pr-summary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: PR summary from commits

on:
pull_request:
types: [opened, reopened, synchronize, edited]

permissions:
contents: read
pull-requests: write

jobs:
summarize:
runs-on: ubuntu-latest
steps:
- name: Update PR body with commit summary
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const pr = context.payload.pull_request;

// For PRs from forks, GITHUB_TOKEN may be read-only (depends on repo settings).
// We'll try; if forbidden, the action will fail.
const prNumber = pr.number;

const markerStart = "<!-- pr-summary:start -->";
const markerEnd = "<!-- pr-summary:end -->";

// 1) Collect commits
const commits = await github.paginate(github.rest.pulls.listCommits, {
owner,
repo,
pull_number: prNumber,
per_page: 100
});

// 2) Collect changed files (limited to 300 for readability)
const files = await github.paginate(github.rest.pulls.listFiles, {
owner,
repo,
pull_number: prNumber,
per_page: 100
});

const commitLines = commits.map(c => {
const msg = (c.commit?.message || "").split("\n")[0].trim();
const sha = (c.sha || "").slice(0, 7);
return `- ${msg} (\`${sha}\`)`;
});

// Group files by status
const limit = 300;
const trimmedFiles = files.slice(0, limit);

const byStatus = new Map();
for (const f of trimmedFiles) {
const st = f.status || "modified";
if (!byStatus.has(st)) byStatus.set(st, []);
byStatus.get(st).push(f.filename);
}

const statusOrder = ["added", "modified", "removed", "renamed", "changed"];
const fileSectionParts = [];
for (const st of statusOrder) {
if (!byStatus.has(st)) continue;
const names = byStatus.get(st);
const shown = names.slice(0, 80);
const extra = names.length - shown.length;
const list = shown.map(n => ` - \`${n}\``).join("\n");
fileSectionParts.push(`**${st}** (${names.length})\n${list}${extra > 0 ? `\n - ... (+${extra} more)` : ""}`);
}

const commitCount = commits.length;
const fileCount = files.length;

const summaryBlock =
`${markerStart}\n` +
`## Auto summary\n\n` +
`**Commits:** ${commitCount} \n` +
`**Files changed:** ${fileCount}\n\n` +
`### Commit messages\n` +
`${commitLines.length ? commitLines.join("\n") : "_No commits found_"}\n\n` +
`### Changed files\n` +
`${fileSectionParts.length ? fileSectionParts.join("\n\n") : "_No files found_"}\n\n` +
`_This block is auto-generated. Edit outside this section._\n` +
`${markerEnd}\n`;

const currentBody = pr.body || "";

const hasMarkers = currentBody.includes(markerStart) && currentBody.includes(markerEnd);

let newBody;
if (hasMarkers) {
const startIdx = currentBody.indexOf(markerStart);
const endIdx = currentBody.indexOf(markerEnd) + markerEnd.length;
newBody = currentBody.slice(0, startIdx).trimEnd() + "\n\n" + summaryBlock + "\n" + currentBody.slice(endIdx).trimStart();
} else {
// Prepend summary
newBody = summaryBlock + "\n" + currentBody;
}

// Avoid unnecessary updates
if (newBody !== currentBody) {
await github.rest.pulls.update({
owner,
repo,
pull_number: prNumber,
body: newBody
});
}
2 changes: 1 addition & 1 deletion .github/workflows/process-closed-issues.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- run: |
ISSUE=${{ inputs.issue_number != '' && inputs.issue_number || github.event.issue.number }}

gh issue edit $ISSUE --remove-label "state: in review,state: approved"
gh issue edit $ISSUE --remove-label "state: in review,state: approved" || true

if [ "${{ github.event.issue.state_reason }}" = "not_planned" ]; then
gh issue edit $ISSUE --remove-milestone
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/process-closed-prs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
steps:
- run: |
gh pr edit ${{ inputs.pr_number != '' && inputs.pr_number || github.event.pull_request.number }} \
--remove-label "state: in review,state: approved"
--remove-label "state: in review,state: approved" || true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
Expand Down
25 changes: 22 additions & 3 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,20 @@ mc_BACKGROUND
mc_EXT2FS_ATTR
mc_VFS_CHECKS

AC_ARG_WITH([vfs-plugins-dir],
AS_HELP_STRING([--with-vfs-plugins-dir=DIR],
[Directory for dynamic VFS plugins [LIBDIR/mc/vfs-plugins]]),
[vfs_plugins_dir="$withval"],
[vfs_plugins_dir='${libdir}/mc/vfs-plugins'])
AC_SUBST([vfs_plugins_dir])

AC_ARG_WITH([panel-plugins-dir],
AS_HELP_STRING([--with-panel-plugins-dir=DIR],
[Directory for dynamic panel plugins [LIBDIR/mc/panel-plugins]]),
[panel_plugins_dir="$withval"],
[panel_plugins_dir='${libdir}/mc/panel-plugins'])
AC_SUBST([panel_plugins_dir])

dnl ############################################################################
dnl Directories
dnl ############################################################################
Expand Down Expand Up @@ -543,9 +557,8 @@ AM_CONDITIONAL(USE_INTERNAL_EDIT, [test x"$use_internal_edit" = xyes ])
AM_CONDITIONAL(USE_ASPELL, [test x"$enable_aspell" = xyes ])
AM_CONDITIONAL(USE_DIFF, [test -n "$use_diff"])
AM_CONDITIONAL(CONS_SAVER, [test -n "$cons_saver"])
dnl Clarify do we really need GModule
AM_CONDITIONAL([HAVE_GMODULE], [test -n "$g_module_supported" && \
test x"$textmode_x11_support" = x"yes" -o x"$enable_aspell" = x"yes"])
dnl Enable GModule when available (used for X11, aspell, and dynamic VFS plugins)
AM_CONDITIONAL([HAVE_GMODULE], [test -n "$g_module_supported"])

AC_ARG_ENABLE([configure-args],
AS_HELP_STRING([--enable-configure-args], [Embed ./configure arguments into binaries]))
Expand Down Expand Up @@ -588,6 +601,12 @@ src/viewer/Makefile
src/diffviewer/Makefile
src/filemanager/Makefile

src/panel-plugins/Makefile
src/panel-plugins/hello/Makefile
src/panel-plugins/systemd/Makefile
src/panel-plugins/sftp/Makefile
src/panel-plugins/docker/Makefile

src/vfs/Makefile

src/vfs/cpio/Makefile
Expand Down
16 changes: 15 additions & 1 deletion doc/INSTALL
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,11 @@ VFS options:

`--enable-vfs-sftp'
(auto)
Support for SFTP vfs
Support for SFTP vfs. When enabled, SFTP is built as a dynamic
plugin (mc-vfs-sftp.so) installed into the VFS plugins directory
(see --with-vfs-plugins-dir). Requires libssh2 >= 1.2.8.
The main mc binary does not link against libssh2; the plugin
carries that dependency itself.

`--enable-vfs-extfs'
(on by default)
Expand All @@ -186,6 +190,16 @@ VFS options:
(on by default)
Support for sfs

`--with-vfs-plugins-dir=DIR'
Set the directory where Midnight Commander looks for dynamic VFS
plugins at runtime [default=LIBDIR/mc/vfs-plugins]. A dynamic VFS
plugin is a shared object (.so) that exports the mc_vfs_plugin_init
symbol. When MC starts, it scans this directory and loads every
matching plugin, allowing third-party VFS modules (e.g. SMB, WebDAV)
to be added without recompiling MC. GModule (part of GLib) is
required for this feature; if unavailable, dynamic loading is
silently skipped.


Screen library:
- - - - - - - -
Expand Down
121 changes: 121 additions & 0 deletions doc/PLUGINS
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
Dynamic VFS plugins for GNU Midnight Commander
===============================================

Since version 4.8.33 Midnight Commander can load VFS modules at runtime
from shared objects (.so). This lets distributions package optional VFS
modules (and their external dependencies) separately from the core mc
binary.

How it works
------------

At startup, after initialising the built-in (static) VFS modules,
mc scans the directory specified by --with-vfs-plugins-dir (default:
${libdir}/mc/vfs-plugins). Every .so file found there is opened with
GModule (dlopen) and probed for the symbol:

void mc_vfs_plugin_init (void);

If the symbol is found it is called immediately. The plugin is expected
to register its VFS class via vfs_init_subclass() / vfs_register_class()
-- exactly the same way a statically linked module does.

The mc binary is linked with -export-dynamic so that plugin code can
call back into mc (vfs_init_subclass, tcp_init, query_dialog, etc.)
without linking those symbols explicitly.

GModule support (part of GLib) is required. If it is unavailable at
build time, dynamic loading is silently skipped.


Current dynamic plugins
-----------------------

mc-vfs-sftp.so SFTP filesystem (requires libssh2 >= 1.2.8)


Candidates for future conversion
---------------------------------

The following modules are currently compiled as static libraries and
linked into libmc-vfs.la. They are listed in order of conversion
difficulty.

Phase 1 -- no code changes needed (zero external symbol references):

tar tar archives no external deps
cpio cpio archives no external deps
extfs extension filesystem no external deps (uses helper scripts)
sfs single-file fs no external deps

These modules are fully self-contained: their init functions are only
called from src/vfs/plugins_init.c and no code outside src/vfs/
references their symbols. Conversion is mechanical: rename the init
function to mc_vfs_plugin_init, switch Makefile.am from noinst to
vfsplugin, and remove the static init call.

Phase 2 -- minimal refactoring (one exported global variable):

shell SHELL/SSH filesystem no external deps

src/setup.c reads/writes shell_directory_timeout. A getter/setter
pair or an mc_config-based approach would decouple it.

Phase 3 -- moderate refactoring (multiple exported globals):

ftpfs FTP filesystem no external deps

src/setup.c and src/filemanager/boxes.c directly access ~11 global
variables (ftpfs_use_netrc, ftpfs_proxy_host, ftpfs_anonymous_passwd,
ftpfs_use_passive_connections, etc.) for the configuration dialog and
settings persistence. These would need to be exposed through a
callback/getter API or moved to mc_config key lookups.

Not convertible:

local local filesystem core module, must be first

Other VFS modules depend on local_close(), local_read(), etc.
It must be initialised before any other VFS class.


Writing a new plugin
--------------------

1. Create a directory under src/vfs/ (e.g. src/vfs/myfs/).

2. Implement your VFS class. Use src/vfs/sftpfs/ as a reference.
The key function is:

void
mc_vfs_plugin_init (void)
{
tcp_init (); /* if network-based */
vfs_init_subclass (&myfs_subclass, "myfs", flags, "myfs");
/* assign callbacks to vfs_myfs_ops->open, ->read, ... */
vfs_register_class (vfs_myfs_ops);
}

3. In Makefile.am:

vfsplugindir = $(vfs_plugins_dir)
vfsplugin_LTLIBRARIES = mc-vfs-myfs.la

mc_vfs_myfs_la_SOURCES = ...
mc_vfs_myfs_la_LDFLAGS = -module -avoid-version
mc_vfs_myfs_la_LIBADD = $(GLIB_LIBS) $(MY_EXTERNAL_LIBS)

Do NOT add -no-undefined -- the plugin resolves mc symbols at
runtime via -export-dynamic on the mc binary.

4. If the plugin has an external library dependency, add a PKG_CHECK
in an m4 file but do NOT append to MCLIBS -- the plugin links the
library itself.

5. Add SUBDIRS += myfs to src/vfs/Makefile.am (guarded by your
AM_CONDITIONAL), but do NOT add to libmc_vfs_la_LIBADD.

6. Install the resulting .so into $(vfs_plugins_dir) -- this happens
automatically via vfsplugin_LTLIBRARIES.

7. Test: run mc and verify your prefix is accessible (e.g. cd myfs://).
5 changes: 4 additions & 1 deletion lib/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ libmc_la_SOURCES = \
global.c global.h \
keybind.c keybind.h \
lock.c lock.h \
panel-plugin.c panel-plugin.h \
panel-plugin-loader.c \
serialize.c serialize.h \
shell.c shell.h \
stat-size.h \
Expand All @@ -57,7 +59,8 @@ libmc_la_SOURCES += charsets.c charsets.h
EXTRA_DIST = \
stdckdint.in.h

AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir) \
-DMC_PANEL_PLUGINS_DIR=\""$(panel_plugins_dir)"\"

libmc_la_LIBADD = \
event/libmcevent.la \
Expand Down
Loading