Opened 10 years ago

Closed 10 years ago

Last modified 10 years ago

#22862 closed Bug (fixed)

makemigrations --merge broken because --noinput isn't registered

Reported by: art@… Owned by: Huu Nguyen
Component: Migrations Version: 1.7-beta-2
Severity: Release blocker Keywords:
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

  • The makemigrations management command doesn't register a --noinput option with the argument parser.
  • The logic for the --merge option expects --noinput to populate an instance variable on Command, self.interactive, with False if present, or True by default.
  • As the option isn't registered, self.interactive is None, leading to an incorrect default value, and the command behaves as if --noinput is set.
  • The non-interactive decision-maker [MigrationQuestioner] is safe/conservative, and appears to silently deny all merge operations.
  • Consequently, the command makemigrations --merge is effectively broken.

Steps to reproduce

An example Django project is attached, with the default SQlite database configuration.
To reproduce, unpack the archive, create and activate a virtualenv containing django/stable@… (not included),
and cd to the directory containing manage.py.

Assuming you're using virtualenvwrapper,

$ mkvirtualenv django-makemigrations-noinput
$ tar -xvzf django-makemigrations-noinput.tar.gz
$ cd djangobugs
$ pip install -r requirements.txt

The following procedure assumes the same working directory and active virtualenv.

The project's one model, testapp.MergeExample, has had independent modifications requiring migrating.
Makemigrations was run independently, and consequently there are two 0002-series migrations in testapp/migrations.

$ ls -1 testapp/migrations
0001_initial.py
0002_auto_20140618_1441.py
0002_mergeexample_unrelated_int.py
__init__.py
__pycache__/
$ ./manage.py migrate
CommandError: Conflicting migrations detected (0002_auto_20140618_1441, 0002_mergeexample_unrelated_int in testapp).
To fix them run 'python manage.py makemigrations --merge'

Clear enough.

$ ./manage.py makemigrations --merge
Merging testapp
  Branch 0002_mergeexample_unrelated_int
    - Add field unrelated_int to mergeexample
  Branch 0002_auto_20140618_1441
    - Add field long_title to mergeexample
    - Remove field short_title from mergeexample
$ ls -1 testapp/migrations
0001_initial.py
0002_auto_20140618_1441.py
0002_mergeexample_unrelated_int.py
__init__.py
__pycache__/

Nothing has been changed.

$ ./manage.py migrate
CommandError: Conflicting migrations detected (0002_mergeexample_unrelated_int, 0002_auto_20140618_1441 in testapp).
To fix them run 'python manage.py makemigrations --merge'

Relevant Django code

The following excerpt from django/core/management/commands/makemigrations.py shows the lines where --noinput
has been omitted, and the code interpreting its absence.

    ...
17 class Command(BaseCommand):
18     option_list = BaseCommand.option_list + (
19         make_option('--dry-run', action='store_true', dest='dry_run', default=False,
20             help="Just show what migrations would be made; don't actually write them."),
21         make_option('--merge', action='store_true', dest='merge', default=False,
22             help="Enable fixing of migration conflicts."),
23         make_option('--empty', action='store_true', dest='empty', default=False,
24             help="Create an empty migration."),
25     )
    ...
31     def handle(self, *app_labels, **options):
32
33         self.verbosity = int(options.get('verbosity'))
34         self.interactive = options.get('interactive')
35         self.dry_run = options.get('dry_run', False)
36         self.merge = options.get('merge', False)
37         self.empty = options.get('empty', False)
    ...
151     def handle_merge(self, loader, conflicts):
152         """
153         Handles merging together conflicted migrations interactively,
154         if it's safe; otherwise, advises on how to fix it.
155         """
156         if self.interactive:
157             questioner = InteractiveMigrationQuestioner()
158         else:
159             questioner = MigrationQuestioner()
    ...
195             if questioner.ask_merge(app_label):
196                 # If they still want to merge it, then write out an empty
197                 # file depending on the migrations needing merging.
    ...

Temporary workaround

Interestingly, it is possible to work around the lack of a registered --noinput option by passing the option
straight to command programmatically, executing the command as it ought to function.

$ DJANGO_SETTINGS_MODULE=djangobugs.settings python -c 'import django; django.setup(); django.core.management.call_command("makemigrations", merge=True, interactive=True)'
Merging testapp
  Branch 0002_auto_20140618_1441
    - Add field long_title to mergeexample
    - Remove field short_title from mergeexample
  Branch 0002_mergeexample_unrelated_int
    - Add field unrelated_int to mergeexample

Merging will only work if the operations printed above do not conflict
with each other (working on different fields or models)
Do you want to merge these migration branches? [y/N] y

Created new merge migration /Users/artortenburger/Sites/djangobugs/djangobugs/testapp/migrations/0003_merge.py
$ ./manage.py migrate
Operations to perform:
  Synchronize unmigrated apps: (none)
  Apply all migrations: admin, sessions, contenttypes, auth, testapp
Synchronizing apps without migrations:
  Creating tables...
  Installing custom SQL...
  Installing indexes...
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying sessions.0001_initial... OK
  Applying testapp.0001_initial... OK
  Applying testapp.0002_mergeexample_unrelated_int... OK
  Applying testapp.0002_auto_20140618_1441... OK
  Applying testapp.0003_merge... FAKED

(Edit: as tarballed, the example contains another change to testapp.MergeExample, which has no migrations yet.
If you successfully run the merge and migrate, re-running migrate will produce a warning about unmigrated changes.
In my test-run, makemigrations and migrate applied it successfully, producing testapp/migrations/0004_remove_mergeexample_unrelated_int.py.)

Proposed resolution

The minimal fix appears to be registering the --noinput option for the makemigrations command.
The two lines have been sourced from django/core/management/commands/migrate.py.

  • makemigrations.py

    old new  
    1616
    1717class Command(BaseCommand):
    1818    option_list = BaseCommand.option_list + (
     19        make_option('--noinput', action='store_false', dest='interactive', default=True,
     20            help='Tells Django to NOT prompt the user for input of any kind.'),
    1921        make_option('--dry-run', action='store_true', dest='dry_run', default=False,
    2022            help="Just show what migrations would be made; don't actually write them."),
    2123        make_option('--merge', action='store_true', dest='merge', default=False,

A patchfile for django/core/management/commands/makemigrations.py is attached, reproduced above.

Attachments (2)

django-makemigrations-noinput.tar.gz (2.3 KB ) - added by art@… 10 years ago.
Example Django project for reproducing the bug
makemigrations-add-noinput.patch (747 bytes ) - added by art@… 10 years ago.
potential patch for django/core/management/commands/makemigrations.py

Download all attachments as: .zip

Change History (7)

by art@…, 10 years ago

Example Django project for reproducing the bug

by art@…, 10 years ago

potential patch for django/core/management/commands/makemigrations.py

comment:1 by Tim Graham, 10 years ago

Component: UncategorizedMigrations
Severity: NormalRelease blocker
Triage Stage: UnreviewedAccepted

The patch will be different on master because we are now using argparse.

comment:2 by Huu Nguyen, 10 years ago

Owner: changed from nobody to Huu Nguyen
Status: newassigned

comment:3 by Huu Nguyen, 10 years ago

Has patch: set

comment:4 by Tim Graham <timograham@…>, 10 years ago

Resolution: fixed
Status: assignedclosed

In fbb684d95ed71487fe16135321d59ddb043ee903:

Fixed #22862 -- Added --noinput option to makemigrations.

Thanks artortenburger for the report.

comment:5 by Tim Graham <timograham@…>, 10 years ago

In d9a83d597ee5aa510cf1aaa086e2fc3f1d7ee4c6:

[1.7.x] Fixed #22862 -- Added --noinput option to makemigrations.

Thanks artortenburger for the report.

Backport of fbb684d95e from master

Note: See TracTickets for help on using tickets.
Back to Top