add --overwrite option to ignore file conflicts

Allows for safer, more fine-grained control for overwriting files than
--force's all-or-nothing approach.

Implements FS#31549.

Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com>
Signed-off-by: Allan McRae <allan@archlinux.org>
This commit is contained in:
Andrew Gregory 2017-04-09 20:42:01 -04:00 committed by Allan McRae
parent 86f5c74694
commit 04d211effa
12 changed files with 101 additions and 4 deletions

View file

@ -270,6 +270,18 @@ Upgrade Options (apply to '-S' and '-U')[[UO]]
*\--needed*:: *\--needed*::
Do not reinstall the targets that are already up-to-date. Do not reinstall the targets that are already up-to-date.
*\--overwrite* <glob>::
Bypass file conflict checks and overwrite conflicting files. If the
package that is about to be installed contains files that are already
installed and match 'glob', this option will cause all those files to be
overwritten. Using '\--overwrite' will not allow overwriting a directory
with a file or installing packages with conflicting files and directories.
Multiple patterns can be specified by separating them with a comma. May be
specified multiple times. Patterns can be negated, such that files
matching them will not be overwritten, by prefixing them with an
exclamation mark. Subsequent matches will override previous ones. A leading
literal exclamation mark or backslash needs to be escaped.
Query Options (apply to '-Q')[[QO]] Query Options (apply to '-Q')[[QO]]
----------------------------------- -----------------------------------

View file

@ -830,6 +830,11 @@ int alpm_option_add_hookdir(alpm_handle_t *handle, const char *hookdir);
int alpm_option_remove_hookdir(alpm_handle_t *handle, const char *hookdir); int alpm_option_remove_hookdir(alpm_handle_t *handle, const char *hookdir);
/** @} */ /** @} */
alpm_list_t *alpm_option_get_overwrite_files(alpm_handle_t *handle);
int alpm_option_set_overwrite_files(alpm_handle_t *handle, alpm_list_t *globs);
int alpm_option_add_overwrite_file(alpm_handle_t *handle, const char *glob);
int alpm_option_remove_overwrite_file(alpm_handle_t *handle, const char *glob);
/** Returns the logfile name. */ /** Returns the logfile name. */
const char *alpm_option_get_logfile(alpm_handle_t *handle); const char *alpm_option_get_logfile(alpm_handle_t *handle);
/** Sets the logfile name. */ /** Sets the logfile name. */

View file

@ -385,6 +385,12 @@ static alpm_list_t *alpm_db_find_file_owners(alpm_db_t* db, const char *path)
return owners; return owners;
} }
static int _alpm_can_overwrite_file(alpm_handle_t *handle, const char *path)
{
return handle->trans->flags & ALPM_TRANS_FLAG_FORCE
|| _alpm_fnmatch_patterns(handle->overwrite_files, path) == 0;
}
/** /**
* @brief Find file conflicts that may occur during the transaction. * @brief Find file conflicts that may occur during the transaction.
* *
@ -448,8 +454,8 @@ alpm_list_t *_alpm_db_find_fileconflicts(alpm_handle_t *handle,
/* can skip file-file conflicts when forced * /* can skip file-file conflicts when forced *
* checking presence in p2_files detects dir-file or file-dir * checking presence in p2_files detects dir-file or file-dir
* conflicts as the path from p1 is returned */ * conflicts as the path from p1 is returned */
if((handle->trans->flags & ALPM_TRANS_FLAG_FORCE) && if(_alpm_can_overwrite_file(handle, filename)
alpm_filelist_contains(p2_files, filename)) { && alpm_filelist_contains(p2_files, filename)) {
_alpm_log(handle, ALPM_LOG_DEBUG, _alpm_log(handle, ALPM_LOG_DEBUG,
"%s exists in both '%s' and '%s'\n", filename, "%s exists in both '%s' and '%s'\n", filename,
p1->name, p2->name); p1->name, p2->name);
@ -654,8 +660,8 @@ alpm_list_t *_alpm_db_find_fileconflicts(alpm_handle_t *handle,
} }
/* skip file-file conflicts when being forced */ /* skip file-file conflicts when being forced */
if((handle->trans->flags & ALPM_TRANS_FLAG_FORCE) && if(!S_ISDIR(lsbuf.st_mode)
!S_ISDIR(lsbuf.st_mode)) { && _alpm_can_overwrite_file(handle, filestr)) {
_alpm_log(handle, ALPM_LOG_DEBUG, _alpm_log(handle, ALPM_LOG_DEBUG,
"conflict with file on filesystem being forced\n"); "conflict with file on filesystem being forced\n");
resolved_conflict = 1; resolved_conflict = 1;

View file

@ -283,6 +283,12 @@ alpm_list_t SYMEXPORT *alpm_option_get_ignoregroups(alpm_handle_t *handle)
return handle->ignoregroup; return handle->ignoregroup;
} }
alpm_list_t SYMEXPORT *alpm_option_get_overwrite_files(alpm_handle_t *handle)
{
CHECK_HANDLE(handle, return NULL);
return handle->overwrite_files;
}
alpm_list_t SYMEXPORT *alpm_option_get_assumeinstalled(alpm_handle_t *handle) alpm_list_t SYMEXPORT *alpm_option_get_assumeinstalled(alpm_handle_t *handle)
{ {
CHECK_HANDLE(handle, return NULL); CHECK_HANDLE(handle, return NULL);
@ -657,6 +663,21 @@ int SYMEXPORT alpm_option_remove_ignoregroup(alpm_handle_t *handle, const char *
return _alpm_option_strlist_rem(handle, &(handle->ignoregroup), grp); return _alpm_option_strlist_rem(handle, &(handle->ignoregroup), grp);
} }
int SYMEXPORT alpm_option_add_overwrite_file(alpm_handle_t *handle, const char *glob)
{
return _alpm_option_strlist_add(handle, &(handle->overwrite_files), glob);
}
int SYMEXPORT alpm_option_set_overwrite_files(alpm_handle_t *handle, alpm_list_t *globs)
{
return _alpm_option_strlist_set(handle, &(handle->overwrite_files), globs);
}
int SYMEXPORT alpm_option_remove_overwrite_file(alpm_handle_t *handle, const char *glob)
{
return _alpm_option_strlist_rem(handle, &(handle->overwrite_files), glob);
}
int SYMEXPORT alpm_option_add_assumeinstalled(alpm_handle_t *handle, const alpm_depend_t *dep) int SYMEXPORT alpm_option_add_assumeinstalled(alpm_handle_t *handle, const alpm_depend_t *dep)
{ {
alpm_depend_t *depcpy; alpm_depend_t *depcpy;

View file

@ -84,6 +84,7 @@ struct __alpm_handle_t {
char *gpgdir; /* Directory where GnuPG files are stored */ char *gpgdir; /* Directory where GnuPG files are stored */
alpm_list_t *cachedirs; /* Paths to pacman cache directories */ alpm_list_t *cachedirs; /* Paths to pacman cache directories */
alpm_list_t *hookdirs; /* Paths to hook directories */ alpm_list_t *hookdirs; /* Paths to hook directories */
alpm_list_t *overwrite_files; /* Paths that may be overwritten */
/* package lists */ /* package lists */
alpm_list_t *noupgrade; /* List of packages NOT to be upgraded */ alpm_list_t *noupgrade; /* List of packages NOT to be upgraded */

View file

@ -783,6 +783,8 @@ static int setup_libalpm(void)
alpm_option_set_cachedirs(handle, config->cachedirs); alpm_option_set_cachedirs(handle, config->cachedirs);
} }
alpm_option_set_overwrite_files(handle, config->overwrite_files);
alpm_option_set_default_siglevel(handle, config->siglevel); alpm_option_set_default_siglevel(handle, config->siglevel);
config->localfilesiglevel = merge_siglevel(config->siglevel, config->localfilesiglevel = merge_siglevel(config->siglevel,

View file

@ -123,6 +123,7 @@ typedef struct __config_t {
alpm_list_t *assumeinstalled; alpm_list_t *assumeinstalled;
alpm_list_t *noupgrade; alpm_list_t *noupgrade;
alpm_list_t *noextract; alpm_list_t *noextract;
alpm_list_t *overwrite_files;
char *xfercommand; char *xfercommand;
/* our connection to libalpm */ /* our connection to libalpm */
@ -172,6 +173,7 @@ enum {
OP_GPGDIR, OP_GPGDIR,
OP_DBONLY, OP_DBONLY,
OP_FORCE, OP_FORCE,
OP_OVERWRITE_FILES,
OP_COLOR, OP_COLOR,
OP_DBPATH, OP_DBPATH,
OP_CASCADE, OP_CASCADE,

View file

@ -190,6 +190,8 @@ static void usage(int op, const char * const myname)
case PM_OP_SYNC: case PM_OP_SYNC:
case PM_OP_UPGRADE: case PM_OP_UPGRADE:
addlist(_(" --force force install, overwrite conflicting files\n")); addlist(_(" --force force install, overwrite conflicting files\n"));
addlist(_(" --overwrite <path>\n"
" overwrite conflicting files (can be used more than once)\n"));
addlist(_(" --asdeps install packages as non-explicitly installed\n")); addlist(_(" --asdeps install packages as non-explicitly installed\n"));
addlist(_(" --asexplicit install packages as explicitly installed\n")); addlist(_(" --asexplicit install packages as explicitly installed\n"));
addlist(_(" --ignore <pkg> ignore a package upgrade (can be used more than once)\n")); addlist(_(" --ignore <pkg> ignore a package upgrade (can be used more than once)\n"));
@ -708,6 +710,9 @@ static int parsearg_upgrade(int opt)
case OP_FORCE: case OP_FORCE:
config->flags |= ALPM_TRANS_FLAG_FORCE; config->flags |= ALPM_TRANS_FLAG_FORCE;
break; break;
case OP_OVERWRITE_FILES:
parsearg_util_addlist(&(config->overwrite_files));
break;
case OP_ASDEPS: case OP_ASDEPS:
config->flags |= ALPM_TRANS_FLAG_ALLDEPS; config->flags |= ALPM_TRANS_FLAG_ALLDEPS;
break; break;
@ -929,6 +934,7 @@ static int parseargs(int argc, char *argv[])
{"assume-installed", required_argument, 0, OP_ASSUMEINSTALLED}, {"assume-installed", required_argument, 0, OP_ASSUMEINSTALLED},
{"debug", optional_argument, 0, OP_DEBUG}, {"debug", optional_argument, 0, OP_DEBUG},
{"force", no_argument, 0, OP_FORCE}, {"force", no_argument, 0, OP_FORCE},
{"overwrite", required_argument, 0, OP_OVERWRITE_FILES},
{"noprogressbar", no_argument, 0, OP_NOPROGRESSBAR}, {"noprogressbar", no_argument, 0, OP_NOPROGRESSBAR},
{"noscriptlet", no_argument, 0, OP_NOSCRIPTLET}, {"noscriptlet", no_argument, 0, OP_NOSCRIPTLET},
{"ask", required_argument, 0, OP_ASK}, {"ask", required_argument, 0, OP_ASK},

View file

@ -80,6 +80,9 @@ TESTS += test/pacman/tests/mode001.py
TESTS += test/pacman/tests/mode002.py TESTS += test/pacman/tests/mode002.py
TESTS += test/pacman/tests/mode003.py TESTS += test/pacman/tests/mode003.py
TESTS += test/pacman/tests/noupgrade-inverted.py TESTS += test/pacman/tests/noupgrade-inverted.py
TESTS += test/pacman/tests/overwrite-files-match-negated.py
TESTS += test/pacman/tests/overwrite-files-match.py
TESTS += test/pacman/tests/overwrite-files-nonmatch.py
TESTS += test/pacman/tests/pacman001.py TESTS += test/pacman/tests/pacman001.py
TESTS += test/pacman/tests/pacman002.py TESTS += test/pacman/tests/pacman002.py
TESTS += test/pacman/tests/pacman003.py TESTS += test/pacman/tests/pacman003.py

View file

@ -0,0 +1,13 @@
self.description = "Install a package with an existing file matching a negated --overwrite pattern"
p = pmpkg("dummy")
p.files = ["foobar"]
self.addpkg(p)
self.filesystem = ["foobar*"]
self.args = "-U --overwrite=foobar --overwrite=!foo* %s" % p.filename()
self.addrule("!PACMAN_RETCODE=0")
self.addrule("!PKG_EXIST=dummy")
self.addrule("!FILE_MODIFIED=foobar")

View file

@ -0,0 +1,13 @@
self.description = "Install a package with an existing file matching an --overwrite pattern"
p = pmpkg("dummy")
p.files = ["foobar"]
self.addpkg(p)
self.filesystem = ["foobar*"]
self.args = "-U --overwrite=foobar %s" % p.filename()
self.addrule("PACMAN_RETCODE=0")
self.addrule("PKG_EXIST=dummy")
self.addrule("FILE_MODIFIED=foobar")

View file

@ -0,0 +1,13 @@
self.description = "Install a package with an existing file not matching --overwrite patterns"
p = pmpkg("dummy")
p.files = ["foobar"]
self.addpkg(p)
self.filesystem = ["foobar*"]
self.args = "-U --overwrite=foo %s" % p.filename()
self.addrule("!PACMAN_RETCODE=0")
self.addrule("!PKG_EXIST=dummy")
self.addrule("!FILE_MODIFIED=foobar")