mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-06-27 11:50:00 +00:00
## [2.23.0] - 2023-04-04 Thanks to: @angeldeejay, @buxxi, @CarJem, @dariom, @DaveChild, @dWoolridge, @grenagit, @Hirschberger, @KristjanESPERANTO, @MagMar94, @naveensrinivasan, @nfogal, @psieg, @rajniszp, @retroflex, @SkySails and @tomzt. Special thanks to @khassel, @rejas and @sdetweil for taking over most (if not all) of the work on this release as project collaborators. This version would not be there without their effort. Thank you guys! You are awesome! ### Added - Added increments for hourly forecasts in weather module (#2996) - Added tests for hourly weather forecast - Added possibility to ignore MagicMirror repo in updatenotification module - Added Pirate Weather as new weather provider (#3005) - Added possibility to use your own templates in Alert module - Added error message if `<modulename>.js` file is missing in module folder to get a hint in the logs (#2403) - Added possibility to use environment variables in `config.js` (#1756) - Added option `pastDaysCount` to default calendar module to control of how many days past events should be displayed - Added thai language to alert module - Added option `sendNotifications` in clock module (#3056) ### Removed - Removed darksky weather provider - Removed unneeded (and unwanted) '.' after the year in calendar repeatingCountTitle (#2896) ### Updated - Use develop as target branch for dependabot - Update issue template, contributing doc and sample config - The weather modules clearly separates precipitation amount and probability (risk of rain/snow) - This requires all providers that only supports probability to change the config from `showPrecipitationAmount` to `showPrecipitationProbability`. - Update tests for weather and calendar module - Changed updatenotification module for MagicMirror repo only: Send only notifications for `master` if there is a tag on a newer commit - Update dates in Calendar widgets every minute - Cleanup jest coverage for patches - Update `stylelint` dependencies, switch to `stylelint-config-standard` and handle `stylelint` issues, update `main.css` matching new rules - Update Eslint config, add new rule and handle issue - Convert lots of callbacks to async/await - Revise require imports (#3071 and #3072) ### Fixed - Fix wrong day labels in envcanada forecast (#2987) - Fix for missing default class name prefix for customEvents in calendar - Fix electron flashing white screen on startup (#1919) - Fix weathergov provider hourly forecast (#3008) - Fix message display with HTML code into alert module (#2828) - Fix typo in french translation - Yr wind direction is no longer inverted - Fix async node_helper stopping electron start (#2487) - The wind direction arrow now points in the direction the wind is flowing, not into the wind (#3019) - Fix precipitation css styles and rounding value - Fix wrong vertical alignment of calendar title column when wrapEvents is true (#3053) - Fix empty news feed stopping the reload forever - Fix e2e tests (failed after async changes) by running calendar and newsfeed tests last - Lint: Use template literals instead of string concatenation - Fix default alert module to render HTML for title and message - Fix Open-Meteo wind speed units
193 lines
5.6 KiB
JavaScript
193 lines
5.6 KiB
JavaScript
const util = require("util");
|
|
const exec = util.promisify(require("child_process").exec);
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const Log = require("logger");
|
|
|
|
const BASE_DIR = path.normalize(`${__dirname}/../../../`);
|
|
|
|
class GitHelper {
|
|
constructor() {
|
|
this.gitRepos = [];
|
|
}
|
|
|
|
getRefRegex(branch) {
|
|
return new RegExp(`s*([a-z,0-9]+[.][.][a-z,0-9]+) ${branch}`, "g");
|
|
}
|
|
|
|
async execShell(command) {
|
|
const { stdout = "", stderr = "" } = await exec(command);
|
|
|
|
return { stdout, stderr };
|
|
}
|
|
|
|
async isGitRepo(moduleFolder) {
|
|
const { stderr } = await this.execShell(`cd ${moduleFolder} && git remote -v`);
|
|
|
|
if (stderr) {
|
|
Log.error(`Failed to fetch git data for ${moduleFolder}: ${stderr}`);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
async add(moduleName) {
|
|
let moduleFolder = BASE_DIR;
|
|
|
|
if (moduleName !== "MagicMirror") {
|
|
moduleFolder = `${moduleFolder}modules/${moduleName}`;
|
|
}
|
|
|
|
try {
|
|
Log.info(`Checking git for module: ${moduleName}`);
|
|
// Throws error if file doesn't exist
|
|
fs.statSync(path.join(moduleFolder, ".git"));
|
|
|
|
// Fetch the git or throw error if no remotes
|
|
const isGitRepo = await this.isGitRepo(moduleFolder);
|
|
|
|
if (isGitRepo) {
|
|
// Folder has .git and has at least one git remote, watch this folder
|
|
this.gitRepos.push({ module: moduleName, folder: moduleFolder });
|
|
}
|
|
} catch (err) {
|
|
// Error when directory .git doesn't exist or doesn't have any remotes
|
|
// This module is not managed with git, skip
|
|
}
|
|
}
|
|
|
|
async getStatusInfo(repo) {
|
|
let gitInfo = {
|
|
module: repo.module,
|
|
behind: 0, // commits behind
|
|
current: "", // branch name
|
|
hash: "", // current hash
|
|
tracking: "", // remote branch
|
|
isBehindInStatus: false
|
|
};
|
|
|
|
if (repo.module === "MagicMirror") {
|
|
// the hash is only needed for the mm repo
|
|
const { stderr, stdout } = await this.execShell(`cd ${repo.folder} && git rev-parse HEAD`);
|
|
|
|
if (stderr) {
|
|
Log.error(`Failed to get current commit hash for ${repo.module}: ${stderr}`);
|
|
}
|
|
|
|
gitInfo.hash = stdout;
|
|
}
|
|
|
|
const { stderr, stdout } = await this.execShell(`cd ${repo.folder} && git status -sb`);
|
|
|
|
if (stderr) {
|
|
Log.error(`Failed to get git status for ${repo.module}: ${stderr}`);
|
|
// exit without git status info
|
|
return;
|
|
}
|
|
|
|
// only the first line of stdout is evaluated
|
|
let status = stdout.split("\n")[0];
|
|
// examples for status:
|
|
// ## develop...origin/develop
|
|
// ## master...origin/master [behind 8]
|
|
// ## master...origin/master [ahead 8, behind 1]
|
|
status = status.match(/## (.*)\.\.\.([^ ]*)(?: .*behind (\d+))?/);
|
|
// examples for status:
|
|
// [ '## develop...origin/develop', 'develop', 'origin/develop' ]
|
|
// [ '## master...origin/master [behind 8]', 'master', 'origin/master', '8' ]
|
|
// [ '## master...origin/master [ahead 8, behind 1]', 'master', 'origin/master', '1' ]
|
|
gitInfo.current = status[1];
|
|
gitInfo.tracking = status[2];
|
|
|
|
if (status[3]) {
|
|
// git fetch was already called before so `git status -sb` delivers already the behind number
|
|
gitInfo.behind = parseInt(status[3]);
|
|
gitInfo.isBehindInStatus = true;
|
|
}
|
|
|
|
return gitInfo;
|
|
}
|
|
|
|
async getRepoInfo(repo) {
|
|
const gitInfo = await this.getStatusInfo(repo);
|
|
|
|
if (!gitInfo) {
|
|
return;
|
|
}
|
|
|
|
if (gitInfo.isBehindInStatus && (gitInfo.module !== "MagicMirror" || gitInfo.current !== "master")) {
|
|
return gitInfo;
|
|
}
|
|
|
|
const { stderr } = await this.execShell(`cd ${repo.folder} && git fetch -n --dry-run`);
|
|
|
|
// example output:
|
|
// From https://github.com/MichMich/MagicMirror
|
|
// e40ddd4..06389e3 develop -> origin/develop
|
|
// here the result is in stderr (this is a git default, don't ask why ...)
|
|
const matches = stderr.match(this.getRefRegex(gitInfo.current));
|
|
|
|
// this is the default if there was no match from "git fetch -n --dry-run".
|
|
// Its a fallback because if there was a real "git fetch", the above "git fetch -n --dry-run" would deliver nothing.
|
|
let refDiff = `${gitInfo.current}..origin/${gitInfo.current}`;
|
|
if (matches && matches[0]) {
|
|
refDiff = matches[0];
|
|
}
|
|
|
|
// get behind with refs
|
|
try {
|
|
const { stdout } = await this.execShell(`cd ${repo.folder} && git rev-list --ancestry-path --count ${refDiff}`);
|
|
gitInfo.behind = parseInt(stdout);
|
|
|
|
// for MagicMirror-Repo and "master" branch avoid getting notified when no tag is in refDiff
|
|
// so only releases are reported and we can change e.g. the README.md without sending notifications
|
|
if (gitInfo.behind > 0 && gitInfo.module === "MagicMirror" && gitInfo.current === "master") {
|
|
let tagList = "";
|
|
try {
|
|
const { stdout } = await this.execShell(`cd ${repo.folder} && git ls-remote -q --tags --refs`);
|
|
tagList = stdout.trim();
|
|
} catch (err) {
|
|
Log.error(`Failed to get tag list for ${repo.module}: ${err}`);
|
|
}
|
|
// check if tag is between commits and only report behind > 0 if so
|
|
try {
|
|
const { stdout } = await this.execShell(`cd ${repo.folder} && git rev-list --ancestry-path ${refDiff}`);
|
|
let cnt = 0;
|
|
for (const ref of stdout.trim().split("\n")) {
|
|
if (tagList.includes(ref)) cnt++; // tag found
|
|
}
|
|
if (cnt === 0) gitInfo.behind = 0;
|
|
} catch (err) {
|
|
Log.error(`Failed to get git revisions for ${repo.module}: ${err}`);
|
|
}
|
|
}
|
|
|
|
return gitInfo;
|
|
} catch (err) {
|
|
Log.error(`Failed to get git revisions for ${repo.module}: ${err}`);
|
|
}
|
|
}
|
|
|
|
async getRepos() {
|
|
const gitResultList = [];
|
|
|
|
for (const repo of this.gitRepos) {
|
|
try {
|
|
const gitInfo = await this.getRepoInfo(repo);
|
|
|
|
if (gitInfo) {
|
|
gitResultList.push(gitInfo);
|
|
}
|
|
} catch (e) {
|
|
Log.error(`Failed to retrieve repo info for ${repo.module}: ${e}`);
|
|
}
|
|
}
|
|
|
|
return gitResultList;
|
|
}
|
|
}
|
|
|
|
module.exports = GitHelper;
|