From 5bb0d5e406dbefa3283ac6a969ac95405d59304f Mon Sep 17 00:00:00 2001 From: Lenrui Date: Thu, 5 Nov 2026 23:16:37 -0800 Subject: [PATCH] upload package --- net.lenrui.git-auto-sync/CHANGELOG.md | 5 + net.lenrui.git-auto-sync/CHANGELOG.md.meta | 7 + net.lenrui.git-auto-sync/Editor.meta | 8 + .../Editor/GitAutoSync.cs | 408 ++++++++++++++++++ .../Editor/GitAutoSync.cs.meta | 11 + .../Editor/Lenrui.GitSync.Editor.asmdef | 16 + .../Editor/Lenrui.GitSync.Editor.asmdef.meta | 7 + net.lenrui.git-auto-sync/README.md | 36 ++ net.lenrui.git-auto-sync/README.md.meta | 7 + net.lenrui.git-auto-sync/package.json | 11 + net.lenrui.git-auto-sync/package.json.meta | 7 + 11 files changed, 523 insertions(+) create mode 100644 net.lenrui.git-auto-sync/CHANGELOG.md create mode 100644 net.lenrui.git-auto-sync/CHANGELOG.md.meta create mode 100644 net.lenrui.git-auto-sync/Editor.meta create mode 100644 net.lenrui.git-auto-sync/Editor/GitAutoSync.cs create mode 100644 net.lenrui.git-auto-sync/Editor/GitAutoSync.cs.meta create mode 100644 net.lenrui.git-auto-sync/Editor/Lenrui.GitSync.Editor.asmdef create mode 100644 net.lenrui.git-auto-sync/Editor/Lenrui.GitSync.Editor.asmdef.meta create mode 100644 net.lenrui.git-auto-sync/README.md create mode 100644 net.lenrui.git-auto-sync/README.md.meta create mode 100644 net.lenrui.git-auto-sync/package.json create mode 100644 net.lenrui.git-auto-sync/package.json.meta diff --git a/net.lenrui.git-auto-sync/CHANGELOG.md b/net.lenrui.git-auto-sync/CHANGELOG.md new file mode 100644 index 0000000..26d9a99 --- /dev/null +++ b/net.lenrui.git-auto-sync/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## [1.0.0] - 2026-11-05 + +- First release: pull on Editor open, commit and push on Editor close, with Tools > Git Auto Sync menu. diff --git a/net.lenrui.git-auto-sync/CHANGELOG.md.meta b/net.lenrui.git-auto-sync/CHANGELOG.md.meta new file mode 100644 index 0000000..c28cb62 --- /dev/null +++ b/net.lenrui.git-auto-sync/CHANGELOG.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 22b2020c0403416e9227c258cac1eb8c +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/net.lenrui.git-auto-sync/Editor.meta b/net.lenrui.git-auto-sync/Editor.meta new file mode 100644 index 0000000..077752a --- /dev/null +++ b/net.lenrui.git-auto-sync/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a132878ad0514698b327c18a341cf723 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/net.lenrui.git-auto-sync/Editor/GitAutoSync.cs b/net.lenrui.git-auto-sync/Editor/GitAutoSync.cs new file mode 100644 index 0000000..0767cb4 --- /dev/null +++ b/net.lenrui.git-auto-sync/Editor/GitAutoSync.cs @@ -0,0 +1,408 @@ +// GitAutoSync.cs - part of the Git Auto Sync package (net.lenrui.git-auto-sync). +// +// When Unity opens: fetches, and if the branch's upstream has new commits, updates to them. +// Unpushed local commits are rebased on top; uncommitted files are stashed and restored. +// When Unity closes: saves scenes/assets, commits everything under the project folder, +// brings in anything pushed meanwhile, then pushes. +// If anything fails you can stay open or quit anyway; your work is always committed locally first. +// +// Menu: Tools > Git Auto Sync (on/off, ask-before-commit, and manual Pull / Commit and Push) + +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using Debug = UnityEngine.Debug; + +namespace Lenrui.GitSync +{ + [InitializeOnLoad] + public static class GitAutoSync + { + const string Title = "Git Auto Sync"; + const string MenuRoot = "Tools/Git Auto Sync/"; + const string StartupDoneKey = "GitAutoSync.StartupDone"; + + static readonly string ProjectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + static bool busy; + + // ---------------------------------------------------------------- settings (per project, per machine) + + static string Key(string name) { return "GitAutoSync." + name + "." + ProjectRoot; } + + static bool Enabled + { + get { return EditorPrefs.GetBool(Key("Enabled"), true); } + set { EditorPrefs.SetBool(Key("Enabled"), value); } + } + + static bool AskBeforeCommit + { + get { return EditorPrefs.GetBool(Key("AskBeforeCommit"), false); } + set { EditorPrefs.SetBool(Key("AskBeforeCommit"), value); } + } + + // ---------------------------------------------------------------- hooks + + static GitAutoSync() + { + if (Application.isBatchMode) return; + + EditorApplication.wantsToQuit -= OnWantsToQuit; + EditorApplication.wantsToQuit += OnWantsToQuit; + + // Static constructors re-run on every script reload; only pull once per editor launch. + if (SessionState.GetBool(StartupDoneKey, false)) return; + SessionState.SetBool(StartupDoneKey, true); + EditorApplication.delayCall += () => { if (Enabled) Guarded(() => PullLatest(false)); }; + } + + static bool OnWantsToQuit() + { + if (!Enabled || busy) return true; + bool quit = true; + Guarded(() => quit = CommitAndPush(true), onError: () => + quit = EditorUtility.DisplayDialog(Title, "Unexpected error while syncing; see the Console.", "Quit Anyway", "Stay Open")); + return quit; + } + + static void Guarded(Action action, Action onError = null) + { + busy = true; + try { action(); } + catch (Exception e) { Debug.LogException(e); if (onError != null) onError(); } + finally { busy = false; EditorUtility.ClearProgressBar(); } + } + + // ---------------------------------------------------------------- menu + + [MenuItem(MenuRoot + "Enabled", false, 0)] + static void ToggleEnabled() { Enabled = !Enabled; } + + [MenuItem(MenuRoot + "Enabled", true)] + static bool ToggleEnabledCheck() { Menu.SetChecked(MenuRoot + "Enabled", Enabled); return true; } + + [MenuItem(MenuRoot + "Ask Before Committing on Close", false, 1)] + static void ToggleAsk() { AskBeforeCommit = !AskBeforeCommit; } + + [MenuItem(MenuRoot + "Ask Before Committing on Close", true)] + static bool ToggleAskCheck() { Menu.SetChecked(MenuRoot + "Ask Before Committing on Close", AskBeforeCommit); return true; } + + [MenuItem(MenuRoot + "Pull Latest Now", false, 20)] + static void PullNow() { Guarded(() => PullLatest(true)); } + + [MenuItem(MenuRoot + "Commit and Push Now", false, 21)] + static void PushNow() { Guarded(() => CommitAndPush(false)); } + + // ---------------------------------------------------------------- open: fetch + update + + static void PullLatest(bool manual) + { + string why; + if (!CheckRepo(out why)) { Info(why, manual); return; } + + string branch = CurrentBranch(); + if (branch == null) { Info("HEAD is detached (not on a branch), so nothing was pulled.", manual); return; } + + if (manual) AssetDatabase.SaveAssets(); + + var fetch = Git("fetch --prune", "Checking for remote changes...", 120); + if (!fetch.Ok) { Warn("Couldn't fetch (offline?). Working with the local copy.\n\n" + fetch.Tail, manual); return; } + + if (!HasUpstream(branch)) { Info("'" + branch + "' has no upstream yet, so there's nothing to pull. It will be pushed when you close Unity.", manual); return; } + + int ahead, behind; + AheadBehind(out ahead, out behind); + if (behind == 0) { Info("'" + branch + "' is up to date.", manual); return; } + + // Uncommitted edits to files that also changed remotely would end up full of conflict markers, + // which breaks Unity assets. Leave them alone; they get committed on close and any clash is reported then. + var remoteChanged = Lines(Git("diff --name-only HEAD...@{u}").Out); + var localDirty = Lines(Git("diff --name-only HEAD").Out) + .Concat(Lines(Git("ls-files --others --exclude-standard --full-name").Out)); + string[] overlap = localDirty.Intersect(remoteChanged).ToArray(); + if (overlap.Length > 0) + { + Alert("Didn't pull " + behind + " new commit(s) on '" + branch + "': you have uncommitted edits to files that were also changed remotely:\n\n" + + Clip(string.Join("\n", overlap), 10) + "\n\nThey'll be committed when you close Unity, and you'll be told if they conflict."); + return; + } + + string incoming = Git("log --oneline --no-decorate HEAD..@{u}").Out.Trim(); + int stashesBefore = StashCount(); + + var rebase = Git("rebase --autostash @{u}", "Pulling " + behind + " new commit(s)...", 300); + if (!rebase.Ok) + { + Git("rebase --abort"); + Alert("Couldn't update '" + branch + "': your " + ahead + " unpushed commit(s) conflict with the " + behind + + " new remote commit(s). Your files were left as they were; resolve it in your git client.\n\n" + rebase.Tail); + return; + } + + AssetDatabase.Refresh(); + Debug.Log("[GitAutoSync] Pulled " + behind + " commit(s) into '" + branch + "':\n" + incoming); + + if (StashCount() > stashesBefore) + Alert("Pulled the latest changes, but your uncommitted edits clashed with them. Check for conflict markers; " + + "a copy of your edits is saved in `git stash`."); + else if (manual) + EditorUtility.DisplayDialog(Title, "Pulled " + behind + " commit(s):\n\n" + Clip(incoming, 15), "OK"); + } + + // ---------------------------------------------------------------- close: commit + push + + // Returns true if it's OK to quit (when quitting). + static bool CommitAndPush(bool quitting) + { + string why; + if (!CheckRepo(out why)) { Info(why, !quitting); return true; } + + // Make what's on disk match the editor. + if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return false; // user hit Cancel + AssetDatabase.SaveAssets(); + + // Never commit Unity's cache. + if (Git("check-ignore -q Library/").Code != 0) + return Fail("Library/ isn't in .gitignore, so auto-commit is off to avoid committing gigabytes of cache. " + + "Add a Unity .gitignore to the repo.", quitting); + + string branch = CurrentBranch(); + if (branch == null) return Fail("HEAD is detached (not on a branch), so nothing was committed or pushed.", quitting); + + // ---- commit + string[] pending = Lines(Git("status --porcelain -- .").Out); + if (pending.Length > 0) + { + if (quitting && AskBeforeCommit) + { + int choice = EditorUtility.DisplayDialogComplex(Title, + "Commit and push " + pending.Length + " change(s) on '" + branch + "'?\n\n" + Clip(string.Join("\n", pending), 15), + "Commit and Push", "Stay Open", "Quit Without Committing"); + if (choice == 1) return false; + if (choice == 2) return true; + } + + var add = Git("add -A -- .", "Staging changes...", 300); + if (!add.Ok) return Fail("git add failed:\n\n" + add.Tail, quitting); + + string[] staged = Lines(Git("diff --cached --name-status").Out); + string message = "Auto-commit from Unity (" + Environment.MachineName + ", " + DateTime.Now.ToString("yyyy-MM-dd HH:mm") + ")\n\n" + + Clip(string.Join("\n", staged), 100); + + string msgFile = Path.GetTempFileName(); + File.WriteAllText(msgFile, message, new UTF8Encoding(false)); + var commit = Git("commit -q -F \"" + msgFile + "\"", "Committing...", 300); + File.Delete(msgFile); + if (!commit.Ok) return Fail("Commit failed:\n\n" + commit.Tail, quitting); + + Debug.Log("[GitAutoSync] Committed " + staged.Length + " file(s) on '" + branch + "'."); + } + + // ---- push + bool hasUpstream = HasUpstream(branch); + string remote = hasUpstream ? GitValue("config --get branch." + branch + ".remote") : DefaultRemote(); + if (remote == null) { Info("No git remote is set up, so changes were only committed locally.", !quitting); return true; } + + var fetch = Git("fetch --prune", "Checking for remote changes...", 120); + if (!fetch.Ok) + return Fail("Committed locally, but couldn't reach the remote, so nothing was pushed. " + + "It will be pushed the next time you close Unity.\n\n" + fetch.Tail, quitting); + + GitResult push; + if (hasUpstream) + { + int ahead, behind; + AheadBehind(out ahead, out behind); + if (behind > 0) + { + var rebase = Git("rebase --autostash @{u}", "Bringing in " + behind + " remote commit(s)...", 300); + if (!rebase.Ok) + { + Git("rebase --abort"); + return Fail("New commits on the remote conflict with yours. Your work is committed locally but not pushed; " + + "resolve it in your git client, then push.\n\n" + rebase.Tail, quitting); + } + AheadBehind(out ahead, out behind); + } + + if (ahead == 0) { Info("'" + branch + "' is already in sync.", !quitting); return true; } + + string mergeRef = GitValue("config --get branch." + branch + ".merge"); + push = Git("push " + remote + " HEAD:" + mergeRef, "Pushing " + ahead + " commit(s)...", 900); + } + else + { + push = Git("push -u " + remote + " HEAD", "Pushing new branch '" + branch + "'...", 900); + } + + if (!push.Ok) + return Fail("Committed locally, but the push failed. It will be retried the next time you close Unity.\n\n" + push.Tail, quitting); + + Info("Pushed '" + branch + "' to " + remote + ".", !quitting); + if (!quitting) AssetDatabase.Refresh(); + return true; + } + + // ---------------------------------------------------------------- git helpers + + static bool CheckRepo(out string why) + { + var r = Git("rev-parse --is-inside-work-tree"); + why = null; + if (r.Code == -1) why = r.Err; + else if (!r.Ok || r.Out.Trim() != "true") why = "This project isn't inside a git repository, so Git Auto Sync is idle."; + return why == null; + } + + static string CurrentBranch() { return GitValue("symbolic-ref --short -q HEAD"); } + + static bool HasUpstream(string branch) + { + return GitValue("config --get branch." + branch + ".merge") != null && Git("rev-parse -q --verify @{u}").Ok; + } + + static string DefaultRemote() + { + string[] remotes = Lines(Git("remote").Out); + if (remotes.Contains("origin")) return "origin"; + return remotes.Length > 0 ? remotes[0] : null; + } + + static void AheadBehind(out int ahead, out int behind) + { + string[] parts = Git("rev-list --left-right --count HEAD...@{u}").Out.Split(new[] { '\t', ' ', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); + ahead = parts.Length > 0 ? int.Parse(parts[0]) : 0; + behind = parts.Length > 1 ? int.Parse(parts[1]) : 0; + } + + static int StashCount() { return Lines(Git("stash list").Out).Length; } + + static string GitValue(string args) + { + var r = Git(args); + string v = r.Out.Trim(); + return r.Ok && v.Length > 0 ? v : null; + } + + struct GitResult + { + public int Code; + public string Out, Err; + public bool Ok { get { return Code == 0; } } + public string Tail + { + get + { + var all = Lines((Err ?? "") + "\n" + (Out ?? "")) + .Where(l => !l.StartsWith("hint:") && !l.StartsWith("Rebasing (")).ToArray(); + return string.Join("\n", all.Skip(Math.Max(0, all.Length - 12)).ToArray()); + } + } + } + + // Runs git in the project folder. With a progress title it shows a cancelable progress bar. + static GitResult Git(string args, string progress = null, int timeoutSeconds = 30) + { + var psi = new ProcessStartInfo("git", args) + { + WorkingDirectory = ProjectRoot, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + StandardOutputEncoding = Encoding.UTF8, + StandardErrorEncoding = Encoding.UTF8, + }; + psi.EnvironmentVariables["GIT_TERMINAL_PROMPT"] = "0"; // fail instead of waiting on an invisible password prompt + + var output = new StringBuilder(); + var errors = new StringBuilder(); + using (var p = new Process { StartInfo = psi }) + { + p.OutputDataReceived += (s, e) => { if (e.Data != null) lock (output) output.AppendLine(e.Data); }; + p.ErrorDataReceived += (s, e) => { if (e.Data != null) lock (errors) errors.AppendLine(e.Data); }; + + try { p.Start(); } + catch (Exception e) + { + return new GitResult { Code = -1, Out = "", Err = "Couldn't run git (is it installed and on PATH?): " + e.Message }; + } + p.BeginOutputReadLine(); + p.BeginErrorReadLine(); + + var timer = Stopwatch.StartNew(); + string abortReason = null; + while (!p.WaitForExit(100)) + { + if (progress != null && + EditorUtility.DisplayCancelableProgressBar(Title, progress + " (" + (int)timer.Elapsed.TotalSeconds + "s)", + (float)(timer.Elapsed.TotalSeconds % 4.0 / 4.0))) + abortReason = "Cancelled."; + else if (timer.Elapsed.TotalSeconds > timeoutSeconds) + abortReason = "Timed out after " + timeoutSeconds + "s."; + + if (abortReason != null) + { + try { p.Kill(); } catch { } + break; + } + } + p.WaitForExit(); // flush async output + if (progress != null) EditorUtility.ClearProgressBar(); + + string err; + lock (errors) err = errors.ToString(); + if (abortReason != null) err += "\n" + abortReason; + lock (output) return new GitResult { Code = abortReason != null ? -2 : p.ExitCode, Out = output.ToString(), Err = err }; + } + } + + // ---------------------------------------------------------------- reporting + + static void Info(string msg, bool showDialog) + { + Debug.Log("[GitAutoSync] " + msg); + if (showDialog) EditorUtility.DisplayDialog(Title, msg, "OK"); + } + + static void Warn(string msg, bool showDialog) + { + Debug.LogWarning("[GitAutoSync] " + msg); + if (showDialog) EditorUtility.DisplayDialog(Title, msg, "OK"); + } + + static void Alert(string msg) { Warn(msg, true); } + + // When quitting: ask whether to quit anyway. Otherwise: just report. Returns true to quit. + static bool Fail(string msg, bool quitting) + { + Debug.LogWarning("[GitAutoSync] " + msg); + if (!quitting) + { + EditorUtility.DisplayDialog(Title, msg, "OK"); + AssetDatabase.Refresh(); + return false; + } + bool quit = EditorUtility.DisplayDialog(Title, msg, "Quit Anyway", "Stay Open"); + if (!quit) AssetDatabase.Refresh(); // a partial sync may have changed files on disk + return quit; + } + + static string[] Lines(string s) + { + return (s ?? "").Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); + } + + static string Clip(string text, int maxLines) + { + string[] lines = Lines(text); + if (lines.Length <= maxLines) return string.Join("\n", lines); + return string.Join("\n", lines.Take(maxLines).ToArray()) + "\n...and " + (lines.Length - maxLines) + " more"; + } + } +} diff --git a/net.lenrui.git-auto-sync/Editor/GitAutoSync.cs.meta b/net.lenrui.git-auto-sync/Editor/GitAutoSync.cs.meta new file mode 100644 index 0000000..aacec74 --- /dev/null +++ b/net.lenrui.git-auto-sync/Editor/GitAutoSync.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 95f51f4cbf504bab95ddcdc7abc56d2a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/net.lenrui.git-auto-sync/Editor/Lenrui.GitSync.Editor.asmdef b/net.lenrui.git-auto-sync/Editor/Lenrui.GitSync.Editor.asmdef new file mode 100644 index 0000000..8de2481 --- /dev/null +++ b/net.lenrui.git-auto-sync/Editor/Lenrui.GitSync.Editor.asmdef @@ -0,0 +1,16 @@ +{ + "name": "Lenrui.GitSync.Editor", + "rootNamespace": "Lenrui.GitSync", + "references": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": false, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/net.lenrui.git-auto-sync/Editor/Lenrui.GitSync.Editor.asmdef.meta b/net.lenrui.git-auto-sync/Editor/Lenrui.GitSync.Editor.asmdef.meta new file mode 100644 index 0000000..483b65e --- /dev/null +++ b/net.lenrui.git-auto-sync/Editor/Lenrui.GitSync.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4019e83783dc456880774eb5f81f9097 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/net.lenrui.git-auto-sync/README.md b/net.lenrui.git-auto-sync/README.md new file mode 100644 index 0000000..d4fd505 --- /dev/null +++ b/net.lenrui.git-auto-sync/README.md @@ -0,0 +1,36 @@ +# Git Auto Sync + +Keeps a Unity project in step with its git remote. + +- **When the Editor opens:** fetches. If the branch's upstream has new commits, it updates to them. Unpushed local commits are rebased on top, and uncommitted files are stashed and restored. If an uncommitted edit touches a file that also changed remotely, the pull is skipped and the files are listed, so no conflict markers end up in scenes or prefabs. +- **When the Editor closes:** saves scenes and assets, commits everything in the Unity project folder, brings in anything pushed meanwhile, then pushes. A branch with no upstream is pushed with `-u`. + +If something can't be done safely (a conflict, no network, a failed push), it asks whether to **Quit Anyway** or **Stay Open**. Your work is always committed locally first, and an unpushed commit goes up the next time you close. + +## Install + +**Embedded:** copy the `net.lenrui.git-auto-sync` folder into the project's `Packages/` folder. It becomes part of the project and is committed along with it. + +**From git:** put this folder in its own repository, then in Unity open **Window > Package Manager > + > Add package from git URL...** and enter the repository URL, for example `https://git.lenrui.net//git-auto-sync.git`. If the package sits in a subfolder of a repository, add `?path=/that/subfolder` to the URL. + +## Requirements + +- Unity 2019.4 or newer, and `git` on the PATH. +- Git credentials that work without typing anything: an SSH key loaded in an agent, or a credential manager for HTTPS. The Editor can't show git's password prompts. +- `Library/` must be ignored by git, or the tool refuses to commit. If the Unity project is in a subfolder of the repository, note that Unity's usual `/[Ll]ibrary/` pattern only matches next to the `.gitignore` it's in: keep that `.gitignore` inside the Unity project folder, or use `[Ll]ibrary/` at the repository root. + +## Menu + +**Tools > Git Auto Sync** + +- **Enabled**: on by default. +- **Ask Before Committing on Close**: off by default, so closing commits and pushes without a prompt. +- **Pull Latest Now** and **Commit and Push Now**: run either half by hand. + +Settings are stored per project, per machine. + +## What gets committed + +Only the Unity project folder (the folder containing `Assets/`). Other files elsewhere in the repository are left alone. Pulls and pushes act on the whole branch as usual. + +Commit messages look like `Auto-commit from Unity (MACHINE, 2026-11-05 22:27)`, followed by the list of changed files. diff --git a/net.lenrui.git-auto-sync/README.md.meta b/net.lenrui.git-auto-sync/README.md.meta new file mode 100644 index 0000000..3fb33d2 --- /dev/null +++ b/net.lenrui.git-auto-sync/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6153aa2aaeb64d4692de58774649c968 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/net.lenrui.git-auto-sync/package.json b/net.lenrui.git-auto-sync/package.json new file mode 100644 index 0000000..5c2b2a6 --- /dev/null +++ b/net.lenrui.git-auto-sync/package.json @@ -0,0 +1,11 @@ +{ + "name": "net.lenrui.git-auto-sync", + "version": "1.0.0", + "displayName": "Git Auto Sync", + "description": "Pulls the latest commits when the Unity Editor opens, and commits and pushes your changes when it closes. Stops and asks instead of guessing when there's a conflict, no network, or a failed push.", + "unity": "2019.4", + "keywords": ["git", "version control", "sync", "editor"], + "author": { + "name": "Len" + } +} diff --git a/net.lenrui.git-auto-sync/package.json.meta b/net.lenrui.git-auto-sync/package.json.meta new file mode 100644 index 0000000..6ddc13d --- /dev/null +++ b/net.lenrui.git-auto-sync/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 024811bbf838481e9ccb234f216b873d +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: