#include <bits/stdc++.h>
using namespace std;
template <typename S, typename T>
ostream& operator<<(ostream& out, const pair<S, T> p) {
  out << "(" << p.first << "," << p.second << ")";
  return out;
}
template <typename T>
ostream& operator<<(ostream& out, const vector<T>& v) {
  for (auto a : v) out << a << " ";
  return out;
}
template <typename T>
ostream& operator<<(ostream& out, const set<T>& S) {
  for (auto a : S) cout << a << " ";
  return out;
}
template <typename T>
ostream& operator<<(ostream& out, const multiset<T>& S) {
  for (auto a : S) cout << a << " ";
  return out;
}
template <typename S, typename T>
ostream& operator<<(ostream& out, const map<S, T>& M) {
  for (auto m : M) cout << "(" << m.first << "->" << m.second << ") ";
  return out;
}
template <typename S, typename T>
pair<S, T> operator+(pair<S, T> a, pair<S, T> b) {
  return make_pair(a.first + b.first, a.second + b.second);
}
template <typename S, typename T>
pair<S, T> operator-(pair<S, T> a, pair<S, T> b) {
  return make_pair(a.first - b.first, a.second - b.second);
}
template <typename T>
struct UndirectedTreeGraph {
  int n;
  vector<vector<int>> E;
  vector<int> vis;
  vector<int> parent;
  vector<int> dist;
  vector<int> root;
  vector<int> topo_sort;
  pair<int, int> diameter;
  int diameter_value;
  void init(int n) {
    this->n = n;
    E.resize(n);
    for (int i = 0; i < n; i++) E[i].clear();
  }
  void add_undirected_edge(int s, int t) {
    E[s].push_back(t);
    E[t].push_back(s);
  }
  void refactor_edge() {
    for (int i = 0; i < n; i++) {
      sort(E[i].begin(), E[i].end());
      E[i].erase(unique(E[i].begin(), E[i].end()), E[i].end());
    }
  }
  void dfs(int s) {
    vis[s] = 1;
    for (auto t : E[s]) {
      if (!vis[t]) {
        dist[t] = dist[s] + 1;
        parent[t] = s;
        dfs(t);
      }
    }
    topo_sort.push_back(s);
    vis[s] = 2;
  }
  void source_dfs() {
    vis.assign(n, 0);
    dist.assign(n, numeric_limits<int>::max());
    parent.assign(n, -1);
    topo_sort.clear();
    root.clear();
    for (int i = 0; i < n; i++) {
      if (!vis[i]) {
        root.push_back(i);
        dist[i] = 0;
        dfs(i);
      }
    }
    reverse(topo_sort.begin(), topo_sort.end());
  }
  void source_dfs(const vector<int>& A) {
    vis.assign(n, 0);
    dist.assign(n, numeric_limits<int>::max());
    parent.assign(n, -1);
    topo_sort.clear();
    root.clear();
    for (auto a : A) {
      if (!vis[a]) {
        root.push_back(a);
        dist[a] = 0;
        dfs(a);
      }
    }
    reverse(topo_sort.begin(), topo_sort.end());
  }
  void source_dfs(int src) {
    vis.assign(n, 0);
    dist.assign(n, numeric_limits<int>::max());
    parent.assign(n, -1);
    topo_sort.clear();
    root.clear();
    if (!vis[src]) {
      root.push_back(src);
      dist[src] = 0;
      dfs(src);
      reverse(topo_sort.begin(), topo_sort.end());
    }
  }
  void source_dynamic_programming(vector<T>& dp) {
    dp.resize(n);
    for (int i = n - 1; i >= 0; i--) {
      int s = topo_sort[i];
      dp[s] = 1;
      for (auto t : E[s]) {
        if (parent[t] == s) {
          dp[s] = max(dp[s], dp[t] + 1);
        }
      }
    }
  }
  void source_diameter() {
    source_dfs(0);
    int s = max_element(dist.begin(), dist.end()) - dist.begin();
    source_dfs(s);
    int t = max_element(dist.begin(), dist.end()) - dist.begin();
    diameter = make_pair(s, t);
    diameter_value = dist[t];
  }
  vector<T> lower_cost;
  vector<T> upper_cost;
  void source_maximum_node_cost_of_path_from_all(vector<int>& A,
                                                 vector<T>& dp) {
    lower_cost.resize(n);
    for (int i = n - 1; i >= 0; i--) {
      int s = topo_sort[i];
      lower_cost[s] = A[s];
      for (auto t : E[s]) {
        if (parent[t] == s) {
          lower_cost[s] = max(lower_cost[s], lower_cost[t] + A[s]);
        }
      }
    }
    upper_cost.assign(n, 0);
    for (auto s : topo_sort) {
      multiset<int> S;
      for (auto t : E[s]) {
        if (parent[t] == s) {
          S.insert(lower_cost[t]);
        }
      }
      for (auto t : E[s]) {
        if (parent[t] == s) {
          upper_cost[t] = max(upper_cost[t], upper_cost[s] + A[s]);
          S.erase(S.find(lower_cost[t]));
          if (!S.empty())
            upper_cost[t] = max(upper_cost[t], *S.rbegin() + A[s]);
          S.insert(lower_cost[t]);
        }
      }
    }
    dp.resize(n);
    for (int s = 0; s < n; s++) {
      dp[s] = max(upper_cost[s] + A[s], lower_cost[s]);
    }
  }
  pair<int, int> query_diameter() { return diameter; }
  int query_diameter_value() { return diameter_value; }
  void dump_edge() {
    for (int i = 0; i < E.size(); i++)
      cout << "E[" << i << "]: " << E[i] << endl;
  }
  void dump() {
    cout << "root: " << root << endl;
    for (int i = 0; i < n; i++) {
      cout << "vis[" << i << "]: " << vis[i] << ", dist[" << i
           << "]: " << dist[i] << ", parent[" << i << "]: " << parent[i]
           << endl;
    }
    cout << "topo_sort: " << topo_sort << endl;
    cout << "diameter: " << diameter << endl;
    cout << "diameter_value: " << diameter_value << endl;
  }
};
UndirectedTreeGraph<int> graph;
int main() {
  ios_base::sync_with_stdio(0);
  cin.tie(0);
  int n, m;
  cin >> n >> m;
  graph.init(n);
  while (m--) {
    int a, b;
    cin >> a >> b;
    graph.add_undirected_edge(a - 1, b - 1);
  }
  graph.source_diameter();
  cout << graph.query_diameter_value() << endl;
  return 0;
}
