GithubLinkedIn

Fix Unity3d "Missing Script" issues

Unity3D

2015-06-16

If you've ever worked on a team using Unity3d, somebody has forgotten to commit a .meta file. It's fairly easy to do, and pretty hard to notice you're doing it (things still work for you, just not others who pull your work in). I've been bitten by this mistake plenty, from myself and others.

Why does this happen

The .meta file holds GUID for the given asset, which is how the editor associates assets for serialized data (your prefabs and scenes). When you're serialized data references the wrong id, then you get missing references that show up like "Missing Script" in the editor.

The solution

Don't let people commit and push assets without metas.

Most version control systems have hooks that you can run your own scripts in, Mercurials no different.

You can make a pre-commit hook that will look at all your assets and make sure none are missing a meta.

I've made one below. It's worked fine for windows and mac machines, and some version of it has been in every unity project I've done since.

1  import os
2  import mercurial.commands
3
4  class HG:
5    def __init__(self,ui,repo):
6      self.ui = ui
7      self.repo = repo
8      self.files = self.getAddedFiles()
9
10    def existsInRepo(self, filename):
11      self.ui.pushbuffer()
12      mercurial.commands.locate(self.ui,self.repo,include=[filename])
13      files = self.ui.popbuffer().split("\n")
14
15      for repoFile in files:
16        if(pathsEqual(filename,repoFile)):
17          return True
18
19      return False
20
21    def getAddedFiles(self):
22      self.ui.pushbuffer()
23      mercurial.commands.status(self.ui,self.repo,no_status=True, added = True)
24      files = self.ui.popbuffer().split("\n")
25      return files
26
27    def filesExistInDir(self,dirname):
28      self.ui.pushbuffer()
29      mercurial.commands.locate(self.ui,self.repo,include=[dirname])
30      files = self.ui.popbuffer().split("\n")
31      files = filter(lambda x:x != "", files)
32      return len(files) > 0
33
34    def show(self, message):
35      self.ui.status(message + "\n")
36
37  def precommit(ui,repo, **kwargs):
38    hg = HG(ui,repo)
39    clean = checkFiles(hg)
40    return not clean;
41
42  def pathsEqual(a,b):
43    return os.path.normcase(a) == os.path.normcase(b)
44
45
46  def checkFiles(hg):
47
48    path = os.path.normcase("UnityProject/Assets/")
49
50    result = True
51    files = hg.files
52    for singleFile in files:
53      if singleFile == '':
54        continue
55
56      if not os.path.normcase(singleFile).startswith(path):
57        continue
58
59      isMetaFile = singleFile.endswith(".meta")
60
61      filename = singleFile if not isMetaFile else singleFile.replace(".meta", "")
62      metaFile = singleFile if isMetaFile else singleFile + ".meta"
63
64      isFolder = os.path.isdir(os.path.normcase(filename))
65
66      if isMetaFile:
67        if isFolder:
68          if not hg.filesExistInDir(filename):
69            hg.show("Commiting a meta file'" + metaFile +
70              "' for a directory which contains no files." )
71            return False
72        else:
73          if not hg.existsInRepo(filename):
74            hg.show("Commiting a meta file'" + metaFile +
75              "' without adding associated file '" + filename + "'.")
76            return False
77
78      else:
79        if not hg.existsInRepo(metaFile):
80          hg.show("Committing a file '" + filename +
81            "' without adding associated meta file '" + metaFile + "'.")
82          return False
83
84
85    return result
86

This doesn't handle .meta files for directories. I couldn't think of an elegant way To locate directories from the hg API. Mercurial has no concept of them, files are just at paths. This hasn't been a problem and, AFAIK, directories aren't referenced in serialized data.

Usage

Configure your asset folder path

in checkFiles alter the path variable so that it points to your assets directory. The current value reflects my projects, which generally have the unity project folder start at the path UnityProject. The script only checks the files under that path, and it's relative to your repo's root.

Add the hook

Mercurial needs you to register the hook in your hg config. Everyone on the team should register and use this pre-commit hook in order for it to be effective. I keep them in hooks directory for the project for that reason.

For example, Lets say you saved the above script to a file in your repo at hooks/checkMetas.py.

Then you would add these lines to your .hg/hgrc file.

1    [hooks]
2    precommit.checkMetas = python:/pathToUnityRepo/hooks/checkMetas.py:precommit
3