xaizek / vifm (License: GPLv2+) (since 2018-12-07)
Vifm is a file manager with curses interface, which provides Vi[m]-like environment for managing objects within file systems, extended with some useful ideas from mutt.
Commit 3090ee80f94f024bd91205da59233382829d8aff

Sometimes offer "Merge all" for files
If there are some directories left to process.

Thanks to dalvand.

Closes #764 on GitHub.
Author: xaizek
Author date (UTC): 2022-06-19 18:20
Committer name: xaizek
Committer date (UTC): 2022-06-19 21:59
Parent(s): 2e4166a9d0cfec75ac52184a0deff00722e7a936
Signing key: 99DC5E4DB05F6BE2
Tree: f21f1e718ea4608e7d73441f03ce9f1b46593034
File Lines added Lines deleted
ChangeLog 3 0
src/fops_put.c 54 17
tests/fileops/put_files.c 53 0
File ChangeLog changed (mode: 100644) (index 3bd66dc4e..d7e91a0b6)
1 1 0.12 to current 0.12 to current
2 2
3 Changed conflict dialog to offer "Merge all" option even for files if
4 there are some directories left to process. Thanks to dalvand.
5
3 6 Added 'autocd' option. Makes invalid :commands be interpreted as an Added 'autocd' option. Makes invalid :commands be interpreted as an
4 7 implicit :cd command. Thanks to Taras Halturin (a.k.a. halturin). implicit :cd command. Thanks to Taras Halturin (a.k.a. halturin).
5 8
File src/fops_put.c changed (mode: 100644) (index 794f017df..798211f5a)
... ... static int is_dir_clash(const char src_path[], const char dst_dir[]);
59 59 static int put_files_i(view_t *view, int start); static int put_files_i(view_t *view, int start);
60 60 static void update_cursor_position(view_t *view); static void update_cursor_position(view_t *view);
61 61 static int put_next(int force); static int put_next(int force);
62 static int unprocessed_dirs_present(void);
62 63 TSTATIC int merge_dirs(const char src[], const char dst[], ops_t *ops); TSTATIC int merge_dirs(const char src[], const char dst[], ops_t *ops);
63 64 static int handle_clashing(int move, const char src[], const char dst[]); static int handle_clashing(int move, const char src[], const char dst[]);
64 65 static void prompt_what_to_do(const char fname[], const char caused_by[]); static void prompt_what_to_do(const char fname[], const char caused_by[]);
 
... ... static struct
85 86 int overwrite_all; /* Overwrite all future conflicting files/directories. */ int overwrite_all; /* Overwrite all future conflicting files/directories. */
86 87 int append; /* Whether we're appending ending of a file or not. */ int append; /* Whether we're appending ending of a file or not. */
87 88 int allow_merge; /* Allow merging of files in directories. */ int allow_merge; /* Allow merging of files in directories. */
89 int allow_merge_all; /* Allow merging of files in directories by default. */
88 90 int merge; /* Merge conflicting directory once. */ int merge; /* Merge conflicting directory once. */
89 91 int merge_all; /* Merge all conflicting directories. */ int merge_all; /* Merge all conflicting directories. */
90 92 ops_t *ops; /* Currently running operation. */ ops_t *ops; /* Currently running operation. */
 
... ... put_next(int force)
639 641 else else
640 642 { {
641 643 struct stat dst_st; struct stat dst_st;
642 put_confirm.allow_merge = os_lstat(dst_buf, &dst_st) == 0 &&
643 S_ISDIR(dst_st.st_mode) && S_ISDIR(src_st.st_mode);
644 put_confirm.allow_merge = S_ISDIR(src_st.st_mode)
645 && (os_lstat(dst_buf, &dst_st) == 0)
646 && S_ISDIR(dst_st.st_mode);
647
648 /* Enable "merge all" in conflict resolution options if at least one of
649 * source paths left to process (including current) is a directory. */
650 put_confirm.allow_merge_all = unprocessed_dirs_present();
651
644 652 prompt_what_to_do(dst_name, src_buf); prompt_what_to_do(dst_name, src_buf);
645 653 return 1; return 1;
646 654 } }
 
... ... put_next(int force)
771 779 return 0; return 0;
772 780 } }
773 781
782 /* Checks whether current or any other unprocessed paths point to directories.
783 * Returns non-zero if so, otherwise zero is returned. */
784 static int
785 unprocessed_dirs_present(void)
786 {
787 int i;
788 for(i = put_confirm.index; i < put_confirm.reg->nfiles; ++i)
789 {
790 const char *path = put_confirm.reg->files[put_confirm.file_order[i]];
791 if(path == NULL)
792 {
793 /* This file has been excluded from processing. */
794 continue;
795 }
796
797 struct stat src_st;
798 if(os_lstat(path, &src_st) == 0 && S_ISDIR(src_st.st_mode))
799 {
800 return 1;
801 }
802 }
803 return 0;
804 }
805
774 806 /* Merges src into dst. Returns zero on success, otherwise non-zero is /* Merges src into dst. Returns zero on success, otherwise non-zero is
775 807 * returned. */ * returned. */
776 808 TSTATIC int TSTATIC int
 
... ... prompt_what_to_do(const char fname[], const char caused_by[])
948 980 /* Strange spacing is for left alignment. Doesn't look nice here, but it is /* Strange spacing is for left alignment. Doesn't look nice here, but it is
949 981 * problematic to get such alignment otherwise. */ * problematic to get such alignment otherwise. */
950 982 static const response_variant static const response_variant
951 compare = { .key = 'c', .descr = "[c]ompare files \n" },
952 rename = { .key = 'r', .descr = "[r]ename (also Enter) \n" },
953 enter = { .key = '\r', .descr = "" },
954 skip = { .key = 's', .descr = "[s]kip " },
955 skip_all = { .key = 'S', .descr = " [S]kip all \n" },
956 append = { .key = 'a', .descr = "[a]ppend the tail \n" },
957 overwrite = { .key = 'o', .descr = "[o]verwrite " },
958 overwrite_all = { .key = 'O', .descr = " [O]verwrite all\n" },
959 merge = { .key = 'm', .descr = "[m]erge " },
960 merge_all = { .key = 'M', .descr = " [M]erge all \n" },
961 escape = { .key = NC_C_c, .descr = "\nEsc or Ctrl-C to cancel" };
983 compare = { .key = 'c', .descr = "[c]ompare files \n" },
984 rename = { .key = 'r', .descr = "[r]ename (also Enter) \n" },
985 enter = { .key = '\r', .descr = "" },
986 skip = { .key = 's', .descr = "[s]kip " },
987 skip_all = { .key = 'S', .descr = " [S]kip all \n" },
988 append = { .key = 'a', .descr = "[a]ppend the tail \n" },
989 overwrite = { .key = 'o', .descr = "[o]verwrite " },
990 overwrite_all = { .key = 'O', .descr = " [O]verwrite all\n" },
991 merge = { .key = 'm', .descr = "[m]erge " },
992 merge_all = { .key = 'M', .descr = " [M]erge all \n" },
993 merge_all_only = { .key = 'M', .descr = "[M]erge all \n" },
994 escape = { .key = NC_C_c, .descr = "\nEsc or Ctrl-C to cancel" };
962 995
963 996 char response; char response;
964 response_variant responses[11] = {};
997 /* Last element is a terminator. */
998 response_variant responses[12] = {};
965 999 size_t i = 0; size_t i = 0;
966 1000
967 1001 char dst_buf[PATH_MAX + 1]; char dst_buf[PATH_MAX + 1];
 
... ... prompt_what_to_do(const char fname[], const char caused_by[])
991 1025 if(put_confirm.allow_merge) if(put_confirm.allow_merge)
992 1026 { {
993 1027 responses[i++] = merge; responses[i++] = merge;
994 responses[i++] = merge_all;
1028 }
1029 if(put_confirm.allow_merge_all)
1030 {
1031 responses[i++] = (put_confirm.allow_merge ? merge_all : merge_all_only);
995 1032 } }
996 1033 } }
997 1034
998 1035 responses[i++] = escape; responses[i++] = escape;
999 assert(i < ARRAY_LEN(responses) && "Array is too small.");
1036 assert(i + 1 <= ARRAY_LEN(responses) && "Array is too small.");
1000 1037
1001 1038 /* Screen needs to be restored after displaying progress dialog. */ /* Screen needs to be restored after displaying progress dialog. */
1002 1039 modes_update(); modes_update();
 
... ... handle_prompt_response(const char fname[], const char caused_by[],
1074 1111 put_confirm.merge = 1; put_confirm.merge = 1;
1075 1112 put_continue(1); put_continue(1);
1076 1113 } }
1077 else if(put_confirm.allow_merge && response == 'M')
1114 else if(put_confirm.allow_merge_all && response == 'M')
1078 1115 { {
1079 1116 put_confirm.merge_all = 1; put_confirm.merge_all = 1;
1080 1117 put_continue(1); put_continue(1);
File tests/fileops/put_files.c changed (mode: 100644) (index 863ac6f69..c83329060)
10 10
11 11 #include "../../src/cfg/config.h" #include "../../src/cfg/config.h"
12 12 #include "../../src/compat/fs_limits.h" #include "../../src/compat/fs_limits.h"
13 #include "../../src/modes/dialogs/msg_dialog.h"
13 14 #include "../../src/utils/fs.h" #include "../../src/utils/fs.h"
14 15 #include "../../src/utils/path.h" #include "../../src/utils/path.h"
15 16 #include "../../src/filelist.h" #include "../../src/filelist.h"
 
... ... static void parent_overwrite_with_put(int move);
45 46 static void double_clash_with_put(int move); static void double_clash_with_put(int move);
46 47
47 48 static fo_prompt_cb rename_cb; static fo_prompt_cb rename_cb;
49 static int options_count;
48 50
49 51 static char *saved_cwd; static char *saved_cwd;
50 52
 
... ... TEARDOWN()
69 71 view_teardown(&lwin); view_teardown(&lwin);
70 72 regs_reset(); regs_reset();
71 73 restore_cwd(saved_cwd); restore_cwd(saved_cwd);
74 fops_init(NULL, NULL);
72 75 } }
73 76
74 77 static void static void
 
... ... static char
112 115 options_prompt_abort(const char title[], const char message[], options_prompt_abort(const char title[], const char message[],
113 116 const struct response_variant *variants) const struct response_variant *variants)
114 117 { {
118 options_count = 0;
119 while(variants->key != '\0')
120 {
121 ++options_count;
122 ++variants;
123 }
124
115 125 return '\x03'; return '\x03';
116 126 } }
117 127
 
... ... TEST(files_can_be_diffed)
682 692 assert_success(rmdir(SANDBOX_PATH "/dir")); assert_success(rmdir(SANDBOX_PATH "/dir"));
683 693 } }
684 694
695 TEST(show_merge_all_option_if_paths_include_dir)
696 {
697 char path[PATH_MAX + 1];
698
699 create_file(SANDBOX_PATH "/a");
700 create_dir(SANDBOX_PATH "/dir");
701 create_file(SANDBOX_PATH "/dir/a");
702 create_file(SANDBOX_PATH "/dir/b");
703 create_dir(SANDBOX_PATH "/dir/sub");
704
705 fops_init(&line_prompt, &options_prompt_abort);
706
707 make_abs_path(path, sizeof(path), SANDBOX_PATH, "/dir/a", saved_cwd);
708 assert_success(regs_append('a', path));
709 make_abs_path(path, sizeof(path), SANDBOX_PATH, "/dir/b", saved_cwd);
710 assert_success(regs_append('a', path));
711
712 options_count = 0;
713 (void)fops_put(&lwin, -1, 'a', 0);
714 assert_int_equal(9, options_count);
715
716 restore_cwd(saved_cwd);
717 saved_cwd = save_cwd();
718
719 make_abs_path(path, sizeof(path), SANDBOX_PATH, "/dir/sub", saved_cwd);
720 assert_success(regs_append('a', path));
721
722 options_count = 0;
723 (void)fops_put(&lwin, -1, 'a', 0);
724 assert_int_equal(10, options_count);
725
726 restore_cwd(saved_cwd);
727 saved_cwd = save_cwd();
728
729 assert_success(unlink(SANDBOX_PATH "/dir/a"));
730 assert_success(unlink(SANDBOX_PATH "/dir/b"));
731 assert_success(rmdir(SANDBOX_PATH "/dir/sub"));
732 assert_success(rmdir(SANDBOX_PATH "/dir"));
733 assert_success(unlink(SANDBOX_PATH "/a"));
734 /* This one doesn't always get copied. */
735 (void)unlink(SANDBOX_PATH "/b");
736 }
737
685 738 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
686 739 /* vim: set cinoptions+=t0 filetype=c : */ /* vim: set cinoptions+=t0 filetype=c : */
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/vifm

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/vifm

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master