A simplified look at a leg in our source control would be:
\DatabasePatches
\Core
\Data
\DatabaseSource
\Core
\SchemaA
\SchemaB
\SchemaC
DDL for a particular chunk of work is written and checked in under core patches with DML/migration checked in under data patches. Part of the patch label is a sequence number (manually add 10 each time allowing future insertion of patches in between) so a patch might be called "DATAPATCH01530 - migrate of xyz.sql".
When deploying to a new environment all the core patches are run and then data patches are run. If there is DML important for the next part of a core patch then it may be included in that core patch.
Once a patch has been run for the first time on a new location the file is marked FINAL in the source control (we use PVCS and lock the file with a user called FINAL) to make sure it can't be changed and cause inconsistency. Any additional changes should be included in a seperate patch.
Stored procedures, functions, packages etc. are saved and checked in under the DatabaseSource leg. You are unable to guarantee if these objects are promoted before scripts are run so we accommodate for this by creating stubs in our scripts (e.g views would be created as SELECT '1' FROM dual, packages would contain a dummy procedure) that guarantee the object exists and allows you to grant privileges etc. When the actual object gets promoted it replaces the stub and retains privileges.